[WEB API]針對SWAGGER設定response code的說明和如何產生pdf

針對SWAGGER設定response code的說明和如何產生pdf

前言

前篇已經有介紹了一些設定還有說明的部份,此篇想緊接著記錄一下自己其他實務上會遇到的問題,包括一些回應的部份,還有如果不想或不能提供給呼叫端swagger的線上文件,那如何把我們寫的swagger的內容,一樣能提供給對方呢?

導覽

  1. 定義更詳細的說明
  2. 把重覆的state code用別的方式定義
  3. 如何定義state code回傳的response model
  4. 使用swagger editor來滿足不能直接訪問我們swagger ui的人
  5. 直接產生pdf文件給使用者或者進入版控
  6. 結論

 

定義更詳細的說明

從上一篇文章可以看到,說明都有補上了,但是實際上的狀況是在說明的時候,在title我們希望是比較簡短的說明,必須要按下細項之後,才會顯示更完整的說明,甚至是當call了我們的api之後,可能會有多少種預設的例外狀況,接下來就以圖示來標記一下。

我們來看一下程式碼的summary該怎麼寫

/// <summary>
/// 新增資料
/// </summary>
/// <remarks>
/// 新增員工資料,並回傳所有員工
/// </remarks>
/// <param name="employee">員工</param>
/// <response code="200">成功</response>
/// <response code="400">作業錯誤</response>
/// <response code="401">驗證錯誤</response>
/// <response code="500">伺服器嚴重錯誤</response>
/// <returns></returns>
[ResponseType(typeof(Employee))]
[Route("Post")]
[HttpPost]
public IHttpActionResult Post(Employee employee)
{
    EmployeeDao.Employees.Add(employee);
    return Ok(EmployeeDao.Employees);
}

 

把重覆的state code集中起來

其實在所有的api調用,幾乎所有的例外狀況處理都會是一樣的,所以我們就必須要對每個action都得有說明,但是實際上如果我們要針對每個方法都要標記response code的話,我們就只能一直用複製貼上的方式了,但是如果我們採用一直複製貼上的話,那以後如果有別種response的改變的話,我們就要一次改掉以前所有複製貼上的部份,這顯然不會是個好方法,好在swagger提供了atrribute的方式,那接下來看看程式碼示例囉

    /// <summary>
    /// 員工相關的
    /// </summary>
    [RoutePrefix("Api/Values")]
    [SwaggerResponse(HttpStatusCode.OK,"成功")]
    [SwaggerResponse(HttpStatusCode.BadRequest,"作業錯誤")]
    [SwaggerResponse(HttpStatusCode.Unauthorized,"驗證錯誤")]
    [SwaggerResponse(HttpStatusCode.InternalServerError, "嚴重錯誤")]
    public class ValuesController : ApiController
    {
        /// <summary>
        /// 取得資料
        /// </summary>
        /// <returns></returns>
        [Route("Get")]
        [HttpGet]
        public IHttpActionResult Get()
        {
            return Ok(EmployeeDao.Employees);
        }
        
        /// <summary>
        /// 用單號取得單筆資料
        /// </summary>
        /// <param name="id">單號</param>
        /// <returns></returns>
        [Route("GetById")]
        [HttpGet]
        public IHttpActionResult GetById(int id)
        {
            return Ok(EmployeeDao.Employees.FirstOrDefault(x => x.EmployeeId == id));
        }

        /// <summary>
        /// 搜尋資料
        /// </summary>
        /// <param name="search">搜尋物件</param>
        /// <returns></returns>
        [Route("Search")]
        [HttpGet]
        public IHttpActionResult Search([FromUri]Search search)
        {
            return Ok(EmployeeDao.Employees.FirstOrDefault(x => x.Name.Contains(search.Name) &&
                    x.Address.Contains(search.Address)));
        }

        /// <summary>
        /// 新增資料
        /// </summary>
        /// <remarks>
        /// 新增員工資料,並回傳所有員工
        /// </remarks>
        /// <param name="employee">員工</param>
        /// <returns></returns>
        [ResponseType(typeof(Employee))]
        [Route("Post")]
        [HttpPost]
        public IHttpActionResult Post(Employee employee)
        {
            EmployeeDao.Employees.Add(employee);
            return Ok(EmployeeDao.Employees);
        }

        /// <summary>
        /// 修改資料
        /// </summary>
        /// <param name="employee">員工</param>
        /// <returns></returns>
        [Route("Put")]
        [HttpPut]
        public IHttpActionResult Put(Employee employee)
        {
            var result=EmployeeDao.Employees.FirstOrDefault(x => x.EmployeeId == employee.EmployeeId);
            if (result != null)
            {
                result.Name = employee.Name;
                result.PhoneNumber = employee.PhoneNumber;
                result.Address = employee.Address;
            }
            return Ok(EmployeeDao.Employees);
        }

        /// <summary>
        /// 刪除資料
        /// </summary>
        /// <param name="id">單號</param>
        /// <returns></returns>
        [Route("Delete")]
        [HttpDelete]
        public IHttpActionResult Delete(int id)
        {
            EmployeeDao.Employees.RemoveAll(x=>x.EmployeeId==id);
            return Ok(EmployeeDao.Employees);
        }
    }

 

當然雖然我們放在類別層級了,不過我們會有很多web api呀,那我們就定義一支父類別的web api來讓所有的api繼承吧。

BaseController

    [SwaggerResponse(HttpStatusCode.OK, "成功")]
    [SwaggerResponse(HttpStatusCode.BadRequest, "作業錯誤")]
    [SwaggerResponse(HttpStatusCode.Unauthorized, "驗證錯誤")]
    [SwaggerResponse(HttpStatusCode.InternalServerError, "嚴重錯誤")]
    public class BaseController:ApiController
    {
    }

ValuesController

    /// <summary>
    /// 員工相關的
    /// </summary>
    [RoutePrefix("Api/Values")]
    public class ValuesController : BaseController
    {
        /// <summary>
        /// 取得資料
        /// </summary>
        /// <returns></returns>
        [Route("Get")]
        [HttpGet]
        public IHttpActionResult Get()
        {
            return Ok(EmployeeDao.Employees);
        }
        
        /// <summary>
        /// 用單號取得單筆資料
        /// </summary>
        /// <param name="id">單號</param>
        /// <returns></returns>
        [Route("GetById")]
        [HttpGet]
        public IHttpActionResult GetById(int id)
        {
            return Ok(EmployeeDao.Employees.FirstOrDefault(x => x.EmployeeId == id));
        }

        /// <summary>
        /// 搜尋資料
        /// </summary>
        /// <param name="search">搜尋物件</param>
        /// <returns></returns>
        [Route("Search")]
        [HttpGet]
        public IHttpActionResult Search([FromUri]Search search)
        {
            return Ok(EmployeeDao.Employees.FirstOrDefault(x => x.Name.Contains(search.Name) &&
                    x.Address.Contains(search.Address)));
        }

        /// <summary>
        /// 新增資料
        /// </summary>
        /// <remarks>
        /// 新增員工資料,並回傳所有員工
        /// </remarks>
        /// <param name="employee">員工</param>
        /// <returns></returns>
        [ResponseType(typeof(Employee))]
        [Route("Post")]
        [HttpPost]
        public IHttpActionResult Post(Employee employee)
        {
            EmployeeDao.Employees.Add(employee);
            return Ok(EmployeeDao.Employees);
        }

        /// <summary>
        /// 修改資料
        /// </summary>
        /// <param name="employee">員工</param>
        /// <returns></returns>
        [Route("Put")]
        [HttpPut]
        public IHttpActionResult Put(Employee employee)
        {
            var result=EmployeeDao.Employees.FirstOrDefault(x => x.EmployeeId == employee.EmployeeId);
            if (result != null)
            {
                result.Name = employee.Name;
                result.PhoneNumber = employee.PhoneNumber;
                result.Address = employee.Address;
            }
            return Ok(EmployeeDao.Employees);
        }

        /// <summary>
        /// 刪除資料
        /// </summary>
        /// <param name="id">單號</param>
        /// <returns></returns>
        [Route("Delete")]
        [HttpDelete]
        public IHttpActionResult Delete(int id)
        {
            EmployeeDao.Employees.RemoveAll(x=>x.EmployeeId==id);
            return Ok(EmployeeDao.Employees);
        }
    }

 

如何定義state code回傳的response model

我們來假設一個情況,假設我回傳Unauthorized(401)的時候,有分成三種情況(管理者權限不足、未認證、未登入),這種情況我們只回傳401的話,就無法可以解釋各種權限不足的情境,這時候我們就可以定義另外的類別來做處理,比如說我定義了一個類別,裡面包含了我自己在定義的enum還有想讓前端直接顯示的message

    /// <summary>
    /// 驗證錯誤的資訊
    /// </summary>
    public enum UnauthorizedEnum
    {
        MustLogin,
        MustAdmin,
        MustRegister
    }
    public class HttpStateModel
    {
        /// <summary>
        /// 為非法訪問額外定義
        /// <para>0:必須登入</para>
        /// <para>1:必須是管理者</para>
        /// <para>2:必須認證</para>
        /// </summary>
        public UnauthorizedEnum UnauthorizedCode { get; set; }

        /// <summary>
        /// 伺服器回傳的訊息
        /// </summary>
        public string Message { get; set; }

    }

接著把原本父類別的web api最上面定義的attribute改成如下

    [SwaggerResponse(HttpStatusCode.OK, "成功")]
    [SwaggerResponse(HttpStatusCode.BadRequest, "作業錯誤")]
    [SwaggerResponse(HttpStatusCode.Unauthorized, "驗證錯誤", typeof(HttpStateModel))] //顯示回傳的類別資訊
    [SwaggerResponse(HttpStatusCode.InternalServerError, "嚴重錯誤")]
    public class BaseController:ApiController
    {
    }

最後swagger顯示如下圖

 

使用swagger editor來滿足不能直接訪問我們swagger ui的人

其實在某種情況之下,我們可能只願意提供api給人調用,但是不願意提供swagger的線上文件供使用,尤其是production環境,好在swagger很容易的就能產生json檔出來,swagger官網也提供了線上可以直接顯示文檔的需求

 

接著去swagger editor的網址 http://swagger.io/swagger-editor/

然後看你要import URL或者是json file,it is up to you

匯完成功就自動產生線上文件了。

 

直接產生pdf文件給使用者或者進入版控

其實產生pdf實在是比較沒有這類的需求,但是上面的人有問我說能不能做到,其實工程師應該都知道,我們只要有json的格式,我們就能自己寫程式碼,然後產生任何你想要的文件,但是這實在也太不符合成本效益了吧,最後我個人是找到了一個node js的project,人家已經辛苦努力的刻好成果了,那我們就只要去把原始碼clone下來,然後照著他的指示就能產生pdf了,但前題是先安裝起您的npm吧,github位址在此(https://github.com/simonstewart/swagger-to-PDF)

 

結論

swagger其實還有很多應用,有任何更深入的應用,就留給讀者您囉,有任何更好的方式,也請多多指導。