LoginSignup
1
1

More than 3 years have passed since last update.

モデルバインディング機能を拡張してつかう。(Custom Model Binding)

Last updated at Posted at 2018-12-25

初めに

この記事は、NEXTSCAPE クラウドインテグレーション事業本部 Advent Calendar 2018
12月22日分の記事となります。

そもそもモデルバインドとは

おおざっぱに言ってしまうとHTTPリクエストのデータから
アクションメソッドを呼び出すときの引数オブジェクトにマッピングしてくれる機能です。

例えば

    public class LocalGovCodeController : ControllerBase
    {
        public ActionResult<string> Get(string code) => Ok(code);
    }

上記のようなコントローラのアクションメソッドがあって、

   http://foo.com/api/LocalGovCode?code=12345

上記のようなURLでアクセスしたときに 12345code に代入されるのはこの機能のおかげです。

このモデルバインディングの機能はカスタマイズがすることができて、
例えば

  • Actionメソッドのパラメータにデフォルトではバインド不可能なアプリケーション独自の型をバインドするようにできる。
  • URLのパラメータに半角/全角が混在していてもどちらかに統一してアクションメソッドの引数に渡すことができる。
  • 1,234,567 のような値でもint型の引数として受け取ることができる。

以上のようなことができます。
今回はアプリケーション独自の型にバインドできるようにしてみましょう。

例として、とある、5-6桁の文字列からなるコードを表すクラスを利用します。
クラス定義としてはこんな感じでしょうか。

    public class LocalCode
    {

        string _code = string.Empty;
        private LocalCode(string source)
        {
            _code = source;
        }

        public static bool TryParse(string source, out LocalGovCode result)
        {
            bool ret = false;
            result = null;

            if(string.IsNullOrWhiteSpace(source))
            {
                // 引数指定なし
                return ret;
            }

            if(source.Length < 5 || 6 < source.Length)
            {
                // コード長不正
                return ret;
            }

            result = new LocalGovCode(source);

            return true;
        }

        public override string ToString() => _code;

    }

Action メソッドはこんな感じに実装したいとします。

        public ActionResult<string> Find(LocalCode code)
        {
            if(!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            return Ok(code);
        }

このまま実行すると…
Binderがないエラー.png
バインダがないとエラーになってしまいます。

ではそのバインダーを作成しましょう。
ただし、モデルバインダ本体だけでなくモデルバインダを提供するファクトリが必要になります。

まずはモデルバインダーの本体から。
IModelBinder インターフェースを実装するだけです。

    public class LocalCodeBinder : IModelBinder
    {
        public Task BindModelAsync(ModelBindingContext bindingContext)
        {
            if (bindingContext == null)
            {
                throw new ArgumentNullException(nameof(bindingContext));
            }

            var modelName = bindingContext.ModelName;

            var valueProviderResult =
                bindingContext.ValueProvider.GetValue(modelName);

            if (valueProviderResult == ValueProviderResult.None)
            {
                return Task.CompletedTask;
            }

            bindingContext.ModelState.SetModelValue(modelName,
                valueProviderResult);

            var value = valueProviderResult.FirstValue;

            if (string.IsNullOrEmpty(value))
            {
                return Task.CompletedTask;
            }

            if (!LocalCode.TryParse(value, out LocalCode result))
            {
                // 入力チェックエラーがある場合
                bindingContext.ModelState.TryAddModelError(
                                        modelName,
                                        "コードの指定が不正です。");
                return Task.CompletedTask;
            }

            bindingContext.Result = ModelBindingResult.Success(result);
            return Task.CompletedTask;
        }
    }

大きな流れとしては、

  • リクエストデータの取得
  • 入力チェック
  • 独自のクラスのインスタンスの生成

以上を行えばモデルバインダの実装が完了です。
モデルバインダのファクトリは IModelBinderProviderインターフェースを実装するだけです。
実装も至って単純で先に作ったモデルバインダのインスタンスを返すように設定します。

        public IModelBinder GetBinder(ModelBinderProviderContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            if (context.Metadata.ModelType == typeof(LocalCode))
            {
                return new BinderTypeModelBinder(typeof(LocalCodeBinder));
            }

            return null;
        }

それでは、あともう一息。
LocalCode型の引数が指定された場合は今回つくったバインダを利用するように設定しましょう。
LocalCodeクラスにModelBinder属性を付与するだけです。

    [ModelBinder(BinderType = typeof(LocalCodeBinder))]
    public class LocalCode
    {
        // 省略
    }

デバッグ実行すれば…
できました.png

ブラウザでもちゃんとデータが返却されていることが確認できます。

今回はアプリケーション独自の型にバインドできるように対応しましたが、
この対応をすることによってアクションメソッドもすっきりしたかと思います。

この記事を読んだ機会にぜひ使ってみてはいかがでしょうか。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1