Home [WebAPI] Swagger擴展版本控制(不符合Restful風格)
Post
Cancel

[WebAPI] Swagger擴展版本控制(不符合Restful風格)

Swagger擴展版本控制(不符合Restful風格), 勿試…

Step1. 新增一個ApiVersions.cs檔,使用列舉(enum)定義版本的名稱

1
2
3
4
5
6
7
8
9
namespace NET6Demo.WebApi.Utility.Swagger
{
    public enum ApiVersions
    {
        V1,
        V2,
        V3
    }
}

Step2. 在Program.cs 中設定Swagger配置

1
2
3
4
5
6
7
8
9
10
11
builder.Services.AddSwaggerGen(option => {
    #region 分版本的Swagger配置
    typeof(ApiVersions).GetEnumNames().ToList().ForEach(version => {
        option.SwaggerDoc(version, new OpenApiInfo() {
            Title = $"RivaDemo版本{version} API文件",
            Version = version,
            Description = $"通用版本CoreApi版本{version}"
        });
    });
    #endregion
});
1
2
3
4
5
6
    app.UseSwaggerUI(option => {
        foreach (string version in typeof(ApiVersions).GetEnumNames())
        {
            option.SwaggerEndpoint($"/swagger/{version}/swagger.json", $"API版本:{version}");
        }
    });

Step3. 在Controller加入版次 [ApiExplorerSettings(IgnoreApi = false, GroupName = nameof(ApiVersions.V1))]

1
2
3
4
5
    [Route("api/[controller]")]
    [ApiController]
    [ApiExplorerSettings(IgnoreApi = false, GroupName = nameof(ApiVersions.V1))]
    public class UserController : ControllerBase
    { ... }

Swagger版本控制(調用第三方API版本控制)

Step1. 安裝Nuget包:

  • Microsoft.AspNetCore.Mvc.Versioning
  • Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer

Step2. Swagger 調用第三方API版本控制

獲取APIversion中各個版本的資訊,可以利用 services.BuildServiceProvider().GetRequiredService<>獲取。

1
builder.Services.AddSwaggerGen(option => {todo...}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#region Swagger版本控制(調用第三方API版本控制)
{
	//獲取API version中各個版本的資訊
	var provider = builder.Services.BuildServiceProvider().GetRequiredService<IApiVersionDescriptionProvider>();
	//讀取API各版本的內容生成文檔
	foreach (var description in provider.ApiVersionDescriptions)
	{
		option.SwaggerDoc(description.GroupName, new OpenApiInfo {
			Title = "RivaDemo Net6 WebAPI 文件",
			Description = "RivaDemo Net6 WebAPI 文件",
			Contact = new OpenApiContact { Name = "Rii", Email = "ooxx@gmail.com" },
			Version = description.ApiVersion.ToString()
		});
	}

	option.DocInclusionPredicate((version, apiDescription) => {
		if (!version.Equals(apiDescription.GroupName)) return false;

		IEnumerable<string>? values = apiDescription!.RelativePath
			.Split('/')
			.Select(e => e.Replace("v{version}", apiDescription.GroupName));

		apiDescription.RelativePath = String.Join("/", values);
		return true;
	});

	option.DescribeAllParametersInCamelCase();//參數全小寫
}
#endregion
1
app.UseSwaggerUI(option => { todo...}
1
2
3
4
5
6
7
8
9
#region Swagger版本控制(調用第三方API版本控制)
var provider = app.Services.GetRequiredService<IApiVersionDescriptionProvider>();

//默認載入最新的API版本
foreach (var description in provider.ApiVersionDescriptions.Reverse())
{
	option.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json", $"RIVA API {description.GroupName.ToUpperInvariant()}");
}
#endregion

Step3. 註冊:在programe.cs 註冊 Swagger版本控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#region API版本控制
{
    //添加支持API版本
    builder.Services.AddApiVersioning(option => {
        option.ReportApiVersions = true; //是否在header信息中返回API版本資訊
        option.DefaultApiVersion = new ApiVersion(1,0); //默認的API版本
        option.AssumeDefaultVersionWhenUnspecified = true; //未指定api版本時,設置API版本為默認的API版本
    });

    //配置API版本
    builder.Services.AddVersionedApiExplorer(option => {
        option.GroupNameFormat = "'v'VVV"; //Api版本分組名稱
        option.AssumeDefaultVersionWhenUnspecified = true; //未指定api版本時,設置API版本為默認的API版本
    });
}
#endregion

Step4. 套用:調用第三方API版本控制

1
2
[ApiVersion("1.0")]
[ApiVersion("2.0")]

Swagger 顯示註解

Step1. 將註解成生xml檔 路徑:專案 > 屬性 > 輸出 > 文件檔案 > 勾選「產生包含API文件檔案」

通過這個屬性將xml檔產生出來

Step2. 在Program.cs的AddSwaggerGen讀出xml檔

1
2
3
4
5
#region Swagger顯示註解
var file = Path.Combine(AppContext.BaseDirectory, "NET6Demo.WebApi.xml"); //將xml讀取出來
option.IncludeXmlComments(file, true); //true: 顯示控制層註解
option.OrderActionsBy(o => o.RelativePath); //對action名稱進行排序
#endregion

Swagger傳入Token

1
2
3
4
5
6
7
8
9
//安全定義
option.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme {
    Description = "請輸入Token,格式為: Bearer xxxxxx (注意中間必須要有空格)",
    Name = "Authorization",
    In = ParameterLocation.Header,
    Type = SecuritySchemeType.ApiKey,
    BearerFormat = "JWT",
    Scheme = "Bearer"
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//安全要求
option.AddSecurityRequirement(
    new OpenApiSecurityRequirement {
    {
        new OpenApiSecurityScheme
        {
            Reference = new OpenApiReference
            {
                Type = ReferenceType.SecurityScheme,
                Id = "Bearer"
            }
        },
        new string[] {}
    }
    });

擴展Swagger文件上傳按鈕

  1. 建立一個新檔,名為: FileUploadFilter.cs
  2. 繼承 IOperationFilter 並實作 Apply方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    
    public class FileUploadFilter : IOperationFilter
    {
     /// <summary>
     /// 擴展文件上傳,顯示選擇文件按鈕
     /// </summary>
     /// <param name="operation"></param>
     /// <param name="context"></param>
     /// <exception cref="NotImplementedException"></exception>
     void IOperationFilter.Apply(OpenApiOperation operation, OperationFilterContext context)
     {
         const string fileUploadContentType = "multipart/form-data";
         //檔案為空、或非"multipart/form-data"類型的格式,不處理
         if (operation.RequestBody == null || !operation.RequestBody.Content.Any(x => x.Key.Equals(fileUploadContentType, StringComparison.InvariantCultureIgnoreCase)))
         {
             return;
         }
    
         //限定上傳類型為IFormCollection
         if (context.ApiDescription.ParameterDescriptions[0].Type == typeof(IFormCollection))
         {
             operation.RequestBody = new OpenApiRequestBody {
                 Description = "文件上傳",
                 Content = new Dictionary<string, OpenApiMediaType> {
                     {
                         fileUploadContentType, new OpenApiMediaType {
                             Schema = new OpenApiSchema {
                                 Type ="object",
                                 Required = new HashSet<string> { "file"},
                                 Properties = new Dictionary<string, OpenApiSchema> {
                                     {
                                         "file", new OpenApiSchema() {
                                             Type="string",
                                             Format="binary"
                                         }
                                     }
                                 }
                             }
                         }
                     }
                 }
             };
         }
     }
    }
    

    3.Program.cs 的 AddSwaggerGen中配置option.OperationFilter<FileUploadFilter>();

Swagger功能封裝

將Swagger擴展方法獨立封裝成一個檔案

  1. 在Swagger Folder新增一個靜態類別:SwaggerExtension.cs
  2. 新增一個”Swagger完整配置”的靜態方法:AddSwaggerGenExt(this WebApplicationBuilder builder) 將Program.cs中AddSwaggerGen的Code移置該方法中

  3. 新增一個”Swagger中間件應用”的靜態方法:UseSwaggerUIExt(this WebApplication app) 將Program.cs中AddSwaggerUI的Code移置該方法中

  4. 在Program.cs替換成Swagger封裝的方法

全域路由前綴配置

定義一個類別實現 IApplicationModelConvention 介面,遍歷所有 Controller 來為它們加上一個前綴路由。

RouteConvention.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/// <summary>
/// 全域路由前綴配置
/// </summary>
public class RouteConvention : IApplicationModelConvention
{
	private readonly AttributeRouteModel _centralPrefix; //路由前綴變數

	/// <summary>
	/// 調用前傳入指定的路由前綴
	/// </summary>
	/// <param name="routeTemplateProvider"></param>
	public RouteConvention(IRouteTemplateProvider routeTemplateProvider)
	{
		_centralPrefix = new AttributeRouteModel(routeTemplateProvider);
	}

	/// <summary>
	/// 根據情況來加入api路由前綴
	/// </summary>
	/// <param name="application"></param>
	/// <exception cref="NotImplementedException"></exception>
	public void Apply(ApplicationModel application)
	{
		//遍歷所有的Control
		foreach (var controller in application.Controllers)
		{
			//1.已標記 RouteAttribute的controller
			var matchedSelectors = controller.Selectors.Where(e => e.AttributeRouteModel != null).ToList();

			if (matchedSelectors.Any()) //判斷集合中是否有物件
			{
				foreach (var selectModel in matchedSelectors)
				{
					//在當前路由上再加上路由前綴
					selectModel.AttributeRouteModel = AttributeRouteModel.CombineAttributeRouteModel(_centralPrefix, selectModel.AttributeRouteModel);
				}
			}

			//2.沒有標記 RouteAttribute的controller
			var unmatchedSelectors = controller.Selectors.Where(e => e.AttributeRouteModel == null).ToList();
			if (unmatchedSelectors.Any()) //判斷集合中是否有物件
			{
				foreach (var selectModel in unmatchedSelectors)
				{
					//加上路由前綴
					selectModel.AttributeRouteModel = _centralPrefix;
				}
			}
		}
	}
}

Program.cs

1
2
3
4
builder.Services.AddControllers(option => 
    {
        option.Conventions.Insert(0, new RouteConvention(new RouteAttribute("RIVAapi/")));
    });

This post is licensed under CC BY 4.0 by the author.