2011-06-21 15 views
28

Wygląda na to, że gdy MVC sprawdza poprawność Modelu, który uruchamia za pośrednictwem atrybutów DataAnnotation (jak wymagane lub zakres), a jeśli którykolwiek z nich zawodzi, pomija metodę Validate w moim modelu IValidatableObject.Jak zmusić MVC do walidacji IValidatableObject

Czy istnieje sposób, aby uruchomić MVC i uruchomić tę metodę, nawet jeśli druga weryfikacja nie powiedzie się?

+0

Szczerze, zaczynam lubić to domyślne zachowanie. W przypadku sprawdzania poprawności na poziomie biznesowym w metodzie sprawdzania poprawności, która wiąże się z kosztownymi materiałami, takimi jak połączenia z bazami danych, lepiej jest je wywoływać, chyba że model jest prawidłowy. – Graham

Odpowiedz

32

Można ręcznie wywołać Sprawdź poprawność(), przekazując w nowej instancji ValidationContext, tak:

[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); 
    } 
} 

zastrzeżenie tego podejścia jest to, że w przypadkach, gdzie nie ma właściwość poziomu (DataAnnotation) błędy walidacja zostanie uruchomiona dwukrotnie. Aby tego uniknąć, możesz dodać właściwość do swojego modelu, powiedzmy wartość logiczną sprawdzoną, która jest ustawiana na wartość true w metodzie Validate() po uruchomieniu, a następnie sprawdź przed ręcznym wywołaniem metody w kontrolerze.

Więc w kontrolerze:

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); 
} 

A w modelu:

public bool Validated { get; set; } 

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

    Validated = true; 
} 
+0

Dzięki jego pracy. Ale mam jedno pytanie. Czy jest to powód, dla którego uruchamiany jest obiekt Ivalidate Object? Kiedyś może nie? – user998405

26

Istnieje sposób to zrobić bez konieczności kod szablonowe na górze każdej akcji kontrolera.

Musisz wymienić spoiwo domyślnego modelu z jednym z własną rękę:

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

Operator modelu spoiwo wygląda następująco:

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

Teraz stworzenie własnego modelu spoiwo, które faktycznie wymusza walidację. Tutaj wykonano ciężki lifting:

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); 
      } 
     } 
    } 
} 

Potrzebna jest również metoda rozszerzenia IndexOf. To jest tania implementacja, ale zadziała:

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

Dzięki, bardzo mi to pomogło. – Haney

+0

Myślę, że to rozwiązanie działa tylko dla DefaultModelBinder – HamedH

Powiązane problemy