為控制器生成OpenAPI註釋

2023-07-04 15:00:29

非常喜歡. NET 的 /// 註釋,寫程式碼的時候就順道完成寫檔案的過程,簡直不要太爽了。 ASP. NET CORE 也是一樣的,通過 Swagger 工具,可以自動生成 API 的介面檔案(OpenAPI規範),提供給前端使用,也可以用過 APIPOST/APIFOX 之類的工具提供給前端同學直接呼叫。

生成 OpenAPI 註釋

只需要安裝 swashbuckle.aspnetcore 包,在專案上設定生成 XML 格式的註釋,並且如下設定即可自動生成 OpenAPI 的檔案,對我這個例子,可以通過 swagger/v{version}/swagger.json 存取。

            services.AddSwaggerGen(options =>
            {
                // options.CustomSchemaIds(type => type.AssemblyQualifiedName);
                var fileName = Assembly.GetExecutingAssembly().GetName().Name + ".xml";
                var filePath = Path.Combine(AppContext.BaseDirectory, fileName);

                // integrate xml comments
                options.IncludeXmlComments(filePath);
            });


                app.UseSwagger();
                app.UseSwaggerUI(
                    options =>
                    {
                        foreach (var description in app.DescribeApiVersions())
                        {
                            var url = $"/swagger/{description.GroupName}/swagger.json";
                            var name = description.GroupName.ToUpperInvariant();
                            options.SwaggerEndpoint(url, name);
                        }
                    });

註釋如下:

    /// <summary>
    /// 這個介面
    /// </summary>
    public class CoverageDataController : ODataController
	{
        /// <summary>
        /// 獲取蓋度資料
        /// </summary>
        /// <param name="key"></param>
        /// <param name="options"></param>
        /// <returns></returns>
        [HttpGet]
        [ProducesResponseType(typeof(CoverageDataDto), Status200OK)]
        public async Task<IActionResult> Get(string key, ODataQueryOptions<CoverageDataDto> options)
        {
        }
    }

生成 Tags 註釋

在使用 APIFOX 匯入 swagger.json 匯入時,我發現,對每一個 path 的註釋能夠正常顯示,但是對的控制器的註釋不能正常被識別。

檢視生成的 swagger.json,這個 CoverageData 被解釋成了 OpenAPI 的 Tags,那對應控制器的相關注釋,是需要使用另外的標註實現的,而不能直接使用///的註釋實現。

paths": {
        "/api/v{version}/CoverageData({key})": {
            "get": {
                "tags": [
                    "CoverageData"
                ],
                "summary": "獲取蓋度資料",

安裝的新的包 swashbuckle.aspnetcore.annotations,然後增加啟用語句,如下:

            services.AddSwaggerGen(options =>
            {
                // options.CustomSchemaIds(type => type.AssemblyQualifiedName);
                var fileName = Assembly.GetExecutingAssembly().GetName().Name + ".xml";
                var filePath = Path.Combine(AppContext.BaseDirectory, fileName);

                // integrate xml comments
                options.IncludeXmlComments(filePath);
                options.EnableAnnotations();
            });

在控制器的宣告上面,新增 [SwaggerTag("接受蓋度資料")] 註解:

    /// <summary>
    /// 這個介面
    /// </summary>
    [SwaggerTag("接受蓋度資料")]
    public class CoverageDataController : ODataController
	{
    }

最後生成的 swagger.json 檔案在末尾多了幾行:

    "tags": [
        {
            "name": "CoverageData",
            "description": "接受蓋度資料"
        }
    ]

Swagger 裡面就可以看到註釋了:

但是匯入到 APIFOX 中,顯示的組別名稱依然是 CoverageData ,沒有達到我想要的效果,我想將其替換成可以顯示友好的名稱。實質上是為 CoverageData 取一個別名。

注:這種方法不能與 swagger 設定的 TagActionsBy 方法的一起使用。

Tags 註解

在 ASP. NET CORE 中,可以在控制器上使用 [Tags("蓋度介面")],對控制器的組別進行標註。這樣生成的 tag 名稱直接就換成了的中文名稱。

"paths": {
        "/api/v{version}/CoverageData({key})": {
            "get": {
                "tags": [
                    "蓋度介面"
                ],
                "summary": "獲取蓋度資料",

....

    "tags": [
        {
            "name": "CoverageData",
            "description": "接受蓋度資料"
        }
    ]

但是 swagger 變得非常奇怪:

出現了兩個不同的 tag,其中 CoverageData 名稱的下面沒有從屬的 api。

如果沒有對 Tag 寫 description 的要求,那麼使用這個方案是最簡單的:設定[Tags],不要設定[SwaggerTag]。

DisplayName 註解

這麼看應該是通過 swagger 生成的 tag 與通過 [Tags] 註解生成的 tag 物件不能匹配,導致 swagger 生成的沒用被參照。

查了很久資料,說這個是一個現在的 Bug,有人通過重寫 DisplayName,在貼文中給了臨時的解決方案

  1. 先增加一個新的型別。
/// <summary>
/// Uses the [DisplayName] attribute as the Controller name in OpenAPI spec and Swagger/ReDoc UI if available else the default Controller name.
/// </summary>
public class ControllerDocumentationConvention : IControllerModelConvention
{
    void IControllerModelConvention.Apply(ControllerModel controller)
    {
        if (controller == null)
        {
            return;
        }
        
        foreach (var attribute in controller.Attributes)
        {
            if (attribute is DisplayNameAttribute displayNameAttribute && !string.IsNullOrWhiteSpace(displayNameAttribute.DisplayName))
            {
                controller.ControllerName = displayNameAttribute.DisplayName;
            }
        }
    }
}
  1. 給 Controller 設定這個命名轉換。
services.AddControllers(o =>
{
   o.Conventions.Add(new ControllerDocumentationConvention());
});
  1. 在需要調整名稱的控制器上新增 [DisplayName("targetNames")] 即可。可以看到名稱與註釋都得到的保留,最終效果如下:

匯入 APIFOX 也可以正常識別了。

參考資料