1. 程式人生 > >swagger文件轉換為WebApiClient宣告式程式碼

swagger文件轉換為WebApiClient宣告式程式碼

1 swagger簡介

Swagger是一個規範且完整的框架,提供描述、生產、消費和視覺化RESTful Web Service。其核心是使用json來規範描述RESTful介面,另外有提供UI來檢視介面說明,並有一套生成不同語言的客戶端呼叫程式碼生成器。

1.1 對Api提供者

自頂向下

使用Swagger編輯器建立Swagger定義,然後使用Swagger程式碼生成工具生成伺服器實現。

自底向上

為已有的REST API建立Swagger定義。一般的,Api提供者都會選擇這種方式,比如在asp.net裡整合swagger的支援,在寫好介面程式碼之後,訪問對應的swagger的訪問Uri地址,就可以得到swagger.json。例如:http://petstore.swagger.io/v2/swagger.json

{
  "swagger": "2.0",
  "info": {
    "tags": null,
    "description": "This is a sample server Petstore server.  You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/).  For this sample, you can use the api key `special-key` to test the authorization filters.",
    "version": "1.0.0",
    "title": "Swagger Petstore",
    "termsOfService": "http://swagger.io/terms/",
    "contact": {
      "email": "
[email protected]
" }, "license": { "name": "Apache 2.0", "url": "http://www.apache.org/licenses/LICENSE-2.0.html" } }, "host": "petstore.swagger.io", "basePath": "/v2", "tags": [ ...

1.2 對Api使用者

使用swagger UI

一些提供者的站點會提供swagger ui來檢視其swagger.json,例如:http://petstore.swagger.io/ 有了這些UI,自己手工編寫客戶端呼叫程式碼也非常簡單了。

使用Swagger Codegen

可以Swagger Codegen的將swagger.json逆向生成你需要的客戶端呼叫介面程式碼,本質上是使用了程式碼模板結合swagger.json描述來生成程式碼。在.net裡,有一個Nswag專案,可以將swagger.json生成使用HttpClient來請求介面的c#程式碼。但是這些程式碼的質量也比較差,比如以下程式碼的HttpClient的生命週期也就無法很好的維護。

/// <summary>Add a new pet to the store</summary>
/// <param name="body">Pet object that needs to be added to the store</param>
/// <exception cref="SwaggerException">A server side error occurred.</exception>
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
public async System.Threading.Tasks.Task AddPetAsync(Pet body, System.Threading.CancellationToken cancellationToken)
{
    var urlBuilder_ = new System.Text.StringBuilder();
    urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/pet");

    var client_ = new System.Net.Http.HttpClient();
    try
    {
        using (var request_ = new System.Net.Http.HttpRequestMessage())
        {
            var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value));
            content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json");
            request_.Content = content_;
            request_.Method = new System.Net.Http.HttpMethod("POST");

            PrepareRequest(client_, request_, urlBuilder_);
            var url_ = urlBuilder_.ToString();
            request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
            PrepareRequest(client_, request_, url_);

            var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
            try
            {
                var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value);
                if (response_.Content != null && response_.Content.Headers != null)
                {
                    foreach (var item_ in response_.Content.Headers)
                        headers_[item_.Key] = item_.Value;
                }

                ProcessResponse(client_, response_);

                var status_ = ((int)response_.StatusCode).ToString();
                if (status_ == "405")
                {
                    var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
                    throw new SwaggerException("Invalid input", (int)response_.StatusCode, responseData_, headers_, null);
                }
                else
                if (status_ != "200" && status_ != "204")
                {
                    var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
                    throw new SwaggerException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", (int)response_.StatusCode, responseData_, headers_, null);
                }
            }
            finally
            {
                if (response_ != null)
                    response_.Dispose();
            }
        }
    }
    finally
    {
        if (client_ != null)
            client_.Dispose();
    }
}       

2 WebApiClient.tools簡介

WebApiClient是.net平臺的一款RESTful宣告式的面向切面客戶端,其幾乎100%實現了swagger定義的規範,WebApiClient.tools.swagger旨在將swagger.json逆向生成符合WebApiClient的宣告式c#程式碼。

2.1 作用

使用原生HttpClient,你可能需要20行程式碼包裝呼叫一個介面;使用WebApiClient,你可能只需要一行程式碼來定義介面方法;使用WebApiClient + WebApiClient.tools.swagger,你一行程式碼都不用寫。

2.2 工作原理

  1. 使用NSwag解析json得到SwaggerDocument
  2. 使用RazorEngine將SwaggerDocument傳入cshtml模板編譯得到html
  3. 使用AngleSharp將html的文字程式碼提取,得到WebApiClient的宣告式程式碼
  4. 程式碼美化,輸出到本地檔案

2.3 樣例效果

介面程式碼

    /// <summary>
    /// Everything about your Pets
    /// </summary>
    [TraceFilter]
    [HttpHost("https://petstore.swagger.io/v2/")]
    public interface IPetApi : IHttpApi
    {
        /// <summary>
        /// Add a new pet to the store
        /// </summary>
        /// <param name="body">Pet object that needs to be added to the store</param>
        [HttpPost("pet")]
        ITask<HttpResponseMessage> AddPetAsync( [Required] [JsonContent] Pet body );

        /// <summary>
        /// Update an existing pet
        /// </summary>
        /// <param name="body">Pet object that needs to be added to the store</param>
        [HttpPut("pet")]
        ITask<HttpResponseMessage> UpdatePetAsync( [Required] [JsonContent] Pet body );
    
    ...

模型程式碼

    public class Pet
    {
        [AliasAs("id")]
        public long? Id { get; set; }

        [AliasAs("category")]
        public Category Category { get; set; }

        [AliasAs("name")]
        [Required(AllowEmptyStrings = true)]
        public string Name { get; set; }

        [AliasAs("photoUrls")]
        [Required]
        public List<string> PhotoUrls { get; set; } = new List<string>();
        
    ...

3 相關資源

WebApiClient

github: https://github.com/dotnetcore/WebApiClient

WebApiClient.tools

github: https://github.com/xljiulang/WebApiClient.Tools

NSwag

github: https://github.com/RSuter/NSwag

RazorEngine

github: https://github.com/Antaris/RazorEngine