ASP.NET Web API でモデルバインド時検証をカスタム ActionFilter を使って実装する

今日は、以前のこちらの記事 “ASP.NET Web API で GET クエリ文字列パラメーターをモデル バインドする” の続きで、長らく温めていたトピックを書いてみます。

● データアノテーション属性によるモデル検証

モデルクラスのプロパティにデータアノテーション属性を付けることで、モデルバインド時に値の検証(バリデーション)を行うことが出来ます。

例えば、下記のモデルクラス MyData の Title プロパティに付加した Required 属性と StringLength 属性が System.ComponetModel.DataAnnotation 名前空間にあるデータアノテーションで、”Title は必須項目で、最大 140 文字である” という注釈がつけられていることになります。

[MyData.cs]

 using System.ComponentModel.DataAnnotations;

namespace WebAPIValidation.Models
{
    public class MyData
    {
         [Required]  
         [StringLength(140)] 
        public string Title { get; set; }
    }
}

このデータを、POST のアクションメソッドで受け取りモデル検証する場合のコードは下記になります。ModelState.IsValid で検証結果を確認して、エラー処理を切り分ける形です。

[MyDataController.cs]

 using WebAPIValidation.Models;

namespace WebAPIValidation.Controllers
{
    public class MyDataController : ApiController
    {
        // POST api/mydata
        public HttpResponseMessage Post([FromBody]MyData data)
        {
            if ( !ModelState.IsValid)
            {
                // validation error.
                return new HttpResponseMessage(HttpStatusCode.BadRequest);
            }

            // do something.
            return new HttpResponseMessage(HttpStatusCode.OK);
        }
    }
}

● カスタムアクションフィルターを使って、モデルバインド時の検証を実装する

上記のコードのように、検証エラーの場合は、BadRequest (ステータスコード: 400) を返し、それ以外の場合は OK (ステータスコード: 200) を返すといった処理は、例えば他のアクションメソッドでも使用する場合がありますので、このままだとコントローラーが冗長になりがちです。ASP.NET MVC/Web API では、アクションメソッドの呼び出し前後に処理を追加するアクションフィルターを使用することが出来ますので、この検証部分をカスタムアクションフィルターとして、下記のように実装可能です。

[ValidationFilterAttribute.cs]

 using System.Web.Http.Filters;

namespace WebAPIValidation.Filters
{
    // モデルバインド時の Data Annotation バリデーション用フィルター
    public class ValidationFilterAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            if (!actionContext.ModelState.IsValid)
            {
                actionContext.Response = actionContext.Request.CreateErrorResponse(
                    HttpStatusCode.BadRequest, actionContext.ModelState);
            }
        }
    }
}

このアクションフィルターを使用するには、ASP.NET Web API では WebApiConfig.cs で下記のように登録します(※ FilterConfig.cs ではないので注意です)。

[WebApiConfig.cs]

 using WebAPIValidation.Filters;

namespace WebAPIValidation
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // モデルバインド時の Data Annotation バリデーション用フィルターの登録
            config.Filters.Add(new ValidationFilterAttribute()); 

            ...

        }
    }
}

あとは、コントローラーのアクションメソッドに [ValidationFilter] 属性を付加するだけです。

 namespace WebAPIValidation.Controllers
{
    public class MyDataController : ApiController
    {
        // POST api/mydata
         [ValidationFilter] 
        public HttpResponseMessage Post([FromBody]MyData data)
        {
            // do something.

            return new HttpResponseMessage(HttpStatusCode.OK);
        }
    }
}

コントローラーのコードがシンプルにキレイになりましたね★

● HTTP GET のクエリ文字列パラメータの日付データを検証するには?

それでは応用です。URL で GET のパラメータとして渡された日付データを検証するにはどうしたらよいでしょうか・・・?

答えは下記の通りです。(簡単ですね♥)

データアノテーション属性に用意されている DataType 属性で DataType.DateTime が使用できます。そのほか、RegularExpression 属性を使って任意の正規表現を指定することもできます。

[MyData.cs]

 using System.ComponentModel.DataAnnotations;

namespace WebAPIValidation.Models
{
    public class MyData
    {
         [DataType(DataType.DateTime)] 
        public DateTime PubDate { get; set; }
    }
}

コントローラーは前述の通り ValidationFilter 属性を付加するとともに、メソッド引数に [FromUri] 属性を付けます。これで、URL で指定されたパラメータがモデルバインドされます。

[MyDataController.cs]

 using WebAPIValidation.Models;
using WebAPIValidation.Filters;

namespace WebAPIValidation.Controllers
{
    public class MyDataController : ApiController
    {
        // GET api/mydata
         [ValidationFilter] 
        public MyData Get( [FromUri] MyData data)
        {
            // do something.
            return data;
        }
    }
}

これで実行してみましょう。Fiddler を使って GET リクエストを投げてみます。

a) 不正な日付を指定した場合

例: http://localhost:58326/api/MyData?PubDate=2012/10/33

image

image

ValidationFilter で検証 NG となり、”400 Bad Request” が返ってくると同時に、レスポンスボディにはエラーの詳細情報がセットされていることが確認できます。(ここでは Content-Type: text/json と指定しているのでエラー情報も JSON 形式で返されます)

b) 正しい日付を指定した場合

例:http://localhost:58326/api/MyData?PubDate=2012/10/19

image

image

正しい日付データの場合は、もちろん “200 OK” となり、このサンプルでは Get アクションメソッドで単純に受け取ったデータを返すだけの実装としているので、日付データのみセットされた MyData が JSON 形式で返ってきていることが確認できます。

◆◆◆

ASP.NET Web API でのモデルバインディングと検証、そしてアクションフィルターの例をご紹介しました。ぜひぜひ、ご活用いただけると幸いです。