(27) ASP.NET Core .NET standard REST library Refit

1 Introduction

Refit is an automatic type-safe REST library inspired by Square's Retrofit library (Java). Through the HttpClient network request (POST, GET, PUT, DELETE, etc.) to convert the data returned by the REST API into POCO (Plain Ordinary C # Object, simple C # object) to JSON. Our application requests the network through Refit. In fact, it uses the Refit interface layer to encapsulate the request parameters, header, Url and other information. After that, HttpClient completes the subsequent request operations. After the server returns the data, HttpClient hands the original result to Refit , The latter is the process of analyzing the results according to the needs of users. Install component command line:

Install-Package refit

Code example:

[Headers ( " User-Agent: Refit Integration Tests " )] // Here, because the target source is GitHubApi, you must add this static request header information to make it a test request, otherwise it will return data exceptions. 
public  interface IGitHubApi 
{ 
    [Get ( " / users / {user} " )] 
    Task <User> GetUser ( string user); 
} 
public  class GitHubApi 
{ 
    public  async Task <User> GetUser () 
    { 
        var gitHubApi = RestService.For <IGitHubApi > ( " https://api.github.com " );
         var octocat =await gitHubApi.GetUser("octocat");
        return octocat;
    }
}
public class User
{
    public string login { get; set; }
    public int? id { get; set; }
    public string url { get; set; }
}
[HttpGet]
public async Task<ActionResult<IEnumerable<string>>> Get()
{
    var result = await new GitHubApi().GetUser();
    return new string[] { result.id.Value.ToString(), result.login };
}

Note: The properties of Headers and Get in the interface are called the characteristics of Refit.
Define the REST API interface of IGitHubApi above. The interface defines a function GetUser, which will access the server's / users / {user} path through an HTTP GET request. The returned result is encapsulated as a User POCO object and returned. The value of {user} in the URL path is the value of the parameter user in the GetUser function, where the value is assigned to octocat. Then through the RestService class to generate an implementation of the IGitHubApi interface for HttpClient to call.

 

 

2. API properties

Each method must have the request URL and HTTP attributes. There are six built-in annotations for HTTP attributes: Get, Post, Put, Delete, Patch and Head , for example:

[Get("/users/list")]

You can also specify query parameters in the request URL:

[Get("/users/list?sort=desc")]

You can also use alternate blocks and parameters on relative URLs to request resources dynamically. The replacement block is an alphanumeric string surrounded by {and, &}. If the parameter name does not match the name in the URL path, use the AliasAs attribute, for example:

[Get("/group/{id}/users")]
Task<List<User>> GroupList([AliasAs("id")] int groupId);

The request URL can also bind the replacement block to a custom object, for example:

[Get("/group/{request.groupId}/users/{request.userId}")]
Task<List<User>> GroupList(UserGroupRequest request);
class UserGroupRequest{
    int groupId { get;set; }
    int userId { get;set; }
}

Parameters not specified as URL replacements will automatically be used as query parameters. This is different from Retrofit. In Retrofit, all parameters must be specified explicitly, for example:

[Get("/group/{id}/users")]
Task<List<User>> GroupList([AliasAs("id")] int groupId, [AliasAs("sort")] string sortOrder);
GroupList(4, "desc");

Output result: "/ group / 4 / users? Sort = desc"

3. Dynamic query string parameters (Dynamic Querystring Parameters)

The method can also pass a custom object and append the object properties to the query string parameters, for example:

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 params);
[Get("/group/{id}/users")]
Task<List<User>> GroupListWithAttribute([AliasAs("id")] int groupId, [Query(".","search")]MyQueryParams params);
params.SortOrder = "desc";
params.Limit = 10;
GroupList(4, params)

Output result: "/ group / 4 / users? Order = desc & Limit = 10"

GroupListWithAttribute(4, params)

Output: "/group/4/users?search.order=desc&search.Limit=10"
You can also use [Query] to specify querystring parameters and flatten them in non-GET requests, similar to:

[Post("/statuses/update.json")]
Task<Tweet> PostTweet([Query]TweetParams params);

5. Collections as Querystring Parameters

In addition to supporting the transfer of custom object queries, the method also supports collection queries, for example:

[Get("/users/list")]
Task Search([Query(CollectionFormat.Multi)]int[] ages);
Search(new [] {10, 20, 30})

Output: "/ users / list? Ages = 10 & ages = 20 & ages = 30"

[Get("/users/list")]
Task Search([Query(CollectionFormat.Csv)]int[] ages);
Search(new [] {10, 20, 30})

Output result: "/ users / list? Ages = 10% 2C20% 2C30"

6. Unescape Querystring Parameters

Use the QueryUriFormat attribute to specify whether query parameters should escape the URL, for example:

[Get("/query")]
[QueryUriFormat(UriFormat.Unescaped)]
Task Query(string q);
Query("Select+Id,Name+From+Account")

Output result: "/ query? Q = Select + Id, Name + From + Account"

7. Body content

By using the Body property, you can append custom object parameters to the HTTP request Body.

[Post("/users/new")]
Task CreateUser([Body] User user)

According to the type of parameters, there are four possibilities to provide Body data:
● If the type is Stream, the content will be transmitted through StreamContent stream.
● If the type is string, the string will be used directly as the content, unless [Body (BodySerializationMethod.Json)] sets the string, otherwise it will be regarded as StringContent.
● If the parameter has the attribute [Body (BodySerializationMethod.UrlEncoded)], the content will be URL-encoded.
● For all other types, the content serializer specified in RefitSettings will be used to serialize the object (default is JSON).
● Buffering and Content-Length header
By default, Refit readjusts the content of the streaming body without buffering it. For example, this means that you can stream files from disk without the overhead of loading the entire file into memory. The disadvantage of this is that the content-length header is not set on the request. If your API requires you to send a content length header with the request, you can disable this traffic behavior by setting the buffer parameter of the [Body] attribute to true:

Task CreateUser([Body(buffered: true)] User user);

7.1. JSON content

Use Json.NET to serialize / deserialize JSON requests and responses. By default, Refit will use serializer settings that can be configured by setting Newtonsoft.Json.JsonConvert.DefaultSettings:

JsonConvert.DefaultSettings =
    () => new JsonSerializerSettings() {
        ContractResolver = new CamelCasePropertyNamesContractResolver(),
        Converters = {new StringEnumConverter()}
    };
//Serialized as: {"day":"Saturday"}
await PostSomeStuff(new { Day = DayOfWeek.Saturday });

Since the default static configurations are global settings, they will affect your entire application. Sometimes we only want to set some specific APIs, you can choose to use the RefitSettings property to allow you to specify the serializer you want to set, which allows you to set different serializer settings for individual APIs:

var gitHubApi = RestService.For<IGitHubApi>("https://api.github.com",
    new RefitSettings {
        ContentSerializer = new JsonContentSerializer(
            new JsonSerializerSettings {
                ContractResolver = new SnakeCasePropertyNamesContractResolver()
        }
    )});
var otherApi = RestService.For<IOtherApi>("https://api.example.com",
    new RefitSettings {
        ContentSerializer = new JsonContentSerializer(
            new JsonSerializerSettings {
                ContractResolver = new CamelCasePropertyNamesContractResolver()
        }
    )});

You can also use Json.NET's JsonProperty property to customize property serialization / deserialization:

public  class Foo 
{ 
    // Like [AliasAs (“b”)], it will be published in the form 
    [JsonProperty (PropertyName = " b " )]
     public  string Bar { get ; set ;} 
}

7.2 XML content

XML requests and responses are serialized / deserialized using System.XML.Serialization.XmlSerializer. By default, Refit will only use JSON to serialize content. To use XML content, configure ContentSerializer to use XmlContentSerializer:

var gitHubApi = RestService.For<IXmlApi>("https://www.w3.org/XML",
    new RefitSettings {
        ContentSerializer = new XmlContentSerializer()
});

Attribute serialization / deserialization can be customized using attributes in the System.Xml.serialization namespace:

public class Foo
{
   [XmlElement(Namespace = "https://www.w3.org/XML")]
   public string Bar { get; set; }
}

System.Xml.Serialization.XmlSerializer provides many serialization options, which can be set by providing XmlContentSerializer settings to the XmlContentSerializer constructor:

var gitHubApi = RestService.For<IXmlApi>("https://www.w3.org/XML",
    new RefitSettings {
        ContentSerializer = new XmlContentSerializer(
            new XmlContentSerializerSettings
            {
                XmlReaderWriterSettings = new XmlReaderWriterSettings()
                {
                    ReaderSettings = new XmlReaderSettings
                    {
                        IgnoreWhitespace = true
                    }
                }
            }
        )
});

7.3. Form posts

For APIs published in form (that is, serialized as application / x-www-form-urlencoded), please use the Initial Body property BodySerializationMethod.UrlEncoded property, the parameter can be an IDictionary dictionary, for example:

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);

If we pass the object and the field name in the request form is inconsistent, we can add [AliasAs ("you define field name")] attribute to the object attribute name, then the object field added with the attribute will be serialized into the form in the request Field:

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);

8. Set the request header

8.1 Static headers

You can set one or more static request headers for requests that apply the headers attribute to methods:

[Headers("User-Agent: Awesome Octocat App")]
[Get("/users/{user}")]
Task<User> GetUser(string user);

By applying the headers attribute to the interface, you can also add static headers to each request in the API:

[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);
}

8.2 Dynamic headers

If you need to set the contents of the header at runtime, you can add headers with dynamic values ​​to the request by applying header attributes to the 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"); 

8.3 Authorization (dynamic header redux)

The most common reason for using headers is for authorization. Most APIs now use some oAuth-style access tokens, which will expire and refresh tokens with longer lifetimes. One way to encapsulate these types of tokens is to insert a custom HttpClientHandler. There are two classes for doing this: one is AuthenticatedHttpClientHandler, which accepts a Func <Task <string >> parameter, in which a signature can be generated without having to know the request. The other is authenticatedparameteredhttpclienthandler, which accepts a Func <HttpRequestMessage, Task <string >> parameter, where the signature requires information about the request (see the previous note about the Twitter API),
for example:

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);
    }
}

or:

class AuthenticatedParameterizedHttpClientHandler : DelegatingHandler
    {
        readonly Func<HttpRequestMessage, Task<string>> getToken;
        public AuthenticatedParameterizedHttpClientHandler(Func<HttpRequestMessage, Task<string>> getToken, HttpMessageHandler innerHandler = null)
            : base(innerHandler ?? new HttpClientHandler())
        {
            this.getToken = getToken ?? throw new ArgumentNullException(nameof(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(request).ConfigureAwait(false);
                request.Headers.Authorization = new AuthenticationHeaderValue(auth.Scheme, token);
            }
            return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
        }
    }

Although HttpClient contains an almost identical method signature, it is used differently. Reinstallation does not call HttpClient.SendAsync. The HttpClientHandler must be modified instead. The usage of this class is similar to this (the example uses the ADAL library to manage automatic token refresh, but the body is used for Xamarin.Auth or any other library:

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 Task 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, whenever a method that requires authentication is called, AuthenticatedHttpClientHandler will try to obtain a new access token. Provided by the application, check the expiration time of existing access tokens, and obtain new access tokens when needed.

References:
Refit

Guess you like

Origin www.cnblogs.com/mtxcat/p/12702884.html
Recommended