Refit is a type - safe open source REST library, which is based on RESTful architecture NET client implementation, encapsulated internally with HttpClient class, can access the Web API interface more easily and safely through refit. To use the refit framework, you only need to install it through NuGet package installer in the project.
Install-Package refit
The method of use is simple:
public interface IGitHubApi { [Get("/users/{userid}")] Task<User> GetUser(string userid); }
The above method defines a REST API interface, which defines the GetUser function. The function accesses the / users/{userid} path of the server through the HTTP GET request and encapsulates the returned result as a User object. The value of {userid} in the URL path is the value of the userid parameter of the GetUser function, and then generates the proxy implementation of IGitHubApi through the RestService class, Call the Web API interface directly through the proxy.
var gitHubApi = RestService.For<IGitHubApi>("https://api.xcode.me"); var octocat = await gitHubApi.GetUser("xcode");
API Attributes attribute
Specify the request method and relative URL address through the Attribute tag. The built-in support of Get, Post, Put, Delete and Head methods.
[Get("/users/list")]
You can also specify query parameters in the URL:
[Get("/users/list?sort=desc")]
Method. The placeholder is a string surrounded by {}. If the function parameter is different from the placeholder name in the URL path, alias as can be used to specify the alias.
[Get("/group/{id}/users")] Task<List<User>> GroupList([AliasAs("id")] int groupId)
It is worth noting that parameter names and URL parameter placeholders are not case sensitive. If a function parameter is not used by a URL placeholder, it will be automatically used as a QueryString query string
[Get("/group/{id}/users")] Task<List<User>> GroupList([AliasAs("id")] int groupId, [AliasAs("sort")] string sortOrder);
When we call the GroupList method, it is equivalent to requesting the address "/ group/4/users?sort=desc", where the sort parameter is automatically used as a GET parameter.
Dynamic Querystring Parameters
Function parameters can be passed to the object, and the field properties of the object will be automatically appended to the Querystring query string.
public class MyQueryParams { [AliasAs("order")] public string SortOrder { get; set; } public int Limit { get; set; } } [Get("/group/{id}/users")] Task<List<User>> GroupList([AliasAs("id")] int groupId, MyQueryParams param); [Get("/group/{id}/users")] Task<List<User>> GroupListWithAttribute([AliasAs("id")] int groupId, [Query(".","search")] MyQueryParams param); param.SortOrder = "desc"; param.Limit = 10; GroupList(4, param) >>> "/group/4/users?order=desc&Limit=10" GroupListWithAttribute(4, param) >>> "/group/4/users?search.order=desc&search.Limit=10"
Collections as Querystring parameters
In addition to supporting object parameters, it also supports collection parameters. The following is an example:
[Get("/users/list")] Task Search([Query(CollectionFormat.Multi)]int[] ages); Search(new [] {10, 20, 30}) >>> "/users/list?ages=10&ages=20&ages=30" [Get("/users/list")] Task Search([Query(CollectionFormat.Csv)]int[] ages); Search(new [] {10, 20, 30}) >>> "/users/list?ages=10%2C20%2C30"
Body content
Append the function parameters to the Body part of the HTTP request by using the BodyAttribute attribute.
[Post("/users/new")] Task CreateUser([Body] User user);
According to the type of parameter, there are four possibilities to provide Body data: if the type is Stream stream type, the content will be streamed through StreamContent. If the type is String, the String will be used directly as content. If the parameter has the [Body(BodySerializationMethod.UrlEncoded)] attribute, the content will be used after URL encoding. For types other than the above, the object will be serialized as a JSON transport.
JSON content
JSON based requests and responses, internally using JSON Net framework. By default, the refine framework will use jsonconvert Defaultsettings to configure the behavior of the serializer:
JsonConvert.DefaultSettings = () => new JsonSerializerSettings() { ContractResolver = new CamelCasePropertyNamesContractResolver(), Converters = {new StringEnumConverter()} }; // Serialized as: {"day":"Saturday"} await PostSomeStuff(new { Day = DayOfWeek.Saturday });
Because the static attribute DefaultSettings is a global setting, it will affect the whole application. Sometimes, we want to use specific serialization settings for some API requests, which can be specified by using RefitSettings.
var gitHubApi = RestService.For<IGitHubApi>("https://api.xcode.me", new RefitSettings { JsonSerializerSettings = new JsonSerializerSettings { ContractResolver = new SnakeCasePropertyNamesContractResolver() } }); var otherApi = RestService.For<IOtherApi>("https://api.xcode.me", new RefitSettings { JsonSerializerSettings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() } });
The serialization behavior of object properties can be through JSON Customization of JsonPropertyAttribute attribute of. Net framework itself:
public class Foo { // Works like [AliasAs("b")] would in form posts (see below) [JsonProperty(PropertyName="b")] public string Bar { get; set; } }
Form posts
For the API interface using form submitted data (application/x-www-form-urlencoded), use BodySerializationMethod.UrlEncoded to initialize the BodyAttribute attribute. The parameter can be an IDictionary dictionary.
public interface IMeasurementProtocolApi { [Post("/collect")] Task Collect([Body(BodySerializationMethod.UrlEncoded)] Dictionary<string, object> data); } var data = new Dictionary<string, object> { {"v", 1}, {"tid", "UA-1234-5"}, {"cid", new Guid("d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c")}, {"t", "event"}, }; // Serialized as: v=1&tid=UA-1234-5&cid=d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c&t=event await api.Collect(data);
Data can be transmitted through form submission, or any object. All public properties and fields of the object will be serialized. Alias can be specified using AliasAs:
public interface IMeasurementProtocolApi { [Post("/collect")] Task Collect([Body(BodySerializationMethod.UrlEncoded)] Measurement measurement); } public class Measurement { // Properties can be read-only and [AliasAs] isn't required 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; } public object IgnoreMe { private get; set; } } var measurement = new Measurement { WebPropertyId = "UA-1234-5", ClientId = new Guid("d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c"), Type = "event" }; // Serialized as: v=1&tid=UA-1234-5&cid=d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c&t=event await api.Collect(measurement);
Set static request header
You can set one or more HTTP static request headers using the HeadersAttribute attribute:
[Headers("User-Agent: Awesome Octocat App")] [Get("/users/{user}")] Task<User> GetUser(string user);
You can also apply the HeadersAttribute attribute to an interface, which will affect all request methods in the interface:
[Headers("User-Agent: Awesome Octocat App")] public interface IGitHubApi { [Get("/users/{user}")] Task<User> GetUser(string user); [Post("/users/new")] Task CreateUser([Body] User user); }
Set dynamic request header
If the request header needs to be set at run time, you can add dynamic values to the request header by applying the HeaderAttribute attribute to the function parameters.
[Get("/users/{user}")] Task<User> GetUser(string user, [Header("Authorization")] string authorization); // Will add the header "Authorization: token OAUTH-TOKEN" to the request var user = await GetUser("octocat", "token OAUTH-TOKEN");
Authorization (dynamic request header)
The most common use of the header is authorization. Today, most APIs use the oAuth protocol to authorize through the access token, apply for the access token, and then access the API interface. After the access token expires, you need to refresh the token to obtain a token with a longer life. The operation of encapsulating these tokens can be realized by customizing the HttpClientHandler:
class AuthenticatedHttpClientHandler : HttpClientHandler { private readonly Func<Task<string>> getToken; public AuthenticatedHttpClientHandler(Func<Task<string>> getToken) { if (getToken == null) throw new ArgumentNullException(nameof(getToken)); this.getToken = getToken; } protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { // See if the request has an authorize header var auth = request.Headers.Authorization; if (auth != null) { var token = await getToken().ConfigureAwait(false); request.Headers.Authorization = new AuthenticationHeaderValue(auth.Scheme, token); } return await base.SendAsync(request, cancellationToken).ConfigureAwait(false); } }
Although HttpClient contains almost the same method signature, it is used in different ways Sendasync has not been modified. You must modify HttpClientHandler instead. Use it like this:
class LoginViewModel { AuthenticationContext context = new AuthenticationContext(...); private async Task<string> GetToken() { // The AcquireTokenAsync call will prompt with a UI if necessary // Or otherwise silently use a refresh token to return // a valid access token var token = await context.AcquireTokenAsync("http://my.service.uri/app", "clientId", new Uri("callback://complete")); return token; } public async void LoginAndCallApi() { var api = RestService.For<IMyRestService>(new HttpClient(new AuthenticatedHttpClientHandler(GetToken)) { BaseAddress = new Uri("https://the.end.point/") }); var location = await api.GetLocationOfRebelBase(); } } interface IMyRestService { [Get("/getPublicInfo")] Task<Foobar> SomePublicMethod(); [Get("/secretStuff")] [Headers("Authorization: Bearer")] Task<Location> GetLocationOfRebelBase(); }
In the above example, when an authenticated interface needs to be called, the AuthenticatedHttpClientHandler will attempt to obtain a new access token. The application provides a to check the expiration time of an existing access token and obtain a new access token when needed.
Redefine header
When defining HTTP headers, for headers with the same name set multiple times, these headers with the same name will not overlap each other and will be added to the request header. It is worth noting that when the priorities set by the headers are different, the redefined headers will be replaced. Their priorities are:
1. Headers feature on interface (lowest priority), 2. Headers feature on method, 3. Headers feature on method parameter (highest priority)
[Headers("X-Emoji: :rocket:")] public interface IGitHubApi { [Get("/users/list")] Task<List> GetUsers(); [Get("/users/{user}")] [Headers("X-Emoji: :smile_cat:")] Task<User> GetUser(string user); [Post("/users/new")] [Headers("X-Emoji: :metal:")] Task CreateUser([Body] User user, [Header("X-Emoji")] string emoji); } // X-Emoji: :rocket: var users = await GetUsers(); // X-Emoji: :smile_cat: var user = await GetUser("octocat"); // X-Emoji: :trollface: await CreateUser(user, ":trollface:");
Delete header
When the HeadersAttribute is used, the request header will be automatically removed if no value is provided or the provided value is null.
[Headers("X-Emoji: :rocket:")] public interface IGitHubApi { [Get("/users/list")] [Headers("X-Emoji")] // Remove the X-Emoji header Task<List> GetUsers(); [Get("/users/{user}")] [Headers("X-Emoji:")] // Redefine the X-Emoji header as empty Task<User> GetUser(string user); [Post("/users/new")] Task CreateUser([Body] User user, [Header("X-Emoji")] string emoji); } // No X-Emoji header var users = await GetUsers(); // X-Emoji: var user = await GetUser("octocat"); // No X-Emoji header await CreateUser(user, null); // X-Emoji: await CreateUser(user, "");
Multipart uploads
The refine framework also supports the upload of byte stream and file stream:
public interface ISomeApi { [Multipart] [Post("/users/{id}/photo")] Task UploadPhoto(int id, [AliasAs("myPhoto")] StreamPart stream); someApiInstance.UploadPhoto(id, new StreamPart(myPhotoStream, "photo.jpg", "image/jpeg")); }
Response processing
In order to improve performance, it only supports the return of Task and iobserveable types by methods. If synchronous requests are required, async and await asynchronous technologies can be used.
[Post("/users/new")] Task CreateUser([Body] User user); // This will throw if the network call fails await CreateUser(someUser);
If the type parameter is HttpResponseMessage or string, it can be returned respectively through overloaded functions.
// Returns the content as a string (i.e. the JSON data) [Get("/users/{user}")] Task<string> GetUser(string user); // Returns the raw response, as an IObservable that can be used with the // Reactive Extensions [Get("/users/{user}")] IObservable<HttpResponseMessage> GetUser(string user);
Use common interface
Some web APIs have a complete set of REST services based on CRUD operations. It allows you to define interfaces using general generics:
public interface IReallyExcitingCrudApi<T, in TKey> where T : class { [Post("")] Task<T> Create([Body] T payload); [Get("")] Task<List<T>> ReadAll(); [Get("/{key}")] Task<T> ReadOne(TKey key); [Put("/{key}")] Task Update(TKey key, [Body]T payload); [Delete("/{key}")] Task Delete(TKey key); }
The above interface encapsulation can be called in this way:
// The "/users" part here is kind of important if you want it to work for more // than one type (unless you have a different domain for each type) var api = RestService.For<IReallyExcitingCrudApi<User, string>>("http://api.xcode.me/users");
Using HttpClientFactory
In ASP In net core 2.1, the client type can be injected through the extension method provided by the Refix framework:
public void ConfigureServices(IServiceCollection services) { services.AddHttpClient("hello", c => { c.BaseAddress = new Uri("http://api.xcode.me"); }) .AddTypedClient(c => Refit.RestService.For<IHelloClient>(c)); services.AddMvc(); }
In addition, it also supports the creation of request proxies through HttpClientFactory. Refit provides an extension for this. Before using this extension, you need to reference the following packages through NuGet,
Install-Package Refit.HttpClientFactory
After referencing a package, you can configure it as follows:
services.AddRefitClient<IWebApi>() .ConfigureHttpClient(c => c.BaseAddress = new Uri("https://api.xcode.me")); // Add additional IHttpClientBuilder chained methods as required here: // .AddHttpMessageHandler<MyHandler>() // .SetHandlerLifetime(TimeSpan.FromMinutes(2));
You can also set the behavior through RefitSettings:
var settings = new RefitSettings(); // Configure refit settings here services.AddRefitClient<IWebApi>(settings) .ConfigureHttpClient(c => c.BaseAddress = new Uri("https://api.xcode.me")); // Add additional IHttpClientBuilder chained methods as required here: // .AddHttpMessageHandler<MyHandler>() // .SetHandlerLifetime(TimeSpan.FromMinutes(2));
You can then inject the request interface into the controller using the constructor:
public class HomeController : Controller { public HomeController(IWebApi webApi) { _webApi = webApi; } private readonly IWebApi _webApi; public async Task<IActionResult> Index(CancellationToken cancellationToken) { var thing = await _webApi.GetSomethingWeNeed(cancellationToken); return View(thing); } }
publicinterfaceISomeApi { [Multipart] [Post("/users/{id}/photo")] Task UploadPhoto(int id, [AliasAs("myPhoto")] StreamPart stream); someApiInstance.UploadPhoto(id, new StreamPart(myPhotoStream, "photo.jpg", "image/jpeg")); }