2011-06-21 23 views
28

Dường như khi MVC xác nhận một mô hình mà nó chạy qua DataAnnotation thuộc tính (như yêu cầu, hoặc range) đầu tiên và nếu bất kỳ của những thất bại nó bỏ qua chạy phương pháp Validate trên mô hình IValidatableObject tôi.Làm thế nào để buộc MVC để Validate IValidatableObject

Có cách nào để MVC tiếp tục và chạy phương thức đó ngay cả khi xác thực khác không thành công?

+0

Thành thật mà nói, tôi bắt đầu thích hành vi mặc định này. Nếu bạn thực hiện xác thực cấp doanh nghiệp trong phương thức Xác thực của bạn có liên quan đến các công cụ đắt tiền như kết nối cơ sở dữ liệu, thì tốt hơn là KHÔNG gọi cho chúng trừ khi mô hình hợp lệ. – Graham

Trả lời

32

Bạn có thể tự gọi Validate() bằng cách đi qua trong một trường hợp mới của ValidationContext, như vậy:

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

Một caveat của phương pháp này là trong trường hợp không có tài sản cấp (DataAnnotation) lỗi , việc xác thực sẽ được chạy hai lần. Để tránh điều đó, bạn có thể thêm một thuộc tính vào mô hình của bạn, nói một boolean Validated, mà bạn đặt thành true trong phương thức Validate() của bạn khi nó chạy và sau đó kiểm tra trước khi gọi phương thức đó trong bộ điều khiển của bạn theo cách thủ công.

Vì vậy, trong điều khiển của bạn:

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

Và trong mô hình của bạn:

public bool Validated { get; set; } 

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

    Validated = true; 
} 
+0

Cảm ơn công việc của mình. Nhưng tôi có một câu hỏi.Đó là bất cứ lý do nào mà đối tượng Ivalidate cos đang bắn. Đôi khi nó có thể không? – user998405

26

Có một cách để làm điều đó mà không cần mã boilerplate ở phía trên cùng của mỗi hành động điều khiển.

Bạn sẽ cần phải thay thế mô hình mặc định chất kết dính với một trong những của riêng bạn:

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

cung cấp mô hình chất kết dính của bạn trông như thế này:

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

Bây giờ tạo ra một mô hình chất kết dính tùy chỉnh mà thực sự buộc xác nhận. Đây là nơi nâng hạng nặng được thực hiện:

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

Bạn cũng sẽ cần một phương pháp mở rộng IndexOf. Đây là triển khai giá rẻ nhưng sẽ hoạt động:

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

Cảm ơn, điều này đã giúp tôi rất nhiều. – Haney

+0

Tôi nghĩ giải pháp này chỉ hoạt động đối với DefaultModelBinder – HamedH

Các vấn đề liên quan