本文大部分内容是针对Refit官网的翻译。

官网地址: https://github.com/reactiveui/refit

Refit是一个类似于Retrofit的Restful Api库,使用它,你可以将你的Restful Api定义在接口中。

例如:

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

这里RestService类生成了一个IGitHubApi接口的实现,它使用HttpClient来进行api调用。

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

Refit可以在哪些地方使用?

当前Refit支持一下平台。

•UWP•Xamarin.Android•Xamarin.Mac•Xamarin.iOS•Desktop .NET 4.6.1•.NET Core

.NET Core的注意事项:

对于.NET Core的构建时支持(Build-Time support), 你必须使用.NET Core 2.x SDK。你可以针对所有的支持平台构建你的库,只要构建时使用2.x SDK即可。

API属性

基本用法

针对每个方法都必须提供一个HTTP属性,这个属性指定了请求的方式和相关的URL。这里有6种内置的批注:Get, Post, Put, Delete, Patch和Head。在批注中需要指定资源对应的URL。

[Get("/users/list")]

你同样可以指定URL中的查询字符串。

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

动态URL

你还可以使用可替换块(replacement block)和方法参数创建动态URL。这里可替换块是一个被大括号包裹的字符串变量。

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

URL中没有指定的参数,就会自动作为URL的查询字符串。这与Retrofit不同,在Retrofit中所有参数都必须显示指定。

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

这里当调用GroupList(4, "desc");方法时,调用API会是"/group/4/users?sort=desc"

回转路由语法

回转路由参数语法:使用双星号的捕获所有参数(catch-all parameter)且不会对"/"进行编码,

在生成链接的过程, 路由系统将编码双星号捕获的全部参数(catch-all parameter),而不会编码"/"。

[Get("/search/{**page}")]
Task<List<Page>> Search(string page);

回转路由参数必须是字符串

这里当调用Search("admin/products");时,生成的连接是"/search/admin/products"

动态查询字符串参数

当你指定一个对象作为查询参数的时候,所有非空的public属性将被用作查询参数。使用Query特性将改变默认的行为,它会扁平化你的查询字符串对象。如果使用Query特性,你还可以针对扁平化查询字符串对象添加指定的分隔符和前缀。

例:

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)
//结果 "/group/4/users?order=desc&Limit=10"   GroupListWithAttribute(4, params)
//结果 "/group/4/users?search.order=desc&search.Limit=10"

集合作为查询字符串参数

Query特性同样可以指定查询字符串中应该如何格式化集合对象。

例:

[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特性将参数中的一个标记为正文内容。

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

这里Refit支持4种请求体数据

•如果正文内容类型是Stream, 其内容会包裹在一个StreamContent对象中。•如果正文内容类型是string, 其内容会直接用作正文内容。当指定当前参数拥有特性[Body(BodySerializationMethod.Json)]时,它会被包裹在一个StringContent对象中。•如果当前参数拥有特性[Body(BodySerializationMethod.UrlEncoded)], 其内容会被URL编码。•针对其他类型,当前指定的参数会被默认序列化成JSON。

缓冲及Content-Header头部设置

默认情况下,Refit会流式传输正文内容,而不会缓冲它。这意味着,你可以从磁盘流式传输文件,而不产生将整个文件加载到内存中的开销。这样做的缺点是,请求头部没有设置Content-Length。如果你的API需要发送一个请求并指定Content-Length请求头,则需要将Body特性的buffered参数设置为true。

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

Json内容

JSON请求和响应可以使用Json.NET来序列化和反序列化,默认情况下,Refit会使用Newtonsoft.Json.JsonConvert.DefaultSettings的默认序列化配置。

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

因为默认设置是全局设置,它会影响你的整个应用。所以这里我们最好使用针对特定API使用独立的配置。当使用Refit生成一个接口对象的时候,你可以传入一个RefitSettings参数,这个参数可以指定你使用的JSON序列化配置。

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

针对自定义属性的序列化和反序列化,我们同样可以使用Json.NET的JsonProperty属性。

public class Foo
{   // Works like [AliasAs("b")] would in form posts (see below)  [JsonProperty(PropertyName="b")]     public string Bar { get; set; }
} 

Xml内容

针对XML请求和响应的序列化和反序列化,Refit使用了System.Xml.Serialization.XmlSerializer。默认情况下, Refit会使用JSON内容序列化器,如果想要使用XML内容序列化器,你需要将RefitSettingContentSerializer属性指定为XmlContentSerializer

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

我们同样可以使用System.Xml.Serialization命名空间下的特性,自定义属性的序列化和反序列化。

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

System.Xml.Serialization.XmlSerializer提供了多种序列化方式,你可以通过在XmlContentSerialier对象的构造函数中指定一个XmlContentSerializerSettings 对象类进行配置。

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

表单Post

针对采用表单Post的API( 正文会被序列化成application/x-www-form-urlencoded ), 我们可以将指定参数的正文特性指定为BodySerializationMethod.UrlEncoded

这个参数可以是字典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"},
};  // 序列化为: v=1&tid=UA-1234-5&cid=d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c&t=event
await api.Collect(data);

当然参数也可以是一个普通对象,Refit会将对象中所有public, 可读取的属性序列化成表单字段。当然这里你可以使用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"
};  // 序列化为: v=1&tid=UA-1234-5&cid=d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c&t=event
await api.Collect(measurement);

如果当前属性同时指定了[JsonProperty(PropertyName)] 和AliasAs(), Refit会优先使用AliasAs() 中指定的名称。这意味着,以下类型会被序列化成one=value1&two=value2

public class SomeObject
{   [JsonProperty(PropertyName = "one")] public string FirstProperty { get; set; }   [JsonProperty(PropertyName = "notTwo")]  [AliasAs("two")]  public string SecondProperty { get; set; }
}

注意: AliasAs只能应用在请求参数和Form正文Post中,不能应用于响应对象。如果要为响应对象属性起别名,你依然需要使用[JsonProperty("full-property-name")]

设置请求Header

静态头

你可以使用Headers特性指定一个或多个静态的请求头。

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

为了简便使用,你也可以将Headers特性放在接口定义上,从而使当前接口中定义的所有Rest请求都添加相同的静态头。

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

动态头

如果头部内容需要在运行时动态设置,你可以在方法签名处,使用Header特性指定一个动态头部参数,你可以在调用Api时,为这个参数指定一个dynamic类型的值,从而实现动态头。

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

授权(动态头的升级版)

使用请求头的最常见场景就是授权。当今绝大多数的API都是使用OAuth, 它会提供一个带过期时间的access token和一个负责刷新access token的refresh token。

为了封装这些授权令牌的使用,我们可以自定义一个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);  }
}

虽然HttpClient包含了几乎相同的方法签名,但是它的使用方式不同。Refit不会调用HttpClient.SendAsync方法,这里必须使用自定义的HttpClientHandler替换它。

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

在以上代码中,当任何需要身份验证的的方法被调用的时候,AuthenticatedHttpClientHandler会尝试获取一个新的access token。 这里程序会检查access token是否到期,并在需要时获取新的令牌。

分段上传

当一个接口方法被指定为[Multipart], 这意味着当前Api提交的内容中包含分段内容类型。针对分段方法,Refit当前支持一下几种参数类型

•字符串•二进制数组•Stream流•FileInfo

这里参数名会作为分段数据的字段名。当然你可以用AliasAs特性复写它。

为了给二进制数组,Stream流以及FileInfo参数的内容指定文件名和内容类型,我们必须要使用封装类。Refit中默认的封装类有3种,ByteArrarPartStreamPartFileInfoPart

public interface ISomeApi
{   [Multipart] [Post("/users/{id}/photo")]   Task UploadPhoto(int id, [AliasAs("myPhoto")] StreamPart stream);
}

为了将一个Stream流对象传递给以上定义的方法,我们需要构建一个StreamObject对象:

someApiInstance.UploadPhoto(id, new StreamPart(myPhotoStream, "photo.jpg", "image/jpeg"));

异常处理

为了封装可能来自服务的任何异常,你可以捕获包含请求和响应信息的ApiException。 Refit还支持捕获由于不良请求而引发的验证异常,以解决问题详细信息。 有关验证异常的问题详细信息的特定信息,只需捕获ValidationApiException

// ...
try
{   var result = await awesomeApi.GetFooAsync("bar");
}
catch (ValidationApiException validationException)
{   // handle validation here by using validationException.Content,     // which is type of ProblemDetails according to RFC 7807
}
catch (ApiException exception)
{   // other exception handling
}
// ...

针对.NET Core, Xamarin以及.NET的自动类型安全Rest库: Refit相关推荐

  1. SAP WM 针对采购订单收货时候不能自动获取物料主数据里的Special Movement Indicator?

    SAP WM 针对采购订单收货时候不能自动获取物料主数据里的Special Movement Indicator? SAP WM模块里有一个标记叫做Special Movement Indicator ...

  2. 好东西要分享,PCB自动生成元件库和封装库的方法

    到http://www.ti.com.cn/下载相应元件的.bxl 文件: Ultra Librarian 的安装和使用: https://blog.csdn.net/likai_lian/artic ...

  3. python安装缺失_python: 自动安装缺失库文件的方法

    python: 自动安装缺失库文件的方法 Method 通过一条指令即可完成: os.system('所需指令') Note: os.system('所需指令') 还可以完成许多其他任务,非常强大. ...

  4. 自动类型安全的.NET标准REST库refit

    在SCOTT HANSELMAN 博客上看到一个好东西<Exploring refit, an automatic type-safe REST library for .NET Standar ...

  5. 自动类型安全的REST .NET标准库refit

    在SCOTT HANSELMAN 博客上看到一个好东西<Exploring refit, an automatic type-safe REST library for .NET Standar ...

  6. .NET 5中的EF Core 5数据迁移:在单独的库中并自动部署

    目录 1.简介 2.先决条件 3.使用Dotnet CLI创建解决方案和项目框架 4.在DataAcess中创建模型和数据上下文 5.在WebApp中启动数据库 6.添加数据迁移功能 7.更改数据库模 ...

  7. .NET Core 如何禁止.resx文件自动生成Designer.cs

    点击上方蓝字关注"汪宇杰博客" 在 Visual Studio 中,如果我们在一个 .NET Core 工程里加入了一个资源文件(.resx),那么你会发现有个对应的 .Desig ...

  8. 浅析 .Net Core中Json配置的自动更新

    Pre 很早在看 Jesse 的Asp.net Core快速入门的课程的时候就了解到了在Asp .net core中,如果添加的Json配置被更改了,是支持自动重载配置的,作为一名有着严重" ...

  9. python编写一个简单的程序验证码_针对验证码,做一个简单的自动网上签到程序(一)...

    现在签到改成网络签到,比较麻烦,总是需要登陆再签,所以想着做个自动登录并签到的东西,看了看,其他的问题都不大,登录的东西很简单,就是post下就可以了. 查了查,主要问题在验证码部分,看了看,有几种途 ...

最新文章

  1. java-number2
  2. Matlab的File菜单功能图解 - 导入数据、保存工作空间、搜索路径、系统参数
  3. python中的协程:greenlet和gevent
  4. cassandra生产监控_碎玻璃:诊断生产Cassandra问题
  5. mysql8.0创建属性_MySQL8.0新特性——资源管理
  6. 2017.8.14 文本生成器 失败总结
  7. FPGA异步复位设计代码
  8. Oracle的锁表与解锁
  9. 年报掘金:机构增仓路线图曝光(2010-03-06转载)
  10. Linux内核移植入门
  11. php公众号开发 点菜,微信公众号点餐系统怎么弄 微信点餐系统怎么开发
  12. IOMeter存储测试工具
  13. sql 2000 数据库置疑
  14. 基地树洞 | 自动化小系列之番外篇
  15. 《考研-数据结构-哈弗曼树-已知某段通信报文内容,对该报文进行哈弗曼编码,并计算平均码长》
  16. 乐学Python作业题
  17. x86架构和arm架构的cpu简图
  18. 以一元及二元函数为例,通过多项式的函数图像观察其拟合性能;以及对用多项式作目标函数进行机器学习时的一些理解。
  19. 看完这篇,黑苹果驱动VoodooI2C编译打包所有错误全搞定
  20. R语言patchwork包将多个可视化结果组合起来、使用plot_annotation函数以及tag_level参数将组合图用大写字母进行顺序编码、为组合图的标签添加自定义前缀信息

热门文章

  1. SpringBoot 内置 Tomcat 线程数优化配置,你学会了吗?
  2. mac idea 和系统常用快捷键
  3. 「MOSS - 00」MOSS队:团队介绍
  4. 操作系统基础知识总结
  5. ES6、ES7、ES8、ES9、ES10、ES11新增特性一览-介绍
  6. 两台电脑其中一台无法ping通的问题
  7. FPGA基础知识极简教程(2)抛却软件思维去设计硬件电路
  8. 时间日期格式转换大全
  9. 2022秋冬穿搭趋势!小红书榜单,挖掘4大时髦模式
  10. stm32-mini学习笔记-LCD-TFTLCD原理与配置介绍