.NET Magazine國際中文電子雜誌
作 者:許薰尹
文章編號: N201222501
出刊日期: 2020/12/9
這篇文章是《設計與使用ASP.NET Core Web API》系列的文章中的最後一篇,延續《設計與使用ASP.NET Core Web API 1-使用Entity Framework Core存取資料》、《設計與使用ASP.NET Core Web API 2- Swagger》、《設計與使用ASP.NET Core Web API 3-CRUD增刪查改》三篇文章的情境,我們將介紹「Swagger」一些常用設定來客製化ASP.NET Core Web API文件。
「Swagger」常用設定
在這系列文章的最後,我們想來談談「Swagger」常用設定,以客製化更完備、易懂的Web API文件,例如加上服務條款、連絡人、授權等相關資訊。
修改「MyWebAPI」專案「Startup」類別程式碼,在「ConfigureServices」方法叫用「SwaggerDoc」方法時,除了利用「OpenApiInfo」物件的「Title」屬性設定標題;以及「Version」屬性設定文件的版本之外,還可以設定以下屬性:
- · 「Description」:可以較詳細的文字來Web API描述服務。
- · 「TermsOfService」:服務條款文件所在的URI位置。
- · 「Contact」:連絡人資訊。
- · 「License」:授權資訊。
參考以下範例程式碼:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models;
using MyWebAPI.Models;
namespace MyWebAPI {
public class Startup {
public Startup( IConfiguration configuration ) {
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices( IServiceCollection services ) {
services.AddControllers( );
services.AddDbContext<PubsContext>( options =>
options.UseSqlServer( "Server=.\\sqlexpress;Database=Pubs;Trusted_Connection=True;" ) );
services.AddSwaggerGen( gen => {
gen.SwaggerDoc( "v1.0", new Microsoft.OpenApi.Models.OpenApiInfo {
Title = "My Web API",
Version = "v1.0",
Description = " 提供商店資訊的 ASP.NET Core Web API 範例",
TermsOfService = new Uri( "https://test.com/terms" ),
Contact = new OpenApiContact {
Name = "Mary",
Email ="Mary@test.com",
Url = new Uri( "https://test.com/mary" ),
},
License = new OpenApiLicense {
Name = " 版權所有,非經授權,不許轉載本網站內容",
Url = new Uri( "https://test.com/license" ),
}
} );
} );
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure( IApplicationBuilder app, IWebHostEnvironment env ) {
if ( env.IsDevelopment( ) ) {
app.UseDeveloperExceptionPage( );
}
app.UseHttpsRedirection( );
app.UseRouting( );
app.UseAuthorization( );
app.UseSwagger( );
app.UseSwaggerUI( ui => {
ui.SwaggerEndpoint( "/swagger/v1.0/swagger.json", "My Web API Swagger Endpoint" );
//ui.RoutePrefix = string.Empty;
} );
app.UseEndpoints( endpoints => {
endpoints.MapControllers( );
} );
}
}
}
選取Visual Studio 2019開發工具「Build」-「Build Solution」項目編譯目前的專案,確認程式碼能正確編譯,完成後可以同時執行兩個網站,只要在Visual Studio 2019開發工具,按「CTRL」+「F5」組合鍵來執行網站應用程式。在瀏覽器輸入以下網址,檢視Swagger UI文件:
https://localhost:44311/swagger
可以看到畫面如下:

圖 1:客製化Web API文件。
產生XML文件
你可以為ASP.NET Core Web API個別的方法提供更詳細的使用說明,或呼叫範例文件。要達到這個目地,我們需要在程式中使用「///」符號幫程式加上XML註解,並將註解產生成一個XML文件檔案。
從「Solution Explorer」視窗- 「MyWebAPI」專案名稱上方按一下滑鼠右鍵,從快捷選單選擇「Edit Project File」,請參考下圖所示:

圖 2:編輯專案檔案。
參考以下程式碼,加入「GenerateDocumentationFile」項目,設定值為「true」;「NoWarn」項目的值為「$(NoWarn);1591」:
<Project Sdk = "Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include = "Bricelam.EntityFrameworkCore.Pluralizer" Version = "1.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include = "Microsoft.EntityFrameworkCore.Sqlite" Version = "3.1.6" />
<PackageReference Include = "Microsoft.EntityFrameworkCore.SqlServer" Version = "3.1.7" />
<PackageReference Include = "Microsoft.EntityFrameworkCore.Tools" Version = "3.1.7">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include = "Microsoft.VisualStudio.Web.CodeGeneration.Design" Version = "3.1.4" />
<PackageReference Include = "Swashbuckle.AspNetCore" Version = "5.5.1" />
</ItemGroup>
</Project>
修改「Startup」類別程式碼,利用「SwaggerGenOptions」物件的「IncludeXmlComments」方法設定上個步驟產生的XML所在路徑,請參考以下程式列表:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models;
using MyWebAPI.Models;
namespace MyWebAPI {
public class Startup {
public Startup( IConfiguration configuration ) {
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices( IServiceCollection services ) {
services.AddControllers( );
services.AddDbContext<PubsContext>( options =>
options.UseSqlServer( "Server=.\\sqlexpress;Database=Pubs;Trusted_Connection=True;" ) );
services.AddSwaggerGen( gen => {
gen.SwaggerDoc( "v1.0", new Microsoft.OpenApi.Models.OpenApiInfo {
Title = "My Web API",
Version = "v1.0",
Description = " 提供商店資訊的 ASP.NET Core Web API 範例",
TermsOfService = new Uri( "https://test.com/terms" ),
Contact = new OpenApiContact {
Name = "Mary",
Email ="Mary@test.com",
Url = new Uri( "https://test.com/mary" ),
},
License = new OpenApiLicense {
Name = " 版權所有,非經授權,不許轉載本網站內容",
Url = new Uri( "https://test.com/license" ),
}
} );
var xmlFile = $"{Assembly.GetExecutingAssembly( ).GetName( ).Name}.xml";
var xmlPath = Path.Combine( AppContext.BaseDirectory, xmlFile );
gen.IncludeXmlComments( xmlPath );
} );
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure( IApplicationBuilder app, IWebHostEnvironment env ) {
if ( env.IsDevelopment( ) ) {
app.UseDeveloperExceptionPage( );
}
app.UseHttpsRedirection( );
app.UseRouting( );
app.UseAuthorization( );
app.UseSwagger( );
app.UseSwaggerUI( ui => {
ui.SwaggerEndpoint( "/swagger/v1.0/swagger.json", "My Web API Swagger Endpoint" );
//ui.RoutePrefix = string.Empty;
} );
app.UseEndpoints( endpoints => {
endpoints.MapControllers( );
} );
}
}
}
修改「StoresController」類別程式碼,在方法上方輸入「///」符號,工具會自動產生XML註解區塊,讓我們撰寫程式註解,請參考以下程式列表:
- MyWebAPI\Controllers\StoresController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MyWebAPI.Models;
namespace MyWebAPI.Controllers {
[Route( "[controller]" )]
[ApiController]
public class StoresController : ControllerBase {
private readonly PubsContext _context;
public StoresController( PubsContext context ) {
_context = context;
}
/// <summary>
/// 讀取所有商店的資訊
/// </summary>
/// <returns>商店清單</returns>
// GET: api/Stores
[HttpGet( "GetStores" )]
public async Task<ActionResult<IEnumerable<Store>>> GetStores( ) {
return await _context.Stores.ToListAsync( );
}
/// <summary>
/// 根據id讀取一筆商店的資訊
/// </summary>
/// <param name="id">商店編號</param>
/// <returns>商店 Store </returns>
// GET: api/Stores/5
[HttpGet( "GetStoreById/{id}" )]
public async Task<ActionResult<Store>> GetStore( string id ) {
var store = await _context.Stores.FindAsync( id );
if ( store == null ) {
return NotFound( );
}
return store;
}
/// <summary>
/// 根據id修改一筆商店的資訊
/// </summary>
/// <param name="id">商店編號</param>
/// <param name="store">商店Store</param>
/// <returns>NoContent</returns>
// PUT: api/Stores/5
// To protect from overposting attacks, enable the specific properties you want to bind to, for
// more details, see https://go.microsoft.com/fwlink/?linkid=2123754.
[HttpPut( "UpdateStore/{id}" )]
public async Task<IActionResult> PutStore( string id, Store store ) {
if ( id != store.StorId ) {
return BadRequest( );
}
_context.Entry( store ).State = EntityState.Modified;
try {
await _context.SaveChangesAsync( );
}
catch ( DbUpdateConcurrencyException ) {
if ( !StoreExists( id ) ) {
return NotFound( );
}
else {
throw;
}
}
return NoContent( );
}
/// <summary>
/// 新增一筆商店的資訊
/// </summary>
/// <param name="store">商店store</param>
/// <returns>商店store</returns>
// POST: api/Stores
// To protect from overposting attacks, enable the specific properties you want to bind to, for
// more details, see https://go.microsoft.com/fwlink/?linkid=2123754.
[HttpPost("CreateStore")]
public async Task<ActionResult<Store>> PostStore( Store store ) {
_context.Stores.Add( store );
try {
await _context.SaveChangesAsync( );
}
catch ( DbUpdateException ex ) {
if ( StoreExists( store.StorId ) ) {
return Conflict( );
}
else {
throw;
}
}
//return await Task.FromResult( store );
return CreatedAtAction( "GetStore", new { id = store.StorId }, store );
}
/// <summary>
/// 根據id刪除一筆商店的資訊
/// </summary>
/// <param name="id">商店編號</param>
/// <returns>商店 store</returns>
// DELETE: api/Stores/5
[HttpDelete( "DeleteStore/{id}" )]
public async Task<ActionResult<Store>> DeleteStore( string id ) {
var store = await _context.Stores.FindAsync( id );
if ( store == null ) {
return NotFound( );
}
_context.Stores.Remove( store );
await _context.SaveChangesAsync( );
return store;
}
private bool StoreExists( string id ) {
return _context.Stores.Any( e => e.StorId == id );
}
}
}
取Visual Studio 「Build」-「Build Solution」項目編譯目前的專案,確認程式碼能正確編譯。檢視專案「MyWebAPI\bin\Debug\netcoreapp3.1」資料夾下,便會產生一個和專案名稱相同的「MyWebAPI.xml」檔案,內容大致如下:
- MyWebAPI\bin\Debug\netcoreapp3.1\ MyWebAPI.xml
<?xml version = "1.0"?>
<doc>
<assembly>
<name> MyWebAPI </name>
</assembly>
<members>
<member name = "M:MyWebAPI.Controllers.StoresController.GetStores">
<summary>
讀取所有商店的資訊
</summary>
<returns> 商店清單 </returns>
</member>
<member name = "M:MyWebAPI.Controllers.StoresController.GetStore(System.String)">
<summary>
根據id讀取一筆商店的資訊
</summary>
<param name = "id" example = "6380"> 商店編號 </param>
<returns> 商店 Store </returns>
</member>
<member name = "M:MyWebAPI.Controllers.StoresController.PutStore(System.String,MyWebAPI.Models.Store)">
<summary>
根據id修改一筆商店的資訊
</summary>
<param name = "id"> 商店編號 </param>
<param name = "store"> 商店Store </param>
<returns> NoContent </returns>
</member>
//以下略
</doc>
選取Visual Studio 2019開發工具「Build」-「Build Solution」項目編譯目前的專案,確認程式碼能正確編譯。按CTRL+F5執行「MyWebAPI」網站,在瀏覽器輸入以下網址,便可檢視Swagger UI:
https://localhost:44311/swagger
執行結果參考如下:

圖 3:顯示XML註解。
在瀏覽器輸入以下網址:
https://localhost:44311/swagger/v1.0/swagger.json
執行結果參考如下,XML註解資訊會出現在「swagger.json」之中:

圖 4:顯示XML註解。
使用「Remark」項目
在「Remark」項目加入的文字會出現在方法測試畫面的最上方,舉例來說,修改「StoresController」類別「PostStore」方法如下:
- MyWebAPI\Controllers\StoresController.cs
/// <summary>
/// 新增一筆商店的資訊
/// </summary>
/// <remarks>
/// Store 範例 :
///
/// POST /Stores
/// <br />
///{
/// "storId": "6380", \
/// "storName": "Eric the Read Books", \
/// "storAddress": "788 Catamaugus Ave.", \
/// "city": "Seattle", \
/// "state": "WA", \
/// "zip": "98056" \
///}
/// </remarks>
/// <param name="store">商店store</param>
/// <returns>商店store</returns>
[HttpPost( "CreateStore" )]
public async Task<ActionResult<Store>> PostStore( Store store ) {
//略
}
參考下圖為「GreateStore」方法的Swagger UI文件:

圖 5:「Remark」項目。
「/// Store 範例 :」這行註解下方的「///」這行程式,會導致在Swagger UI中換行呈現:
/// Store 範例 :
///
換行也可以使用<br/>標籤:
/// <br />
或使用反斜線「\」。
檢視Schema
「StoresController」中使用到「Store」類別,由工具產生的「Store」類別中,定義了一些描述資料欄位結構的Attribute,例如「StringLength」、「Required」等等,參考以下「Store」類別程式碼:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace MyWebAPI.Models {
public partial class Store {
public Store( ) {
Sales = new HashSet<Sale>( );
}
[StringLength( 4 )]
[Required]
public string StorId { get; set; }
[StringLength(40)]
public string StorName { get; set; }
[StringLength( 40 )]
public string StorAddress { get; set; }
[StringLength( 20 )]
public string City { get; set; }
[StringLength( 2 )]
public string State { get; set; }
[StringLength( 5 )]
public string Zip { get; set; }
public virtual ICollection<Sale> Sales { get; set; }
}
}
而在Swagger UI中可以從「Schema」區塊中看到這些資訊:

圖 6:檢視Schema。
設定Media Type產生JSON資料
在ASP.NET Core Web API類別的上方套用「Produces」Attribute,可以指明Web API方法回應的內容類型:
- MyWebAPI\Controllers\StoresController.cs
[Produces( "application/json" )]
[Route( "[controller]" )]
[ApiController]
public class StoresController : ControllerBase {
//以下略
}
在Swagger UI中可以看到「Media Type」將設定為「application/json」,請參考下圖所示:

圖 7:設定Media Type。
設定回應HTTP狀態碼
「ProducesResponseType」Attribute可以設定ASP.NET Core Web API方法回傳的HTTP狀態碼,例如修改「PostStore」方法如下:
[ProducesResponseType( StatusCodes.Status201Created )]
[ProducesResponseType( StatusCodes.Status400BadRequest )]
[HttpPost( "CreateStore" )]
public async Task<ActionResult<Store>> PostStore( Store store ) {
//以下略
}
在Swagger UI中可以看到以下的結果:

圖 8:設定回應HTTP狀態碼。
修改「Example Value」中的範例資料
最後若想要在文件中顯示測試資料範例,可以利用「example」項目,例如修改「Store」類別的程式碼,在每個屬性上方加上「example」項目,請參考以下程式列表:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace MyWebAPI.Models {
public partial class Store {
public Store( ) {
Sales = new HashSet<Sale>( );
}
/// <summary>
/// Store Id
/// </summary>
/// <example>6381</example>
[StringLength( 4 )]
[Required]
public string StorId { get; set; }
/// <summary>
/// Store Name
/// </summary>
/// <example>Eric the Read Books</example>
[StringLength(40)]
public string StorName { get; set; }
/// <summary>
/// Store Address
/// </summary>
/// <example>788 Catamaugus Ave.</example>
[StringLength( 40 )]
public string StorAddress { get; set; }
/// <summary>
/// City
/// </summary>
/// <example>Seattle</example>
[StringLength( 20 )]
public string City { get; set; }
/// <summary>
/// State
/// </summary>
/// <example>WA</example>
[StringLength( 2 )]
public string State { get; set; }
/// <summary>
/// Zip
/// </summary>
/// <example>98056</example>
[StringLength( 5 )]
public string Zip { get; set; }
public virtual ICollection<Sale> Sales { get; set; }
}
}
在Swagger UI中可以看到以下「Example Value」的結果:

圖 9:修改Example Value中的範例資料。
我們也可以在XML文件註解中使用「example」,例如修改「GetStore」方法的XML文件註解,為「id」參數設定「example="6380"」範例,請參考以下程式列表:
- MyWebAPI\Controllers\StoresController.cs
/// <summary>
/// 根據id讀取一筆商店的資訊
/// </summary>
/// <param name="id" example="6380">商店編號</param>
/// <returns>商店 Store </returns>
// GET: api/Stores/5
[HttpGet( "GetStoreById/{id}" )]
public async Task<ActionResult<Store>> GetStore( string id ) {
var store = await _context.Stores.FindAsync( id );
if ( store == null ) {
return NotFound( );
}
return store;
}
預設在Swagger UI中可以看到以下商店編號下方的文字方塊出現「6380」,請參考下圖所示:

圖 10:提供example。
點選畫面中的「Try it out」按鈕,進到下個畫面,預設商店編號的值便被設定為「6380」,請參考下圖所示:

圖 11:設定商店編號預設值。