[ASP.NET MVC] ASP.NET Web API (2) - 方法與路由

我們在前面一篇文章中使用了 ValuesController 這個預設的 API Controller,但畢竟它只是展示給你看 API Controller 要怎麼寫,所以這回我們要自己寫一個簡單的 Web API。

我們在前面一篇文章中使用了 ValuesController 這個預設的 API Controller,但畢竟它只是展示給你看 API Controller 要怎麼寫,所以這回我們要自己寫一個簡單的 Web API。

不過在寫 Web API 之前,我們要先回頭來看一下 ASP.NET Routing 這東西,因為在 URL 與方法對應的核心處理中,它扮演了重要的角色,在 MVC 4.0 專案中的 WebApiConfig.cs 或 RouteConfig.cs 中,可以找到下面這樣的對應:

public static void Register(HttpConfiguration config)
{
    config.Routes.MapHttpRoute(
        name: "DefaultApi",
        routeTemplate: "api/{controller}/{id}"
    );
}

MapHttpRoute() 是針對 Web API 所設計的對應功能,用法和 MVC 中的 MapRoute() 十分相似,同樣也是設定 URL 的格式以對應指定的方法,不過在 Web API 的概念中,方法的呼叫反而並不重要,而是要使用類似 REST API 風格的 URL 樣式,所以預設的範本是 api/{controller}/{id},它會以 /api/ 後的名稱作為 Controller 的呼叫名稱標準,以 ValuesController 為例:

  • GET /api/values:呼叫 ValuesController 的 Get 方法。
  • GET /api/values/1:呼叫 ValuesController 的 Get 方法,並傳入 1 作為 Get 方法的 id 參數值。
  • POST /api/values:呼叫 ValuesController 的 Post 方法,並傳入 POST 訊息的內容並對應到相應的參數。
  • PUT /api/values/1:呼叫 ValuesController 的 Put 方法,並傳入 1 作為 Put 方法的 id 參數值,並傳入 POST 訊息的內容並對應到相應的參數。
  • DELETE /api/values/1:呼叫 ValuesController 的 Delete 方法,並傳入 1 作為 Delete 方法的 id 參數值。

不過讀者不要誤會了,HTTP 動詞的對應並不是在 MapHttpRoute() 中設定,它只是讓 Web API 核心找到對的 Controller 來呼叫,而不是要全部處理對應的問題,真正的 HTTP 動詞與參數對應還是在 Web API 核心內。

更多有趣的 Web API Routing,可參考:http://blog.kkbruce.net/2012/04/aspnet-web-api-4-web-api-routing.html

談完了路由後,我們就可以來寫一個簡單的 Web API 了,首先可以建立一個 ASP.NET MVC 4 的空白專案,當然要建立 Web API 範本專案也可以,然後我們新增一個空白的 AccountController:

image

我們會得到一個空白的 AccountController 的類別,然後加入下列程式碼:

public HttpResponseMessage RequestToken(string ConsumerKey, string ConsumerSecret)
{
    return this.Request.CreateResponse(HttpStatusCode.OK);
}

public HttpResponseMessage AccessToken(string ConsumerKey, string Verifier)
{
    return this.Request.CreateResponse(HttpStatusCode.OK);
}

然後啟動 Fiddler 或是 HttpWatch 或是讀者自己喜歡的 HTTP 要求產生工具,本例是使用 Fiddler,然後按 F5 啟動應用程式,此時瀏覽器會出現,但會顯示找不到網頁的訊息,畢竟我們使用的是 Web API,理論上是不可能會有瀏覽器看得懂的 HTML 介面,所以我們可以到專案的組態中,設定不啟動瀏覽器:

image

啟動完成後,我們按照 /api/{controller} 的格式,去呼叫 /api/account/RequestToken,會得到這樣的訊息:

image

Why? 明明有按照 URL 的要求做啊?為什麼會無效呢?

這就是編寫 Web API 第一個要注意的地方,如果說我們使用的方法名稱不是 Get/Post/Put/Delete 的規則時,我們就一定要宣告它的接受動詞 (Accept Verb),所以我們可以修改為這樣的程式碼:

[HttpGet]
public HttpResponseMessage RequestToken(string ConsumerKey, string ConsumerSecret)
{
    return this.Request.CreateResponse(HttpStatusCode.OK);
}

[HttpGet]
public HttpResponseMessage AccessToken(string ConsumerKey, string Verifier)
{
    return this.Request.CreateResponse(HttpStatusCode.OK);
}

然後我們再試一次,這次得到的是:

image

喔,我們忘了給參數,因為 Web API 核心會將參數列也算在掃描方法的條件之一,所以如果沒有給參數,它會去搜尋 RequestToken() 而不是我們所期待的 Request(s1, s2),所以我們要給參數:

/api/account/RequestToken?ConsumerKey=abc&ConsumerSecret=123

然後再試一次:

image

成功得到 HTTP 200 的數值,表示呼叫有成功,我們可以如法泡製的呼叫另一個方法 AccessToken,可以得到一樣的結果:

image

不過有趣的是,如果我們把接受動詞改成 HttpPost,再重試一次,會得到很怪的結果:

image

這個是一個令人困惑的問題,原本在 GET 模式可以用的方法在 POST 卻不能使用,這可能是因為 Web API 核心對 POST 訊息的處理和對 GET 處理不一致的原因 (行為不同不能算是 bug),我們可以有幾種方式來處理:

1. 使用一個 DTO 將它包裝起來:

我們可以在專案中新增兩個 DTO 物件,將參數包裝起來:

public class RequestTokenParams
{
    public string ConsumerKey { get; set; }
    public string ConsumerSecret { get; set; }
}

public class AccessTokenParams
{
    public string ConsumerKey { get; set; }
    public string Verifier{ get; set; }
}

然後將 API 方法修改為:

[HttpPost]
public HttpResponseMessage RequestToken(RequestTokenParams Params)
{
    return this.Request.CreateResponse(HttpStatusCode.OK);
}

[HttpPost]
public HttpResponseMessage AccessToken(AccessTokenParams Params)
{
    return this.Request.CreateResponse(HttpStatusCode.OK);
}

再測試一次,還是會遇到問題:

image

這次的問題不是程式本身的問題,而是在路由表中的條件,因為找到了相同參數的多種方法,因此回傳錯誤,這時我們就要修改路由表的宣告:

config.Routes.MapHttpRoute(
        name: "MapActionApi",
        routeTemplate: "api/{controller}/{action}",
        defaults:
        new
        {
            action = RouteParameter.Optional,
            id =
                RouteParameter.Optional
        }
);

然後再試一次就可以了:

image

2. 使用 FormCollection 宣告

我們將 API 的參數改為 FormCollection 的宣告 (需要加入 System.Web.Mvc 的引用,若是用 Web API 2,請改用 System.Net.Http.Formatting 內的 FormDataCollection):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using WebAPI = System.Web.Http;
using System.Web.Mvc;

namespace MvcWebApiExample.Controllers
{
    public class AccountController : WebAPI.ApiController
    {
        [WebAPI.HttpPost]
        public HttpResponseMessage RequestToken(FormCollection request)
        {
            return this.Request.CreateResponse(HttpStatusCode.OK);
        }

        [WebAPI.HttpPost]
        public HttpResponseMessage AccessToken(FormCollection request)
        {
            return this.Request.CreateResponse(HttpStatusCode.OK);
        }
    }
}

3. 使用 JObject 宣告

如果要求是使用 JSON 的話,我們就可以使用 Json.NET 中的 JObject 來實作:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using WebAPI = System.Web.Http;
using System.Web.Mvc;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace MvcWebApiExample.Controllers
{
    public class AccountController : WebAPI.ApiController
    {
        [WebAPI.HttpPost]
        public HttpResponseMessage RequestToken(JObject request)
        {
            return this.Request.CreateResponse(HttpStatusCode.OK);
        }

        [WebAPI.HttpPost]
        public HttpResponseMessage AccessToken(JObject request)
        {
            return this.Request.CreateResponse(HttpStatusCode.OK);
        }
    }
}

然後對它發出要求 (POST 內容必須是 JSON 格式,而且要求的 Content-Type 要設為 application/json),我們可以在程式中下中斷點去看 request 的內容,可以看到 request 中包含了 JSON 的要求訊息:

image

 

以上就是這回簡單的 Web API 實作,告訴大家一些基本 Web API 方法的實作方式以及會遇到的問題,接下來我們再進一步的處理方法的實作。

Reference:

http://www.west-wind.com/weblog/posts/2012/May/08/Passing-multiple-POST-parameters-to-Web-API-Controller-Methods

http://blog.kkbruce.net/2012/04/aspnet-web-api-4-web-api-routing.html#.UO4wcW_qlyU