前篇文章已介紹過 ASP.NET Web API 2 資料驗證做法,這篇會談談我在處理 TaskLogController 時的衍生需求 - 支援 GET 方式呼叫 Web API,用一行 URL 傳參數完成寫入 Log 動作。

首先聲明,使用 GET 方式更新有違一般資安原則(延伸閱讀:隱含殺機的GET式AJAX資料更新),但考量這個 Web API 會鎖定呼叫來源 IP 限定特定伺服器呼叫,而改為單一 URL 完成動作可降低呼叫端的程式複雜度(有些古老排程可用程式庫很貧乏,且不一定能引用 curl 等第三方軟體),故我還是花了點時間研究納入選項。

直覺做法是將 TaskLogEntity 屬性拆成輸入參數,方法內部以輸入值建構 TaskLogEntity 資料物件,再呼叫 Validate<T> 觸發驗證,剩下的程序與前篇文章範例相同,若 ModelState.IsValid == false 則用 Request.CreateErrorResponse() 傳回錯誤訊息。

/// <summary>
/// 新增作業執行記錄
/// </summary>
/// <param name="taskName">作業名稱</param>
/// <param name="action">動作</param>
/// <param name="eventType">事件別</param>
/// <param name="message">訊息</param>
/// <returns></returns>
[HttpGet]
public HttpResponseMessage Add(string taskName, string action, string eventType, string message = null)
{
    var entity = new TaskLogEntity()
    {
        LogTime = DateTime.Now,
        TaskName = taskName,
        Action = action,
        Message = message
    };

    //呼叫Validate<T>觸發驗證
    Validate<TaskLogEntity>(entity);

    //補上列舉轉換
    EventTypes et;
    if (Enum.TryParse<EventTypes>(eventType, out et))
    {
        entity.EventType = et;
    }
    else
    {
        ModelState.AddModelError("EventType", $"無效的 EventType 列舉值 - {eventType}");
    }

    //若資料驗證未過,傳回錯誤訊息
    if (!ModelState.IsValid)
        return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);

    entity.LogTime = DateTime.Now;

    //TODO: 將 Entity 寫入資料庫
    //TaskLogDataHelper.Insert(entity);

    //成功時傳回OK
    return Request.CreateResponse(HttpStatusCode.OK);
}

列舉處理部分要花點工夫。POST JSON 時 EventTypes 轉換是由 Json.NET 處理,改由 URL 參數由字串轉列舉有兩種做法,第一種 eventType 參數直接宣告為 EventTypes 型別並自訂 ModelBinder 轉換(參考:Enumeration Model Binder For Asp.Net Mv),缺點是當轉換失敗即抛出例外不執行方法,因此錯誤訊息不會透過 ModelState 反映,回傳行為不一致。因此我採用第二種做法,接入字串型別 eventType 參數再自行轉換,轉換失敗時用 ModelState.AddModelError() 加入錯誤訊息。

執行結果如下:

與前篇文章的測試結果比較,錯誤回傳訊息的屬性名稱為 "taskName"、"eventType",而非 "entity.TaskName", "entity.EventType",若要求一致有兩處要稍加修改:Validate<TaskLogEntity>(entity, "entity");ModelState.AddModelError("entity.EventType", $"無效的 EventType 列舉值 - {eventType}");

除了將屬性轉為輸入參數,我後來又找到更簡便的做法 - 使用 [FromUri] Attribute 搭配輸入參數型別 TaskLogEntity,交由 Web API 2 將 URL 參數映對到 TaskLogEntity 屬性,如此寫法跟 POST Json 相同:

/// <summary>
/// 新增作業執行記錄
/// </summary>
/// <param name="entity"></param>
[HttpGet]
public HttpResponseMessage UriAdd([FromUri]TaskLogEntity entity)
{
    //若資料驗證未過,傳回錯誤訊息
    if (!ModelState.IsValid)
        return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);

    entity.LogTime = DateTime.Now;

    //TODO: 將 Entity 寫入資料庫
    //TaskLogDataHelper.Insert(entity);

    //成功時傳回OK
    return Request.CreateResponse(HttpStatusCode.OK);
}

Swagger UI 會解析 [FromUri] 依各屬性必填與否及最大長度加入客戶端檢查,網頁介面在輸入無效內容時會如下圖欄位變紅無法送出。

為測試效果改用 curl 測試,如下圖,輸入的無效值被成功阻攔。其中列舉型別 eventType,ASP.NET Web API 2 會自動將文字轉換成列舉,轉換失敗訊息跟 Json.NET 有所不同,但一樣會合併於 ModelState 輸出。

二者相比,[FromUri] 方法簡便許多,確定勝出。只有一個小問題,若 [HttpPost] Add([FromBody]TaskLogEntity entity)、[HttpGet] Add([FromUri] entity) 並存會有 Overloading 重複問題,需修改方法名稱避開,在本例是將 [HttpGet] 改名為 UriAdd() 以與 [HttpPost] Add() 區隔。

Tips of how to implement data validation when pass entity parameter via HttpGet.


Comments

# by supershowwei

單就同時支援 HttpGet、HttpPost 複雜型別參數這點,ASP.NET MVC 比 ASP.NET Web API 友善許多。

# by nick

等待複核中,留言將在稍後顯示 / The comment is awaiting review.

Post a comment