ASP.NET MVC で Html.DropDownListFor を使った場合、どうやって検証を行っていますか?不正な値が選択されないという想定で作る人もいると思いますが、大抵の場合はブラウザの開発者ツールで値を改ざんされて送られる可能性を考慮する必要があります。
困ったことに ASP.NET MVC は SelectList や SelectListItem を使ってドロップダウンリストは作れるのに、その送られてきた値を検証する方法が無いんですよね。以下のようにアクションで検証する方法はありますが、それだと ModelState.IsValid が true になるのでチェックがめんどくさいですね。
[HttpPost] public ActionResult Create(UserCreateModel model) { if (ModelState.IsValid) { // true の時にもう一回チェックしないといけない if (!_prefectures.ContainsKey(model.PrefectureId)) { ModelState.AddError("PrefectureId", "都道府県が不正な値です"); } } return View(model); }
アクションがごちゃごちゃするし、再利用もめんどくさいし、とにかくわかりにくいですよね。
という訳で SelectList や SelectListItem を使って属性ベースで入力値の検証を行う Contains 検証属性を作ってみました。
public class ContainsAttribute : ValidationAttribute { public Type Type { get; set; } public string PropertyName { get; set; } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { var property = Type.GetProperty(PropertyName); var selectList = (IEnumerable<SelectListItem>)property.GetValue(null); var strValue = value.ToString(); if (selectList.All(p => p.Value != strValue)) { return new ValidationResult(ErrorMessage); } return null; } }
例によってエラー処理などは省いたコードになってますが、誰でも思いつくような実装ですね。属性ベースで行うために、リフレクションを使って SelectList を取得するようにしています。
実際に使う場合には以下のようにモデルに属性を付けます。
// 静的クラスに SelectList を持たせておく public static class Master { public static IEnumerable<SelectListItem> Prefectures { get { ... } } } public class UserCreateModel { [Contains(Type = typeof(Master), PropertyName = "Prefectures", ErrorMessage = "都道府県の値が不正です。")] public int PrefectureId { get; set; } }
型とプロパティ名を指定するのがめんどくさいですが、属性では定数しか持たせられないので仕方ないです。しかし、アクションで検証するのに比べると断然シンプルに書けますよね。
実際にブラウザの開発者ツールで値を改ざんして POST してみると、ちゃんと以下のようにエラーが表示されました。
みんな一度は悩む部分だと思うんですが、案外気にしてないんでしょうか?