上一篇介紹了Swashbuckle ,地址:.net core的Swagger接口文檔使用教程(一):Swashbuckle
講的東西還挺多,怎奈微軟還推薦了一個NSwag,那就繼續寫吧!
但是和Swashbuckle一樣,如果還是按照那樣寫,東西有點多了,所以這里就偷個懶吧,和Swashbuckle對照的去寫,介紹一些常用的東西算了,所以建議看完上一篇再繼續這里。
一、一般用法
注:這里一般用法的Demo源碼已上傳到百度雲:https://pan.baidu.com/s/1Z4Z9H9nto_CbNiAZIxpFFQ (提取碼:pa8s ),下面第二、三部分的功能可在Demo源碼基礎上去嘗試。
創建一個.net core項目(這里采用的是.net core3.1),然后使用nuget安裝NSwag.AspNetCore,建議安裝最新版本。
同樣的,假如有一個接口:
/// <summary> /// 測試接口 /// </summary> [ApiController] [Route("[controller]")] public class HomeController : ControllerBase { /// <summary> /// Hello World /// </summary> /// <returns>輸出Hello World</returns> [HttpGet] public string Get() { return "Hello World"; } }
接口修改Startup,在ConfigureServices和Configure方法中添加服務和中間件
public void ConfigureServices(IServiceCollection services) { services.AddOpenApiDocument(settings => { settings.DocumentName = "v1"; settings.Version = "v0.0.1"; settings.Title = "測試接口項目"; settings.Description = "接口文檔說明"; }); ... } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
... app.UseOpenApi(); app.UseSwaggerUi3(); ... }
然后運行項目,輸入http://localhost:5000/swagger,得到接口文檔頁面:
點擊Try it out可以直接調用接口。
同樣的,這里的接口沒有注解,不太友好,可以和Swashbuckle一樣生成xml注釋文件加載:
右鍵項目=》切換到生成(Build),在最下面輸出輸出中勾選【XML文檔文件】,同時,在錯誤警告的取消顯示警告中添加1591代碼:
不過,與Swashbuckle不一樣的是,Swashbuckle需要使用IncludeXmlComments方法加載注釋文件,如果注釋文件不存在,IncludeXmlComments方法還會拋出異常,但是NSwag不需要手動加載,默認xml注釋文件和它對應點dll應該放在同一目錄且同名才能完成加載!
按照上面的操作,運行項目后,接口就有注解了:
但是控制器標簽欄還是沒有注解,這是因為NSwag的控制器標簽默認從OpenApiTagAttribute中讀取
[OpenApiTag("測試標簽",Description = "測試接口")] public class HomeController : ControllerBase
運行后顯示:
其實還可以修改這個默認行為,settings有一個UseControllerSummaryAsTagDescription屬性,將它設置成 true就可以從xml注釋文件中加載描述了:
services.AddOpenApiDocument(settings => { ... //可以設置從注釋文件加載,但是加載的內容可被OpenApiTagAttribute特性覆蓋 settings.UseControllerSummaryAsTagDescription = true; });
運行后顯示:
接着是認證,比如JwtBearer認證,這個和Swashbuckle是類似的,只不過拓展方法換成了AddSecurity:
public void ConfigureServices(IServiceCollection services) { services.AddOpenApiDocument(settings => { settings.DocumentName = "v1"; settings.Version = "v0.0.1"; settings.Title = "測試接口項目"; settings.Description = "接口文檔說明"; //可以設置從注釋文件加載,但是加載的內容可悲OpenApiTagAttribute特性覆蓋 settings.UseControllerSummaryAsTagDescription = true; //定義JwtBearer認證方式一 settings.AddSecurity("JwtBearer", Enumerable.Empty<string>(), new OpenApiSecurityScheme() { Description = "這是方式一(直接在輸入框中輸入認證信息,不需要在開頭添加Bearer)", Name = "Authorization",//jwt默認的參數名稱 In = OpenApiSecurityApiKeyLocation.Header,//jwt默認存放Authorization信息的位置(請求頭中) Type = OpenApiSecuritySchemeType.Http, Scheme = "bearer" }); //定義JwtBearer認證方式二 settings.AddSecurity("JwtBearer", Enumerable.Empty<string>(), new OpenApiSecurityScheme() { Description = "這是方式二(JWT授權(數據將在請求頭中進行傳輸) 直接在下框中輸入Bearer {token}(注意兩者之間是一個空格))", Name = "Authorization",//jwt默認的參數名稱 In = OpenApiSecurityApiKeyLocation.Header,//jwt默認存放Authorization信息的位置(請求頭中) Type = OpenApiSecuritySchemeType.ApiKey }); }); ... }
到這里,就是NSwag的一般用法了,可以滿足一般的需求了。
二、服務注入(AddOpenApiDocument和AddSwaggerDocument)
NSwag注入服務有兩個方法:AddOpenApiDocument和AddSwaggerDocument,兩者的區別就是架構類型不一樣,AddOpenApiDocument的SchemaType使用的是OpenApi3,AddSwaggerDocument的SchemaType使用的是Swagger2:
/// <summary>Adds services required for Swagger 2.0 generation (change document settings to generate OpenAPI 3.0).</summary> /// <param name="serviceCollection">The <see cref="IServiceCollection"/>.</param> /// <param name="configure">Configure the document.</param> public static IServiceCollection AddOpenApiDocument(this IServiceCollection serviceCollection, Action<AspNetCoreOpenApiDocumentGeneratorSettings, IServiceProvider> configure = null) { return AddSwaggerDocument(serviceCollection, (settings, services) => { settings.SchemaType = SchemaType.OpenApi3; configure?.Invoke(settings, services); }); } /// <summary>Adds services required for Swagger 2.0 generation (change document settings to generate OpenAPI 3.0).</summary> /// <param name="serviceCollection">The <see cref="IServiceCollection"/>.</param> /// <param name="configure">Configure the document.</param> public static IServiceCollection AddSwaggerDocument(this IServiceCollection serviceCollection, Action<AspNetCoreOpenApiDocumentGeneratorSettings, IServiceProvider> configure = null) { serviceCollection.AddSingleton(services => { var settings = new AspNetCoreOpenApiDocumentGeneratorSettings { SchemaType = SchemaType.Swagger2, }; configure?.Invoke(settings, services); ... }); ... }
個人推薦使用AddOpenApiDocument。
services.AddOpenApiDocument(settings => { //添加代碼 });
同樣的,無論是AddOpenApiDocument還是AddSwaggerDocument,最終都是依賴AspNetCoreOpenApiDocumentGeneratorSettings來完成,與Swashbuckle不同的是,AddOpenApiDocument方法每次調用只會生成一個swagger接口文檔對象,從上面的例子也能看出來:
DocumentName
接口文檔名,也就是Swashbuckle中SwaggerDoc方法中的name參數。
Version
接口文檔版本,也就是Swashbuckle中SwaggerDoc方法中的第二個OpenApiInfo的Version屬性。
Title
接口項目名稱,也就是Swashbuckle中SwaggerDoc方法中的第二個OpenApiInfo的Title屬性。
Description
接口項目介紹,也就是Swashbuckle中SwaggerDoc方法中的第二個OpenApiInfo的Description屬性。
PostProcess
這個是一個委托,在生成SwaggerDocument之后執行,需要注意的是,因為NSwag有緩存機制的存在PostProcess可能只會執行一遍。
比如:因為NSwag沒有直接提供Swashbuckle中SwaggerDoc方法中的第二個OpenApiInfo的Contact屬性的配置,這時我們可以使用PostProcess實現。
settings.PostProcess = document => { document.Info.Contact = new OpenApiContact() { Name = "zhangsan", Email = "xxx@qq.com", Url = null }; };
ApiGroupNames
無論是Swashbuckle還是NSwag都支持生成多個接口文檔,但是在接口與文檔歸屬上不一致:
在Swashbuckle中,通過ApiExplorerSettingsAttribute特性的GroupName屬性指定documentName來實現的,而NSwag雖然也是用ApiExplorerSettingsAttribute特性實現,但是此時的GroupName不在是documentName,而是ApiGroupNames屬性指定的元素值了:
比如下面三個接口:
/// <summary> /// 未使用ApiExplorerSettings特性,表名屬於每一個swagger文檔 /// </summary> /// <returns>結果</returns> [HttpGet("All"), Authorize] public string All() { return "All"; } /// <summary> /// 使用ApiExplorerSettings特性表名該接口屬於swagger文檔v1 /// </summary> /// <returns>Get結果</returns> [HttpGet] [ApiExplorerSettings(GroupName = "demo1")] public string Get() { return "Get"; } /// <summary> /// 使用ApiExplorerSettings特性表名該接口屬於swagger文檔v2 /// </summary> /// <returns>Post結果</returns> [HttpPost] [ApiExplorerSettings(GroupName = "demo2")] public string Post() { return "Post"; }
定義兩個文檔:
services.AddOpenApiDocument(settings => { settings.DocumentName = "v1"; settings.Version = "v0.0.1"; settings.Title = "測試接口項目"; settings.Description = "接口文檔說明"; settings.ApiGroupNames = new string[] { "demo1" }; settings.PostProcess = document => { document.Info.Contact = new OpenApiContact() { Name = "zhangsan", Email = "xxx@qq.com", Url = null }; }; }); services.AddOpenApiDocument(settings => { settings.DocumentName = "v2"; settings.Version = "v0.0.2"; settings.Title = "測試接口項目v0.0.2"; settings.Description = "接口文檔說明v0.0.2"; settings.ApiGroupNames = new string[] { "demo2" }; settings.PostProcess = document => { document.Info.Contact = new OpenApiContact() { Name = "lisi", Email = "xxx@qq.com", Url = null }; }; });
這時不用像Swashbuckle還要在中間件中添加文檔地址,NSwag中間件會自動根據路由模板和文檔生成文檔地址信息,所以直接運行就可以了:
可以注意到,All既不屬於v1文檔也不屬於v2文檔,也就是說,如果設置了ApiGroupNames,那就回嚴格的按ApiGroupNames來比較,只有匹配的GroupName在ApiGroupNames屬性中才算屬於這個接口文檔,這也是NSwag和Swashbuckle不同的一點。
另外,同樣的,NSwag也支持使用IActionModelConvention和IControllerModelConvention設置GroupName,具體可以參考上一篇博文。
UseControllerSummaryAsTagDescription
這個屬性上面例子有介紹,因為NSwag的控制器標簽默認從OpenApiTagAttribute中讀取,而不是從注釋文檔讀取,將此屬性設置成 true就可以從注釋文檔讀取了,但是讀取的內容可被OpenApiTagAttribute特性覆蓋。
AddSecurity
AddSecurity拓展方法用於添加認證,它是兩個重載方法:
public static OpenApiDocumentGeneratorSettings AddSecurity(this OpenApiDocumentGeneratorSettings settings, string name, OpenApiSecurityScheme swaggerSecurityScheme); public static OpenApiDocumentGeneratorSettings AddSecurity(this OpenApiDocumentGeneratorSettings settings, string name, IEnumerable<string> globalScopeNames, OpenApiSecurityScheme swaggerSecurityScheme);
雖然是重載,但是兩個方法的作用差別還挺大,第一個(不帶globalScopeNames參數)的方法的作用類似Swashbuckle的AddSecurityDefinition方法,只是聲明的作用,而第二個(有globalScopeNames參數)的方法作用類似於Swashbuckle的AddSecurityRequirement方法,也就是說,這兩個重載方法,一個僅僅是聲明認證,另一個是除了聲明認證,還會將認證全局的作用於每個接口,不過這兩個方法的實現是使用DocumentProcessors(類似Swashbuckle的DocumentFilter)來實現的
/// <summary>Appends the OAuth2 security scheme and requirement to the document's security definitions.</summary> /// <remarks>Adds a <see cref="SecurityDefinitionAppender"/> document processor with the given arguments.</remarks> /// <param name="settings">The settings.</param> /// <param name="name">The name/key of the security scheme/definition.</param> /// <param name="swaggerSecurityScheme">The Swagger security scheme.</param> public static OpenApiDocumentGeneratorSettings AddSecurity(this OpenApiDocumentGeneratorSettings settings, string name, OpenApiSecurityScheme swaggerSecurityScheme) { settings.DocumentProcessors.Add(new SecurityDefinitionAppender(name, swaggerSecurityScheme)); return settings; } /// <summary>Appends the OAuth2 security scheme and requirement to the document's security definitions.</summary> /// <remarks>Adds a <see cref="SecurityDefinitionAppender"/> document processor with the given arguments.</remarks> /// <param name="settings">The settings.</param> /// <param name="name">The name/key of the security scheme/definition.</param> /// <param name="globalScopeNames">The global scope names to add to as security requirement with the scheme name in the document's 'security' property (can be an empty list).</param> /// <param name="swaggerSecurityScheme">The Swagger security scheme.</param> public static OpenApiDocumentGeneratorSettings AddSecurity(this OpenApiDocumentGeneratorSettings settings, string name, IEnumerable<string> globalScopeNames, OpenApiSecurityScheme swaggerSecurityScheme) { settings.DocumentProcessors.Add(new SecurityDefinitionAppender(name, globalScopeNames, swaggerSecurityScheme)); return settings; }
而SecurityDefinitionAppender是一個實現了IDocumentProcessor接口的類,它實現的Porcess如下,其中_scopeNames就是上面方法傳進來的globalScopeNames:
/// <summary>Processes the specified Swagger document.</summary> /// <param name="context"></param> public void Process(DocumentProcessorContext context) { context.Document.SecurityDefinitions[_name] = _swaggerSecurityScheme; if (_scopeNames != null) { if (context.Document.Security == null) { context.Document.Security = new Collection<OpenApiSecurityRequirement>(); } context.Document.Security.Add(new OpenApiSecurityRequirement { { _name, _scopeNames } }); } }
至於其他用法,可以參考上面的一般用法和上一篇中介紹的Swashbuckle的AddSecurityDefinition方法和AddSecurityRequirement方法的用法,很相似。
DocumentProcessors
DocumentProcessors類似於Swashbuckle的DocumentFilter方法,只不過DocumentFilter方法時實現IDocumentFilter接口,而DocumentProcessors一個IDocumentProcessor集合屬性,是需要實現IDocumentProcessor接口然后添加到集合中去。需要注意的是,因為NSwag有緩存機制的存在DocumentProcessors可能只會執行一遍。
另外,你可能注意到,上面有介紹過一個PostProcess方法,其實個人覺得PostProcess和DocumentProcessors區別不大,但是DocumentProcessors是在PostProcess之前調用執行,源碼中:
public async Task<OpenApiDocument> GenerateAsync(ApiDescriptionGroupCollection apiDescriptionGroups) { ...
foreach (var processor in Settings.DocumentProcessors) { processor.Process(new DocumentProcessorContext(document, controllerTypes, usedControllerTypes, schemaResolver, Settings.SchemaGenerator, Settings)); } Settings.PostProcess?.Invoke(document); return document; }
可能是作者覺得DocumentProcessors有點繞,所以提供了一個委托供我們簡單處理吧,用法也可以參考上一篇中的Swashbuckle的DocumentFilter方法,比如全局的添加認證,全局的添加Server等等。
OperationProcessors
OperationProcessors類似Swashbuckle的OperationFilter方法,只不過OperationFilter實現的是IOperationFilter,而OperationProcessors是IOperationProcessor接口集合。需要注意的是,因為NSwag有緩存機制的存在OperationProcessors可能只會執行一遍。
同樣的,可能作者為了方便我們使用,已經定義好了一個OperationProcessor類,我們可以將我們的邏輯當做參數去實例化OperationProcessor類,然后添加到OperationProcessors集合中即可,不過作者還提供了一個AddOperationFilter方法,可以往OperationProcessors即可開始位置添加過期操作:
/// <summary>Inserts a function based operation processor at the beginning of the pipeline to be used to filter operations.</summary> /// <param name="filter">The processor filter.</param> public void AddOperationFilter(Func<OperationProcessorContext, bool> filter) { OperationProcessors.Insert(0, new OperationProcessor(filter)); }
所以我們可以這么用:
settings.AddOperationFilter(context => { //我們的邏輯 return true; });
另外,因為無論使用AddOperationFilter方法,還是直接往OperationProcessors集合中添加IOperationProcessor對象,都會對所有Action(或者說Operation)進行調用,NSwag還有一個SwaggerOperationProcessorAttribute特性(新版已改為OpenApiOperationProcessorAttribute),用於指定某些特定Action才會調用執行。當然,SwaggerOperationProcessorAttribute的實例化需要指定一個實現了IOperationProcessor接口的類型以及實例化它所需要的的參數。
與Swashbuckle不同的是,IOperationProcessor的Process接口要求返回一個bool類型的值,表示接口是否要在swaggerUI頁面展示,如果返回false,接口就不會在前端展示了,而且后續的IOperationProcessor對象也不再繼續調用執行。
private bool RunOperationProcessors(OpenApiDocument document, Type controllerType, MethodInfo methodInfo, OpenApiOperationDescription operationDescription, List<OpenApiOperationDescription> allOperations, OpenApiDocumentGenerator swaggerGenerator, OpenApiSchemaResolver schemaResolver) { var context = new OperationProcessorContext(document, operationDescription, controllerType, methodInfo, swaggerGenerator, Settings.SchemaGenerator, schemaResolver, Settings, allOperations); // 1. Run from settings foreach (var operationProcessor in Settings.OperationProcessors) { if (operationProcessor.Process(context)== false) { return false; } } // 2. Run from class attributes var operationProcessorAttribute = methodInfo.DeclaringType.GetTypeInfo() .GetCustomAttributes() // 3. Run from method attributes .Concat(methodInfo.GetCustomAttributes()) .Where(a => a.GetType().IsAssignableToTypeName("SwaggerOperationProcessorAttribute", TypeNameStyle.Name)); foreach (dynamic attribute in operationProcessorAttribute) { var operationProcessor = ObjectExtensions.HasProperty(attribute, "Parameters") ? (IOperationProcessor)Activator.CreateInstance(attribute.Type, attribute.Parameters) : (IOperationProcessor)Activator.CreateInstance(attribute.Type); if (operationProcessor.Process(context) == false) { return false; } } return true; }
至於其它具體用法,具體用法可以參考上一篇介紹的Swashbuckle的OperationFilter方法,如給特定Operation添加認證,或者對響應接口包裝等等。
SchemaProcessors
SchemaFilter的作用類似Swashbuckle的SchemaFilter方法,這里就不重提了,舉個例子:
比如我們有一個性別枚舉類型:
public enum SexEnum { /// <summary> /// 未知 /// </summary> Unknown = 0, /// <summary> /// 男 /// </summary> Male = 1, /// <summary> /// 女 /// </summary> Female = 2 }
然后有個User類持有此枚舉類型的一個屬性:
public class User { /// <summary> /// 用戶Id /// </summary> public int Id { get; set; } /// <summary> /// 用戶名稱 /// </summary> public string Name { get; set; } /// <summary> /// 用戶性別 /// </summary> public SexEnum Sex { get; set; } }
如果將User類作為接口參數或者返回類型,比如有下面的接口:
/// <summary> /// 獲取一個用戶信息 /// </summary> /// <param name="userId">用戶ID</param> /// <returns>用戶信息</returns> [HttpGet("GetUserById")] public User GetUserById(int userId) { return new User(); }
直接運行后得到的返回類型的說明是這樣的:
這就有個問題了,枚舉類型中的0、1、2等等就是何含義,這個沒有在swagger中體現出來,這個時候我們可以通過SchemaProcessors來修改Schema信息。
比如,可以先用一個特性(例如使用DescriptionAttribute)標識枚舉類型的每一項,用於說明含義:
public enum SexEnum { /// <summary> /// 未知 /// </summary> [Description("未知")] Unknown = 0, /// <summary> /// 男 /// </summary> [Description("男")] Male = 1, /// <summary> /// 女 /// </summary> [Description("女")] Female = 2 }
接着我們創建一個MySchemaProcessor類,實現ISchemaProcessor接口:

public class MySchemaProcessor : ISchemaProcessor { static readonly ConcurrentDictionary<Type, Tuple<string, object>[]> dict = new ConcurrentDictionary<Type, Tuple<string, object>[]>(); public void Process(SchemaProcessorContext context) { var schema = context.Schema; if (context.Type.IsEnum) { var items = GetTextValueItems(context.Type); if (items.Length > 0) { string decription = string.Join(",", items.Select(f => $"{f.Item1}={f.Item2}")); schema.Description = string.IsNullOrEmpty(schema.Description) ? decription : $"{schema.Description}:{decription}"; } } else if (context.Type.IsClass && context.Type != typeof(string)) { UpdateSchemaDescription(schema); } } private void UpdateSchemaDescription(JsonSchema schema) { if (schema.HasReference) { var s = schema.ActualSchema; if (s != null && s.Enumeration != null && s.Enumeration.Count > 0) { if (!string.IsNullOrEmpty(s.Description)) { string description = $"【{s.Description}】"; if (string.IsNullOrEmpty(schema.Description) || !schema.Description.EndsWith(description)) { schema.Description += description; } } } } foreach (var key in schema.Properties.Keys) { var s = schema.Properties[key]; UpdateSchemaDescription(s); } } /// <summary> /// 獲取枚舉值+描述 /// </summary> /// <param name="enumType"></param> /// <returns></returns> private Tuple<string, object>[] GetTextValueItems(Type enumType) { Tuple<string, object>[] tuples; if (dict.TryGetValue(enumType, out tuples) && tuples != null) { return tuples; } FieldInfo[] fields = enumType.GetFields(); List<KeyValuePair<string, int>> list = new List<KeyValuePair<string, int>>(); foreach (FieldInfo field in fields) { if (field.FieldType.IsEnum) { var attribute = field.GetCustomAttribute<DescriptionAttribute>(); if (attribute == null) { continue; } string key = attribute?.Description ?? field.Name; int value = ((int)enumType.InvokeMember(field.Name, BindingFlags.GetField, null, null, null)); if (string.IsNullOrEmpty(key)) { continue; } list.Add(new KeyValuePair<string, int>(key, value)); } } tuples = list.OrderBy(f => f.Value).Select(f => new Tuple<string, object>(f.Key, f.Value.ToString())).ToArray(); dict.TryAdd(enumType, tuples); return tuples; } }
最后在Startup中使用這個MySchemaProcessor類:
services.AddOpenApiDocument(settings => { ... settings.SchemaProcessors.Add(new MySchemaProcessor()); });
再次運行項目后,得到的架構就有每個枚舉項的屬性了,當然,你也可以安裝自己的意願去生成特定格式的架構,這只是一個簡單的例子
其它配置
AspNetCoreOpenApiDocumentGeneratorSettings繼承於OpenApiDocumentGeneratorSettings和JsonSchemaGeneratorSettings還有茫茫多的配置,感興趣的自己看源碼吧,畢竟它和Swashbuckle差不多,一般的需求都能滿足了,實現滿足不了,可以使用DocumentProcessors和OperationProcessors來實現,就跟Swashbuckle的DocumentFilter和OperationFilter一樣。
但是有些問題可能就不行了,比如虛擬路徑問題,Swashbuckle采用在Server上加路徑來實現,而因為NSwag沒有像Swashbuckle的AddServer方法,想到可以使用上面的PostProcess方法或者使用DocumentProcessors來實現,但是現實是打臉,因為作者的處理方式是,執行PostProcess方法和DocumentProcessors之后,會把OpenAPIDocument上的Servers先清空,然后再加上當前SwaggerUI所在的域名地址,可能作者覺着這樣能滿足大部分人的需求吧。但是作者還是提供了其他的方式來操作,會在后面的中間件中介紹
三、添加Swagger中間件(UseOpenApi、UseSwagger和UseSwaggerUi3、UseSwaggerUi)
UseOpenApi、UseSwagger
首先UseOpenApi、UseSwagger和Swashbuckle的UseSwagger的作用一樣的,主要用於攔截swagger.json請求,從而可以獲取返回所需的接口架構信息,不同點在於NSwag的UseOpenApi、UseSwagger具有緩存機制,也就是說,如果第一次獲取到了接口文檔,會已json格式將文檔加入到本地緩存中,下次直接從緩存獲取,因為緩存的存在,所以上面介紹的OperationProcessors和DocumentProcessors都不會再執行了。
另外,UseSwagger是舊版本,已經不推薦使用了,推薦使用UseOpenApi:
app.UseOpenApi(settings => { //中間件設置 });
OpenApiDocumentMiddlewareSettings
UseOpenApi依賴OpenApiDocumentMiddlewareSettings對象完成配置過程,主要屬性有:
Path
Path表示攔截請求的格式,也就是攔截swagger.json的路由格式,這個跟Swashbuckle一樣,因為需要從路由知道是哪個文檔,然后才能去找這個文檔的所有接口解析返回,它的默認值是 /swagger/{documentName}/swagger.json。
同樣的,因為這個值關系比較重要,盡可能不要去修改吧。
DocumentName
從上面的Path參數的默認值中可以看到,其中有個{documentName}參數,NSwag並沒有要求Path中必須有{documentName}參數。
如果沒有這個參數,就必須指定這個屬性DocumentName,只是也就是說NSwag只為一個接口文檔服務。
如果有這個參數,NSwag會遍歷所有定義的接口文檔,然后分別對Path屬性替換掉其中中的{documentName}參數,然后分別攔截每個文檔獲取架構信息的swagger.json請求。
PostProcess
服務注入部分有一個PostProcess方法,功能其實類似於DocumentProcessors,就是對接口文檔做一個調整,而現在這里又有一個PostProcess方法,它則是根據當前請求來調整接口文檔用的。
比如,上面有介紹,如果在服務注入部分使用PostProcess方法或者DocumentProcessors添加了Server,是沒有效果的,這個是因為NSwag在獲取到文檔之后,有意的清理了文檔的Servers屬性,然后加上了當前請求的地址:
/// <summary>Generates the Swagger specification.</summary> /// <param name="context">The context.</param> /// <returns>The Swagger specification.</returns> protected virtual async Task<OpenApiDocument> GenerateDocumentAsync(HttpContext context) { var document = await _documentProvider.GenerateAsync(_documentName); document.Servers.Clear(); document.Servers.Add(new OpenApiServer { Url = context.Request.GetServerUrl() }); _settings.PostProcess?.Invoke(document, context.Request); return document; }
注意到上面的源碼,在清理之后,還調用了這個PostProcess委托,因此,我們可以將添加Server部分的代碼寫到這個PostProcess中:
app.UseOpenApi(settings => { settings.PostProcess = (document, request) => { //清理掉NSwag加上去的 document.Servers.Clear(); document.Servers.Add(new OpenApiServer() { Url = "http://localhost:90/NSwag", Description = "地址1" }); document.Servers.Add(new OpenApiServer() { Url = "http://127.0.0.1:90/NSwag", Description = "地址2" }); //192.168.28.213是我本地IP document.Servers.Add(new OpenApiServer() { Url = "http://192.168.28.213:90/NSwag", Description = "地址3" }); }; });
看來,作者還是很友好的,做了點小動作還提供給我們一個修改的方法。
CreateDocumentCacheKey
上面有提到,NSwag的接口文旦有緩存機制,第一次獲取之后就會以json格式被緩存,接下就會從緩存中讀取,而CreateDocumentCacheKey就是緩存的鍵值工廠,用於生成緩存鍵值用的,如果不設置,那么緩存的鍵值就是string.Empty。
那可能會問,如果不想用緩存呢,不妨設置CreateDocumentCacheKey成這樣:
app.UseOpenApi(settings => { settings.CreateDocumentCacheKey = request => DateTime.Now.ToString(); });
然后你就會發現,過了一段時間之后,你的程序掛了,OutOfMemory!
所以,好好的用緩存的,從源碼中目前沒發現有什么辦法可以取消緩存,況且使用緩存可以提高響應速度,為何不用?如果實在要屏蔽緩存,那就是改改源碼再編譯引用吧。
ExceptionCacheTime
既然是程序,那就有可能會拋出異常,獲取接口文檔架構也不例外,而ExceptionCacheTime表示在獲取接口文檔發生異常后的一段時間內,使用返回這個異常,ExceptionCacheTime默認是TimeSpan.FromSeconds(10)
UseSwaggerUi3、UseSwaggerUi
UseSwaggerUi3、UseSwaggerUi的作用和Swashbuckle的UseSwaggerUI作用是一樣,主要用於攔截swagger/index.html頁面請求,返回頁面給前端。
UseSwaggerUi返回的是基於Swagger2.0的頁面,而UseSwaggerUi3返回的是基於Swagger3.0的頁面,所以這里推薦使用UseSwaggerUi3
app.UseSwaggerUi3(settings => { //中間件操作 });
SwaggerUi3Settings
UseSwaggerUi3依賴SwaggerUi3Settings完成配置,SwaggerUi3Settings繼承於SwaggerUiSettingsBase和SwaggerSettings,所以屬性比較多,這里介紹常用的一些屬性:
EnableTryItOut
這個屬性很簡單,就是設置允許你是否可以在SwaggerUI使用Try it out去調用接口
DocumentTitle
這是SwaggerUI頁面的Title信息,也就是返回的html的head標簽下的title標簽值,默認是 Swagger UI
CustomHeadContent
自定義頁面head標簽內容,可以使用自定義的腳本和樣式等等,作用於Swashbuckle中提到的HeadContent是一樣的
Path
Path是SwaggerUI的index.html頁面的地址,作用與Swashbuckle中提到的RoutePrefix是一樣的
CustomInlineStyles
自定外部樣式,不是鏈接,就是具體的樣式!
CustomInlineStyles
自定義的外部樣式文件的鏈接
CustomJavaScriptPath
自定義外部JavaScript腳本文件的連接
DocumentPath
接口文檔獲取架構swagger.json的Url模板,NSwag不需要想Swashbuckle調用SwaggerEndpoint添加文檔就是因為它會自動根據這個將所有文檔按照DocumentPath的格式進行設置,它的默認值是 /swagger/{documentName}/swagger.json。
同樣的,盡可能不要修改這個屬性,如果修改了,切記要和上面介紹的OpenApiDocumentMiddlewareSettings的Path屬性同步修改。
SwaggerRoutes
這是屬性包含了接口文檔列表,在Swashbuckle中是通過SwaggerEndpoint方法添加的,但是NSwag會自動生成根據DocumentPath屬性自動生成。
app.UseSwaggerUi3(settings => { settings.SwaggerRoutes.Add(new NSwag.AspNetCore.SwaggerUi3Route("demo", "/swagger/v1/swagger.json")); });
需要注意的是,如果自己往SwaggerRoutes中添加接口文檔對象,那么NSwag不會自動生成了,比如上面的例子,雖然定義了多個文檔,但是我們手動往SwaggerRoutes添加了一個,那SwaggerUI中就只會顯示我們自己手動添加的了。
TransformToExternalPath
TransformToExternalPath其實是一個路徑轉化,主要是轉換swagger內部的連接,比如獲取架構新的的請求 /swagger/v1/swagger.json和獲取swaggerUI頁面的連接 /swagger,這個很有用,比如上面提到的虛擬路徑處理的一個完整的例子:

using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using NSwag; namespace NSwagDemo { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddOpenApiDocument(settings => { settings.DocumentName = "v1"; settings.Version = "v0.0.1"; settings.Title = "測試接口項目"; settings.Description = "接口文檔說明"; settings.ApiGroupNames = new string[] { "demo1" }; settings.PostProcess = document => { document.Info.Contact = new OpenApiContact() { Name = "zhangsan", Email = "xxx@qq.com", Url = null }; }; settings.AddOperationFilter(context => { //我們的邏輯 return true; }); //可以設置從注釋文件加載,但是加載的內容可被OpenApiTagAttribute特性覆蓋 settings.UseControllerSummaryAsTagDescription = true; //定義JwtBearer認證方式一 settings.AddSecurity("JwtBearer", Enumerable.Empty<string>(), new OpenApiSecurityScheme() { Description = "這是方式一(直接在輸入框中輸入認證信息,不需要在開頭添加Bearer)", Name = "Authorization",//jwt默認的參數名稱 In = OpenApiSecurityApiKeyLocation.Header,//jwt默認存放Authorization信息的位置(請求頭中) Type = OpenApiSecuritySchemeType.Http, Scheme = "bearer" }); //定義JwtBearer認證方式二 settings.AddSecurity("JwtBearer", Enumerable.Empty<string>(), new OpenApiSecurityScheme() { Description = "這是方式二(JWT授權(數據將在請求頭中進行傳輸) 直接在下框中輸入Bearer {token}(注意兩者之間是一個空格))", Name = "Authorization",//jwt默認的參數名稱 In = OpenApiSecurityApiKeyLocation.Header,//jwt默認存放Authorization信息的位置(請求頭中) Type = OpenApiSecuritySchemeType.ApiKey }); }); services.AddAuthentication(); services.AddControllers(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseRouting(); //NSwag是虛擬路徑 var documentPath = "/swagger/{documentName}/swagger.json"; app.UseOpenApi(settings => { settings.PostProcess = (document, request) => { //清理掉NSwag加上去的 document.Servers.Clear(); document.Servers.Add(new OpenApiServer() { Url = "http://localhost:90/NSwag", Description = "地址1" }); document.Servers.Add(new OpenApiServer() { Url = "http://127.0.0.1:90/NSwag", Description = "地址2" }); //192.168.28.213是我本地IP document.Servers.Add(new OpenApiServer() { Url = "http://192.168.28.213:90/NSwag", Description = "地址3" }); }; settings.Path = documentPath; }); app.UseSwaggerUi3(settings => { //settings.SwaggerRoutes.Add(new NSwag.AspNetCore.SwaggerUi3Route("demo", "/swagger/v1/swagger.json")); settings.TransformToExternalPath = (s, r) => { if (s.EndsWith("swagger.json")) { return $"/NSwag{s}"; } return s; }; }); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); } } }
比如這里我們的虛擬路徑是NSwag,使用IIS部署:
項目運行后
四、總結
后面還有東西就不寫了,還是那三個注意點:
主要就是記住三點:
1、服務注入使用AddOpenApiDocument方法(盡量不要用AddSwaggerDocument),主要就是生成接口相關信息,如認證,接口注釋等等,還有幾種過濾器幫助我們實現自己的需求
2、中間件注入有兩個:UseOpenApi(盡量不要使用UseSwagger,后續版本將會被移除)和UseSwaggerUi3(盡量不要使用UseSwaggerUi,后續版本將會被移除):
UseOpenApi負責返回接口架構信息,返回的是json格式的數據
UseSwaggerUi3負責返回的是頁面信息,返回的是html內容
3、如果涉及到接口生成的,盡可能在AddOpenApiDocument中實現,如果涉及到UI頁面的,盡可能在UseSwaggerUi3中實現