上一篇介绍了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;
}
}

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。

  同样的,因为这个值关系比较重要,尽可能不要去修改吧。

  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中实现

.net core的Swagger接口文档使用教程(二):NSwag的更多相关文章

  1. .net core的Swagger接口文档使用教程(一):Swashbuckle

    现在的开发大部分都是前后端分离的模式了,后端提供接口,前端调用接口.后端提供了接口,需要对接口进行测试,之前都是使用浏览器开发者工具,或者写单元测试,再或者直接使用Postman,但是现在这些都已经o ...

  2. springboot+swagger接口文档企业实践(下)

    目录 1.引言 2. swagger接口过滤 2.1 按包过滤(package) 2.2 按类注解过滤 2.3 按方法注解过滤 2.4 按分组过滤 2.4.1 定义注解ApiVersion 2.4.2 ...

  3. SpringBoot开发mockserver及生成swagger接口文档

    通过springboot开发mock server,包含get及post接口,用于练习接口自动化及jmeter很方便 当然,也为后面jenkins持续集成做基础(开发push代码后  → jenkin ...

  4. .Net Core---- WebApi生成Swagger接口文档

    1. Swagger是什么? Swagger 是一个规范和完整的框架,用于生成.描述.调用和可视化 RESTful 风格的 Web 服务.总体目标是使客户端和文件系统作为服务器以同样的速度来更新.文件 ...

  5. REST-framework快速构建API--生成Swagger接口文档

    一.Swagger概述 1.引言 当接口开发完成,紧接着需要编写接口文档.传统的接口文档使用Word编写,or一些接口文档管理平台进行编写,但此类接口文档维护更新比较麻烦,每次接口有变更,需要手动修改 ...

  6. swagger接口文档

    1 在Visual Studio 中创建一个Asp.NET  WebApi 项目,项目名:Com.App.SysApi(本例创建的是 .net 4.5 框架程序) 2  打开Nuget 包管理软件,查 ...

  7. springboot+swagger接口文档企业实践(上)

    目录 1.引言 2.swagger简介 2.1 swagger 介绍 2.2 springfox.swagger与springboot 3. 使用springboot+swagger构建接口文档 3. ...

  8. Swagger 接口文档规范

    导语: 相信无论是前端还是后端开发,都或多或少地被接口文档折磨过.前端经常抱怨后端给的接口文档与实际情况不一致.后端又觉得编写及维护接口文档会耗费不少精力,经常来不及更新.其实无论是前端调用后端,还是 ...

  9. .netcore 3.1高性能微服务架构:加入swagger接口文档

    本文为原创文章:首发:http://www.zyiz.net/tech/detail-108663.html swagger是什么? Swagger 是一个规范和完整的框架,用于生成.描述.调用和可视 ...

随机推荐

  1. Spring Boot下使用拦截器

    Spring Boot对于原来在配置文件配置的内容,现在全部体现在一个类中,该类需要继承自WebMvcConfigurationSupport类,并使用@Configuration进行注解,表示该类为 ...

  2. Spring Boot中使用Mybatis

    一.步骤 导入依赖:MySQL驱动.Druid依赖.MyBatis与Spring Boot整合依赖.Lombok依赖 在Service接口实现类上添加@Service注解 在Dao接口上添加@Mapp ...

  3. java中子类继承父类什么?

    1.继承public和protected修饰的属性和方法,不管子类和父类是否在同一个包: 2.继承默认权限修饰符修饰的属性和方法,前提是子类和父类在同一个包.

  4. Java 将Word转为OFD

    通常在工作中比较常用到的Microsoft Word是属于国外的文档内容编辑软件,其编译技术均属国外.而OFD是一种我国的自主文档格式,在某些特定行业或企业的文档存储技术上是一种更为安全的选择.下面将 ...

  5. numpy基础教程--where函数的使用

    在numpy中,where函数是一个三元运算符,函数原型为where(condition, x, y),意思是当条件成立的时候,将矩阵的值设置为x,否则设置为y 一个很简单的应用就是,在一个矩阵当中, ...

  6. 『学了就忘』Linux服务管理 — 75、Linux系统中的服务

    目录 1.服务的介绍 2.Windows系统中的服务 3.Linux系统中服务的分类 4.独立的服务和基于xinetd服务的区别 5.如何查看一个服务是独立的服务还是基于xinetd的服务 (1)查看 ...

  7. 编译工具grdle部署

    目录 一.简介 二.部署 三.测试 一.简介 Gradle 是以 Groovy 语言为基础,面向Java应用为主.基于DSL(领域特定语言)语法的自动化构建工具.在github上,gradle项目很多 ...

  8. PHP安装sqlsrv扩展( Centos系统、或宝塔面板)

    最近新安装了一台Centos服务器, 由于软件使用PHP + sqlserver , 因此需要给PHP安装一个sqlsrv扩展, 虽然这个扩展自己也安装过很多次了,但是从来都没有记录下来过,导致偶尔还 ...

  9. Python小组作业:基于yolov5的口罩佩戴识别

    Python老师给了三个小组项目:1.自身专业问题 2.人工智能 3.游戏或者小工具 提前告知了,写游戏不好拿高分,小工具又不能展示自己的水平.大一刚来也没碰到什么专业问题,于是经过讨论,决定了做人工 ...

  10. java 输入输出IO流 字符流 FileWriter FileReader

    为什么要使用字符流 当使用字节流读取文本文件时,可能会有一个小问题.就是遇到中文字符时,可能不会显示完整的字符,那是因为一个中文字符可能占用多个字节存储.所以Java提供一些字符流类,以字符为单位读写 ...