※ご注意
WCF Web APIはマイクロソフトにより現在開発はキャンセルされ、ASP.NET MVC 4のASP.NET Web APIとして制作・公開されています。
.NET Framework/ASP.NETでRESTfulなAPI構築を目的に記事を探されている方は是非以下のリンクからASP.NET Web APIについてご確認下さい。
https://opcdiary.net/?page_id=5981
とりあえず前々回と前回でサービスと、そのクライアントを一通り作ったわけですが、現状RESTfulなWEBサービスとして考えるとあまり良くありません。
どの辺が良くないかというと、POSTのリターンが今のままだと200(OK)となっていて、実際にやっていることはサービス内でヒーローというリソースの新規作成(レコードの追加)しているので、ここは201(Created)を返したい。また、DELETEとPUTでリソースが見つからなかった場合に500(サーバーエラー)としていたのですが、これもやはり良い返し方ではなく、404(Not Found)でクライアントに返すべきでしょう。
ということで、今回は前々回で作成したサービス側にこれらの修正を加えます。
using句の追加
SuperHeroApi.csに以下のusing句を追加します。
using System.Net;
using Microsoft.ApplicationServer.Http;
using Microsoft.ApplicationServer.Http.Dispatcher;
POSTの修正
POSTの成功で201を返すようにコードを修正します。
///
/// POSTコマンド
/// ヒーローを追加する
///
/// 追加するHeroのリスト
/// 追加したHeroのリスト
[WebInvoke(UriTemplate = "", Method = "POST")]
public HttpResponseMessage> Add(IEnumerable heroes) {
List addedHeroes = new List();
using (var context = new Models.HeroContext()) {
//最後のIDE接続の場合を取得する。
int lastId = 0;
if (context.Heros.Count() > 0) {
lastId = context.Heros.OrderByDescending(h => h.HeroId).First().HeroId;
}
//データの追加
foreach (var _hero in heroes) {
context.Heros.Add(_hero);
}
//DBへの反映
context.SaveChanges();
//クライアントに返す反映後のデータのリストを作成する
if (lastId != 0) {
addedHeroes = context.Heros.Where(h => h.HeroId > lastId)
.OrderBy(h => h.HeroId).ToList();
}
else {
addedHeroes = context.Heros.ToList();
}
}
//ステータスコードとして201(Created)を返すように修正。
var result = new HttpResponseMessage>(addedHeroes);
result.StatusCode = HttpStatusCode.Created;
return result;
}
変更した部分は34行です。201(Created)をリターンする応答メッセージのステータスにセットしています。
HTTPリクエストにたする応答のステータスを任意に変更したい場合にはまず、メソッドの戻り値をデータのカスタムタイプそのものではなくHttpResponseMessage(T)を戻り値の方として使用し、HttpResponseMessage.StatusCodeにHttpStatusCode列挙子をセットし、返すようにします。データのカスタムタイプそのものを返すようした場合にはステータスは200がWeb APIにより自動的につけられます。
DELETEの修正
引数で与えられたIDが見つからなかった場合に404を返すように修正します。
///
/// DELETEコマンド
/// ヒーローを削除する
///
/// 削除するヒーローのid番号
/// 削除されたヒーロー
[WebInvoke(UriTemplate = "{id}", Method = "DELETE")]
public Models.Hero Delete(int id) {
Models.Hero _hero;
using (var context = new Models.HeroContext()) {
_hero = context.Heros.Find(id);
if (_hero != null) {
context.Heros.Remove(_hero);
context.SaveChanges();
}
else {
//500エラーにするのではなく404(Not Found)を返すようにする。
throw new HttpResponseException(HttpStatusCode.NotFound);
}
}
return _hero;
}
修正したの18行目です。HttpResponseExceptionをスローするように変更しています。ここではHttpResponseExceptionの引数にHttpStatusCode列挙子を渡して404(Not Found)を返すようにしています。HttpResponseExceptionの引数なしのコンストラクタで初期化した場合には500を返すように初期化されます。
PUTに対しても同様の修正を行います。
以上の修正を加えることでよりRESTfulになるよう修正できました。
全体コード
SuperHeroAPi.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Net.Http;
using SuperHero.Resources;
using Models = SuperHero.Models;
//以下を追加する
using System.Net;
using Microsoft.ApplicationServer.Http;
using Microsoft.ApplicationServer.Http.Dispatcher;
namespace SuperHero.APIs
{
///
/// REST API Sample
///
[ServiceContract]
public class SuperHeroApi
{
///
/// ヒーローのリストを取得する。
/// ODataのインターフェイスをサポートしている
/// 基本的に@Chackさんのサンプルを変更
///
/// ヒーローのリスト
[WebGet(UriTemplate = "")]
public IQueryable Get() {
List heroes;
using (var context = new Models.HeroContext()) {
heroes = context.Heros.ToList();
}
return heroes.AsQueryable();
}
///
/// POSTコマンド
/// ヒーローを追加する
///
/// 追加するHeroのリスト
/// 追加したHeroのリスト
[WebInvoke(UriTemplate = "", Method = "POST")]
public HttpResponseMessage> Add(IEnumerable heroes) {
List addedHeroes = new List();
using (var context = new Models.HeroContext()) {
//最後のIDE接続の場合を取得する。
int lastId = 0;
if (context.Heros.Count() > 0) {
lastId = context.Heros.OrderByDescending(h => h.HeroId).First().HeroId;
}
//データの追加
foreach (var _hero in heroes) {
context.Heros.Add(_hero);
}
//DBへの反映
context.SaveChanges();
//クライアントに返す反映後のデータのリストを作成する
if (lastId != 0) {
addedHeroes = context.Heros.Where(h => h.HeroId > lastId)
.OrderBy(h => h.HeroId).ToList();
}
else {
addedHeroes = context.Heros.ToList();
}
}
//ステータスコードとして201(Created)を返すように修正。
var result = new HttpResponseMessage>(addedHeroes);
result.StatusCode = HttpStatusCode.Created;
return result;
}
///
/// DELETEコマンド
/// ヒーローを削除する
///
/// 削除するヒーローのid番号
/// 削除されたヒーロー
[WebInvoke(UriTemplate = "{id}", Method = "DELETE")]
public Models.Hero Delete(int id) {
Models.Hero _hero;
using (var context = new Models.HeroContext()) {
_hero = context.Heros.Find(id);
if (_hero != null) {
context.Heros.Remove(_hero);
context.SaveChanges();
}
else {
//500エラーにするのではなく404(Not Found)を返すようにする。
throw new HttpResponseException(HttpStatusCode.NotFound);
}
}
return _hero;
}
///
/// PUTコマンド
/// ヒーローを更新する
///
/// 更新するヒーロー
/// 更新されたヒーロー
[WebInvoke(UriTemplate = "", Method = "PUT")]
public Models.Hero Update(Models.Hero hero) {
using (var context = new Models.HeroContext()) {
var _hero = context.Heros.Find(hero.HeroId);
if (_hero != null) {
_hero.Name = hero.Name;
context.SaveChanges();
}
else {
//500エラーにするのではなく404(Not Found)を返すようにする。
throw new HttpResponseException(HttpStatusCode.NotFound);
}
}
return hero;
}
}
}
コメント