RestWrapper is a small C# library for sending HTTP requests without rebuilding the same request, header, authorization, streaming, and response-handling code in every project.
It wraps HttpClient with a compact API for:
- standard REST verbs
- custom headers and content types
- basic, bearer, and raw authorization headers
- JSON serialization/deserialization
- response timing metadata
- server-sent events
- chunked request and response workflows
- optional caller-supplied
HttpClientinstances
If you want something lighter than a full API client framework but more structured than hand-rolled HttpClient calls, RestWrapper sits in the middle:
- request setup is explicit and easy to read
- response access is simple for text, bytes, streams, JSON, SSE, and chunked flows
- you can keep using your own
HttpClientfor DI, proxies, mTLS, custom handlers, and connection reuse - the library stays close to the underlying HTTP model instead of inventing a large abstraction layer
RestWrapper currently targets:
netstandard2.0netstandard2.1net462net48net6.0net8.0net10.0
dotnet add package RestWrapperSimple GET:
using RestWrapper;
using RestRequest request = new RestRequest("https://api.example.com/status");
using RestResponse response = await request.SendAsync();
Console.WriteLine(response.StatusCode);
Console.WriteLine(response.DataAsString);Simple POST:
using RestWrapper;
using System.Net.Http;
using RestRequest request = new RestRequest("https://api.example.com/messages", HttpMethod.Post);
request.ContentType = "text/plain";
using RestResponse response = await request.SendAsync("Hello, world!");
Console.WriteLine(response.StatusCode);
Console.WriteLine(response.DataAsString);JSON response handling:
using RestWrapper;
using RestRequest request = new RestRequest("https://api.example.com/items/123");
using RestResponse response = await request.SendAsync();
MyDto dto = response.DataFromJson<MyDto>();
Console.WriteLine(dto.Name);Form data:
using RestWrapper;
using System.Net.Http;
using RestRequest request = new RestRequest("https://api.example.com/login", HttpMethod.Post);
Dictionary<string, string> form = new Dictionary<string, string>
{
{ "username", "alice" },
{ "password", "secret" }
};
using RestResponse response = await request.SendAsync(form);
Console.WriteLine(response.StatusCode);using RestWrapper;
using System.Net.Http;
using RestRequest request = new RestRequest("https://api.example.com/items", HttpMethod.Post);
request.ContentType = "application/json; charset=utf-8";
request.Headers.Add("X-Correlation-Id", Guid.NewGuid().ToString());
request.Headers.Add("X-Tenant", "east");
using RestResponse response = await request.SendAsync("{\"name\":\"demo\"}");Basic auth:
using RestWrapper;
using RestRequest request = new RestRequest("https://api.example.com/secure");
request.Authorization.User = "alice";
request.Authorization.Password = "secret";
using RestResponse response = await request.SendAsync();Bearer auth:
using RestWrapper;
using RestRequest request = new RestRequest("https://api.example.com/secure");
request.Authorization.BearerToken = "your-token";
using RestResponse response = await request.SendAsync();Raw authorization header:
using RestWrapper;
using RestRequest request = new RestRequest("https://api.example.com/secure");
request.Authorization.Raw = "Custom scheme-value";
using RestResponse response = await request.SendAsync();using RestWrapper;
using System.Net.Http;
using System.Text;
byte[] payload = Encoding.UTF8.GetBytes("streamed payload");
using MemoryStream stream = new MemoryStream(payload);
using RestRequest request = new RestRequest("https://api.example.com/upload", HttpMethod.Put);
request.ContentType = "text/plain";
using RestResponse response = await request.SendAsync(payload.Length, stream);
Console.WriteLine(response.StatusCode);For regular responses, RestWrapper gives you multiple access patterns:
response.Datafor the raw streamresponse.DataAsBytesfor the buffered byte arrayresponse.DataAsStringfor UTF-8 string contentresponse.DataFromJson<T>()for JSON payloads
Example:
using RestWrapper;
using RestRequest request = new RestRequest("https://api.example.com/items/123");
using RestResponse response = await request.SendAsync();
Console.WriteLine(response.IsSuccessStatusCode);
Console.WriteLine(response.ContentType);
Console.WriteLine(response.ContentLength);
Console.WriteLine(response.DataAsString);Each RestResponse includes a Time property:
using RestWrapper;
using RestRequest request = new RestRequest("https://api.example.com/status");
using RestResponse response = await request.SendAsync();
Console.WriteLine("Start : " + response.Time.Start);
Console.WriteLine("End : " + response.Time.End);
Console.WriteLine("Total ms : " + response.Time.TotalMs);When the server returns text/event-stream, use ReadEventAsync() instead of Data, DataAsString, or DataFromJson<T>().
using RestWrapper;
using RestRequest request = new RestRequest("https://api.example.com/events");
using RestResponse response = await request.SendAsync();
while (true)
{
ServerSentEvent evt = await response.ReadEventAsync();
if (evt == null) break;
Console.WriteLine($"[{evt.Event}] {evt.Data}");
}ServerSentEvent exposes:
IdEventDataRetry
RestWrapper supports chunked request sending and chunked response reading.
using RestWrapper;
using System.Net.Http;
using RestRequest request = new RestRequest("https://api.example.com/chunked", HttpMethod.Post);
request.ChunkedTransfer = true;
await request.SendChunkAsync("chunk-1", false);
await request.SendChunkAsync("chunk-2", false);
using RestResponse response = await request.SendChunkAsync(Array.Empty<byte>(), true);
while (true)
{
ChunkData chunk = await response.ReadChunkAsync();
if (chunk == null) break;
Console.WriteLine(System.Text.Encoding.UTF8.GetString(chunk.Data));
if (chunk.IsFinal) break;
}As with SSE, chunked responses are a specialized mode. Use ReadChunkAsync() instead of Data, DataAsString, DataAsBytes, or DataFromJson<T>().
If you already manage HttpClient instances through dependency injection or need custom handler behavior, you can provide your own HttpClient to RestRequest.
using RestWrapper;
using System.Net.Http;
using HttpClient client = new HttpClient();
using RestRequest request = new RestRequest(
"https://api.example.com/resource",
HttpMethod.Get,
client);
using RestResponse response = await request.SendAsync();
Console.WriteLine(response.StatusCode);This is useful for:
- connection reuse
- proxy settings
- mTLS and client certificates
- custom message handlers
- centralized
HttpClientFactoryusage
Important behavior:
- caller-supplied clients remain caller-owned
RestRequestwill not dispose an externalHttpClient- per-request headers and authorization are applied at the request-message level so shared clients do not leak state between requests
When you supply your own HttpClient, transport-level settings also become caller-owned. In that mode, do not use these RestRequest properties:
TimeoutMillisecondsAllowAutoRedirectIgnoreCertificateErrorsCertificateFilenameCertificatePassword
Configure those on your HttpClient / handler instead.
RestResponse.DataFromJson<T>() uses System.Text.Json by default, but you can replace the serializer:
using RestWrapper;
using RestRequest request = new RestRequest("https://api.example.com/items/123");
using RestResponse response = await request.SendAsync();
response.SerializationHelper = new MySerializer();
MyDto dto = response.DataFromJson<MyDto>();MySerializer just needs to implement ISerializationHelper.
The repository includes both interactive and automated test hosts:
src/Testis the interactive console appsrc/Test.Sharedcontains the shared Touchstone suitessrc/Test.Automatedis the console/CLI automation runnersrc/Test.Xunitexposes the shared suite throughdotnet testsrc/Test.Nunitexposes the same shared suite throughdotnet test
The current automated surface contains 120 shared cases covering:
- internal and caller-supplied
HttpClientflows - positive and negative transport behavior
- shared-client header/auth isolation
- SSE parsing, cancellation, and misuse guards
- chunked read behavior, cancellation, and framing
- response guards for regular vs streaming modes
- serializer and helper utilities
RestWrapper uses the platform HttpClient stack. When targeting localhost, some environments will try IPv6 loopback first. If your local service is only listening on IPv4, that can introduce a noticeable delay.
If you see this behavior, prefer 127.0.0.1 instead of localhost.
See CHANGELOG.md for version history.