WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

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

by WebSurfer 15. February 2020 14:34

ASP.NET Core 3.1 MVC アプリケーションでカスタムモデルバインダを利用するコードを備忘録として書いておきます。(注:.NET Framework の MVC ではありません)

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

先の記事「カスタムモデルバインダ (MVC5)」で .NET Framework の MVC5 のカスタムモデルバインダのコードを書きましたが、それと同じ機能を ASP.NET Core 3.1 MVC で実装してみます。

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

カスタムモデルバインダが継承するインターフェイスは Microsoft.AspNetCore.Mvc.ModelBinding 名前空間に属する IModelBinder Interface となります。MVC5 用と名前は同じですが中身が異なることに注意してください。

実装するのは BindModelAsync(ModelBindingContext) という Task を返す非同期メソッドになります。

加えて、ヘルパーメソッドで使っている GetValue(key) メソッドが返す ValueProviderResult は MVC5 と Core で名前は同じながら別物で、Core 用は ValueProviderResult 構造体となります。ユーザーから POST されてきた値は FirstValue プロパティを使って文字列として取得します。

Model, カスタムモデルバインダ、Controller のサンプルコードを以下にアップしておきます。上の画像を表示したものです。View のコードはスキャフォールディング機能を使って自動生成できるので割愛します。

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

モデルのコードは MVC5 用と全く同じです。カスタムモデルバインダのコードは上に述べた点が異なりますが、他は MVC5 用と同じです。

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using System.Globalization;

namespace MvcCoreApp.Models
{
  // モデル(MVC5 用と同じ)
  public class Person2
  {
    public int PersonId { set; get; }

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

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

    // int? 型にしないと未入力に対応できない
    [Display(Name = "年齢")]
    public int? Age { set; get; }
  }

  // カスタムモデルバインダ
  public class CustomModelBinder : IModelBinder
  {
    public Task BindModelAsync(ModelBindingContext context)
    {
      if (context == null)
      {
        throw new ArgumentNullException("context");
      }

      var model = new Person2();
      model.Name = PostedData(context, "Name");
      model.Mail = PostedData(context, "Mail");
      string age = PostedData(context, "Age");

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

      if (string.IsNullOrEmpty(model.Name))
      {
        context.ModelState.AddModelError("Name", 
                                         "名前は必須");
      }
      else if (model.Name.Length < 2 || 
               model.Name.Length > 20)
      {
        context.ModelState.AddModelError("Name", 
                            "名前は 2 ~ 20 文字の範囲");
      }
      else if (model.Name.StartsWith("佐藤") && 
               model.Age < 20)
      {
        context.ModelState.AddModelError("", 
             "佐藤さんは二十歳以上でなければなりません");
      }

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

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

      context.Result = ModelBindingResult.Success(model);
      return Task.CompletedTask;
    }

    // ヘルパーメソッド
    // GetValue(key) メソッドが返す ValueProviderResult は
    // MVC5 と Core では別物。前者はクラスで後者は構造体。
    // 値を取得するには FirstValue プロパティを使う
    private static string PostedData(
        ModelBindingContext context, string key)
    {
      var result = context.ValueProvider.GetValue(key);
      context.ModelState.SetModelValue(key, result);
      return result.FirstValue;
    }
  }
}

Controller / Action Method

MVC5 の場合と同様に、モデルバインダをターゲットとなる型に関連付けるため、POST データを受けるアクションメソッドの引数に [ModelBinder(typeof(CustomModelBinder))] を付与します。

using System;
using Microsoft.AspNetCore.Mvc;
using MvcCoreApp.Models;

namespace MvcCoreApp.Controllers
{
  public class ValidationController : Controller
  {
    public IActionResult Create4()
    {
      return View();
    }

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

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

Tags: , , ,

CORE

About this blog

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

Calendar

<<  April 2020  >>
MoTuWeThFrSaSu
303112345
6789101112
13141516171819
20212223242526
27282930123
45678910

View posts in large calendar