WCF Web API その3 RESTfulなWEB Serviceにするために改造する

※ご注意
WCF Web APIはマイクロソフトにより現在開発はキャンセルされ、ASP.NET MVC 4のASP.NET Web APIとして制作・公開されています。

.NET Framework/ASP.NETでRESTfulなAPI構築を目的に記事を探されている方は是非以下のリンクからASP.NET Web APIについてご確認下さい。
http://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;
        }    
    }
}

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください