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

针对.NET Core, Xamarin以及.NET的自动类型安全Rest库: Refit本文大部分内容是针对Refit官网的翻译。官网地址:https://github.com/reactiveui/refitRefit是一个类似于Retrofit的Res…

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全家桶1年46,售后保障稳定

640?wx_fmt=jpeg

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

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

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

例如:


Jetbrains全家桶1年46,售后保障稳定

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	
}	
// ...

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/213533.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • Linux 网络环境查看命令

    Linux 网络环境查看命令

  • 线性代数之矩阵秩的求法与示例详解

    线性代数之矩阵秩的求法与示例详解线性代数之矩阵秩的求法K阶子式在m×n的矩阵A中,任取k行、k列(k小于等于m、k小于等于n),位于这些行和列交叉处的个元素,在不改变原有次序的情况下组成的矩阵叫做矩阵A的k阶子式。不难发现矩阵A有个个k阶子式。比如有矩阵A比如取第1行,第3行,第1列,第4列交叉上的元素组成的子式即为其一个2阶子式。即按照如下划线操作:即其中的一个2阶子式是:矩阵的秩设在m×n的矩阵A中有一个不等于0的r阶子式D,且所有r+1阶子式全等于0,则D是该矩阵的最高阶非零子式。非..

  • C语言 排序算法_C语言中三大经典的排序算法

    C语言 排序算法_C语言中三大经典的排序算法文章目录前言一、插入排序1.1直接插入排序1.2希尔排序二、使用步骤1.引入库2.读入数据总结前言常见的排序算法如下:一、插入排序1.1直接插入排序基本思想:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列。实际中我们玩扑克牌时,就用了插入排序的思想:当插入第i(i>=1)个元素时,前面的array[0],array[1],…,array[i-1]已经排好序,此时用array[i]的排序码与array[i-1],a

    2022年10月30日
  • 很黄很暴力国际版「建议收藏」

    很黄很暴力国际版「建议收藏」英文版:Veryeroticandveryviolent正体中文版:很黃很暴力大陆版:很黄很暴力日本版:すごくエッチで乱暴でならない荷兰版:Zeergeelenzeerhevig葡萄牙版:Muitoamareloemuitoviolento俄语版:Оченьжелтыйцветиоченьяростная法语版:Trèsjauneettrèsviolent

    2022年10月14日
  • vscode golang详细配置(持续更新)_vscode docker

    vscode golang详细配置(持续更新)_vscode docker首先安装golang开发环境,这个不用说。到这个页面下载golang的安装包https://studygolang.com/dl,再安装就可以了。一、下载并安装vscodehttps://code.visualstudio.com/二、安装Go插件在vscode插件中搜索Go的开发插件,安装后如图所示三、下载调试工具使用vscode加载go…

  • java静态内部类和非静态内部类的区别_静态内部类有什么问题

    java静态内部类和非静态内部类的区别_静态内部类有什么问题一、非静态内部类:1、变量和方法不能声明为静态的。(类的编译顺序:外部类–静态方法或属性–内部类,如果内部类声明为静态的,造成编译顺序冲突。个人理解)2、实例化的时候需要依附在外部类上面。比如:B是A的非静态内部类,实例化B,则:A.Bb=newA().newB();3、内部类可以引用外部类的静态或者非静态属性或者方法。二、静态内部类:1、属性和方法可以声明为静态的或者非静态的…

    2022年10月11日

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号