Xamarin Forms Api請求開源框架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"); 





免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM