ASP.NET Core Web API Swagger 按標籤Tags分組排序顯示

2023-03-15 15:00:34

需求

  1. swagger頁面按標籤Tags分組顯示。
  2. 沒有打標籤Tags的介面,預設歸到"未分組"。
  3. 分組內按介面路徑排序

說明

為什麼沒有使用GroupName對介面進行分組?
暫時不需要,以及不想點選swagger頁面右上角那個下拉框。
當然Tags和GroupName不衝突,不影響通過GroupName再分組顯示。

如何實現

1. 為controller或action打上標籤

TagsAttribute特性可以打在controller類上,也可以打在action方法上,一個類或方法上可以打多個標籤。範例:

[Tags("標籤1", "標籤2")]

有個小坑,Tags要麼打在controller上,要麼打在action上,不能同時打。

2. 實現IDocumentFilter介面

清空原有的Tags,並按介面類或方法上的標籤重新新增Tags,然後排序。

using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using Utils;

namespace DotnetDatamining.Filters
{
    /// <summary>
    /// Workaround for https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/2162
    /// When adding XML controller descriptions to the swagger description document,
    /// controllers are listed out of alphabetical order.
    ///
    /// This filter explicitly reorders them.
    /// </summary>
    public class TagReorderDocumentFilter : IDocumentFilter
    {
        /// <summary>
        /// Allows customization of the swagger description document
        /// </summary>
        /// <param name="swaggerDoc">The generated swagger description document</param>
        /// <param name="context">Context information</param>
        public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
        {
            swaggerDoc.Tags.Clear(); //清空Tags

            //重新新增Tags
            foreach (var path in swaggerDoc.Paths)
            {
                foreach (var o in path.Value.Operations)
                {
                    foreach (var tag in o.Value.Tags)
                    {
                        swaggerDoc.Tags.Add(tag);
                    }
                }
            }

            //排序
            swaggerDoc.Tags = swaggerDoc.Tags
                .OrderBy(tag => TinyPinYinUtil.GetPinYin(tag.Name)) //按漢字拼音排序
                .ToList();
        }
    }
}

3. Swagger設定

//swagger設定
builder.Services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new OpenApiInfo
    {
        Title = "XXX",
        Version = "1.0",
        Description = "XXX"
    });
    var path = Path.Combine(AppContext.BaseDirectory, "DotnetDatamining.xml"); // xml檔案絕對路徑
    c.IncludeXmlComments(path, true); // 顯示控制器層註釋
    c.TagActionsBy(a =>
    {
        var tagAttr = a.ActionDescriptor.EndpointMetadata.OfType<TagsAttribute>().FirstOrDefault();
        if (tagAttr != null)
        {
            return tagAttr.Tags.ToList();
        }
        return new List<string>() { "未分組" };
    });
    c.DocumentFilter<TagReorderDocumentFilter>(); // Workaround: After adding XML controller descriptions, they are listed out of alphabetical order
    c.OrderActionsBy(a => a.RelativePath); // 對Action排序
});

上述程式碼說明

(1) 使用TagReorderDocumentFilter過濾器

c.DocumentFilter<TagReorderDocumentFilter>();

(2) 對Action按標籤分組

沒有打標籤Tags的介面,預設歸到"未分組"。

c.TagActionsBy(a =>
{
    var tagAttr = a.ActionDescriptor.EndpointMetadata.OfType<TagsAttribute>().FirstOrDefault();
    if (tagAttr != null)
    {
        return tagAttr.Tags.ToList();
    }
    return new List<string>() { "未分組" };
});

(2) 分組內對Action按介面路徑排序

c.OrderActionsBy(a => a.RelativePath);

效果圖

分組按漢字拼音排序,分組內按介面路徑排序

在實現過程中遇到的問題

  1. 部署到linux系統,中文拼音排序問題,不想修改linux系統設定,於是修改程式碼,通過TinyPinyin.Net庫實現。
  2. 分組排序和分組內介面排序問題
    一個介面可以打多個標籤。如果是單個標籤,可以通過OrderActionsBy對分組和介面同時排序。如果是多個標籤,則無法通過OrderActionsBy對分組和介面同時排序,只能對介面進行排序。
    可以在TagReorderDocumentFilter過濾器中對標籤進行排序,但Tags中都是controller預設的標籤,所以要先清空,再重新新增,然後再排序。

為了解決這些問題,提供一個容易閱讀和查詢的swagger檔案目錄,斷斷續續花費了很長時間才算解決。