.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中實現

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM