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

  现在的开发大部分都是前后端分离的模式了,后端提供接口,前端调用接口。后端提供了接口,需要对接口进行测试,之前都是使用浏览器开发者工具,或者写单元测试,再或者直接使用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中实现

  

posted @ 2020-08-20 15:14  没有星星的夏季  阅读(16253)  评论(92编辑  收藏  举报