インクリメンタルなカイハツにっき

.NET 開発手法を中心に、これから始める方の一助となる記事を載せていく予定です。

カスタム属性と ModelBinder を利用して、ASP.NET MVC に全角半角変換処理を組み込む

前回、全角半角の変換方法を紹介しました。

これをふまえて、実際に ASP.NET MVC でどのように利用すればよいか考えてみます。
前提として、次の Model を利用することにします。

public class TestModel
{
    public string Address { get; set; }
}

Model の getter に設定

ぱっと思いつくのは、Model の getter に設定する方法でしょう。
次みたいな感じ

private string _address;

public string Address
{
    get 
    {
        return Strings.StrConv(_address, VbStrConv.Wide);
    }
    set { _address = value; }
}

get を介すれば、必ず全角で取得可能ですね。

これ、少なければ何ら問題ないのですが、例えばこれを50か所とか言われたら...
コピペもやりづらいし...地味~に面倒です...

属性を利用する

例えば次のように

[Conv(VbStrConv.Wide)] // 属性で指定
public string Address { get; set; }

属性で指定できれば便利です。
50か所あっても、コピペしやすいですね。
それでは、まずこのカスタム属性となる ConvAttribute を作成してみます。

[AttributeUsage(AttributeTargets.Property)] // プロパティをターゲットとして指定
public class ConvAttribute : Attribute  // Attribute クラスを継承
{
    // コンストラクタは引数として VbStrConv 列挙型を指定
    public ConvAttribute(VbStrConv vbStrConv)
    {
        // VbStrConv 列挙型のプロパティに引数の値を設定
        this.VbStrConv = vbStrConv;
    }

    // プロパティは VbStrConv 列挙型。ここでは名前を VbStrConv とした。
    public VbStrConv VbStrConv { get; set; }
}

このカスタム属性を、全角変換を行いたい Address プロパティに設定します。

[Conv(VbStrConv.Wide)]
public string Address { get; set; }

...もちろん、これだけでは何も起こりません。
このカスタム属性が指定されているプロパティの値は、こういう処理を行うという部分が必要です。

ModelBinder

ところで、この Model は、Controller の引数で指定することを想定しています。次みたいに。

// Controller
// Form で指定した値が、Model に設定されている
public ActionResult Index(TestModel model)
{
    ...
}

// View
// 別途名前空間が必要
@model TestModel
@using (Html.BeginForm())
{
    @Html.TextBoxFor(model => model.Address)
    <button type="submit">送信</button>
}

テキストボックスに値を入れて submit すると、引数 model の Address プロパティに、指定した値が設定されています。
...なぜでしょう?

その役目を担うのが ModelBinder です。
アクションを実行する前に、POST されたデータを、そのアクションの引数に設定します。
通常利用する場合、そうなるものと理解し、特に気にしない部分ではないでしょうか。
そんな処理を、デフォルトでは、DefaultModelBinder というクラスが用意され、実行されています。

...ということは、ここにちょっと細工を入れてやって、Address プロパティに値が設定されるときに変換を実行すれば...うまくいきそうですね!

というわけで、今度はその ModelBinder を作成してみます。

// System.Web.Mvc.DefaultModelBinder を継承
public class CustomModelBinder : System.Web.Mvc.DefaultModelBinder
{
    // SetProperty メソッドをオーバーライド
    protected override void SetProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, object value)
    {
        // 実際に追加する部分 ここから
        if (value != null)
        {
            foreach (var attribute in propertyDescriptor.Attributes.OfType<ConvAttribute>())
            {
                value = Strings.StrConv(value.ToString(), attribute.VbStrConv);
            }
        }
        // -------- ここまで
        // ベースクラスのメソッドを実行
        base.SetProperty(controllerContext, bindingContext, propertyDescriptor, value);
    }
}

一見なんのこっちゃといった感じですが...ポイントとしては

  • System.Web.Mvc.DefaultModelBinder を継承する
  • SetProperty メソッドをオーバーライドする
  • PropetyDescriptor から プロパティに指定された属性に ConvAttribute が存在するか確認する
  • value を 変換し、もう一度 value に変換後の値を戻す
  • 最後はベースクラスに処理を戻す

SetProperty メソッドは4つのパラメータが存在しますが、ここで利用しているのは後半の2つのみです。そう考えると実はそれ程難しいことはしていません。

DefaultModelBinder を変更する

ここまでできれば、後はこの ModelBinder を利用するようにすれば...って何処でどーやって設定すんの?

どーやっての答えは、ModelBinders クラスにあります。
既定の ModelBinder を設定する DefaultBinder プロパティがありますので、次のように

ModelBinders.Binders.DefaultBinder = new CustomModelBinder();

これで、既定の ModelBinder が変更されます。

後は何処で?これは、ASP.NET であれば、Application_Start メソッド内で解決できそうですね。

// Global.asax.cs
public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
   {
        // ... 色々省略
        ModelBinders.Binders.DefaultBinder = new CustomModelBinder();
   }
}

以上で設定完了です。おつかれさまでした。

で、どっちがいいの?

それ程多くはないといえども、2つクラス作って、Application_Start にも追加して...
対象が少数のプロパティであれば、ちょっと面倒かもしれません。

プロパティがいっぱいあるとか、他のプロジェクトでも利用したいとか、ちょっと難しそうなことやって自己満に浸りたいとか...そんなときにどうぞ。