Overview of Refit
mainHttpClient to make network calls.repository·main·Indexed 27 days ago
https://github.com/reactiveui/refitA type-safe REST library for .NET that transforms interface definitions into functional API clients using HttpClient. Refit supports Roslyn source generation for high-performance, Native AOT, and trimmed applications. It provides features for defining API endpoints via HTTP attributes, custom query string serialization, URL replacement blocks, and integration with .NET's HttpClientFactory. Compatible with .NET 8/9/10/11, .NET Framework 4.6.2+, WinUI, Blazor, and Uno Platform.
HttpClient to make network calls.Refit is compatible with the following platforms and .NET targets:
Refit has first-class support for IHttpClientFactory. To use it, add a reference to Refit.HttpClientFactory and use the AddRefitClient<T> extension method in your service configuration.
// Basic configuration
services.AddRefitClient<IWebApi>()
.ConfigureHttpClient(c => c.BaseAddress = new Uri("https://api.example.com"));
// Configuration with RefitSettings
var settings = new RefitSettings();
// Configure refit settings here
services.AddRefitClient<IWebApi>(settings)
.ConfigureHttpClient(c => c.BaseAddress = new Uri("https://api.example.com"))
.AddHttpMessageHandler<MyHandler>();To use Newtonsoft.Json instead of the default System.Text.Json, add the Refit.Newtonsoft.Json package and pass a NewtonsoftJsonContentSerializer to RefitSettings. You can customize behavior by providing JsonSerializerSettings to the constructor or by setting global JsonConvert.DefaultSettings.
// Basic usage
var settings = new RefitSettings(new NewtonsoftJsonContentSerializer());
// Custom settings per API
var gitHubApi = RestService.For<IGitHubApi>("https://api.github.com",
new RefitSettings {
ContentSerializer = new NewtonsoftJsonContentSerializer(
new JsonSerializerSettings {
ContractResolver = new SnakeCasePropertyNamesContractResolver()
})
});When using Refit.HttpClientFactory, you can resolve tokens from the DI container using AddAuthorizationHeaderValueProvider. This is useful for per-request isolation. The provider receives an IServiceProvider, the HttpRequestMessage, and a CancellationToken.
services.AddRefitClient<IMyApi>()
.ConfigureHttpClient(c => c.BaseAddress = new Uri("https://api.example.com"))
.AddAuthorizationHeaderValueProvider((serviceProvider, request, cancellationToken) =>
{
var tokenService = serviceProvider.GetRequiredService<IMyTokenService>();
return new ValueTask<string>(tokenService.GetTokenForCurrentRequest());
});To avoid reflection-based registration in Native AoT or trimmed applications, use AddRefitGeneratedClient<T> from Refit.HttpClientFactory. This ensures the application uses RestService.ForGenerated<T> and throws an InvalidOperationException if a generated client is missing, rather than silently falling back to reflection.
// Standard registration
services.AddRefitGeneratedClient<IWebApi>()
.ConfigureHttpClient(c => c.BaseAddress = new Uri("https://api.example.com"));
// Registration with settings and custom name
services.AddRefitGeneratedClient<IWebApi>(settings, "my-named-client");Routes in a StubHttp are consumed in the order they are declared. This allows you to simulate a sequence of responses for the same endpoint.
Use Reusable = true in a RouteMatcher for routes that should match any number of requests without being required by VerifyAllCalled. One-shot routes always take priority over reusable ones.
// Sequenced responses
var http = new StubHttp
{
{ Route.Get("/status"), Reply.Json("{\"state\":\"pending\"}") },
{ Route.Get("/status"), Reply.Json("{\"state\":\"done\"}") },
};
// Reusable catch-all route
var http = new StubHttp
{
{ new RouteMatcher { Template = "*", Reusable = true }, Reply.Status(HttpStatusCode.OK) },
};You can split a large API into focused interfaces and aggregate them into a single client by using interface inheritance. The generator walks the full hierarchy to expose all methods.
When using inheritance, header attributes are passed along. The inner-most attribute has precedence. If an interface inherits from multiple interfaces, the precedence follows the order in which they are declared.
public interface IUsersApi
{
[Get("/users/{user}")]
Task<User> GetUser(string user);
}
public interface IReposApi
{
[Get("/users/{user}/repos")]
Task<List<Repo>> GetRepos(string user);
}
// The aggregate client composes the two APIs
public interface IGitHubApi : IUsersApi, IReposApi;
// Usage:
var api = RestService.For<IGitHubApi>("https://api.github.com");
var user = await api.GetUser("octocat");
var repos = await api.GetRepos("octocat");To test your Refit clients without a live server or a general-purpose mocking library, install the Refit.Testing package. This package allows you to define a route table of expected requests and their corresponding replies.
dotnet add package Refit.TestingTo send data as application/x-www-form-urlencoded, use the [Body(BodySerializationMethod.UrlEncoded)] attribute. You can pass an IDictionary<string, object> or a plain object. If using an object, all public, readable properties will be serialized as form fields. Use [AliasAs("name")] to map property names to specific form field keys.
public interface IMeasurementProtocolApi
{
[Post("/collect")]
Task Collect([Body(BodySerializationMethod.UrlEncoded)] Measurement measurement);
}
public class Measurement
{
public int v { get { return 1; } }
[AliasAs("tid")]
public string WebPropertyId { get; set; }
[AliasAs("cid")]
public Guid ClientId { get; set; }
[AliasAs("t")]
public string Type { get; set; }
}To test retry policies (like Polly handlers or DelegatingHandler), use sequenced one-shot routes in StubHttp. The routes are matched in the order they are declared. A transient fault (e.g., HttpStatusCode.ServiceUnavailable) followed by a successful response allows you to verify that the retry logic correctly triggers.
To simulate a transport fault (rather than a status code error), use Reply.From to throw an exception:
Reply.From(HttpResponseMessage (_) => throw new HttpRequestException("boom")).
Use NetworkBehavior.Delay to inject latency into the StubHttp. If the injected delay exceeds the HttpClient.Timeout, the request will be aborted. Refit surfaces this as an ApiRequestException where the InnerException is a TaskCanceledException.
// Testing Retries
var http = new StubHttp
{
{ Route.Get("/users/{id}"), Reply.Status(HttpStatusCode.ServiceUnavailable) }, // first attempt
{ Route.Get("/users/{id}"), Reply.With(new User(7, "octocat")) }, // the retry
};
var settings = new RefitSettings { HttpMessageHandlerFactory = () => new MyRetryHandler(http) };
var api = RestService.For<IUserApi>("https://api.test", settings);
var user = await api.GetUser(7); // succeeded on the retry
Assert.Equal(2, http.Requests.Count); // both attempts were made
// Testing Timeouts
var http = new StubHttp(new NetworkBehavior { Delay = TimeSpan.FromSeconds(10), Variance = 0 })
{
{ Route.Get("/users/{id}"), Reply.With(new User(7, "octocat")) },
};
using var client = new HttpClient(http)
{
BaseAddress = new Uri("https://api.test"),
Timeout = TimeSpan.FromMilliseconds(50),
};
await Assert.ThrowsAsync<ApiRequestException>(() => RestService.For<IUserApi>(client).GetUser(7));Run the generate-publicapi script to update the public API baseline files. You can run it for all tracked libraries or provide an optional case-sensitive substring to filter by project path (e.g., to target a specific library).
When to run:
protected on a public type) API.### Linux / macOS
```bash
tools/generate-publicapi.sh # all tracked libraries, all TFMs
tools/generate-publicapi.sh HttpClient # only projects whose path contains 'HttpClient'
tools/generate-publicapi.sh Refit.Xml./tools/generate-publicapi.ps1 # all tracked libraries
./tools/generate-publicapi.ps1 -Filter HttpClient # path filter