WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

カスタムモデルバインダ (MVC5)

by WebSurfer 14. February 2020 15:29

.NET Framework の ASP.NET MVC5 アプリケーションでカスタムモデルバインダを利用するコードを備忘録として書いておきます。(注:ASP.NET Core 3.1 MVC の記事は「カスタムモデルバインダ (Core 3.1)」を見てください。下のサンプルコードは ASP.NET Core MVC には使えません)

カスタムモデルバインダ

モデルバインド機能だけでなく、ユーザー入力の検証とエラーメッセージの表示ができるようにしてみました。

上の画像を表示する Model と Controller のサンプルコードを以下にアップしておきます。View のコードはスキャフォールディング機能を使って自動生成できるので省略します。

Model とカスタムモデルバインダ

using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
using System.Globalization;

namespace Mvc5App.Models
{
  // Model
  public class Person2
  {
    public int PersonId { set; get; }

    [Display(Name = "名前")]
    public string Name { set; get; }

    [Display(Name = "メールアドレス")]
    public string Mail { set; get; }

    // Age は int? にしないと未入力ではカスタムモデルバイ
    // ンダでも動かない。既定のモデルバインダと同様に null
    // が渡されて例外がスローされるようで「年齢 フィールド
    // が必要です。 」というエラーメッセージが表示される
    [Display(Name = "年齢")]
    public int? Age { set; get; }
  }

  // カスタムモデルバインダー
  public class CustomModelBinder : IModelBinder
  {
    public object BindModel(ControllerContext contContext, 
                          ModelBindingContext bindContext)
    {
      if (bindContext == null)
      {
        throw new ArgumentNullException("引数が null");
      }

      var model = new Person2();
      model.Name = PostedData<string>(bindContext, "Name");
      model.Mail = PostedData<string>(bindContext, "Mail");
      // ここでは string 型で取得する
      string age = PostedData<string>(bindContext, "Age");

      if (string.IsNullOrEmpty(age))
      {
        bindContext.ModelState.AddModelError("Age", 
                                           "年齢は必須");
      }
      else
      {
        int intAge;
        if (!int.TryParse(age, out intAge))
        {
          bindContext.ModelState.AddModelError("Age",
                                            "年齢は整数");
        }
        else
        {
          model.Age = intAge;
          if (intAge < 0 || intAge > 200)
          {
            bindContext.ModelState.AddModelError("Age", 
                                "年齢は 0 ~ 200 の範囲");
          }
        }
      }

      if (string.IsNullOrEmpty(model.Name))
      {
        bindContext.ModelState.AddModelError("Name",
                                            "名前は必須");
      }
      else if (model.Name.Length < 2 || 
               model.Name.Length > 20)
      {
        bindContext.ModelState.AddModelError("Name",
                             "名前は 2 ~ 20 文字の範囲");
      }

      else if (model.Name.StartsWith("佐藤") &&
               model.Age < 20)
      {
        bindContext.ModelState.AddModelError("",
              "佐藤さんは二十歳以上でなければなりません");
      }

      if (string.IsNullOrEmpty(model.Mail))
      {
        bindContext.ModelState.AddModelError("Mail",
                                  "メールアドレスは必須");
      }
      else
      {
        bool isValidEmai = Regex.IsMatch(model.Mail,
          @"・・・正規表現(省略)・・・",
          RegexOptions.IgnoreCase, 
          TimeSpan.FromMilliseconds(250));

        if (!isValidEmai)
        {
          bindContext.ModelState.AddModelError("Mail", 
                        "有効な Email 形式ではありません");
        }
      }

      return model;
    }

    // ヘルパーメソッド
    // Core では ValueProviderResult.ConvertTo メソッドは使え
    // ませんので注意。
    private static T PostedData<T>(ModelBindingContext context,
                                   string key)
    {
      var result = context.ValueProvider.GetValue(key);
      context.ModelState.SetModelValue(key, result);
      return (T)result.ConvertTo(typeof(T));
    }
  }
}

Controller / Action Method

モデルバインダをターゲットとなる型に関連付けるため、POST データを受けるアクションメソッドの引数に [ModelBinder(typeof(CustomModelBinder))] を付与します。(これはローカルな関連付けで、Global.asax の Application_Start メソッドでグローバルに関連付けを行うこともできるそうです)

using System;
using System.Web.Mvc;
using Mvc5App.Models;

namespace Mvc5App.Controllers
{
  public class ValidationController : Controller
  {
    // カスタムモデルバインダーを使ったサンプル
    public ActionResult Create4()
    {
      return View();
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create4(
      [ModelBinder(typeof(CustomModelBinder))] Person2 model)
    {
      if (!ModelState.IsValid)
      {
        return View(model);
      }

      return RedirectToAction("Index", "Home");
    }
  }
}

Tags: , ,

MVC

About this blog

2010年5月にこのブログを立ち上げました。その後 ブログ2 を追加し、ここは ASP.NET 関係のトピックス、ブログ2はそれ以外のトピックスに分けました。

Calendar

<<  October 2020  >>
MoTuWeThFrSaSu
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678

View posts in large calendar