首页 技术 正文
技术 2022年11月15日
0 收藏 814 点赞 3,938 浏览 31339 个字

  现在的开发大部分都是前后端分离的模式了,后端提供接口,前端调用接口。后端提供了接口,需要对接口进行测试,之前都是使用浏览器开发者工具,或者写单元测试,再或者直接使用Postman,但是现在这些都已经out了。后端提供了接口,如何跟前端配合说明接口的性质,参数,验证情况?这也是一个问题。有没有一种工具可以根据后端的接口自动生成接口文档,说明接口的性质,参数等信息,又能提供接口调用等相关功能呢?

  答案是有的。Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。而作为.net core开发,Swashbuckle是swagger应用的首选!本文旨在介绍Swashbuckle的一些常见功能,以满足大部分开发的需要!

  本文旨在介绍Swashbuckle的一般用法以及一些常用方法,让读者读完之后对Swashbuckle的用法有个最基本的理解,可满足绝大部分需求的需要,比如认证问题、虚拟路劲问题,返回值格式问题等等

  如果对Swashbuckle源码感兴趣,可以去github上pull下来看看  

  github中Swashbuckle.AspNetCore源码地址:https://github.com/domaindrivendev/Swashbuckle.AspNetCore

  

  一、一般用法

   注:这里一般用法的Demo源码已上传到百度云:https://pan.baidu.com/s/1Z4Z9H9nto_CbNiAZIxpFFQ (提取码:pa8s ),下面第二、三部分的功能可在Demo源码基础上去尝试。

  创建一个.net core项目(这里采用的是.net core3.1),然后使用nuget安装Swashbuckle.AspNetCore,建议安装5.0以上版本,因为swagger3.0开始已经加入到OpenApi项目中,因此Swashbuckle新旧版本用法还是有一些差异的。

  比如,我们一个Home控制器:  

    /// <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.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo()
{
Version = "v0.0.1",
Title = "swagger测试项目",
Description = $"接口文档说明",
Contact = new OpenApiContact()
{
Name = "zhangsan",
Email = "xxx@qq.com",
Url = null
}
});
}); ...
}
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
... app.UseSwagger();
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "v1");
});

      ...
}

  然后运行项目,输入http://localhost:5000/swagger,得到接口文档页面:

  

  点击Try it out可以直接调用接口。

  这里,发现接口没有注解说明,这不太友好,而Swashbuckle的接口可以从代码注释中获取,也可以使用代码说明,我们做开发的当然想直接从注释获取啦。

  但是另一方面,因为注释在代码编译时会被过滤掉,因此我们需要在项目中生成注释文件,然后让程序加载注释文件,操作如下:

  右键项目=》切换到生成(Build),在最下面输出输出中勾选【XML文档文件】,同时,在错误警告的取消显示警告中添加1591代码:

  注:建议这里添加1591,因为如果不添加,而且勾选【XML文档文件】,那么如果代码中没有注释,项目将会抛出茫茫多的警告,而1591则表示取消这种无注释的警告

  

  生成当前项目时会将项目中所有的注释打包到这个文件中。

  然后修改ConfigureServices:  

    public void ConfigureServices(IServiceCollection services)
{
      ...
      
services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo()
{
Version = "v0.0.1",
Title = "swagger测试项目",
Description = $"接口文档说明",
Contact = new OpenApiContact()
{
Name = "zhangsan",
Email = "xxx@qq.com",
Url = null
}
}); options.IncludeXmlComments("SwashbuckleDemo.xml", true);
}); ...
}

  上面使用IncludeXmlComments方法加载注释,第二个参数true表示注释文件包含了控制器的注释,如果不包含控制器注释(如引用的其他类库),可以将它置为false

  注意上面的xml文件要与它对应的dll文件放到同目录,如果不在同一目录,需要自行指定目录,如果找不到文件,可能会抛出异常!

  另外,如果项目引用的其他项目,可以将其他项目也生成xml注释文件,然后使用IncludeXmlComments方法加载,从而避免部分接口信息无注解情况

  运行后可以得到接口的注释:

  

  接着,既然是提供接口,没有认证怎么行,比如,Home控制器下还有一个Post接口,但是接口需要认证,比如JwtBearer认证:  

    /// <summary>
/// 测试接口
/// </summary>
[ApiController]
[Route("[controller]")]
public class HomeController : ControllerBase
{
... /// <summary>
/// 使用认证获取数据
/// </summary>
/// <returns>返回数据</returns>
[HttpPost, Authorize]
public string Post()
{
return "这是认证后的数据";
}
}

  为了接口能使用认证,修改Startup的ConfigureServices:  

    public void ConfigureServices(IServiceCollection services)
{
     ...

services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo()
{
Version = "v0.0.1",
Title = "swagger测试项目",
Description = $"接口文档说明",
Contact = new OpenApiContact()
{
Name = "zhangsan",
Email = "xxx@qq.com",
Url = null
}
}); options.IncludeXmlComments("SwashbuckleDemo.xml", true);//第二个参数true表示注释文件包含了控制器的注释 //定义JwtBearer认证方式一
options.AddSecurityDefinition("JwtBearer", new OpenApiSecurityScheme()
{
Description = "这是方式一(直接在输入框中输入认证信息,不需要在开头添加Bearer)",
Name = "Authorization",//jwt默认的参数名称
In = ParameterLocation.Header,//jwt默认存放Authorization信息的位置(请求头中)
Type = SecuritySchemeType.Http,
Scheme = "bearer"
}); //定义JwtBearer认证方式二
//options.AddSecurityDefinition("JwtBearer", new OpenApiSecurityScheme()
//{
// Description = "这是方式二(JWT授权(数据将在请求头中进行传输) 直接在下框中输入Bearer {token}(注意两者之间是一个空格))",
// Name = "Authorization",//jwt默认的参数名称
// In = ParameterLocation.Header,//jwt默认存放Authorization信息的位置(请求头中)
// Type = SecuritySchemeType.ApiKey
//}); //声明一个Scheme,注意下面的Id要和上面AddSecurityDefinition中的参数name一致
var scheme = new OpenApiSecurityScheme()
{
Reference = new OpenApiReference() { Type = ReferenceType.SecurityScheme, Id = "JwtBearer" }
};
//注册全局认证(所有的接口都可以使用认证)
options.AddSecurityRequirement(new OpenApiSecurityRequirement()
{
[scheme] = new string[0]
});
});

      ...
}

  程序运行后效果如下:  

  

  上面说了,添加JwtBearer认证有两种方式,两种方式的区别如下:

    

  到这里应该就已经满足大部分需求的用法了,这也是网上很容易就能搜索到的,接下来介绍的是一些常用到的方法。

  

  服务注入(AddSwaggerGen)

  前面介绍到,Swashbuckle的服务注入是在ConfigureServices中使用拓展方法AddSwaggerGen实现的

    services.AddSwaggerGen(options =>
{
//使用options注入服务
});

  确切的说swagger的服务注入是使用SwaggerGenOptions来实现的,下面主要介绍SwaggerGenOptions的一些常用的方法:

  SwaggerDoc

  SwaggerDoc主要用来声明一个文档,上面的例子中声明了一个名称为v1的接口文档,当然,我们可以声明多个接口文档,比如按开发版本进行声明:  

    options.SwaggerDoc("v1", new OpenApiInfo()
{
Version = "v0.0.1",
Title = "项目v0.0.1",
Description = $"接口文档说明v0.0.1",
Contact = new OpenApiContact()
{
Name = "zhangsan",
Email = "xxx@qq.com",
Url = null
}
}); options.SwaggerDoc("v2", new OpenApiInfo()
{
Version = "v0.0.2",
Title = "项目v0.0.2",
Description = $"接口文档说明v0.0.2",
Contact = new OpenApiContact()
{
Name = "lisi",
Email = "xxxx@qq.com",
Url = null
}
});
  
   ...

  开发过程中,可以将接口文档名称设置成枚举或者常量值,以方便文档名的使用。

  至于上面OpenApiInfo声明的各参数,其实就是要在SwaggerUI页面上展示出来的,读者可自行测试一下,这里不过多说明,只是顺带提一下Description属性,这个是一个介绍文档接口的简介,但是这个属性是支持html展示的,也就是说可以生成一些html代码放到Description属性中。

  声明多个文档,可以将接口进行归类,不然一个项目几百个接口,查看起来也不方便,而将要接口归属某个文档,我们可以使ApiExplorerSettingsAttribute指定GroupName来指定,如:  

    /// <summary>
/// 未使用ApiExplorerSettings特性,表名属于每一个swagger文档
/// </summary>
/// <returns>结果</returns>
[HttpGet("All")]
public string All()
{
return "All";
}
/// <summary>
/// 使用ApiExplorerSettings特性表名该接口属于swagger文档v1
/// </summary>
/// <returns>Get结果</returns>
[HttpGet]
[ApiExplorerSettings(GroupName = "v1")]
public string Get()
{
return "Get";
}
/// <summary>
/// 使用ApiExplorerSettings特性表名该接口属于swagger文档v2
/// </summary>
/// <returns>Post结果</returns>
[HttpPost]
[ApiExplorerSettings(GroupName = "v2")]
public string Post()
{
return "Post";
}

  因为我们现在有两个接口文档了,想要在swaggerUI中看得到,还需要在中间件中添加相关文件的swagger.json文件的入口:  

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
... app.UseSwagger();
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "v1");
options.SwaggerEndpoint("/swagger/v2/swagger.json", "v2");
}); ...
}

  运行项目后:

  

  

  上面使用ApiExplorerSettingsAttribute的GroupName属性指定归属的swagger文档(GroupName需要设置成上面SwaggerDoc声明的文档的名称),如果不使用ApiExplorerSettingsAttribute,那么接口将属于所有的swagger文档,上面的例子可以看到/Home/All接口既属于v1也属于v2。

  另外ApiExplorerSettingsAttribute还有个IgnoreApi属性,如果设置成true,将不会在swagger页面展示该接口。

  但是接口一个个的去添加ApiExplorerSettingsAttribute,是不是有点繁琐了?没事,我们可以采用Convertion实现,主要是IActionModelConvention和IControllerModelConvention两个:

  IActionModelConvention方式:  

    public class GroupNameActionModelConvention : IActionModelConvention
{
public void Apply(ActionModel action)
{
if (action.Controller.ControllerName == "Home")
{
if (action.ActionName == "Get")
{
action.ApiExplorer.GroupName = "v1";
action.ApiExplorer.IsVisible = true;
}
else if (action.ActionName == "Post")
{
action.ApiExplorer.GroupName = "v2";
action.ApiExplorer.IsVisible = true;
}
}
}
}

  然后在ConfigureService中使用:  

    services.AddControllers(options =>
{
options.Conventions.Add(new GroupNameActionModelConvention());
});

  或者使用IControllerModelConvention方式:  

    public class GroupNameControllerModelConvention : IControllerModelConvention
{
public void Apply(ControllerModel controller)
{
if (controller.ControllerName == "Home")
{
foreach (var action in controller.Actions)
{ if (action.ActionName == "Get")
{
action.ApiExplorer.GroupName = "v1";
action.ApiExplorer.IsVisible = true;
}
else if (action.ActionName == "Post")
{
action.ApiExplorer.GroupName = "v2";
action.ApiExplorer.IsVisible = true;
}
}
}
}
}

  然后在ConfigureService中使用:  

    services.AddControllers(options =>
{
options.Conventions.Add(new GroupNameControllerModelConvention());
});

  这两种方式实现的效果和使用ApiExplorerSettingsAttribute是一样的,细心的朋友可能会注意,action.ApiExplorer.GroupName与ApiExplorerSettingsAttribute.GroupName是对应的,action.ApiExplorer.IsVisible则与ApiExplorerSettingsAttribute.IgnoreApi是对应的  

  IncludeXmlComments

  IncludeXmlComments是用于加载注释文件,Swashbuckle会从注释文件中去获取接口的注解,接口参数说明以及接口返回的参数说明等信息,这个在上面的一般用法中已经介绍了,这里不再重复说明

  IgnoreObsoleteActions

  IgnoreObsoleteActions表示过滤掉ObsoleteAttribute属性声明的接口,也就是说不会在SwaggerUI中显示接口了,ObsoleteAttribute修饰的接口表示接口已过期,尽可能不要再使用。

  方法调用等价于:  

    options.SwaggerGeneratorOptions.IgnoreObsoleteActions = true;

  IgnoreObsoleteProperties

  IgnoreObsoleteProperties的作用类似于IgnoreObsoleteActions,只不过IgnoreObsoleteActions是作用于接口,而IgnoreObsoleteProperties作用于接口的请求实体和响应实体参数中的属性。

  方法调用等价于:  

    options.SchemaGeneratorOptions.IgnoreObsoleteProperties = true;

  OrderActionsBy

  OrderActionsBy用于同一组接口(可以理解为同一控制器下的接口)的排序,默认情况下,一般都是按接口所在类的位置进行排序(源码中是按控制器名称排序,但是同一个控制器中的接口是一样的)。

  比如上面的例子中,我们可以修改成按接口路由长度排序:  

    options.OrderActionsBy(apiDescription => apiDescription.RelativePath.Length.ToString());

  运行后Get接口和Post接口就在All接口前面了:

  

  需要注意的是,OrderActionsBy提供的排序只有升序,其实也就是调用IEnumerable<ApiDescription>的OrderBy方法,虽然不理解为什么只有升序,但降序也是可以采用这个升序实现的,将就着用吧。

  CustomSchemaIds

  CustomSchemaIds方法用于自定义SchemaId,Swashbuckle中的每个Schema都有唯一的Id,框架会使用这个Id匹配引用类型,因此这个Id不能重复。

  默认情况下,这个Id是根据类名得到的(不包含命名空间),因此,当我们有两个相同名称的类时,Swashbuckle就会报错:  

    System.InvalidOperationException: Can't use schemaId "$XXXXX" for type "$XXXX.XXXX". The same schemaId is already used for type "$XXXX.XXXX.XXXX"

  就是类似上面的异常,一般时候我们都得去改类名,有点不爽,这时就可以使用这个方法自己自定义实现SchemaId的获取,比如,我们自定义实现使用类名的全限定名(包含命名空间)来生成SchemaId,上面的异常就没有了:   

    options.CustomSchemaIds(CustomSchemaIdSelector);    string CustomSchemaIdSelector(Type modelType)
{
if (!modelType.IsConstructedGenericType) return modelType.FullName.Replace("[]", "Array"); var prefix = modelType.GetGenericArguments()
.Select(genericArg => CustomSchemaIdSelector(genericArg))
.Aggregate((previous, current) => previous + current); return prefix + modelType.FullName.Split('`').First();
}

  TagActionsBy

  Tag是标签组,也就是将接口做分类的一个概念。

  TagActionsBy用于获取一个接口所在的标签分组,默认的接口标签分组是控制器名,也就是接口被分在它所属的控制器下面,我们可以改成按请求方法进行分组  

    options.TagActionsBy(apiDescription => new string[] { apiDescription.HttpMethod});

  运行后:

  

  注意到,上面还有一个Home空标签,如果不想要这个空标签,可以将它的注释去掉,(不明白为什么Swashbuckle为什么空标签也要显示出来,难道是因为作者想着只要有东西能展示,就应该显示出来?)

  MapType

  MapType用于自定义类型结构(Schema)的生成,Schema指的是接口参数和返回值等的结构信息。

  比如,我有一个获取用户信息的接口:  

    /// <summary>
/// 获取用户
/// </summary>
/// <returns>用户信息</returns>
[HttpGet("GetUser")]
public User GetUser(int id)
{
//这里根据Id获取用户信息
return new User()
{
Name = "张三"
};
}

  其中User是自己定义的一个实体   

    /// <summary>
/// 用户信息
/// </summary>
public class User
{
/// <summary>
/// 用户名称
/// </summary>
public string Name { get; set; }
/// <summary>
/// 用户密码
/// </summary>
public string Password { get; set; }
/// <summary>
/// 手机号码
/// </summary>
public string Phone { get; set; }
/// <summary>
/// 工作
/// </summary>
public string Job { get; set; }
}

  默认情况下,swagger生成的结构是json格式:

  

  通过MapType方法,可以修改User生成的架构,比如修改成字符串类型:  

    options.MapType<User>(() =>
{
return new OpenApiSchema() {
Type= "string"
};
});

  运行后显示:

  

  AddServer

  Server指的是接口访问的域名和前缀(虚拟路径),以方便访问不同地址的接口(注意设置跨域).

  AddServer用于全局的添加接口域名和前缀(虚拟路径)部分信息,默认情况下,如果我们在SwaggerUi页面使用Try it out去调用接口时,默认使用的是当前swaggerUI页面所在的地址域名信息:

  

  而AddServer方法运行我们添加其他的地址域名,比如:  

    options.AddServer(new OpenApiServer() { Url = "http://localhost:5000", Description = "地址1" });
options.AddServer(new OpenApiServer() { Url = "http://127.0.0.1:5001", Description = "地址2" });
//192.168.28.213是我本地IP
options.AddServer(new OpenApiServer() { Url = "http://192.168.28.213:5002", Description = "地址3" });

  我分别在上面3个端口开启程序,运行后:

  

  注意:如果读者本地访问不到,看看自己程序是否有监听这三个地址,而且记得要设置跨域,否则会导致请求失败:  

   public void ConfigureServices(IServiceCollection services)
{
 ...
      
      services.AddCors();
    
   ...
}

  public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
  {
    ...
        
    app.UseCors(builder =>
    {
        builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader();
    });
    
    ...
  }

  在开发过程中,我们的程序可能会发布到不同的环境,比如本地开发环境,测试环境,预生产环境等等,因此,我们可以使用AddServer方法将不同环境的地址配置上去就能直接实现调用了。

  在项目部署时,可能会涉及到虚拟目录之类的东西,比如,使用IIS部署时,可能会给项目加一层虚拟路径:

  

  或者使用nginx做一层反向代理:

  

  这个时候虽然可以使用http://ip:port/Swashbuckle/swagger/index.html访问到swaggerUI,但是此时可能会报错 Not Found /swagger/v1/swagger.json:

  

  这是因为加了虚拟路径,而swagger并不知道,所以再通过/swagger/v1/swagger.json去获取接口架构信息当然会报404了,我们可以改下Swagger中间件:  

    app.UseSwagger();
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/Swashbuckle/swagger/v1/swagger.json", "v1");
options.SwaggerEndpoint("/Swashbuckle/swagger/v2/swagger.json", "v2");
});

  再使用虚拟路径就可以访问到SwaggerUI页面了,但是问题还是有的,因为所有接口都没有加虚拟路径,上面说道,swagger调用接口默认是使用SwaggerUI页面的地址+接口路径去访问的,这就会少了虚拟路径,访问自然就变成了404:

  

  这个时候就可以调用AddServer方法去添加虚拟路径了:  

    //注意下面的端口,已经变了
   options.AddServer(new OpenApiServer() { Url = "http://localhost:90/Swashbuckle", Description = "地址1" });
options.AddServer(new OpenApiServer() { Url = "http://127.0.0.1:90/Swashbuckle", Description = "地址2" });
//192.168.28.213是我本地IP
options.AddServer(new OpenApiServer() { Url = "http://192.168.28.213:90/Swashbuckle", Description = "地址3" });

  部署运行后就可以访问了:

  

  一般的,开发过程中,我们可以把这个虚拟路径做成配置,在然后从配置读取即可。

  注:我记得Swashbuckle在swagger2.0的版本中SwaggerDocument中有个BasePath,可以很轻松的设置虚拟路径,但是在swagger3+之后把这个属性删除了,不知道什么原因

  AddSecurityDefinition

  AddSecurityDefinition用于声明一个安全认证,注意,只是声明,并未指定接口必须要使用认证,比如声明JwtBearer认证方式:  

    //定义JwtBearer认证方式一
options.AddSecurityDefinition("JwtBearer", new OpenApiSecurityScheme()
{
Description = "这是方式一(直接在输入框中输入认证信息,不需要在开头添加Bearer)",
Name = "Authorization",//jwt默认的参数名称
In = ParameterLocation.Header,//jwt默认存放Authorization信息的位置(请求头中)
Type = SecuritySchemeType.Http,
Scheme = "bearer"
});

  AddSecurityDefinition方法需要提供一个认证名以及一个OpenApiSecurityScheme对象,而这个OpenApiSecurityScheme对象就是描述的认证信息,常用的有:  

   Type:表示认证方式,有ApiKey,Http,OAuth2,OpenIdConnect四种,其中ApiKey是用的最多的。
  Description:认证的描述
  Name:携带认证信息的参数名,比如Jwt默认是Authorization
  In:表示认证信息发在Http请求的哪个位置
  Scheme:认证主题,只对Type=Http生效,只能是basic和bearer
  BearerFormat::Bearer认证的数据格式,默认为Bearer Token(中间有一个空格)
  Flows:OAuth认证相关设置,比如认证方式等等
  OpenIdConnectUrl:使用OAuth认证和OpenIdConnect认证的配置发现地址
  Extensions:认证的其他拓展,如OpenIdConnect的Scope等等
  Reference:关联认证

   这些属性中,最重要的当属Type,它指明了认证的方式,用通俗的话讲:

  ApiKey表示就是提供一个框,你填值之后调用接口,会将填的值与Name属性指定的值组成一个键值对,放在In参数指定的位置通过http传送到后台。

  Http也是提供了一个框,填值之后调用接口,会将填的值按照Scheme指定的方式进行处理,再和Name属性组成一个键值对,放在In参数指定的位置通过http传送到后台。这也就解释了为什么Bearer认证可以有两种方式。

  OAuth2,OpenIdConnect需要提供账号等信息,然后去远程服务进行授权,一般使用Swagger都不推荐使用这种方式,因为比较复杂,而且授权后的信息也可以通过ApiKey方式传送到后台。

  再举个例子,比如我们使用Cookie认证:  

    options.AddSecurityDefinition("Cookies", new OpenApiSecurityScheme()
{
Description = "这是Cookie认证方式",
Name = "Cookies",//这个是Cookie名
In = ParameterLocation.Cookie,//信息保存在Cookie中
Type = SecuritySchemeType.ApiKey
});

  注:如果将信息放在Cookie,那么在SwaggerUI中调用接口时,认证信息可能不会被携带到后台,因为浏览器不允许你自己操作Cookie,因此在发送请求时会过滤掉你自己设置的Cookie,但是SwaggerUI页面调用生成的Curl命令语句是可以成功访问的

   好了,言归正传,当添加了上面JwtBearer认证方式后,这时SwaggerUI多了一个认证的地方:

  

  但是这时调用接口并不需要认证信息,因为还没有指定哪些接口需要认证信息

  AddSecurityRequirement

   AddSecurityDefinition仅仅是声明已一个认证,不一定要对接口用,而AddSecurityRequirement是将声明的认证作用于所有接口(AddSecurityRequirement好像可以声明和引用一起实现),比如将上面的JwtBearer认证作用于所有接口:  

    //声明一个Scheme,注意下面的Id要和上面AddSecurityDefinition中的参数name一致
var scheme = new OpenApiSecurityScheme()
{
Reference = new OpenApiReference() { Type = ReferenceType.SecurityScheme, Id = "JwtBearer" }
};
//注册全局认证(所有的接口都可以使用认证)
options.AddSecurityRequirement(new OpenApiSecurityRequirement()
{
[scheme] = new string[0]
});

  运行后,发现所有接口后面多了一个锁,表明此接口需要认证信息:

  

 AddSecurityRequirement调用需要一个OpenApiSecurityRequirement对象,他其实是一个字典型,也就是说可以给接口添加多种认证方式,而它的键是OpenApiSecurityScheme对象,比如上面的例子中将新定义的OpenApiSecurityScheme关联到已经声明的认证上,而值是一个字符串数组,一般指的是OpenIdConnect的Scope。

  需要注意的是,AddSecurityRequirement声明的作用是对全部的接口生效,也就是说所有接口后面都会加锁,但这并不影响我们接口的调用,毕竟调用逻辑还是由后台代码决定的,但是这里加锁就容易让人误导以为都需要认证。

  DocumentFilter

  document顾名思义,当然指的就是swagger文档了。

  DocumentFilter是文档过滤器,它是在获取swagger文档接口,返回结果前调用,也就是请求swagger.json时调用,它允许我们对即将返回的swagger文档信息做调整,比如上面的例子中添加的全局认证方式和AddSecurityRequirement添加的效果是一样的:  

    public class MyDocumentFilter : IDocumentFilter
{
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
//声明一个Scheme,注意下面的Id要和上面AddSecurityDefinition中的参数name一致
var scheme = new OpenApiSecurityScheme()
{
Reference = new OpenApiReference() { Type = ReferenceType.SecurityScheme, Id = "JwtBearer" }
};
//注册全局认证(所有的接口都可以使用认证)
swaggerDoc.SecurityRequirements.Add(new OpenApiSecurityRequirement()
{
[scheme] = new string[0]
});
}
}

  然后使用DocumentFilter方法添加过滤器:  

    options.DocumentFilter<MyDocumentFilter>();

  DocumentFilter方法需要提供一个实现了IDocumentFilter接口的Apply方法的类型和它实例化时所需要的的参数,而IDocumentFilter的Apply方法提供了OpenApiDocument和DocumentFilterContext两个参数,DocumentFilterContext参数则包含了当前文件接口方法的信息,比如调用的接口的Action方法和Action的描述(如路由等)。而OpenApiDocument即包含当前请求的接口文档信息,它包含的属性全部都是全局性的, 这样我们可以像上面添加认证一样去添加全局配置,比如,如果不使用AddServer方法,我们可以使用DocumentFilter去添加:  

    public class MyDocumentFilter : IDocumentFilter
{
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
swaggerDoc.Servers.Add(new OpenApiServer() { Url = "http://localhost:90", Description = "地址1" });
swaggerDoc.Servers.Add(new OpenApiServer() { Url = "http://127.0.0.1:90", Description = "地址2" });
//192.168.28.213是我本地IP
swaggerDoc.Servers.Add(new OpenApiServer() { Url = "http://192.168.28.213:90", Description = "地址3" });
}
}

  记得使用DocumentFilter添加过滤器。

  再比如,上面我们对接口进行了swagger文档分类使用的是ApiExplorerSettingsAttribute,如果不想对每个接口使用ApiExplorerSettingsAttribute,我们可以使用DocumentFilter来实现,先创建一个类实现IDocumentFilter接口: 

    public class GroupNameDocumentFilter : IDocumentFilter
{
string documentName;
string[] actions; public GroupNameDocumentFilter(string documentName, params string[] actions)
{
this.documentName = documentName;
this.actions = actions;
} public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
foreach (var apiDescription in context.ApiDescriptions)
{
if (actions.Contains(apiDescription.ActionDescriptor.RouteValues["action"]))
{
apiDescription.GroupName = documentName;
}
}
}
}

  然后使用DocumentFilter添加过滤器: 

    //All和Get接口属于文档v1
options.DocumentFilter<GroupNameDocumentFilter>(new object[] { "v1", new string[] { nameof(HomeController.Get) } });
//All和Post接口属于v2
options.DocumentFilter<GroupNameDocumentFilter>(new object[] { "v2", new string[] { nameof(HomeController.Post) } });

  然后取消上面Get方法和Post方法的ApiExplorerSettings特性,这样实现的效果和上面直接使用ApiExplorerSettings特性修饰的效果是相似的。

  这里说相似并非一致,是因为上面的GroupNameDocumentFilter是在第一次获取swagger.json时执行设置GroupName,也就是说第一次获取swagger.json会获取到所有的接口,所以一般也不会采用这种方法,而是采用上面介绍的使用IActionModelConvention和IControllerModelConvention来实现。

  OperationFilter

  什么是Operation?Operation可以简单的理解为一个操作,因为swagger是根据项目中的接口,自动生成接口文档,就自然需要对每个接口进行解析,接口路由是什么,接口需要什么参数,接口返回什么数据等等,而对每个接口的解析就可以视为一个Operation。

  OperationFilter是操作过滤器,这个方法需要一个实现类IOperationFilter接口的类型,而它的第二个参数arguments是这个类型实例化时传入的参数。

  OperationFilter允许我们对已经生成的接口进行修改,比如可以添加参数,修改参数类型等等。

  需要注意的是,OperationFilter在获取swagger文档接口时调用,也就是请求swagger.json时调用,而且只对属于当前请求接口文档的接口进行过滤调用。  

  比如我们有一个Operation过滤器:

    public class MyOperationFilter : IOperationFilter
{
string documentName; public MyOperationFilter(string documentName)
{
this.documentName = documentName;
} public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
//过滤处理
}
}

  接着调用SwaggerGenOptions的OperationFilter方法添加  

    options.OperationFilter<MyOperationFilter>(new object[] { "v1" });

  上面的过滤器实例化需要一个参数documentName,所以在OperationFilter方法中有一个参数。

  这个接口只会对当前请求的接口文档进行调用,也就是说,如果我们请求的是swagger文档v1,也就是请求/swagger/v1/swagger.json时,这个过滤器会对All方法和Get方法执行,如果请求的是swagger文档v2,也就是请求/swagger/v2/swagger.json时,这个过滤器会对All方法和Post方法进行调用。自定义的OperationFilter需要实现IOperationFilter的Apply接口方法,而Apply方法有两个参数:OpenApiOperation和OperationFilterContext,同样的,OpenApiOperation包含了和当前接口相关的信息,比如认证情况,所属的标签,还可以自定义的自己的Servers。而OperationFilterContext则包换了接口方法的的相关引用。

  OperationFilter是用的比较多的方法了,比如上面的全局认证,因为直接调用AddSecurityRequirement添加的是全局认证,但是项目中可能部分接口不需要认证,这时我们就可以写一个OperationFilter对每一个接口进行判断了:  

    public class ResponsesOperationFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var authAttributes = context.MethodInfo.DeclaringType.GetCustomAttributes(true)
.Union(context.MethodInfo.GetCustomAttributes(true))
.OfType<AuthorizeAttribute>(); var list = new List<OpenApiSecurityRequirement>();
if (authAttributes.Any() && !context.MethodInfo.GetCustomAttributes(true).OfType<AllowAnonymousAttribute>().Any())
{
operation.Responses["401"] = new OpenApiResponse { Description = "Unauthorized" };
//operation.Responses.Add("403", new OpenApiResponse { Description = "Forbidden" }); //声明一个Scheme,注意下面的Id要和AddSecurityDefinition中的参数name一致
var scheme = new OpenApiSecurityScheme()
{
Reference = new OpenApiReference() { Type = ReferenceType.SecurityScheme, Id = "JwtBearer" }
};
//注册全局认证(所有的接口都可以使用认证)
operation.Security = new List<OpenApiSecurityRequirement>(){new OpenApiSecurityRequirement()
{
[scheme] = new string[0]
}};
}
}
}

  然后使用OperationFilter添加这个过滤器:  

    options.OperationFilter<ResponsesOperationFilter>();

  现在可以测试一下了,我们将上面的All接口使用Authorize特性添加认证

    /// <summary>
/// 未使用ApiExplorerSettings特性,表名属于每一个swagger文档
/// </summary>
/// <returns>结果</returns>
[HttpGet("All"), Authorize]
public string All()
{
return "All";
}

  然后运行项目得到:

  

  再比如,我们一般写接口,都会对返回的数据做一个规范,比如每个接口都会有响应代码,响应信息等等,而程序中我们是通过过滤器去实现的,所以接口都是直接返回数据,但是我们的swagger不知道,比如上面我们的测试接口返回的都是string类型,所以页面上也是展示string类型没错:

  

  假如我们添加了过滤器对结果进行了一个处理,结果不在是string类型了,这个时候我们就可以使用OperationFilter做一个调整了:  

    public class MyOperationFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
foreach (var key in operation.Responses.Keys)
{
var content = operation.Responses[key].Content;
foreach (var mediaTypeKey in content.Keys)
{
var mediaType = content[mediaTypeKey];
var schema = new OpenApiSchema();
schema.Type = "object";
schema.Properties = new Dictionary<string, OpenApiSchema>()
{
["code"] = new OpenApiSchema() { Type = "integer" },
["message"] = new OpenApiSchema() { Type = "string" },
["error"] = new OpenApiSchema()
{
Type = "object",
Properties = new Dictionary<string, OpenApiSchema>()
{
["message"] = new OpenApiSchema() { Type = "string" },
["stackTrace"] = new OpenApiSchema() { Type = "string" }
}
},
["result"] = mediaType.Schema
};
mediaType.Schema = schema;
}
}
}
}

  记得使用OperationFilter添加过滤器:  

    options.OperationFilter<MyOperationFilter>();

  显示效果如下:

  

  RequestBodyFilter

  RequestBody理所当然的就是请求体了,一般指的就是Post请求,RequestBodyFilter就是允许我们对请求体的信息作出调整,同样的,它是在获取Swagger.json文档时调用,而且只对那些有请求体的接口才会执行。

  RequestBodyFilter的用法类似DocumentFilter和OperationFilter,一般也不会去修改请求体的默认行为,因为它可能导致请求失败,所以一般不常用,这里就不介绍了

  ParameterFilter

  Parameter指的是接口的参数,而ParameterFilter当然就是允许我们对参数的结构信息作出调整了,同样的,它是在获取Swagger.json文档时调用,而且只对那些参数的接口才会执行。

  比如,我们有这么一个接口:  

    /// <summary>
/// 有参数接口
/// </summary>
/// <returns></returns>
[HttpGet("GetPara")]
public string GetPara(string para="default")
{
return $"para is {para},but para from header is {Request.Headers["para"]}";
}

  然后我们可以使用ParameterFilter修改上面para参数在http请求中的位置,比如将它放在请求头中:  

    public class MyParameterFilter : IParameterFilter
{
public void Apply(OpenApiParameter parameter, ParameterFilterContext context)
{
if (context.ParameterInfo.Name == "para")
{
parameter.In = ParameterLocation.Header;
}
}
}

  然后使用ParameterFilter方法添加过滤器:  

    options.ParameterFilter<MyParameterFilter>();

  运行后:

  

  不过一般不会使用ParameterFilter去修改参数的默认行为,因为这可能会导致接口调用失败。

  SchemaFilter

  Schema指的是结构,一般指的是接口请求参数和响应返回的参数结构,比如我们想将所有的int类型换成string类型:  

    public class MySchemaFilter : ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
if (context.Type == typeof(int))
{
schema.Type = "string";
}
}
}

  假如有接口:  

    /// <summary>
/// 测试接口
/// </summary>
/// <returns></returns>
[HttpGet("Get")]
public int Get(int id)
{
return 1;
}

  运行后所有的int参数在swaggerUI上都会显示为string 类型:  

  

  再比如,我们可以使用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中体现出来,这个时候我们可以通过SchemaFilter来修改Schema信息。

  比如,可以先用一个特性(例如使用DescriptionAttribute)标识枚举类型的每一项,用于说明含义:  

    public enum SexEnum
{
/// <summary>
/// 未知
/// </summary>
[Description("未知")]
Unknown = 0,
/// <summary>
/// 男
/// </summary>
[Description("男")]
Male = 1,
/// <summary>
/// 女
/// </summary>
[Description("女")]
Female = 2
}

  接着我们创建一个MySchemaFilter类,实现ISchemaFilter接口:

  

    public class MySchemaFilter : ISchemaFilter
{
static readonly ConcurrentDictionary<Type, Tuple<string, object>[]> dict = new ConcurrentDictionary<Type, Tuple<string, object>[]>();
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
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, context);
}
}
private void UpdateSchemaDescription(OpenApiSchema schema, SchemaFilterContext context)
{
if (schema.Reference!=null)
{
var s = context.SchemaRepository.Schemas[schema.Reference.Id];
if (s != null && s.Enum != null && s.Enum.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, context);
}
}
/// <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;
}
}

MySchemaFilter

  最后在Startup中使用  

    services.AddSwaggerGen(options =>
{
... options.SchemaFilter<MySchemaFilter>();
});

  再次运行项目后,得到的架构就有每个枚举项的属性了,当然,你也可以安装自己的意愿去生成特定格式的架构,这只是一个简单的例子

  

  其他方法

  其他方法就不准备介绍了,比如:

  DescribeAllEnumsAsStrings方法表示在将枚举类型解释成字符串名称而不是默认的整形数字

  DescribeAllParametersInCamelCase方法表示将参数使用驼峰命名法处理

  等等这些方法都用的比较少,而且这些都比较简单,感兴趣的可以看看源码学习

  另外需要注意的是,在Swashbuckle.AspNetCore 6.0+以后的版本中,上面两个方法已经被移除了,作者希望我们通过.net core提供的依赖注入及JsonConverter机制自行去实现。

  但是作者有提供了一个 Swashbuckle.AspNetCore.Newtonsoft 包,基于Newtonsoft.Json 来实现DescribeAllEnumsAsStrings,DescribeAllParametersInCamelCase 原来的这两个方法:  

    services.AddSwaggerGenNewtonsoftSupport();
services.Configure<MvcNewtonsoftJsonOptions>(options =>
{
//等价于原来的DescribeAllEnumsAsStrings方法
options.SerializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
//等价于原来的DescribeAllParametersInCamelCase方法
options.SerializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter(new Newtonsoft.Json.Serialization.CamelCaseNamingStrategy()));
});

  特别注意的是,这样做是解决Swagger页面展示枚举类型时按字符串展示,但真实调用接口返回的格式还是需要自行实现JsonConverter。

  毕竟Swagger只是接口说明文档,它不影响真实接口返回的数据信息,而.net core的MVC序列化有两种方案:Newtonsoft.Json和System.Text.Json,所以这也是预料之中的事。

  

  三、添加Swagger中间件(UseSwagger,UseSwaggerUI)

  细心地朋友应该注意到,在上面的例子中,添加Swagger中间件其实有两个,分别是UseSwagger和UseSwaggerUI两个方法:

  UseSwagger:添加Swagger中间件,主要用于拦截swagger.json请求,从而可以获取返回所需的接口架构信息

  UseSwaggerUI:添加SwaggerUI中间件,主要用于拦截swagger/index.html页面请求,返回页面给前端

  整个swagger页面访问流程如下:

  1、浏览器输入swaggerUI页面地址,比如:http://localhost:5000/swagger/index.html,这个地址是可配置的

  2、请求被SwaggerUI中间件拦截,然后返回页面,这个页面是嵌入的资源文件,也可以设置成外部自己的页面文件(使用外部静态文件拦截)

  3、页面接收到Swagger的Index页面后,会根据SwaggerUI中间件中使用SwaggerEndpoint方法设置的文档列表,加载第一个文档,也就是获取文档架构信息swagger.json

  4、浏览器请求的swagger.json被Swagger中间件拦截,然后解析属于请求文档的所有接口,并最终返回一串json格式的数据

  5、浏览器根据接收到的swagger,json数据呈现UI界面

  UseSwagger方法有个包含SwaggerOptions的重载,UseSwaggerUI则有个包含SwaggerUIOptions的重载,两者相辅相成,所以这里在一起介绍这两个方法

  SwaggerOptions

   SwaggerOptions比较简单,就三个属性:

  RouteTemplate

   路由模板,默认值是/swagger/{documentName}/swagger.json,这个属性很重要!而且这个属性中必须包含{documentName}参数。

  上面第3、4步骤已经说到,index.html页面会根据SwaggerUI中间件中使用SwaggerEndpoint方法设置的文档列表,然后使用第一个文档的路由发送一个GET请求,请求会被Swagger中间件中拦截,然后Swagger中间件中会使用RouteTemplate属性去匹配请求路径,然后得到documentName,也就是接口文档名,从而确定要返回哪些接口,所以,这个RouteTemplate一定要配合SwaggerEndpoint中的路由一起使用,要保证通过SwaggerEndpoint方法中的路由能找到documentName。

  比如,如果将RouteTemplate设置成:  

    app.UseSwagger(options =>
{
options.RouteTemplate = "/{documentName}.json";
});

  那么SwaggerEndpoint就得做出相应的调整:  

    app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/v1.json", "v1");
options.SwaggerEndpoint("/v2.json", "v2");
});

  当然,上面的SwaggerEndpoint方法中的路由可以添加虚拟路径,毕竟虚拟路径会在转发时被处理掉。

  总之,这个属性很重要,尽可能不要修改,然后是上面默认的格式在SwaggerEndpoint方法中声明。

  SerializeAsV2

   表示按Swagger2.0格式序列化生成swagger.json,这个不推荐使用,尽可能的使用新版本的就可以了。

  PreSerializeFilters

  这个属性也是个过滤器,类似于上面介绍的DocumentFilter,在解析完所有接口后得到swaggerDocument之后调用执行,也就是在DocumentFilter,OperationFilter等过滤器之后调用执行。不建议使用这个属性,因为它能实现的功能使用DocumentFilter,OperationFilter等过滤器都能实现。

  SwaggerUIOptions

  SwaggerUIOptions则包含了SwaggerUI页面的一些设置,主要有六个属性:

  RoutePrefix

  设置SwaggerUI的Index页面的地址,默认是swagger,也就是说可以使用http://host:port/swagger可以访问到SwaggerUI页面,如果设置成空字符串,那么久可以使用http://host:port直接访问到SwaggerUI页面了

  IndexStream

  上面解释过,Swagger的UI页面是嵌入的资源文件,默认值是:  

    app.UseSwaggerUI(options =>
{
options.IndexStream = () => typeof(SwaggerUIOptions).GetTypeInfo().Assembly.GetManifestResourceStream("Swashbuckle.AspNetCore.SwaggerUI.index.html");
});

  我们可以修改成自己的页面,比如Hello World:  

    app.UseSwaggerUI(options =>
{
options.IndexStream = () => new MemoryStream(Encoding.UTF8.GetBytes("Hello World"));
});

  DocumentTitle

  这个其实就是html页面的title

  HeadContent

  这个属性是往SwaggerUI页面head标签中添加我们自己的代码,比如引入一些样式文件,或者执行自己的一些脚本代码,比如:  

    app.UseSwaggerUI(options =>
{
options.HeadContent += $"<script type='text/javascript'>alert('欢迎来到SwaggerUI页面')</script>";
});

  然后进入SwaggerUI就会弹出警告框了。

  注意,上面的设置使用的是+=,而不是直接赋值。

  但是一般时候,我们不是直接使用HeadConten属性的,而是使用 SwaggerUIOptions的两个拓展方法去实现:InjectStylesheet和InjectJavascript,这两个拓展方法主要是注入样式和javascript代码:  

    /// <summary>
/// Injects additional CSS stylesheets into the index.html page
/// </summary>
/// <param name="options"></param>
/// <param name="path">A path to the stylesheet - i.e. the link "href" attribute</param>
/// <param name="media">The target media - i.e. the link "media" attribute</param>
public static void InjectStylesheet(this SwaggerUIOptions options, string path, string media = "screen")
{
var builder = new StringBuilder(options.HeadContent);
builder.AppendLine($"<link href='{path}' rel='stylesheet' media='{media}' type='text/css' />");
options.HeadContent = builder.ToString();
} /// <summary>
/// Injects additional Javascript files into the index.html page
/// </summary>
/// <param name="options"></param>
/// <param name="path">A path to the javascript - i.e. the script "src" attribute</param>
/// <param name="type">The script type - i.e. the script "type" attribute</param>
public static void InjectJavascript(this SwaggerUIOptions options, string path, string type = "text/javascript")
{
var builder = new StringBuilder(options.HeadContent);
builder.AppendLine($"<script src='{path}' type='{type}'></script>");
options.HeadContent = builder.ToString();
}

  ConfigObject

  其他配置对象,包括之前介绍的SwaggerDocument文档的地址等等。

  OAuthConfigObject

  和OAuth认证有关的配置信息,比如ClientId、ClientSecret等等。

  对于ConfigObject,OAuthConfigObject两个对象,一般都不是直接使用它,而是用SwaggerUIOptions的拓展方法,比如之前一直介绍的SwaggerEndpoint方法,其实就是给ConfigObject的Urls属性增加对象:  

    /// <summary>
/// Adds Swagger JSON endpoints. Can be fully-qualified or relative to the UI page
/// </summary>
/// <param name="options"></param>
/// <param name="url">Can be fully qualified or relative to the current host</param>
/// <param name="name">The description that appears in the document selector drop-down</param>
public static void SwaggerEndpoint(this SwaggerUIOptions options, string url, string name)
{
var urls = new List<UrlDescriptor>(options.ConfigObject.Urls ?? Enumerable.Empty<UrlDescriptor>());
urls.Add(new UrlDescriptor { Url = url, Name = name} );
options.ConfigObject.Urls = urls;
}

  

  四、总结

  到这里基本上就差不多了,写了这么多该收尾了。

   主要就是记住三点:

  1、服务注入使用AddSwaggerGen方法,主要就是生成接口相关信息,如认证,接口注释等等,还有几种过滤器帮助我们实现自己的需求

  2、中间件注入有两个:UseSwagger和UseSwaggerUI:

     UseSwagger负责返回接口架构信息,返回的是json格式的数据

     UseSwaggerUI负责返回的是页面信息,返回的是html内容

  3、如果涉及到接口生成的,尽可能在AddSwaggerGen中实现,如果涉及到UI页面的,尽可能在UseSwaggerUI中实现

  

相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:9,501
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,915
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,748
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,505
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:8,143
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:5,306