設計與使用ASP.NET Core Web API 4- Swagger常用設定

by vivid 9. 十二月 2020 03:23

.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」:授權資訊。

參考以下範例程式碼:

  • MyWebAPI\Startup.cs

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

可以看到畫面如下:

clip_image002

圖 1:客製化Web API文件。

產生XML文件

你可以為ASP.NET Core Web API個別的方法提供更詳細的使用說明,或呼叫範例文件。要達到這個目地,我們需要在程式中使用「///」符號幫程式加上XML註解,並將註解產生成一個XML文件檔案。

從「Solution Explorer」視窗- 「MyWebAPI」專案名稱上方按一下滑鼠右鍵,從快捷選單選擇「Edit Project File」,請參考下圖所示:

clip_image004

圖 2:編輯專案檔案。

參考以下程式碼,加入「GenerateDocumentationFile」項目,設定值為「true」;「NoWarn」項目的值為「$(NoWarn);1591」:

  • MyWebAPI\MyWebAPI.csproj

<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所在路徑,請參考以下程式列表:

  • MyWebAPI\Startup.cs

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

執行結果參考如下:

clip_image006

圖 3:顯示XML註解。

在瀏覽器輸入以下網址:

https://localhost:44311/swagger/v1.0/swagger.json

執行結果參考如下,XML註解資訊會出現在「swagger.json」之中:

clip_image008

圖 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文件:

clip_image010

圖 5:「Remark」項目。

「/// Store 範例 :」這行註解下方的「///」這行程式,會導致在Swagger UI中換行呈現:

/// Store 範例 :

///

換行也可以使用<br/>標籤:

/// <br />

或使用反斜線「\」。

 

檢視Schema

「StoresController」中使用到「Store」類別,由工具產生的「Store」類別中,定義了一些描述資料欄位結構的Attribute,例如「StringLength」、「Required」等等,參考以下「Store」類別程式碼:

  • MyWebAPI\Models\Store.cs

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」區塊中看到這些資訊:

clip_image012

圖 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」,請參考下圖所示:

clip_image014

圖 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中可以看到以下的結果:

clip_image016

圖 8:設定回應HTTP狀態碼

修改「Example Value」中的範例資料

最後若想要在文件中顯示測試資料範例,可以利用「example」項目,例如修改「Store」類別的程式碼,在每個屬性上方加上「example」項目,請參考以下程式列表:

  • MyWebAPI\Models\Store.cs

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」的結果:

clip_image018

圖 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」,請參考下圖所示:

clip_image020

圖 10:提供example。

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

clip_image022

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

Tags:

.NET Magazine國際中文電子雜誌 | ASP.NET Web API | C# | 許薰尹Vivid Hsu

新增評論




  Country flag
biuquote
  • 評論
  • 線上預覽
Loading






NET Magazine國際中文電子雜誌

NET Magazine國際中文電子版雜誌,由恆逸資訊創立於2000,自發刊日起迄今已發行超過500篇.NET相關技術文章,擁有超過40000名註冊讀者群。NET Magazine國際中文電子版雜誌希望藉於電子雜誌與NET Developer達到共同學習與技術新知分享,歡迎每一位對.NET 技術有興趣的朋友們多多支持本雜誌,讓作者群們可以有持續性的動力繼續爬文。<請加入免費訂閱>

月分類Month List