【.NETCORE】Refit 框架


Refit 是一個類型安全的 REST 開源庫,是一套基於 RESTful 架構的 .NET 客戶端實現,內部使用 HttpClient 類封裝,可通過 Refit 更加簡單安全地訪問 Web API 接口,要使用 Refit 框架,只需要在項目中通過 NuGet 包安裝器安裝即可。

Install-Package refit

使用方法很簡單:

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

  

以上方法定義一個 REST API 接口,該接口定義了 GetUser 函數,該函數通過 HTTP GET 請求去訪問服務器的 /users/{userid} 路徑並把返回的結果封裝為 User 對象返回,其中 URL 路徑中 {userid} 的值為 GetUser 函數的 userid 參數取值,然后,通過 RestService 類生成 IGitHubApi 的代理實現,通過代理直接調用 Web API 接口。

var gitHubApi = RestService.For<IGitHubApi>("https://api.xcode.me");
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<List<User>> GroupList([AliasAs("id")] int groupId)

  

值得注意的是,參數名稱和 URL 參數占位符不區分大小寫,如果一個函數參數未被 URL 占位符所使用,那么它將自動被當作 QueryString 查詢字符串來使用。

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

  

當我們調用 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<List<User>> GroupList([AliasAs("id")] int groupId, MyQueryParams param);

[Get("/group/{id}/users")]
Task<List<User>> GroupListWithAttribute([AliasAs("id")] int groupId, [Query(".","search")] MyQueryParams param);

param.SortOrder = "desc";
param.Limit = 10;

GroupList(4, param)
>>> "/group/4/users?order=desc&Limit=10"

GroupListWithAttribute(4, param)
>>> "/group/4/users?search.order=desc&search.Limit=10"

  

Collections as Querystring parameters

除了支持對象參數外,還是支持集合參數,下面是使用示例:

[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內容

通過使用 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<IGitHubApi>("https://api.xcode.me",
    new RefitSettings {
        JsonSerializerSettings = new JsonSerializerSettings {
            ContractResolver = new SnakeCasePropertyNamesContractResolver()
        }
    });

var otherApi = RestService.For<IOtherApi>("https://api.xcode.me",
    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<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"},
};

// 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<User> GetUser(string user);

  

也可以通過將 HeadersAttribute 特性應用於接口,這將影響該接口中的所有請求方法:

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

  

設置動態請求頭

如果請求頭需要在運行時設置,則可以通過將 HeaderAttribute 特性應用到函數參數,從而為請求頭添加動態值。

[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 協議通過訪問令牌授權,申請訪問令牌,即可訪問 API 接口,訪問令牌到期后需要刷新令牌,取得更長壽命的令牌,封裝這些令牌的操作,可通過自定義 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 包含幾乎相同的方法簽名, 但使用方式不同,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 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 將嘗試獲取一個新的訪問令牌。 由應用程序提供一個,檢查現有訪問令牌的到期時間,並在需要時獲取新的訪問令牌。

‎重新定義標頭‎

當定義 HTTP 標頭時,對於多次設置同名的標頭,這些重名的標頭不會相互覆蓋,都將被添加到請求頭中,值得注意的是,標頭設置的優先級不同時,重新定義標頭將被替換,它們的優先級是:

1、接口上的 Headers 特性(最低優先級),2、方法上的 Headers 特性,3、方法參數上的 Header 特性(最高優先級)

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

  

刪除標頭

當使用 HeadersAttribute 不提供值或者提供的值為 null 時, 請求表頭將被自動移除。

[Headers("X-Emoji: :rocket:")]
public interface IGitHubApi
{
    [Get("/users/list")]
    [Headers("X-Emoji")] // Remove the X-Emoji header
    Task<List> GetUsers();
    
    [Get("/users/{user}")]
    [Headers("X-Emoji:")] // Redefine the X-Emoji header as empty
    Task<User> GetUser(string user);
    
    [Post("/users/new")]
    Task CreateUser([Body] User user, [Header("X-Emoji")] string emoji);
}

// No X-Emoji header
var users = await GetUsers();

// X-Emoji: 
var user = await GetUser("octocat");

// No X-Emoji header
await CreateUser(user, null); 

// X-Emoji: 
await CreateUser(user, ""); 

  

Multipart uploads

Refit 框架也支持字節流和文件流的上傳:

public interface ISomeApi
{
    [Multipart]
    [Post("/users/{id}/photo")]
    Task UploadPhoto(int id, [AliasAs("myPhoto")] StreamPart stream);
	
	someApiInstance.UploadPhoto(id, new StreamPart(myPhotoStream, "photo.jpg", "image/jpeg"));
}

  

響應處理

為了提高性能 Refit 只支持方法返回 Task 和 IObservable 類型,如需同步請求,可使用 async 和 await 異步技術。

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

// This will throw if the network call fails
await CreateUser(someUser);

  

如果類型參數為 HttpResponseMessage 或 string 類型,可以通過重載函數來分別返回。

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

// Returns the raw response, as an IObservable that can be used with the
// Reactive Extensions
[Get("/users/{user}")]
IObservable<HttpResponseMessage> GetUser(string user);

  

使用通用接口

有的 Web API 擁有一整套基於 CRUD 操作的 REST 服務,Refit 允許您使用通用泛型定義接口:

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

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

  

可以這樣來調用以上接口封裝:

// The "/users" part here is kind of important if you want it to work for more 
// than one type (unless you have a different domain for each type)
var api = RestService.For<IReallyExcitingCrudApi<User, string>>("http://api.xcode.me/users"); 

  

Using HttpClientFactory

在 ASP.Net Core 2.1 中,可通過 Refix 框架提供的擴展方法注入類型客戶端:

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient("hello", c =>
    {
        c.BaseAddress = new Uri("http://api.xcode.me");
    })
    .AddTypedClient(c => Refit.RestService.For<IHelloClient>(c));

    services.AddMvc();
}

  

除此之外,還支持通過 HttpClientFactory 創建請求代理,Refit 為此提供擴展,用此擴展前需要通過 NuGet 引用以下包,

Install-Package Refit.HttpClientFactory

引用程序包后,可以這樣來配置:

services.AddRefitClient<IWebApi>()
        .ConfigureHttpClient(c => c.BaseAddress = new Uri("https://api.xcode.me"));
        // Add additional IHttpClientBuilder chained methods as required here:
        // .AddHttpMessageHandler<MyHandler>()
        // .SetHandlerLifetime(TimeSpan.FromMinutes(2));

  

也可以通過 RefitSettings 設置行為:

var settings = new RefitSettings(); 
// Configure refit settings here

services.AddRefitClient<IWebApi>(settings)
        .ConfigureHttpClient(c => c.BaseAddress = new Uri("https://api.xcode.me"));
        // Add additional IHttpClientBuilder chained methods as required here:
        // .AddHttpMessageHandler<MyHandler>()
        // .SetHandlerLifetime(TimeSpan.FromMinutes(2));

  

然后, 您可以使用構造函數將請求接口注入到控制器之中:

public class HomeController : Controller
{
    public HomeController(IWebApi webApi)
    {
        _webApi = webApi;
    }

    private readonly IWebApi _webApi;

    public async Task<IActionResult> Index(CancellationToken cancellationToken)
    {
        var thing = await _webApi.GetSomethingWeNeed(cancellationToken);
        return View(thing);
    }
}

  

 

原文:使用 Refit 框架訪問 REST 接口


免責聲明!

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



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