by WebSurfer
2020年2月14日 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");
}
}
}