Asp.Net Core遇到Swagger(四)-Swashbuckle技巧c篇
1
前言
接Swashbuckle技巧b篇,與Action相關(guān)的配置和操作,此處為c篇內(nèi)容。

2
實(shí)踐技巧
2.1 忽略過(guò)時(shí)控制器和過(guò)時(shí)Action
1)配置服務(wù)
編輯ConfigureServices函數(shù)中的AddSwaggerGen函數(shù):
services.AddSwaggerGen(c =>
{
...
#region 自定義DocInclusionPredicate判定規(guī)則
//options.DocInclusionPredicate((docName, apiDesc) => {
// //判定當(dāng)前執(zhí)行是否為函數(shù)對(duì)象
// if (!apiDesc.TryGetMethodInfo(out MethodInfo methodInfo))
// return false;
// //獲取函數(shù)對(duì)應(yīng)的自定義特性ApiVersionAttribute對(duì)應(yīng)的版本集合
// var versions = methodInfo.GetCustomAttributes(true)
// .OfType<ApiVersionAttribute>()
// .SelectMany(x => x.Versions);
// //判定版本集合中是否與當(dāng)前文檔匹配
// return versions.Any(x => $"v{x}" == docName);
//});
#endregion
#region 忽略過(guò)時(shí)特性
//忽略標(biāo)記過(guò)時(shí)的Action
options.IgnoreObsoleteActions();
//忽略標(biāo)記過(guò)時(shí)的Properties
options.IgnoreObsoleteProperties();
#endregion
...
};
2)添加案例控制器
新建控制器IgnoreObsoleteController,內(nèi)容如下:
namespace swaggertestbase.Controllers.v1
{
/// <summary>
/// 忽略函數(shù)控制器
/// </summary>
[Route("api/[controller]")]
[ApiController]
public class IgnoreObsoleteController : ControllerBase
{
/// <summary>
/// 獲取忽略函數(shù)數(shù)據(jù)
/// </summary>
/// <returns>返回結(jié)果</returns>
[Obsolete]
[HttpGet]
public string Get()
{
return "IgnoreObsolete";
}
[HttpGet("{id}")]
//直接直接使用ApiExplorerSettings設(shè)置屬性IgnoreApi = true
//在當(dāng)前項(xiàng)目中該配置無(wú)效
[ApiExplorerSettings(IgnoreApi = true)]
public string GetById(int id)
{
return "IgnoreObsolete";
}
}
}
運(yùn)行效果如下,在v1組中并不存在對(duì)應(yīng)的控制器以及對(duì)應(yīng)過(guò)時(shí)的函數(shù)。

2.2 按照約定選擇Action函數(shù)
1)自定義約定實(shí)現(xiàn)類
新建類ApiExplorerGetsOnlyConvention,實(shí)現(xiàn)IActionModelConvention接口,進(jìn)行ActionModelConvention的自定義規(guī)則實(shí)現(xiàn)。
/// <summary>
/// 自定義實(shí)現(xiàn)只顯示Get請(qǐng)求的Action模型
/// </summary>
public class ApiExplorerGetsOnlyConvention : IActionModelConvention
{
public void Apply(ActionModel action)
{
//依據(jù)請(qǐng)求是否為get請(qǐng)求處理是是否參與apijson的數(shù)據(jù)接口生成
action.ApiExplorer.IsVisible = action.Attributes.OfType<HttpGetAttribute>().Any();
}
}
2)添加約定到服務(wù)配置
services.AddControllers(configure => {
//添加自定義apiexplor.groupname控制器模型屬性
//configure.Conventions.Add(new ApiExplorerGroupVersionConvention());
//自定義實(shí)現(xiàn)只顯示Get請(qǐng)求的Action模型
configure.Conventions.Add(new ApiExplorerGetsOnlyConvention());
});
在控制器WeatherForecastController中添加一個(gè)Post請(qǐng)求函數(shù)。
/// <summary>
/// 獲取Post數(shù)據(jù)
/// </summary>
/// <returns>處理結(jié)果</returns>
[HttpPost]
public string Post()
{
return "處理結(jié)果";
}
運(yùn)行效果:

2.3 自定義操作標(biāo)簽
默認(rèn)Tag分組是以控制器名稱為依據(jù)進(jìn)行劃分,也可以通過(guò)TagActionsBy函數(shù)進(jìn)行配置。
1)默認(rèn)分組

2)自定義Tags分組
修改ConfigureServices中AddSwaggerGen對(duì)應(yīng)配置,以下為依據(jù)Action的請(qǐng)求方法進(jìn)行分組的案例,需要注意的是IncludeXmlComments函數(shù),需要使用默認(rèn)配置,而不是設(shè)置IncludeXmlComments第二參數(shù)為true。
services.AddSwaggerGen(options =>
{
...
#region 添加xml注釋文件描述性信息
//獲取當(dāng)前執(zhí)行程序集名稱+.xml,作為實(shí)際xml注釋文件名稱
string filename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
//拼接路徑-路徑間隔符由系統(tǒng)決定
string path = System.IO.Path.Combine(System.AppContext.BaseDirectory, filename);
//添加xml注釋文件到swaggergen中用于生成api json
//此時(shí)需要選擇使用該函數(shù)的默認(rèn)配置
options.IncludeXmlComments(path);
#endregion
#region 自定義Tags
options.TagActionsBy(apidescr => {
return new string[] {
apidescr.HttpMethod
};
});
#endregion
...
};
services.AddControllers(configure => {
//添加自定義apiexplor.groupname控制器模型屬性
//configure.Conventions.Add(new ApiExplorerGroupVersionConvention());
//自定義實(shí)現(xiàn)只顯示Get請(qǐng)求的Action模型
//configure.Conventions.Add(new ApiExplorerGetsOnlyConvention());
});
運(yùn)行效果:

2.4 自定義Action排序操作
默認(rèn)情況下,Action能夠按Swagger規(guī)范,在添加到分組之前進(jìn)行排序操作,也可自定義排序規(guī)則,修改ConfigureServices中AddSwaggerGen對(duì)應(yīng)配置,此處以自定義排序規(guī)則Controller與請(qǐng)求方法進(jìn)行排序。
#region 自定義Tags
options.TagActionsBy(apidescr => {
return new string[] {
apidescr.HttpMethod
};
});
#endregion
#region 自定義Tags排序
options.OrderActionsBy(apidesc =>
{
//自定義Tag內(nèi)的Action排序
return $"{apidesc.ActionDescriptor.RouteValues["controller"]}_{apidesc.HttpMethod}";
});
#endregion
運(yùn)行效果:

2.5 自定義架構(gòu)id
如果在文檔生成器遇到復(fù)雜的傳入?yún)?shù)或響應(yīng)類型,生成器會(huì)自動(dòng)生成相應(yīng)Json Schema,并將其添加到全局Components/Schemas字典中。
還是以天氣類WeatherForecast,默認(rèn)情況下,Api Json對(duì)應(yīng)請(qǐng)求鏈接http://localhost:5000/v1/swaggerapi.json內(nèi)容大致如下:
{
"paths": {
"/WeatherForecast": {
"get": {
"tags": [
"GET"
],
"summary": "獲取天氣預(yù)報(bào)信息",
"responses": {
"201": {
"description": "請(qǐng)求成功并且服務(wù)器創(chuàng)建了新的資源",
"content": {
"text/plain": {
"schema": {
"type": "array",
"items": {
// #/components/schemas/{schemasid}
"$ref": "#/components/schemas/WeatherForecast"
}
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"WeatherForecast": {
"type": "object",
"properties": {
"date": {
"type": "string",
"description": "日期",
"format": "date-time"
}
},
"additionalProperties": false,
"description": "天氣預(yù)報(bào)實(shí)體"
}
}
}
}
修改服務(wù)中ConfigureServices的配置,自定義標(biāo)識(shí)的生成規(guī)則CustomSchemaIds。
public void ConfigureServices(IServiceCollection services)
{
services.AddSwaggerGen(
options =>
{
#region 自定義架構(gòu)Id
// 輸出對(duì)應(yīng)類型全名稱
options.CustomSchemaIds(schema => schema.FullName);
#endregion
}
);
}
運(yùn)行后,對(duì)應(yīng)輸出的Json結(jié)構(gòu)如下:
{
"paths": {
"/WeatherForecast": {
"get": {
"tags": [
"GET"
],
"summary": "獲取天氣預(yù)報(bào)信息",
"responses": {
"201": {
"description": "請(qǐng)求成功并且服務(wù)器創(chuàng)建了新的資源",
"content": {
"text/plain": {
"schema": {
"type": "array",
"items": {
// #/components/schemas/{schemasid}
"$ref": "#/components/schemas/swaggertestbase.WeatherForecast"
}
}
}
}
}
}
}
}
},
"components": {
"schemas": {
// {schemasid}
"swaggertestbase.WeatherForecast": {
"type": "object",
"properties": {
"date": {
"type": "string",
"description": "日期",
"format": "date-time"
}
},
"additionalProperties": false,
"description": "天氣預(yù)報(bào)實(shí)體"
}
}
}
}
2.6 覆蓋特定類型架構(gòu)[不常用]
需要對(duì)特定類型進(jìn)行指定序列化處理時(shí),可以通過(guò)自定義架構(gòu)序列化類型處理。
數(shù)據(jù)實(shí)體類:
/// <summary>
/// 天氣預(yù)報(bào)實(shí)體
/// </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>
public string Summary { get; set; }
}
請(qǐng)求http://localhost:5000/v1/swaggerapi.json,原始Json架構(gòu)如下:
"components": {
"schemas": {
"WeatherForecast": {
"type": "object",
"properties": {
"date": {
"type": "string",
"description": "日期",
"format": "date-time"
},
"temperatureC": {
"type": "integer",
"description": "溫度-攝氏度",
"format": "int32"
},
"temperatureF": {
"type": "integer",
"description": "溫度-華攝氏度",
"format": "int32",
"readOnly": true
},
"summary": {
"type": "string",
"description": "描述",
"nullable": true
}
},
"additionalProperties": false,
"description": "天氣預(yù)報(bào)實(shí)體"
}
}
}
修改ConfigureServices中的AddSwaggerGen,設(shè)定類型序列化的架構(gòu)為字符串。
services.AddSwaggerGen(
options =>
{
#region 覆蓋特定類型架構(gòu)
// 指定對(duì)應(yīng)的實(shí)體類型,類型架構(gòu)配置
options.MapType<WeatherForecast>(() => new Microsoft.OpenApi.Models.OpenApiSchema { Type = "string" });
#endregion
}
);
設(shè)置以后,對(duì)應(yīng)的類型在架構(gòu)中,無(wú)生成,目前無(wú)考慮深究原因,運(yùn)行效果如下:

2.7 使用過(guò)濾器擴(kuò)展生成器
1)操作過(guò)濾器
Swashbuckle會(huì)檢索每一個(gè)Asp.Net Core中的每一個(gè)ApiDescription,從中提取到對(duì)應(yīng)的OpenApiOperation,對(duì)應(yīng)OpenApiOperation類型和ApiDescription能夠通過(guò)操作過(guò)濾器列表實(shí)現(xiàn)傳遞。以包含權(quán)限認(rèn)證特性,響應(yīng)中401響應(yīng)狀態(tài)的操作過(guò)濾器為例。
創(chuàng)建類AuthResponsesOperationFilter實(shí)現(xiàn)IOperationFilter。
public class AuthResponsesOperationFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var customAttributes = context.MethodInfo.DeclaringType.GetCustomAttributes(true)
.Union(context.MethodInfo.GetCustomAttributes(true))
.OfType<AuthorizeAttribute>();
if (customAttributes.Any())
{
operation.Responses.Add("401", new OpenApiResponse { Description = "UnAuthorized" });
}
}
}
給控制器中WeatherForecastController對(duì)應(yīng)Post函數(shù)添加特性Authorize。
/// <summary>
/// 天氣預(yù)報(bào)服務(wù)
/// </summary>
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
........
/// <summary>
/// 獲取Post數(shù)據(jù)
/// </summary>
/// <returns>處理結(jié)果</returns>
[HttpPost]
[Authorize]
public string Post()
{
return "處理結(jié)果";
}
........
}
添加到動(dòng)作過(guò)濾器列表中,
services.AddSwaggerGen(
options =>
{
#region 添加權(quán)限認(rèn)證響應(yīng)操作過(guò)濾器
options.OperationFilter<AuthResponsesOperationFilter>();
#endregion
}
);
運(yùn)行效果如下:

需要注意的是,過(guò)濾器管道是DI感知的。可以使用構(gòu)造函數(shù)參數(shù)創(chuàng)建過(guò)濾器,只要參數(shù)類型已在DI框架中注冊(cè),則它們將在過(guò)濾器實(shí)例化時(shí)自動(dòng)注入。
2)架構(gòu)過(guò)濾器
Swashbuckle為控制器操作公開(kāi)的每個(gè)參數(shù)、響應(yīng)和屬性類型生成 Swagger 樣式的 JSONSchema。生成后,它通過(guò)配置的架構(gòu)過(guò)濾器列表傳遞架構(gòu)和類型。
創(chuàng)建枚舉類型。
/// <summary>
/// 自定義枚舉
/// </summary>
public enum MyEnum
{
A,
B,
C
}
創(chuàng)建SchemaFilterController控制器。
[Route("api/[controller]")]
[ApiController]
public class SchemaFilterController : ControllerBase
{
/// <summary>
/// 獲取SchemaFilter過(guò)濾器數(shù)據(jù)
/// </summary>
/// <param name="my">枚舉參數(shù)</param>
[HttpGet]
public void Get(MyEnum my)
{
}
}
自定義類AutoRestSchemaFilter,實(shí)現(xiàn)ISchemaFilter。
/// <summary>
/// 自動(dòng)設(shè)置架構(gòu)過(guò)濾器
/// </summary>
public class AutoRestSchemaFilter : ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
var type = context.Type;
if (type.IsEnum)
{
schema.Extensions.Add("x-ms-enum", new OpenApiObject()
{
["name"] = new OpenApiString(type.Name),
["modelAsString"] = new OpenApiBoolean(true)
});
}
}
}
添加到架構(gòu)過(guò)濾器中:
....
services.AddSwaggerGen(
options =>
{
#region 添加自定義架構(gòu)過(guò)濾器
options.SchemaFilter<AutoRestSchemaFilter>();
#endregion
});
....
訪問(wèn)http://localhost:5000/v1/swaggerapi.json,可以看到對(duì)應(yīng)結(jié)構(gòu)如下如下,x-ms-enum在對(duì)應(yīng)類型的架構(gòu)尾部。
{
"components": {
"schemas": {
"MyEnum": {
"enum": [
0,
1,
2
],
"type": "integer",
"description": "自定義枚舉",
"format": "int32",
"x-ms-enum": {
"name": "MyEnum",
"modelAsString": true
}
}
}
}
}
通過(guò)上述例子,可以看出,對(duì)于枚舉等特殊類型,實(shí)際可以通過(guò)自定義Json schema的方式實(shí)現(xiàn)對(duì)應(yīng)字段值的備注含義。
3)文檔過(guò)濾器
Document Filters在遵守OpenApi的規(guī)范前提下,可以通過(guò)文檔過(guò)濾器對(duì)文檔進(jìn)行任意有效的Swagger Json。例如為當(dāng)前文檔添加額外的Tags,自定義過(guò)濾器TagDescriptionsDocumentFilter,實(shí)現(xiàn)IDocumentFilter。
/// <summary>
/// Tag描述文檔過(guò)濾器
/// </summary>
public class TagDescriptionsDocumentFilter : IDocumentFilter
{
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
swaggerDoc.Tags = new List<OpenApiTag> {
new OpenApiTag{
Name ="Pages",
Description = "分頁(yè)類"
},
new OpenApiTag{
Name ="Tests",
Description = "測(cè)試類"
}
};
}
}
添加到現(xiàn)有的文檔過(guò)濾器中:
public void ConfigureServices(IServiceCollection services)
{
services.AddSwaggerGen(
options =>
{
//忽略部分內(nèi)容
#region 添加自定義文檔過(guò)濾器
options.DocumentFilter<TagDescriptionsDocumentFilter>();
#endregion
//忽略部分內(nèi)容
}
);
}
請(qǐng)求http://localhost:5000/v1/swaggerapi.json,在返回結(jié)果中能夠看到如下部分結(jié)構(gòu):
{
//忽略無(wú)關(guān)項(xiàng)
"tags": [
{
"name": "Pages",
"description": "分頁(yè)類"
},
{
"name": "Tests",
"description": "測(cè)試類"
}
]
}
后續(xù)文章將講解為Swagger添加安全驗(yàn)證。
往期推薦
