2011-06-21 49 views
28

看來,當MVC驗證它首先通過DataAnnotation屬性(如必需或範圍)運行的模型,並且如果其中任何一個失敗,它將跳過在我的IValidatableObject模型上運行驗證方法。如何強制MVC驗證IValidatableObject

有沒有辦法讓MVC繼續並運行該方法,即使其他驗證失敗?

+0

老實說,我開始喜歡這種默認行爲。如果您在您的Validate方法中進行業務級別的驗證,這涉及像數據庫連接這樣的昂貴的東西,那麼除非模型有效,否則最好不要調用它們。 – Graham

回答

32

您可以手動通過傳遞ValidationContext的新實例,像這樣調用驗證():

[HttpPost] 
public ActionResult Create(Model model) { 
    if (!ModelState.IsValid) { 
     var errors = model.Validate(new ValidationContext(model, null, null)); 
     foreach (var error in errors)         
      foreach (var memberName in error.MemberNames) 
       ModelState.AddModelError(memberName, error.ErrorMessage); 

     return View(post); 
    } 
} 

這種做法的一個需要注意的是,在那裏有沒有房產級(DataAnnotation)錯誤的情況下, ,驗證將運行兩次。爲了避免這種情況,你可以添加一個屬性到你的模型中,比如說一個布爾型的Validated,它在你的Validate()方法中設置爲true,然後在手動調用你的控制器中的方法之前檢查。

所以在你的控制器:

if (!ModelState.IsValid) { 
    if (!model.Validated) { 
     var validationResults = model.Validate(new ValidationContext(model, null, null)); 
     foreach (var error in validationResults) 
      foreach (var memberName in error.MemberNames) 
       ModelState.AddModelError(memberName, error.ErrorMessage); 
    } 

    return View(post); 
} 

而在你的模型:

public bool Validated { get; set; } 

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) { 
    // perform validation 

    Validated = true; 
} 
+0

感謝它的工作。但我得到了一個問題。是什麼原因cos Ivalidate對象正在射擊。有時它可能不? – user998405

26

有一個辦法做到這一點,而無需樣板代碼在每個控制器動作的頂部。

你需要用自己的一個替換默認的模型綁定:

protected void Application_Start() 
{ 
    // ... 
    ModelBinderProviders.BinderProviders.Clear(); 
    ModelBinderProviders.BinderProviders.Add(new CustomModelBinderProvider()); 
    // ... 
} 

你的模型粘合劑供應商看起來是這樣的:

public class CustomModelBinderProvider : IModelBinderProvider 
{ 
    public IModelBinder GetBinder(Type modelType) 
    { 
     return new CustomModelBinder(); 
    } 
} 

現在創建一個自定義的模型綁定,實際上強制驗證。這是完成繁重工作的地方:

public class CustomModelBinder : DefaultModelBinder 
{ 
    protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext) 
    { 
     base.OnModelUpdated(controllerContext, bindingContext); 

     ForceModelValidation(bindingContext); 
    } 

    private static void ForceModelValidation(ModelBindingContext bindingContext) 
    { 
     var model = bindingContext.Model as IValidatableObject; 
     if (model == null) return; 

     var modelState = bindingContext.ModelState; 

     var errors = model.Validate(new ValidationContext(model, null, null)); 
     foreach (var error in errors) 
     { 
      foreach (var memberName in error.MemberNames) 
      { 
       // Only add errors that haven't already been added. 
       // (This can happen if the model's Validate(...) method is called more than once, which will happen when 
       // there are no property-level validation failures.) 
       var memberNameClone = memberName; 
       var idx = modelState.Keys.IndexOf(k => k == memberNameClone); 
       if (idx < 0) continue; 
       if (modelState.Values.ToArray()[idx].Errors.Any()) continue; 

       modelState.AddModelError(memberName, error.ErrorMessage); 
      } 
     } 
    } 
} 

您還需要一個IndexOf擴展方法。這是一個便宜的實施,但它會工作:

public static int IndexOf<T>(this IEnumerable<T> source, Func<T, bool> predicate) 
{ 
    if (source == null) throw new ArgumentNullException("source"); 
    if (predicate == null) throw new ArgumentNullException("predicate"); 

    var i = 0; 
    foreach (var item in source) 
    { 
     if (predicate(item)) return i; 
     i++; 
    } 

    return -1; 
} 
+0

謝謝,這對我幫助很大。 – Haney

+0

我認爲這個解決方案只適用於DefaultModelBinder – HamedH