単純なデータ注釈モデルを超えた、より複雑な検証のために私が見つけた最良のソリューションを次に示します。
IDataErrorInfo
実装しようとして、実装する方法が 2 つしか作成されていないことを確認したのは、私だけではないと確信しています。ちょっと待ってください。そこに行って、すべてのために独自のカスタム ルーチンをゼロから作成する必要がありますか? また、検証するモデル レベルのものがある場合はどうでしょうか。IDataErrorInfo 実装内からこのようなことをしたくない場合を除き、それを使用することに決めたときは、自分でいるようです。
私もたまたま質問者様と全く同じ症状でした。米国の郵便番号を検証したかったのですが、国が米国として選択された場合に限りました。明らかに、モデル レベルのデータ アノテーションは、郵便番号がエラーとして赤で強調表示される原因とならないため、適切ではありません。[クラス レベルのデータ アノテーションの良い例は、PropertiesMustMatchAttribute クラスの MVC 2 サンプル プロジェクトにあります]。
解決策は非常に簡単です:
まず、global.asax にモデルバインダーを登録する必要があります。必要に応じてクラス レベルの [属性] としてこれを行うこともできますが、global.asax に登録する方がより柔軟であることがわかります。
private void RegisterModelBinders()
{
ModelBinders.Binders[typeof(UI.Address)] = new AddressModelBinder();
}
次に、モデルバインダー クラスを作成し、複雑な検証を記述します。オブジェクトのすべてのプロパティに完全にアクセスできます。これは、データ注釈が実行された後に実行されるため、検証属性のデフォルトの動作を元に戻したい場合は、いつでもモデルの状態をクリアできます。
public class AddressModelBinder : DefaultModelBinder
{
protected override void OnModelUpdated(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
base.OnModelUpdated(controllerContext, bindingContext);
// get the address to validate
var address = (Address)bindingContext.Model;
// validate US zipcode
if (address.CountryCode == "US")
{
if (new Regex(@"^\d{5}([\-]\d{4})?$", RegexOptions.Compiled).
Match(address.ZipOrPostal ?? "").Success == false)
{
// not a valid zipcode so highlight the zipcode field
var ms = bindingContext.ModelState;
ms.AddModelError(bindingContext.ModelName + ".ZipOrPostal",
"The value " + address.ZipOrPostal + " is not a valid zipcode");
}
}
else {
// we don't care about the rest of the world right now
// so just rely on a [Required] attribute on ZipOrPostal
}
// all other modelbinding attributes such as [Required]
// will be processed as normal
}
}
この利点は、既存のすべての検証属性 ([Required]、[EmailValidator]、[MyCustomValidator]) が引き続き機能することです。
モデル バインダーに追加のコードを追加してフィールドを設定したり、必要に応じてモデル レベルの ModelState エラーを追加したりできます。
私にとってAddress
は、メインモデルの子であることに注意してください-この場合CheckoutModel
、次のようになります:
public class CheckoutModel
{
// uses AddressModelBinder
public Address BillingAddress { get; set; }
public Address ShippingAddress { get; set; }
// etc.
}
そのため、「BillingAddress.ZipOrPostal」と「ShippingAddress.ZipOrPostal」にモデル エラーが設定されるようにする必要があります。bindingContext.ModelName
+ ".ZipOrPostal"
PS。「単体テストの種類」からのコメントを歓迎します。これが単体テストに与える影響についてはわかりません。