ビューモデルに整数値とnull許容値を受け入れることができるプロパティがあります。
[Display(Name = "Code Postal")]
public int? CodePostal { get; set; }
文字列値を入力すると、デフォルトのメッセージとは別のメッセージを表示するにはどうすればよいですか。
The field Code Postal must be a number.
ありがとう
ビューモデルに整数値とnull許容値を受け入れることができるプロパティがあります。
[Display(Name = "Code Postal")]
public int? CodePostal { get; set; }
文字列値を入力すると、デフォルトのメッセージとは別のメッセージを表示するにはどうすればよいですか。
The field Code Postal must be a number.
ありがとう
メタデータを意識した属性を書くことができます:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class MustBeAValidIntegerAttribute : Attribute, IMetadataAware
{
public MustBeAValidIntegerAttribute(string errorMessage)
{
ErrorMessage = errorMessage;
}
public string ErrorMessage { get; private set; }
public void OnMetadataCreated(ModelMetadata metadata)
{
metadata.AdditionalValues["mustbeavalidinteger"] = ErrorMessage;
}
}
この属性を使用するカスタム モデル バインダーは、要求からこれらの整数型をバインドするときに表示されるハードコードされたエラー メッセージを追加する既定のモデル バインダーであるためです。
public class NullableIntegerModelBinder: DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (!bindingContext.ModelMetadata.AdditionalValues.ContainsKey("mustbeavalidinteger"))
{
return base.BindModel(controllerContext, bindingContext);
}
var mustBeAValidIntegerMessage = bindingContext.ModelMetadata.AdditionalValues["mustbeavalidinteger"] as string;
if (string.IsNullOrEmpty(mustBeAValidIntegerMessage))
{
return base.BindModel(controllerContext, bindingContext);
}
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (value == null)
{
return null;
}
try
{
return value.ConvertTo(typeof(int?));
}
catch (Exception)
{
bindingContext.ModelState.AddModelError(bindingContext.ModelName, mustBeAValidIntegerMessage);
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, value);
}
return null;
}
}
に登録されApplication_Start
ます:
ModelBinders.Binders.Add(typeof(int?), new NullableIntegerModelBinder());
この瞬間から、物事はかなり標準的になります。
モデルを見る:
public class MyViewModel
{
[Display(Name = "Code Postal")]
[MustBeAValidInteger("CodePostal must be a valid integer")]
public int? CodePostal { get; set; }
}
コントローラ:
public class HomeController : Controller
{
public ActionResult Index()
{
return View(new MyViewModel());
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
return View(model);
}
}
意見:
@model MyViewModel
@using (Html.BeginForm())
{
@Html.EditorFor(x => x.CodePostal)
@Html.ValidationMessageFor(x => x.CodePostal)
<button type="submit">OK</button>
}
既定のモデル バインダーによって実行される暗黙的な検証のカスタム エラー メッセージだけが必要な場合に、やらなければならない作業の量を見ると、少しがっかりします。その理由は、いくつかの重要なメソッド、特におよびDefaultModelBinder
がプライベートとして隠されているためです。彼らが将来これを大事にしてくれることを願っています。GetValueInvalidResource
GetValueRequiredResource
私は、すべてのタイプのモデル バインダーを作成することを回避する問題の一般的な解決策を提供しようとしていました。
正直なところ、すべてのケース(コレクションをバインドする場合など)で以下の実装をテストしたわけではありませんが、基本的なレベルでテストしました。
だからここにアプローチがあります。
カスタム モデル バインダーのカスタム エラー メッセージを渡すのに役立つ 2 つのカスタム属性があります。基本クラスを持つこともできますが、それで問題ありません。
public class PropertyValueInvalidAttribute: Attribute
{
public string ErrorMessage { get; set; }
public PropertyValueInvalid(string errorMessage)
{
ErrorMessage = errorMessage;
}
}
public class PropertyValueRequiredAttribute: Attribute
{
public string ErrorMessage { get; set; }
public PropertyValueRequired(string errorMessage)
{
ErrorMessage = errorMessage;
}
}
これはモデル バインダーです。これはジェネリックで型に依存せず、必要な検証と無効な検証の両方のエラー メッセージをカスタマイズします。
public class ExtendedModelBinder : DefaultModelBinder
{
protected override void SetProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, object value)
{
base.SetProperty(controllerContext, bindingContext, propertyDescriptor, value);
if (propertyDescriptor.Attributes.OfType<PropertyValueInvalidAttribute>().Any())
{
var attr = propertyDescriptor.Attributes.OfType<PropertyValueInvalidAttribute>().First();
foreach (ModelError error in bindingContext.ModelState[propertyDescriptor.Name]
.Errors.Where(err => String.IsNullOrEmpty(err.ErrorMessage) && err.Exception != null)
.ToList())
{
for (Exception exception = error.Exception; exception != null; exception = exception.InnerException)
{
if (exception is FormatException)
{
bindingContext.ModelState[propertyDescriptor.Name].Errors.Remove(error);
bindingContext.ModelState[propertyDescriptor.Name].Errors.Add(attr.ErrorMessage);
break;
}
}
}
}
}
protected override void OnPropertyValidated(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, object value)
{
if (propertyDescriptor.Attributes.OfType<PropertyValueRequiredAttribute>().Any())
{
var attr = propertyDescriptor.Attributes.OfType<PropertyValueRequiredAttribute>().First();
var isTypeAllowsNullValue = (!propertyDescriptor.PropertyType.IsValueType || Nullable.GetUnderlyingType(propertyDescriptor.PropertyType) != null);
if (value == null && !isTypeAllowsNullValue)
{
bindingContext.ModelState[propertyDescriptor.Name].Errors.Clear();
bindingContext.ModelState.AddModelError(propertyDescriptor.Name, attr.ErrorMessage);
}
}
base.OnPropertyValidated(controllerContext, bindingContext, propertyDescriptor, value);
}
}
OnPropertyValidated
デフォルトのモデル バインダーによってスローされる暗黙の必須エラー メッセージをオーバーライドするためだけにメソッドをオーバーライドし、SetProperty
型が無効な場合に独自のメッセージを使用するためだけにメソッドをオーバーライドしています。
カスタム バインダーを Global.asax.cs の既定として設定します。
ModelBinders.Binders.DefaultBinder = new ExtendedModelBinder();
そして、このようにプロパティを飾ることができます..
[PropertyValueRequired("this field is required")]
[PropertyValueInvalid("type is invalid")]
[Display(Name = "Code Postal")]
public int? CodePostal { get; set; }
最も簡単な方法は、デフォルトの検証リソース文字列を置き換えることです。この他のSOの回答は、それを支援します。
ただし、これらの文字列は、特定のクラスの特定のプロパティだけでなく、すべてのモデルで使用されることを覚えておく必要があります。
注:ダーリンによると(そして私はコードをテストしていません)、私は自分の答えの一部を打っています。リソース文字列を変更することによる単純化されたアプローチは依然として有効です。私はそれを自分でやったことがあり、それが機能することを知っています。
プロパティに属性を追加します。
[Display(Name = "Code Postal")]
[RegularExpression("\d+", ErrorMessage = "I'm now all yours...")]
public int? CodePostal { get; set; }
文字列以外のプロパティに正規表現を設定しても、これは機能するはずです。検証コードを見ると、次のようになります。
public override bool IsValid(object value)
{
this.SetupRegex();
string text = Convert.ToString(value, CultureInfo.CurrentCulture);
if (string.IsNullOrEmpty(text))
{
return true;
}
Match match = this.Regex.Match(text);
return match.Success && match.Index == 0 && match.Length == text.Length;
}
ご覧のとおり、このバリデーターは自動的に値を文字列に変換します。したがって、値が数値である場合、それは文字列に変換され、正規表現によって検証されるため、実際には問題になりません。