Xamarin Forms Api请求开源框架Refit翻译

原文地址:https://github.com/paulcbetts/refit

用于.NET Core,Xamarin和.NET的自动类型安全的REST库,Refit是一个受Square Square Retrofit库影响的库,但它比REST API更容易:

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

RestService类生成一个使用HttpClient进行调用的IGitHubApi实现:

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

var octocat = await gitHubApi.GetUser("octocat");

兼容的平台

Refit目前支持以下平台和任何.NET Standard 1.3Taget

  • Xamarin.Android
  • Xamarin.Mac
  • Xamarin.iOS 64-bit (Unified API)
  • Desktop .NET 4.5
  • Windows Store 8.1+
  • Windows Phone 8.1 Universal Apps
  • .NET Core

以下平台不支持

  • Xamarin.iOS 32-bit

关于.NET Core

对于.NET Core支持,您必须使用csproj类型的项目托管您的Refit接口。 这是因为xproj无法执行不包含在项目文件中的编译时代码生成。 如果您使用xproj作为网站,类库或应用程序,您仍然可以通过创建一个netstandard csproj然后使用从xproj到csproj的项目到项目的引用来使用Refit。 一旦出现“VS 15”和最终的.NET Core工具,此解决方法就不需要了。

API属性

  • 每个方法都必须有一个HTTP属性,提供请求方法和相对URL。 有五个内置注释:Get,Post,Put,Delete和Head。 资源的相对URL在注释中指定。
    [Get("/users/list")]

  • 你可以在URL中指定查询参数:
    [Get("/users/list?sort=desc")]

  • 可以使用方法上的替换块和参数动态更新请求URL。 替换块是由{}包围的字母数字字符串。如果您的参数名称与URL路径中的名称不匹配,请使用AliasAs属性。

[Get("/group/{id}/users")]
Task<List<User>> GroupList([AliasAs("id")] int groupId)
  • 未指定为URL替换的参数将自动用作查询参数。 这与Retrofit不同,其中必须明确指定所有参数。参数名称和URL参数之间的比较不区分大小写,因此如果您在路径/ group/{groupid} /show中命名参数"groupId",它将正常工作。
[Get("/group/{id}/users")]
Task<List<User>> GroupList([AliasAs("id")] int groupId, [AliasAs("sort")] string sortOrder);
GroupList(4, "desc");
>>> "/group/4/users?sort=desc"
  • PS:这里就是说如果你在GroupList的签名里面使用[AliasAs("sort")] 变量类型 变量名的方式,即使原本的路径里面没有“sort"的属性,也会自动加上去。

Body内容

通过使用Body属性,方法中的一个参数可以作为Body

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

根据参数的类型,提供Body数据有四种可能:

  • 如果类型为Stream,则内容将通过StreamContent流式传输。
  • 如果类型是字符串,则该字符串将直接用作内容
  • 如果参数具有[Body(BodySerializationMethod.UrlEncoded)]属性,内容将被URL编码(见下面的Form Posts部分)
  • 对于所有其他类型,对象将被序列化为JSON。

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 });
  • 因为这些是全局设置,它们会影响整个应用程序。 将请求的设置隔离到特定的AP也许是有益的。 当创建一个Refit生成的实时界面时,您可以选择传递一个RefitSettings,这将允许您指定你想要的serializer设置。 这允许您为不同的API具有不同的序列化器设置。
var gitHubApi = RestService.For<IGitHubApi>("https://api.github.com",new RefitSettings
{
        JsonSerializerSettings = new JsonSerializerSettings {
            ContractResolver = new SnakeCasePropertyNamesContractResolver()
} } );
var otherApi = RestService.For<IOtherApi>("https://api.example.com",new RefitSettings 
{
        JsonSerializerSettings = new JsonSerializerSettings {
         ContractResolver = new CamelCasePropertyNamesContractResolver()
} } );
  • 属性序列化/反序列化可以使用Json.NET的JsonProperty属性进行定制:
public class Foo 
{
    // 像[AliasAs(“b”)]的使用将以form posts发布(见下文)
    [JsonProperty(PropertyName="b")] 
    public string Bar { get; set; }
} 

Form posts

  • 对于采用表单帖子(即序列化为应用程序/ x-www-form-urlencoded)的API,使用BodySerializationMethod.UrlEncoded初始化Body属性。参数可以是一个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);
  • 或者您可以传递任何对象,所有公共可读属性将作为请求中的表单字段序列化。 该方法允许您使用[AliasAs(“whatever”)]来别名属性名称,如果API具有隐藏字段名称,则可以帮助您:
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" 
}; 
//序列化为: v=1&tid=UA-1234-5&cid=d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c&t=event
await api.Collect(measurement);

设置请求标头

  • 静态标题

您可以为应用Headers属性的方法设置一个或多个静态请求标头:

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

也可以通过将Headers属性应用于接口,将静态标头添加到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);
}
  • 动态标题

如果头文件的内容需要在运行时设置,则可以通过将Header属性应用到参数来为请求添加具有动态值的头文件:

[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,访问令牌到期并刷新取得更长寿命的令牌。封装这些令牌用法的一种方法是,可以插入一个自定义的HttpClientHandler。举个例子:

class AuthenticatedHttpClientHandler : HttpClientHandler
{
    private readonly Func<Task<string>> getToken;
    public AuthenticatedHttpClientHandler(Func<Task<string>> getToken)
    {
        if (getToken == null) throw new ArgumentNullException("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包含几乎相同的方法签名,但使用方式不同。 HttpClient.SendAsync不被调用。 必须修改HttpClientHandler。这个类是这样使用的(例如使用ADAL库来管理Xamarin.Auth或任何其他库的自动令牌刷新:

class LoginViewModel
{
    AuthenticationContext context = new AuthenticationContext(...);
    private async Task<string> GetToken()
    {
        // 如果需要,AquireTokenAsync调用将提示用户界面
        // 否则默认使用刷新令牌返回一个有效的访问令牌
        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();
}

在上面的例子中,任何时候调用需要身份验证的方法,AuthenticatedHttpClientHandler将尝试获取一个新的访问令牌。 由应用程序提供一个,检查现有访问令牌的到期时间,并在需要时获取新的访问令牌。

  • 重新定义标题

不同于Retrofit,其中标题不会相互覆盖,并且都添加到请求中,而不管定义同一个标题的次数如何,Refit对ASP.NET MVC采用与动作过滤器相似的方法采用类似的方法 - 重新定义标题将替换 它按以下顺序排列:

  • 接口上的标题属性(最低优先级)
  • 标题属性在方法
  • 方法参数的标题属性(最高优先级)
[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:"); 
  • 删除标题

在界面或方法上定义的标题可以通过重新定义没有值的静态标题(即不使用:<value>)或为动态标题传递null来删除。 空字符串将被包括为空标题。

[Headers("X-Emoji: :rocket:")]
public interface IGitHubApi
{
    [Get("/users/list")]
    [Headers("X-Emoji")] // 删除X-Emoji标题
    Task<List> GetUsers();
    
    [Get("/users/{user}")]
    [Headers("X-Emoji:")] // 将X-Emoji标题重新定义为空
    Task<User> GetUser(string user);
   
    [Post("/users/new")]
    Task CreateUser([Body] User user, [Header("X-Emoji")] string emoji);
}

// 没有X-Emoji标题
var users = await GetUsers();
// X-Emoji: 
var user = await GetUser("octocat");
// 没有X-Emoji标题
await CreateUser(user, null); 
// X-Emoji: 
await CreateUser(user, ""); 
  • 断点上传

使用Multipart属性装饰的方法将使用多部分内容类型提交。 此时,multipart方法支持以下参数类型:

  • 字符串(参数名称将用作名称和字符串值作为值)
  • 字节数组
  • FileInfo
    参数名称将用作多部分数据中字段的名称。 这可以被AliasAs属性覆盖。要指定字节数组(byte []),Stream和FileInfo参数的文件名和内容类型,需要使用包装类。 ByteArrayPart,StreamPart和FileInfoPart。
public interface ISomeApi
{
    [Multipart]
    [Post("/users/{id}/photo")]
    Task UploadPhoto(int id, [AliasAs("myPhoto")] StreamPart stream);
}

要将Stream传递给此方法,请构建如下所示的StreamPart对象:
someApiInstance.UploadPhoto(id, new StreamPart(myPhotoStream, "photo.jpg", "image/jpeg"));
注意:此部分以前描述的AttachmentName属性已被弃用,不推荐使用它。

检索回应

请注意,在Refit中,与Retrofit不同,没有同步网络请求的选项 - 所有请求都必须通过任务或通过IObservable进行异步。 不像Retrofit,只能通过回调参数创建异步,因为我们生活在async/await未来。

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

// 如果网络呼叫失败,则会发生这种情况
await CreateUser(someUser);

如果type参数是'HttpResponseMessage'或'string',则原始响应消息或作为字符串的内容将分别返回。

// Returns the content as a string (i.e. the JSON data)
[Get("/users/{user}")]
Task<string> GetUser(string user);

//返回原始响应,作为可用于Reactive Extensions的IObservable
[Get("/users/{user}")]
IObservable<HttpResponseMessage> GetUser(string user);

使用通用接口

当使用像ASP.NET Web API这样的东西,它是一个相当普遍的模式,拥有一整套CRUD REST服务。 Refit现在允许您使用通用类型定义单个API接口:

public interface IReallyExcitingCrudApi<T, in TKey> where T : class
{
    [Post("")]
    Task<T> Create([Body] T paylod);

    [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);
}
  • 可以这样使用:

// 这里的“/ users”部分是很重要的,如果您希望它可以使用多种类型(除非每种类型都有不同的域)
var api = RestService.For<IReallyExcitingCrudApi<User, string>>("http://api.example.com/users"); 
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,185评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,652评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,524评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,339评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,387评论 6 391
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,287评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,130评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,985评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,420评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,617评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,779评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,477评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,088评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,716评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,857评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,876评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,700评论 2 354

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,654评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,099评论 25 707
  • ¥开启¥ 【iAPP实现进入界面执行逐一显】 〖2017-08-25 15:22:14〗 《//首先开一个线程,因...
    小菜c阅读 6,404评论 0 17
  • 斜风未至 雨歇天净 榴花娇艳 碧池浅草如烟漫 雾将散 人未见 小楼渐闻箫声远 速将春来探 凝神盼 蓓蕾初开 胭脂早...
    蛙声一片阅读 137评论 0 0
  • 1.CSS和JS在网页中的放置顺序是怎样的? css一般放在 标签之间,用 标签引入。JS一般放在 标签的最后,用...
    Feiyu_有猫病阅读 90评论 0 0