ASP.NET 5から名称がASP.NET Core 1.0になったりと、betaから追いかけていると変更には慣れっこでしたが、RCになってからも振り回されましたね。
ASP.NET MVC 5で追加していたカスタム機能をASP.NET Core MVC 1.0でも実現してみました。
MVCモデルバインディングのカスタマイズ
まずはこれ。
FormからのPOST等で、ユーザが入力した値をモデルクラスのインスタンスで受け取ることができて便利です。
MVC5では、カスタム版ModelBinderを追加することで「1,024」のようなカンマ区切り数値をdecimal型のプロパティにバインドすることができました。
過程をすっ飛ばして、カスタム版モデルバインダーを追加するサンプルはこちら。
https://github.com/shimitei/ConvertModelBinder
ASP.NET Core MVCはオープンソースなので、とりあえずソースを見ればなんとかなります。
ConvertModelBinderとは?
ASP.NET Core MVC用のカスタムモデルバインダーです。 decimal(decimal?)型プロパティへのバインドを拡張します。
例えば「1,024」を数値の1024としてバインドすることができます。
注意点としては、入力値の変換はConvert.ToDecimalで行っていることです。実行時のカルチャ(ロケール)の影響を受けます。
RC1での実装内容
実装部分の解説です。
モデルバインダーのクラスは、IModelBinderを継承します。
このインターフェースはメソッドを1つ持っているだけなので、このBindModelAsyncメソッドを実装するだけです。
メソッドの処理内容としては、バインド先の型を確認して自分の担当の型であれば入力文字列を対象の型に変換して返します。
自分のバインド対象の型でなければ、決まった値を返すことで他のモデルバインダーに処理が移るようになっています。ModelBindingResult.NoResultAsync以外の値を返すと、他のモデルバインダーには処理が回らないことになります。
モデルバインダーはシステムに追加された順番がバインド処理の優先順位のようなので、今回のカスタムモデルバインダーは標準のモデルバインダーよりも優先させる必要があります。(標準のモデルバインダー処理でdecimal型プロパティへバインドされてしまう。)
カスタムモデルバインダーをシステムに登録するには、Startup.csのConfigureServicesで次のように設定します。
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
// Add this
options.ModelBinders.Insert(0, new ConvertModelBinder());
});
}
RC2への変更に対応
結構変わってるじゃないですか...
RC2からはモデルバインダーを直に登録せずに、代わりにカスタムモデルバインダー用のプロバイダーを登録します。プロバイダーの方でバインダーが対応している型なのかをチェックする仕組みになりました。
RC2とRTMではStartup.csのConfigureServicesで次のように設定します。
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
// Add this
options.ModelBinderProviders.Insert(0, new ConvertModelBinderProvider());
});
}
また、RC2以降はこのカスタムモデルバインダーを追加しなくてもDateTimeへのバインドはConvert.ToDateTimeを通したように動作します。このためConvertModelBinderのRC2対応版ではdecimalへのバインド処理のみ行うように変更しました。
1.0 RTM対応
ようやく正式リリースですが若干変更があり、モデルバインダーのバインド処理の成否の返し方が変更されています。
RC2では成否を明示的に記述していましたが、バインド結果が返されるかどうかで判るためか簡略化されています。詳しくはConvertModelBinderのソースコードを参照ください。
https://github.com/shimitei/ConvertModelBinder