.NET Core基礎篇之:集成Swagger文檔與自定義Swagger UI

Swagger大家都不陌生,Swagger (OpenAPI) 是一個與程式語言無關的介面規範,用於描述項目中的 REST API。它的出現主要是節約了開發人員編寫介面文檔的時間,可以根據項目中的注釋生成對應的可視化介面文檔。

OpenAPI 規範 (openapi.json)

OpenAPI 規範是描述 API 功能的文檔。該文檔基於控制器和模型中的 XML屬性注釋。它是 OpenAPI 流的核心部分,用於驅動諸如 SwaggerUI 之類的工具。

image

.NET 平台下的兩個主要實現Swagger的包是 SwashbuckleNSwag。今天我們從 Swashbuckle 開始了解。

基礎功能

1、在包管理器搜索Swashbuckle.AspNetCore並安裝。

image

2、在Startup.cs文件內的ConfigureServices方法內添加程式碼。
public void ConfigureServices(IServiceCollection services)
{
	services.AddControllers();
	services.AddSwaggerGen();
}
3、在Startup.cs文件內的Configure方法內添加程式碼。
app.UseSwagger();
app.UseSwaggerUI(options =>
{
	options.SwaggerEndpoint("/swagger/v1/swagger.json", "v1");
	options.RoutePrefix = string.Empty;
});
4、修改項目的launchSettings.json文件,將launchUrl的值改為:index.html
5、準備介面
    [ApiController]
    public class HomeController : ControllerBase
    {
        private readonly ILogger<HomeController> _logger;
        public HomeController(ILogger<HomeController> logger)
        {
            _logger = logger;
        }

        /// <summary>
        /// 獲取用戶資訊
        /// </summary>
        /// <returns></returns>
        [HttpGet("home/getuser")]
        public string GetUser()
        {
            return "my name is dotnetboy";
        }

        /// <summary>
        /// 登錄成功
        /// </summary>
        /// <returns></returns>
        [HttpPost("home/login")]
        public string Login()
        {
            return "login success";
        }

        /// <summary>
        /// 刪除用戶
        /// </summary>
        [HttpDelete("home/{id}")]
        public string DeleteUser(string id)
        {
            return $"delete success,id={id}";
        }
    }
6、開啟xml文檔輸出然後啟動項目

image

image

擴展功能

項目描述

services.AddSwaggerGen(options =>
{
    options.SwaggerDoc("v1", new OpenApiInfo
    {
    	Title = "測試介面文檔",
    	Version = "v1",
    	Description = "測試 webapi"
    });
});

介面分組

在實際開發中,如果所有介面都展示在一起非常不利於相關人員查找,我們可以根據業務邏輯對相關介面進行分組,比如:登錄、用戶、訂單、商品等等。

1、準備分組資訊特性
/// <summary>
/// 分組資訊特性
/// </summary>
public class GroupInfoAttribute : Attribute
{
    /// <summary>
    /// 標題
    /// </summary>
    public string Title { get; set; }
    /// <summary>
    /// 版本
    /// </summary>
    public string Version { get; set; }
    /// <summary>
    /// 描述
    /// </summary>
    public string Description { get; set; }
}
2、準備分組枚舉
/// <summary>
/// 介面分組枚舉
/// </summary>
public enum ApiGroupNames
{
    [GroupInfo(Title = "登錄認證", Description = "登錄相關介面", Version = "v1")]
    Login,
    [GroupInfo(Title = "User", Description = "用戶相關介面")]
    User,
    [GroupInfo(Title = "User", Description = "訂單相關介面")]
    Order
}
3、準備介面特性
/// <summary>
/// 分組介面特性
/// </summary>
public class ApiGroupAttribute : Attribute, IApiDescriptionGroupNameProvider
{
    /// <summary>
    /// 
    /// </summary>
    /// <param name="name"></param>
    public ApiGroupAttribute(ApiGroupNames name)
    {
        GroupName = name.ToString();
    }
    /// <summary>
    /// 分組名稱
    /// </summary>
    public string GroupName { get; set; }
}
4、給不同介面加上特性
[ApiController]
public class HomeController : ControllerBase
{
   private readonly ILogger<HomeController> _logger;
   public HomeController(ILogger<HomeController> logger)
    {
        _logger = logger;
    }
    /// <summary>
    /// 獲取用戶資訊
    /// </summary>
    /// <returns></returns>
    [HttpGet("home/getuser")]
    [ApiGroup(ApiGroupNames.User)]
    public string GetUser()
    {
        return "my name is dotnetboy";
    }
    /// <summary>
    /// 登錄成功
    /// </summary>
    /// <returns></returns>
    [HttpPost("home/login")]
    [ApiGroup(ApiGroupNames.Login)]
    public string Login()
    {
        return "login success";
    }
    /// <summary>
    /// 刪除訂單
    /// </summary>
    [HttpDelete("home/{id}")]
    [ApiGroup(ApiGroupNames.Order)]
    public string DeleteOrder(string id)
    {
        return $"delete success,id={id}";
    }
    /// <summary>
    /// 留言
    /// </summary>
    [HttpDelete("home/message")]
    public string DeleteUser(string msg)
    {
        return $"message:{msg}";
    }
}
5、修改 ConfigureServices 方法的 AddSwaggerGen
services.AddSwaggerGen(options =>
{
    options.SwaggerDoc("v1", new OpenApiInfo
    {
        Title = "介面文檔",
        Version = "v1",
        Description = "測試 webapi"
    });

	// 遍歷ApiGroupNames所有枚舉值生成介面文檔,Skip(1)是因為Enum第一個FieldInfo是內置的一個Int值
	typeof(ApiGroupNames).GetFields().Skip(1).ToList().ForEach(f =>
	{
        //獲取枚舉值上的特性
        var info = f.GetCustomAttributes(typeof(GroupInfoAttribute), false).OfType<GroupInfoAttribute>().FirstOrDefault();
        options.SwaggerDoc(f.Name, new OpenApiInfo
        {
            Title = info?.Title,
            Version = info?.Version,
            Description = info?.Description
        });
	});
    // 沒有特性的介面分到NoGroup上
    options.SwaggerDoc("NoGroup", new OpenApiInfo
    {
    	Title = "無分組"
    });
    // 判斷介面歸於哪個分組
    options.DocInclusionPredicate((docName, apiDescription) =>
    {
    	if (docName == "NoGroup")
    	{
            // 當分組為NoGroup時,只要沒加特性的介面都屬於這個組
            return string.IsNullOrEmpty(apiDescription.GroupName);
    	}
    	else
    	{
    		return apiDescription.GroupName == docName;
    	}
    });
});
6、修改 Configure 方法的 UseSwaggerUI
app.UseSwaggerUI(options =>
{
    // 遍歷ApiGroupNames所有枚舉值生成介面文檔
    typeof(ApiGroupNames).GetFields().Skip(1).ToList().ForEach(f =>
    {
        //獲取枚舉值上的特性
        var info = f.GetCustomAttributes(typeof(GroupInfoAttribute), false).OfType<GroupInfoAttribute>().FirstOrDefault();
        options.SwaggerEndpoint($"/swagger/{f.Name}/swagger.json", info != null ? info.Title : f.Name);
    });
    options.SwaggerEndpoint("/swagger/NoGroup/swagger.json", "無分組");
    options.RoutePrefix = string.Empty;
});

image

自定義UI

前幾天,前端同事和我吐槽,Swagger的原生UI太丑了,又不夠直觀,想找個介面還得一個個收縮展開,總之就是很難用。

  1. 不夠直觀
  2. 不方便查找

有了上面的兩點需求何不自己實現一套UI呢?(最終還是用了第三方現成的)

文章最開始有提到OpenAPI 對應的 json 內容,大家也可以在瀏覽器的控制台看看,swagger ui 的數據源都來自於一個叫 swagger.json 的文件,數據源都有了,根據數據源再做一套 UI 也就不是什麼難事了。

image

1、準備一個美觀的單頁面(網上找的)

image

2、將單頁面相關內容放到項目內(記得開啟靜態文件讀取)
app.UseStaticFiles();

image

3、將單頁面指定為 UI 頁面。
app.UseSwaggerUI(options =>
{
	options.IndexStream = () => GetType().Assembly.GetManifestResourceStream("h.swagger.Swagger.index.html");
});
4、在單頁面內處理 swagger.json 數據源。
5、最終效果

image

image

Swagger UI的功能還是比較多的,比如:詳情調試。如果想自己實現一套UI要做的工作還很多。所以,拿來主義永不過時,最終我還是選擇了第三方開源的項目:Knife4j

使用起來也是非常簡單,先引用包:IGeekFan.AspNetCore.Knife4jUI,然後在Startup.Configure中將 app.UseSwaggerUI 替換為:

image

app.UseKnife4UI(c =>
{
	c.RoutePrefix = string.Empty;
	c.SwaggerEndpoint($"/swagger/v1/swagger.json", "h.swagger.webapi v1");
});

image