カスタム属性と 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 にも追加して...
対象が少数のプロパティであれば、ちょっと面倒かもしれません。
プロパティがいっぱいあるとか、他のプロジェクトでも利用したいとか、ちょっと難しそうなことやって自己満に浸りたいとか...そんなときにどうぞ。