Swagger Open API 進階使用

Web Api 中多數都會選擇RESTful Web API 設計
自己習慣自定義識別碼,並配合POST使用,沒有完全採用Http StatusCode作為狀態識別碼
所以需要Swagger改為配合自定義識別碼的使用

環境.Net 6

使用套件

  1. Swashbuckle.AspNetCore.Annotations
  2. Swashbuckle.AspNetCore.Filters
  3. Swashbuckle

需要解決的問題有

  1. 提供範例碼
  2. 列舉值識別碼
  3. Header中擺放Token

步驟

  1. 統一改用IActionResult並配合Ok Function帶入要回傳的Response
  2. 使用FromHeader 提取Header中的Token參數
  3. 使用SwaggerResponse作為Schema呈現
  4. 使用SwaggerResponseExample提供範例碼
  5. 在Program中加入ExampleFilters、AddSwaggerExamplesFromAssemblies,才可使用範例碼呈現
  6. 在Program中加入EnumTypesSchemaFilter,才可在Schema中呈現列舉值

新增Post回應的class

    /// <summary>
    /// Post 回應
    /// </summary>
    public class PostResponse
    {
        /// <summary>
        /// 識別代碼
        /// </summary>
        public Code Code { get; set; }
        /// <summary>
        /// 訊息
        /// </summary>
        public string? Msg { get; set; }
        /// <summary>
        /// 氣象資料
        /// Code = 0 時 此欄位才有值
        /// </summary>
        public WeatherForecast? Data { get; set; }
    }
    /// <summary>
    /// 天氣預報
    /// </summary>
    public class WeatherForecast
    {
        /// <summary>
        /// 時間
        /// </summary>
        public DateTime Date { get; set; }
        /// <summary>
        /// 攝氏
        /// </summary>
        public int TemperatureC { get; set; }
        /// <summary>
        /// 華氏
        /// </summary>
        public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
        /// <summary>
        /// Summary
        /// </summary>
        public string? Summary { get; set; }
    }

列舉值狀態碼

    /// <summary>
    /// 狀態碼
    /// </summary>
    public enum Code
    {
        /// <summary>
        /// 成功
        /// </summary>
        Success= 0,
        /// <summary>
        /// 失敗
        /// </summary>
        Fail = 9999
    }

建立的範例Class

public class PostResponseExample : IExamplesProvider<PostResponse>
    {
        private static readonly string[] Summaries = new[]
        {
            "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
        };

        public PostResponse GetExamples()
        {
            return new PostResponse() { Code = Code.Success, Msg = "成功" , Data = new WeatherForecast()
            {
                Date = DateTime.Today,
                TemperatureC = Random.Shared.Next(-20, 55),
                Summary = Summaries[Random.Shared.Next(Summaries.Length)]
            } };
        }
    }

在Post Function上增加需要使用的Attribute,並在Post參數帶入中使用FromHeader(Name ="Token")

		/// <summary>
        /// 取得預報
        /// </summary>
        /// <returns></returns>
        [HttpPost]
        [SwaggerResponse(200, "成功", typeof(PostResponse))]
        [SwaggerResponseExample(200, typeof(PostResponseExample))]
        public IActionResult Post([FromHeader(Name ="Token")] string token)
        {
            return Ok(new PostResponse()
            {
                Code = Code.Success,
                Msg = "成功",
                Data = new WeatherForecast()
                {
                    Date = DateTime.Now,
                    TemperatureC = Random.Shared.Next(-20, 55),
                    Summary = Summaries[Random.Shared.Next(Summaries.Length)]
                }
            });
        }

建立EnumTypesSchemaFilter

	public class EnumTypesSchemaFilter : ISchemaFilter
    {
        private readonly XDocument? _xmlComments;
        /// <summary>
        /// EnumTypesSchemaFilter 建構式
        /// </summary>
        /// <param name="xmlPath"></param>
        public EnumTypesSchemaFilter(string xmlPath)
        {
            if (File.Exists(xmlPath))
            {
                _xmlComments = XDocument.Load(xmlPath);
            }
        }
        /// <summary>
        /// Apply
        /// </summary>
        /// <param name="schema"></param>
        /// <param name="context"></param>
        public void Apply(OpenApiSchema schema, SchemaFilterContext context)
        {
            if (_xmlComments == null) return;

            if (schema.Enum != null && schema.Enum.Count > 0 &&
                context.Type != null && context.Type.IsEnum)
            {
                schema.Description += "<p>Members:</p><ul>";
                var fullTypeName = context.Type.FullName;

                foreach (var enumMember in schema.Enum.OfType<OpenApiInteger>().
                         Select(v => v.Value))
                {
                    var fullEnumMemberName = $"F:{fullTypeName}.{Enum.ToObject(Type.GetType(fullTypeName!)!, enumMember)}";

                    var enumMemberComments = _xmlComments.Descendants("member")
                        .FirstOrDefault(m => m.Attribute("name")!.Value.Equals
                        (fullEnumMemberName, StringComparison.OrdinalIgnoreCase));

                    if (enumMemberComments == null) continue;

                    var summary = enumMemberComments.Descendants("summary").FirstOrDefault();

                    if (summary == null) continue;

                    schema.Description += $"<li><i>{enumMember}</i> : {summary.Value.Trim()}</li>";
                }

                schema.Description += "</ul>";
            }
        }
    }

Program中增加Example與EumType處理

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
    var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
    options.ExampleFilters();
    var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);

    options.IncludeXmlComments(xmlPath, true);
    options.SchemaFilter<EnumTypesSchemaFilter>(xmlPath);
});

//搭配ExampleFilters使用
builder.Services.AddSwaggerExamplesFromAssemblies(Assembly.GetEntryAssembly());

介面上多出了有使用到的Schema

打開Post後可以看到Header多出Token參數帶入欄位

打開PostResponse可以看到回傳的欄位格式以及描述

Responses中多出了預設的範例碼

結論
每次的Post成功接收後,則統一回傳Http StatusCode 200 並配合Json中的自定義識別碼,來決定該次串接的Response結果
而在Header中帶入的Token也可判別授權API的運用,如此便完成進階的使用

資料來源參考

  1. Enum列舉值
  2. Swashbuckle.AspNetCore.Filters