Refit 是一个类型安全的 REST 开源库,是一套基于 RESTful 架构的 .NET 客户端实现,内部使用 HttpClient 类封装,可通过 Refit 更加简单安全地访问 Web API 接口,要使用 Refit 框架,只需要在项目中通过 NuGet 包安装器安装即可。
Install-Package refit
使用方法很简单:
public interface IGitHubApi
{
【Get("/users/{userid}")】
Task GetUser(string userid);
}
以上方法定义一个 REST API 接口,该接口定义了 GetUser 函数,该函数通过 HTTP GET 请求去访问服务器的 /users/{userid} 路径并把返回的结果封装为 User 对象返回,其中 URL 路径中 {userid} 的值为 GetUser 函数的 userid 参数取值,然后,通过 RestService 类生成 IGitHubApi 的代理实现,通过代理直接调用 Web API 接口。
var gitHubApi = RestService.For("");
var octocat = await gitHubApi.GetUser("xcode");
API Attributes特性
通过 Attribute 特性标记,指定请求方法和相对 URL 地址,内置支持 Get、Post、Put、Delete 和 Head 方法。
【Get("/users/list")】
也可以在 URL 中指定查询参数:
【Get("/users/list?sort=desc")】
方法中的 URL 地址可以使用占位符,占位符是由 {} 包围的字符串,如果函数参数与 URL 路径中的占位符名称不同,可使用 AliasAs 指定别名。
【Get("/group/{id}/users")】
Task
值得注意的是,参数名称和 URL 参数占位符不区分大小写,如果一个函数参数未被 URL 占位符所使用,那么它将自动被当作 QueryString 查询字符串来使用
【Get("/group/{id}/users")】
Task
当我们调用 GroupList 方法时,相当于请求 "/group/4/users?sort=desc"这个地址,其中 sort 参数被当作 GET 参数自动使用。
Dynamic Querystring Parameters
函数参数可传递对象,对象的字段属性将被自动追加到 Querystring 查询字符串。
public class MyQueryParams
{
【AliasAs("order")】
public string SortOrder { get; set; }
public int Limit { get; set; }
}
【Get("/group/{id}/users")】
Task
【Get("/group/{id}/users")】
Task
param.SortOrder = "desc";
param.Limit = 10;
GroupList(4, param)
GroupListWithAttribute(4, param)
Collections as Querystring parameters
除了支持对象参数外,还是支持集合参数,下面是使用示例:
【Get("/users/list")】
Task Search(【Query(CollectionFormat.Multi)】int【】 ages);
Search(new 【】 {10, 20, 30})
【Get("/users/list")】
Task Search(【Query(CollectionFormat.Csv)】int【】 ages);
Search(new 【】 {10, 20, 30})
Body内容
通过使用 BodyAttribute 特性,将函数参数追加到 HTTP 请求的 Body 部分。
【Post("/users/new")】
Task CreateUser(【Body】 User user);
根据参数的类型,提供 Body 数据有四种可能:如果类型为 Stream 流类型,则内容将通过 StreamContent 流式传输。如果类型是 String 字符串类型,则该字符串将直接用作内容。如果参数具有 【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("",
new RefitSettings {
JsonSerializerSettings = new JsonSerializerSettings {
ContractResolver = new SnakeCasePropertyNamesContractResolver()
}
});
var otherApi = RestService.For("",
new RefitSettings {
JsonSerializerSettings = new JsonSerializerSettings {
ContractResolver = new CamelCasePropertyNamesContractResolver()
}
});
对象属性的序列化行为可以通过 JSON.NET 框架本身的 JsonPropertyAttribute 特性定制:
public class Foo
{
// Works like 【AliasAs("b")】 would in form posts (see below)
【JsonProperty(PropertyName="b")】
public string Bar { get; set; }
}
Form posts
对于采用表单提交数据(application/x-www-form-urlencoded)的 API 接口,使用 BodySerializationMethod.UrlEncoded 初始化 BodyAttribute 特性,参数可以是一个 IDictionary 字典。
public interface IMeasurementProtocolApi
{
【Post("/collect")】
Task Collect(【Body(BodySerializationMethod.UrlEncoded)】 Dictionary[span style="color: rgba(0, 0, 255, 1)">string, object
}
var data = new Dictionary[span style="color: rgba(0, 0, 255, 1)">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
{
// 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);
设置静态请求头
您可以使用 HeadersAttribute 特性设置一个或多个 HTTP 静态请求标头:
【Headers("User-Agent: Awesome Octocat App")】
【Get("/users/{user}")】
Task GetUser(string user);
也可以通过将 HeadersAttribute 特性应用于接口,这将影响该接口中的所有请求方法:
【Headers("User-Agent: Awesome Octocat App")】
public interface IGitHubApi
{
【Get("/users/{user}")】
Task GetUser(string user);
【Post("/users/new")】
Task CreateUser(【Body】 User user);
}
设置动态请求头
如果请求头需要在运行时设置,则可以通过将 HeaderAttribute 特性应用到函数参数,从而为请求头添加动态值。
【Get("/users/{user}")】
Task 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");
授权(动态请求头)
标头最常见的用途是授权,今天,大多数 API 都使用 oAuth 协议通过访问令牌授权,申请访问令牌,即可访问 API 接口,访问令牌到期后需要刷新令牌,取得更长寿命的令牌,封装这些令牌的操作,可通过自定义 HttpClientHandler 来实现:
class AuthenticatedHttpClientHandler : HttpClientHandler
{
private readonly Funcstring getToken;
public AuthenticatedHttpClientHandler(Funcstring getToken)
{
if (getToken == null) throw new ArgumentNullException(nameof(getToken));
this.getToken = getToken;
}
protected //代码效果参考:http://www.jhylw.com.cn/112822701.html
override async Task 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);
}
}
虽然 HttpClient 包含几乎相同的方法签名, 但使用方式不同,HttpClient.SendAsync 没有被改装,必须改为修改 HttpClientHandler,像这样使用:
class //代码效果参考:http://www.jhylw.com.cn/285424335.html
LoginViewModel{
AuthenticationContext context = new AuthenticationContext(...);
private async Task[span style="color: rgba(0, 0, 255, 1)">string
{
// 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("", "clientId", new Uri(""));
return token;
}
public async void LoginAndCallApi()
{
var api = RestService.For(new HttpClient(new AuthenticatedHttpClientHandler(GetToken)) { BaseAddress = new Uri("