上一篇介紹了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 = "[email protected]",
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 = "[email protected]",
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 = "[email protected]",
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;
}
}
MySchemaProcessor
最後在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。
同樣的,因為這個值關系比較重要,盡可能不要去修改吧。
從上面的Path參數的預設值中可以看到,其中有個{documentName}參數,NSwag并沒有要求Path中必須有{documentName}參數。
如果沒有這個參數,就必須指定這個屬性DocumentName,隻是也就是說NSwag隻為一個接口文檔服務。
如果有這個參數,NSwag會周遊所有定義的接口文檔,然後分别對Path屬性替換掉其中中的{documentName}參數,然後分别攔截每個文檔擷取架構資訊的swagger.json請求。
服務注入部分有一個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是SwaggerUI的index.html頁面的位址,作用與Swashbuckle中提到的RoutePrefix是一樣的
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 = "[email protected]",
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中實作
一個專注于.NetCore的技術小白