ASP.NET MVC で DataAnnotations のエラーメッセージをカスタマイズ

ASP.NET MVC で DataAnnotations の入力値検証エラーメッセージをカスタマイズしようとしたら微妙にめんどくさかったのでメモっておきます。 ASP.NET MVC 5 で確認しています。

方針

  • リソースファイル( *.resx )にカスタムメッセージを記述し、それを使うようにする
  • DataAnnotations の属性ごとにメッセージをカスタマイズできるようにする

調べると、わりと古いですが以下の日本語情報がひっかかります。

自前で標準の属性を継承したカスタム属性を作ってリソースを指定する方法と、属性のファクトリ?的なアダプターを作成する方法が紹介されていますが、前者は確かにいちいちカスタム属性使うよう変更するのも面倒だし間違えやすそうなので、後者の方法を使います。

属性の指定時にメッセージをベタ書きしてアドホックにカスタマイズすることもできますが、それだったらカスタム属性作る方がましなので選択肢から除外します。

実装

リソースファイル

プロジェクト直下に App_GlobalResources フォルダを作ってその下に作成します。作成方法については以下の記事を参照。 Visual Studio のバージョンによって微妙に違いますが、基本的には同じ。ここではリソースファイルの名前を Messages とでもしておきます。

ここに「名前」と「値」を以下の様な感じで記述します。

名前
PropertyValueRequired {0} は必須項目やで
PropertyValueStringLength {0} は {1} 文字までしかあかんで
アダプターの作成・登録

このへんは最初の記事そのままで OK 。

public class CustomRequiredAttributeAdapter : RequiredAttributeAdapter
{
    public CustomRequiredAttributeAdapter(ModelMetadata metadata, ControllerContext context, RequiredAttribute attribute)
        : base(metadata, context, attribute)
    {
        // 作成したリソースファイルを指定
        attribute.ErrorMessageResourceType = typeof(Messages);
        // リソースファイルに記述した名前を指定
        attribute.ErrorMessageResourceName = "PropertyValueRequired";
    }
}

Global.asaxApplication_Start() で登録します。 App_Start 以下になんとか Config みたいなの作ってそっちに書くのが今風かもしれません。

        // 作成したリソース名を設定
        DefaultModelBinder.ResourceClassKey = "Messages";
        // アダプターの設定
        DataAnnotationsModelValidatorProvider.RegisterAdapter(
            typeof(RequiredAttribute), typeof(CustomRequiredAttributeAdapter));

これでカスタムメッセージが使われるようになります。

その他

ここまでは RequiredAttribute の例ですが、 StringLengthAttribute なんかでもズバリ StringLengthAttributeAdapter なんてクラスがあるので、これを継承して同じようにやれば動きます。

アダプターが用意されていない属性については、 DataAnnotationsModelValidator を継承すれば動きました。ただちょっと注意があって、属性に対して ErrorMessage == null しないと例外が発生します。

以下は EmailAddressAttribute での実装例です。

public class CustomEmailAddressAttributeAdapter : DataAnnotationsModelValidator<EmailAddressAttribute>
{
    public CustomEmailAddressAttributeAdapter(ModelMetadata metadata, ControllerContext context, EmailAddressAttribute attribute)
        : base(metadata, context, attribute)
    {
        attribute.ErrorMessageResourceType = typeof(Messages);
        attribute.ErrorMessageResourceName = "PropertyValueEmailAddress";
        // これをしないと以下の例外
        // 「*System.InvalidOperationException*System.InvalidOperationException: ErrorMessageString と ErrorMessageResourceName は、どちらか 1 つを設定する必要があります。両方は設定できません。」
        attribute.ErrorMessage = null;
    }
}

ValidationAttribute.ErrorMessageString Property (System.ComponentModel.DataAnnotations) | Microsoft Docs によると、以下にのように書かれているのでその辺が原因ですかね?

エラエラー メッセージ文字列は、ErrorMessage プロパティを評価する、または ErrorMessageResourceType および ErrorMessageResourceName プロパティを評価することにより取得されます。 2 つのケースは同時に指定できません。 後者のケースが、ローカライズされたエラー メッセージを表示する場合に使用されます。

「エラエラー」が気になる。

とりあえずやりたいことは出来ましたが、属性ごとにいちいちアダプターを作って登録みたいなことをやる必要があり、やりたいことに対してちょっと手順が多すぎる気がします。もうちょいましな方法はないんだろうか?

なお、型が一致しない場合*1のエラーメッセージは、特にアダプターとか作らなくても、リソースに PropertyValueInvalid という名前で登録すれば使われるようです。

*1:int 型のプロパティに文字列突っ込もうとした場合とか