ASP.NET Web API でもクライアントから送信されてきたデータはアクションメソッドの引数にバインドされますが、その仕組みは ASP.NET MVC とは異なるということを書きます。
詳しくは MSDN Blog の記事「How WebAPI does Parameter Binding」に書いてありますのでそちらを読んでもらうとして、要点だけ書きますと以下の通りです。
-
Model Binding または Formatters を利用するという 2 つの方法があって、Web API の場合は、クエリ文字列からパラメータを取得する場合は Model Binding を、ボディから取得する場合は Formatter を使う。
-
string などのプリミティブ型をアクションメソッドの引数にした場合、引数に属性が付与されてなければクエリ文字列からパラメータを探してモデルバインディングする。
-
コンプレックス型(例:記事の Customer クラス、下のコードの Hero クラス)の場合、デフォルトでは Formatter を使ってボディからパラメータを取得する。
上記の点を踏まえたサンプルコードを以下にアップしておきます。
Model
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Runtime.Serialization;
namespace WebAPI.Models
{
public class Hero
{
public int Id { get; set; }
public string Name { get; set; }
}
}
Api Controller
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using System.Web;
using WebAPI.Models;
namespace WebAPI.Controllers
{
public class ValuesController : ApiController
{
private List<Hero> heroes = new List<Hero> {
new Hero {Id = 1, Name = "スーパーマン"},
new Hero {Id = 2, Name = "バットマン"},
new Hero {Id = 3, Name = "ウェブマトリクスマン"},
new Hero {Id = 4, Name = "チャッカマン"},
new Hero {Id = 5, Name = "スライムマン"}
};
// GET api/values (Read...すべてのレコードを取得)
public List<Hero> Get()
{
return heroes;
}
// GET api/values/5 (Read...id 指定のレコード取得)
public Hero Get(int id)
{
return heroes[id - 1];
}
// POST api/values (Create...レコード追加)
public List<Hero> Post(Hero postedHero)
{
heroes.Add(postedHero);
return heroes;
}
// PUT api/values/5 (Update...id 指定のレコード更新)
public List<Hero> Put(int id, [FromBody] string name)
{
heroes[id - 1].Name = name;
return heroes;
}
// DELETE api/values/5 (Delete...id 指定のレコード削除)
public List<Hero> Delete(int id)
{
heroes.RemoveAt(id - 1);
return heroes;
}
}
}
上の Api を呼び出すスクリプト
<input type="button" value="READ ALL"
onclick="apiHeroesGet();" />
<input type="button" value="READ 5"
onclick="apiHeroesGet5();" />
<input type="button" value="UPDATE 5"
onclick="apiHeroesPut5();" />
<input type="button" value="DELETE 5"
onclick="apiHeroesDelete5();" />
<input type="button" value="CREATE 6"
onclick="apiHeroesPost();" />
<ul id="heroes"></ul>
@section Scripts {
<script type="text/javascript">
//<![CDATA[
function apiHeroesGet() {
$.ajax({
type: "GET",
url: "api/values",
success: function (data, textStatus, jqXHR) {
$('#heroes').empty();
$.each(data, function (key, val) {
var str = val.Id + ': ' + val.Name;
$('<li/>', { html: str }).appendTo($('#heroes'));
});
},
error: function (jqXHR, textStatus, errorThrown) {
$('#heroes').empty();
$('#heroes').text('textStatus: ' + textStatus +
', errorThrown: ' + errorThrown);
}
});
}
function apiHeroesGet5() {
$.ajax({
type: "GET",
url: "api/values/5",
success: function (data, textStatus, jqXHR) {
$('#heroes').empty();
var str = data.Id + ': ' + data.Name;
$('<li/>', { html: str }).appendTo($('#heroes'));
},
error: function (jqXHR, textStatus, errorThrown) {
$('#heroes').empty();
$('#heroes').text('textStatus: ' + textStatus +
', errorThrown: ' + errorThrown);
}
});
}
function apiHeroesPut5() {
$.ajax({
type: "PUT",
url: "api/values/5",
data: encodeURI("=ガッチャマン"),
// 以下のコードがあるとバインドできず null が渡される
//contentType: "application/json; charset=utf-8",
success: function (data) {
$('#heroes').empty();
$.each(data, function (key, val) {
var str = val.Id + ': ' + val.Name;
$('<li/>', { html: str }).appendTo($('#heroes'));
});
},
error: function (jqXHR, textStatus, errorThrown) {
$('#heroes').empty();
$('#heroes').text('textStatus: ' + textStatus +
', errorThrown: ' + errorThrown);
}
});
}
function apiHeroesDelete5() {
$.ajax({
type: "DELETE",
url: "api/values/5",
success: function (data) {
$('#heroes').empty();
$.each(data, function (key, val) {
var str = val.Id + ': ' + val.Name;
$('<li/>', { html: str }).appendTo($('#heroes'));
});
},
error: function (jqXHR, textStatus, errorThrown) {
$('#heroes').empty();
$('#heroes').text('textStatus: ' + textStatus +
', errorThrown: ' + errorThrown);
}
});
}
function apiHeroesPost() {
var j = { Id: 6, Name: "ガッチャマンの息子" };
var jsonString = JSON.stringify(j);
$.ajax({
type: "POST",
url: "api/values",
data: jsonString,
contentType: "application/json; charset=utf-8",
success: function (data) {
$('#heroes').empty();
$.each(data, function (key, val) {
var str = val.Id + ': ' + val.Name;
$('<li/>', { html: str }).appendTo($('#heroes'));
});
},
error: function (jqXHR, textStatus, errorThrown) {
$('#heroes').empty();
$('#heroes').text('textStatus: ' + textStatus +
', errorThrown: ' + errorThrown);
}
});
}
//]]>
</script>
}
特に注意すべき点は Put(int id, [FromBody] string name) メソッドとそれを呼び出すスクリプトです。
アクションメソッドの引数が string 型でボディからパラメータを得たい場合は [FromBody] 属性が必要です。
さらに、
name にバインドするのに Formatter がどういう動きをしているのか調べ��れてませんが、自分が試した限りでは送信するデータをスクリプトの data に "name=value" と設定してはダメで、上のサンプルコードのように "=value" というように設定しないと引数 name にはバインドされませんでした。
もう一つ、Content-Type に application/json を設定するとバインドできず null が渡されてしまいます。理由は、Web API は要求ヘッダの Content-Type を見て適切な Formatter を選択するそうですが、"=value" は JSON ではないからだと思われます。
jQuery ajax で contentType を設定しないと application/x-www-form-urlencoded (デフォルト) となりますが、その場合は期待通り引数 name に "value" がバインドされます。
上記のようなトラブルを避けるために、アクションメソッドの引数は Post(Hero postedHero) と同様にコンプレックス型(Hero クラス)とし、スクリプトのapiHeroesPost() のように JSON 文字列を送信するようにした方が良いかもしれません。