尘叶心繁

使用Refit框架访问REST接口

电脑版发表于:2019/2/27 18:12

使用Refit框架访问REST接口

改装是一个类型安全的REST开源库,是一套基于RESTful架构的.NET客户端实现,内部使用HttpClient类封装,可通过

改装更加简单安全地访问Web API接口,要使用改装框架,只需要在项目中通过NuGet包安装器安装即可。

Install-Package refit

使用方法很简单:

public interface IGitHubApi{
    [Get("/users/{userid}")]    
    Task<User> GetUser(string userid);
}

以上方法定义一个REST API接口,该接口定义了GetUser函数,

该函数通过HTTP GET请求去访问服务器的/ users / {userid}路径并把返回的结果封装为用户对象返回,其中URL路径中{userid}的值为GetUser函数的userid参数取值,然后,

通过RestService类生成IGitHubApi的代理实现,通过代理直接调用Web API接口。

var gitHubApi = RestService.For<IGitHubApi>("https://api.xcode.me");
var octocat = await gitHubApi.GetUser("xcode");

API属性特性

通过属性特性标记,指定请求方法和相对URL地址,内置支持Get,Post,Put,Delete和Head方法。

[Get("/users/list")]

也可以在URL中指定查询参数:

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

方法中的URL地址可以使用占位符,占位符是由    { }   表示

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

值得注意的是,参数名称和URL参数占位符不区分大小写,如果一个函数参数未被URL占位符所使用,那么它将自动被当作QueryString查询字符串来使用。

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

当我们调用GroupList方法时,相当于请求“/ group / 4 / users?sort = desc”这个地址,其中排序参数被当作GET参数自动使用。


动态查询字符串参数

函数参数可传递对象,对象的字段属性将被自动追加到Querystring查询字符串。

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"

集合为Querystring参数

除了支持对象参数外,还是支持集合参数,下面是使用示例:

[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"

体内容

通过使用BodyAttribute特性,将函数参数追加到HTTP请求的身体部分。

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

根据参数的类型,提供Body数据有四种可能:如果类型为Stream流类型,则内容将通过StreamContent流式传输。

如果类型是字符串字符串类型,则该字符串将直接用作内容。如果参数具有

[Body(BodySerializationMethod.UrlEncoded)]属性,内容将被URL编码后使用。对于以上除外的其它类型,对象

将被序列化为JSON传输。

JSON内容

基于JSON的请求和响应,内部使用JSON.NET框架进行序列化和反序列化,默认情况下,Refit框架将使用

JsonConvert.DefaultSettings来配置序列化器的行为:

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

因为静态属性DefaultSettings是全局设置,它会影响整个应用程序,有些时候,我们希望对某些API请求使用特定序

列化设置,可以使用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()
}
});

对象属性的序列化行为可以通过JSON.NET框架本身的JsonPropertyAttribute特性定制:

public class Foo 
{
//与表单发布中的 [AliasAs("b")] 类似
[JsonProperty(PropertyName="b")]
public string Bar { get; set; }
}

表格帖子

对于采用表单提交数据(application / x-www-form-urlencoded)的API接口,使用

BodySerializationMethod.UrlEncoded初始化BodyAttribute特性,参数可以是一个IDictionary字典。

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

通过表单提交传递数据,也可以是任何对象,对象的所有公开属性和字段将被序列化,可使用AliasAs指定别名:

public interface IMeasurementProtocolApi
{
[Post("/collect")]
Task Collect([Body(BodySerializationMethod.UrlEncoded)] Measurement measurement);
}

public class Measurement
{
//属性可以是只读的,不需要[AliasAs]
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);

设置静态请求头

您可以使用HeadersAttribute特性设置一个或多个HTTP静态请求标头:

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

也可以通过将HeadersAttribute特性应用于接口,这将影响该接口中的所有请求方法:

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

设置动态请求头

如果请求头需要在运行时设置,则可以通过将HeaderAttribute特性应用到函数参数,从而为请求头添加动态值。

[Get("/users/{user}")]
Task<User> GetUser(string user, [Header("Authorization")] string authorization);

//将向请求添加标题“authorization:token oauth-token”
var user = await GetUser("octocat", "token OAUTH-TOKEN");

授权(动态请求头)

API头使用oAuth协议通过访问令牌授权,申请访问令牌,即可访问API接口,访问令牌到期后需要刷新令牌,取得更

长寿命的令牌,封装这些令牌的操作,可通过自定义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)
{
//查看请求是否具有authorize头
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);
}
}

虽然HttpClient包含几乎相同的方法签名,但使用方式不同,HttpClient.SendAsync没有被改装,必须改为修改

HttpClientHandler,像这样使用:

class LoginViewModel
{
AuthenticationContext context = new AuthenticationContext(...);

private async Task<string> GetToken()
{
//如果需要,AcquireTokenaSync调用将用UI提示
//另外silently /或使用一个回报refresh令牌
/ /有效的访问令牌
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();
}

AuthentichedHttpClientHandler将尝试获取一个新的访问令,由应用程序提供一个,检查现有访问令牌的到期时

间,并在需要时获取新的访问令牌。

重新定义标头

当定义HTTP标头时,对于多次设置同名的标头,这些重名的标头不会相互覆盖,都将被添加到请求头中,值得注意的

是,标头设置的优先级不同时,重新定义标头将被替换,它们的优先级是:


1,接口上的Headers特性(最低优先级),2,方法上的Headers特性,3,方法参数上的Header特性(最高优先级)

[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:");

删除标头

[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 headervar 
users = await GetUsers();

// X-Emoji: 
var user = await GetUser("octocat");

// No X-Emoji headerawait 
CreateUser(user, null); 

// X-Emoji: 
await CreateUser(user, "");

分段上传

Refit框架也支持字节流和文件流的上传:

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

响应处理

为了提高性能改装只支持方法返回任务和IObservable类型,如需同步请求,可使用异步和等待异步技术。

同步

IObservable<HttpResponseMessage> GetUser(string user);

使用通用接口

有的Web API拥有一整套基于CRUD操作的REST服务,改装允许您使用通用泛型定义接口:

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

可以这样来调用以上接口封装:

var api = RestService.For<IReallyExcitingCrudApi<User, string>>("http://api.xcode.me/users");

使用HttpClientFactory

在ASP.Net Core 2.1中,可通过Refix框架提供的扩展方法注入类型客户端:

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

除此之外,还支持通过HttpClientFactory创建请求代理,改装为此提供扩展,用此扩展前需要通过NuGet引用以下

包,

Install-Package Refit.HttpClientFactory

引用程序包后,可以这样来配置:

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

也可以通过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));

然后,您可以使用构造函数将请求接口注入到控制器之中:

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


本文章出自:零度之编程之美

关于TNBLOG
TNBLOG,技术分享。技术交流:群号677373950
ICP备案 :渝ICP备18016597号-1
App store Android
精彩评论
{{item.replyName}}
{{item.content}}
{{item.time}}
{{subpj.replyName}}
@{{subpj.beReplyName}}{{subpj.content}}
{{subpj.time}}
猜你喜欢