2017-03-13 15 views
8

Tôi có một kịch bản mà tôi muốn thêm một mục vào ValidationContext và kiểm tra nó trong xác thực thực thể được kích hoạt EF. Tôi đang làm điều này trong trình hướng dẫn để tôi chỉ có thể xác thực một số thứ nhất định trên các bước cụ thể. (Nếu có một mô hình tốt cho điều đó xin vui lòng chia sẻ nó).Xác thực MVC và EF với mục ValidationContext được thêm

Vấn đề là xác thực được kích hoạt, hai lần thực sự, trước khi hành động bộ điều khiển thậm chí còn bị trúng. Tôi ước tôi hiểu tại sao. Tôi không chắc chắn làm thế nào để có được các mục trong ValidationContext trước khi điều đó xảy ra, vì vậy tôi không thể nói cho xác nhận những gì bước tôi đang ở trên. Ngoài ra, nếu tôi chỉ thực hiện xác thực tùy chỉnh khi lưu thay đổi được kích hoạt bằng cách kiểm tra mục như tôi có trong mã bên dưới, thì tôi sẽ không nhận được lỗi xác thực mô hình tự động nào khi trang làm mới.

Trong bối cảnh tùy chỉnh của tôi:

public WizardStep Step { get; set; } 

protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items) 
{ 
    items.Add("ValidationStep", Step); 
    return base.ValidateEntity(entityEntry, items); 
} 

Dịch vụ mà bộ thực thể:

public void SaveChanges(WizardStep step) 
{ 
    _context.Step = step; 
    _context.SaveChanges(); 
} 

Trong thực thể của tôi

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) 
{ 
    // Step will only be present when called from save changes. Calls from model state validation won't have it 
    if (validationContext.Items.ContainsKey("ValidationStep")) 
    { 
     var validationStep = (WizardStep)validationContext.Items["ValidationStep"]; 
     if (validationStep == WizardStep.Introduction) 
     { 
      if (criteria) 
      { 
       yield return new ValidationResult($"Error message ", new[] { "field" }); 
      } 
     } 
    } 
} 

Bộ điều khiển:

public ActionResult MyAction(HomeViewModel vm) 
{ 
    try 
    { 
     _incidentService.AddOrUpdate(vm.Enttiy); 
     _incidentService.SaveChanges(WizardStep.Introduction); 
    } 
    catch (Exception ex) 
    { 
     return View(vm); 
    } 
    return RedirectToAction("Index"); 
} 
+1

bạn đang cố gắng để làm quy tắc kinh doanh xác nhận hoặc toàn vẹn dữ liệu kiểm tra như ràng buộc duy nhất và ngoài nước hạn chế chính? Nếu bạn đang cố gắng làm điều trước đây tôi khá chắc chắn rằng một số lib bên ngoài như [FluentValidation] (https://github.com/JeremySkinner/FluentValidation) phù hợp hơn với nhu cầu của bạn. – cleftheris

+0

@cleftheris Đó là một chút của cả hai thực sự. Tôi có mã máy phát poco đảo ngược có các thuộc tính toàn vẹn dữ liệu đã có, trên các thực thể. Sau đó, tôi có các quy tắc nghiệp vụ phức tạp hơn mà tôi đang thêm thủ công vào phương thức Validate trên các thực thể và tôi cần phải nhấn vào cơ sở dữ liệu để kiểm tra văn bản lỗi (người dùng được cấu hình). Kiểm tra db bổ sung đó là lý do tại sao tôi cần bối cảnh trong validationcontext. – Alex

+1

Khi tôi hiểu tình hình, tôi nghĩ bạn nên đảo ngược cách tiếp cận của bạn: Sử dụng khung xác thực phổ biến cho logic nghiệp vụ phức tạp, trong đó một số quy tắc có thể truy vấn cơ sở dữ liệu nội bộ. – cleftheris

Trả lời

1

Xác thực đầu tiên là trên mô hình được tạo MVC được truyền cho bộ điều khiển. MVC sử dụng một lớp ModelBinder để xây dựng, điền và xác thực dữ liệu biểu mẫu http khách hàng vào mô hình. Bất kỳ xác nhận không thành công sẽ được trả lại cho khách hàng. Một mô hình hợp lệ sau đó có thể được thay đổi bởi bộ điều khiển, do đó, việc xác nhận hợp lệ thứ hai được thực hiện bởi EF khi được lưu. Tôi tin rằng khi được lưu, xác thực EF chỉ được kích hoạt nếu thuộc tính mới hoặc có dữ liệu khác với giá trị ban đầu.

Về mặt lý thuyết, có thể có một mẫu MVC ModelValidator tùy chỉnh và chặn phương thức Xác thực để đặt mục ValidationContext. Tuy nhiên, tôi không thể tìm ra cách để làm điều đó. Tuy nhiên tôi đã tìm thấy một giải pháp hơi khác nhau mà làm việc cho tôi. Có lẽ nó có thể được điều chỉnh để phù hợp với nhu cầu của bạn.

Trong trường hợp của tôi, tôi muốn EF DbContext (Trong mã của tôi tên là CmsEntities) có sẵn cho các phương thức xác thực để tôi có thể truy vấn cơ sở dữ liệu (và thực hiện xác thực logic nghiệp vụ phức tạp). Bộ điều khiển có DbContext, nhưng việc xác thực mô hình được gọi bởi ModelBinder trước khi chuyển nó tới hành động của bộ điều khiển.

giải pháp của tôi là:

1) Thêm một tài sản DbContext để Entity tôi (Sử dụng lớp học phần, hoặc trong cơ sở Entity rằng tất cả các đơn vị kế thừa từ)

2) Tạo một ModelBinder Tuỳ rằng sẽ nhận được các DbContext từ bộ điều khiển và cư nó vào mô hình

3) đăng ký ModelBinder Tuỳ chỉnh trong Application_Start()

Bây giờ, bên trong bất kỳ phương pháp xác nhận, mô hình sẽ có một DbContext dân cư. 

Tuỳ chỉnh ModelBinder

public class CmsModelBinder : DefaultModelBinder 
{ 
    protected override bool OnModelUpdating(ControllerContext controllerContext, ModelBindingContext bindingContext) 
    { 
     // Copy CmsEntities from Controller to the Model (Before we update and validate the model) 
     var modelPropertyInfo = bindingContext.Model.GetType().GetProperty("CmsEntities"); 
     if (modelPropertyInfo != null) 
     { 
      var controllerPropertyInfo = controllerContext.Controller.GetType().GetProperty("CmsEntities"); 
      if (controllerPropertyInfo != null) 
      { 
       CmsEntities cmsEntities = controllerPropertyInfo.GetValue(controllerContext.Controller) as CmsEntities; 
       modelPropertyInfo.SetValue(bindingContext.Model, cmsEntities); 
      } 
     }    
     return base.OnModelUpdating(controllerContext, bindingContext); 
    } 

Global.asax.cs

protected void Application_Start() 
    { 
     ... 
     ModelBinders.Binders.DefaultBinder = new CmsModelBinder(); 
    } 
+0

Tôi không điên về việc có bối cảnh là một tài sản trên các thực thể, nhưng đây là câu trả lời duy nhất đã giải quyết câu hỏi cho đến nay, cảm ơn bạn! – Alex

0

Trước hết bạn nên xem xét nếu WizardStep không thuộc ngữ cảnh hoặc đối tượng được sửa đổi trong các bước riêng biệt? Điều khác là tại sao không sử dụng tức là. Chiến lược xử lý logic xác thực trên các bước riêng biệt?

Về xác thực, tôi thấy bạn đang trộn 2 thứ.

Một là xác thực trên ngữ cảnh, nơi bạn nên xử lý logic xác thực cho mọi loại thực thể bạn có trong ngữ cảnh.

Cách khác là triển khai IValidatableObject.Validate sẽ được gọi tự động cho thực thể trên SaveChanges.

Tôi sẽ quyết định và chọn một cách để đi và từ thông tin bạn đã cung cấp cho chúng tôi Tôi nghĩ chỉ có IValidatableObject.Validate có ý nghĩa hơn, nhưng sau đó bạn phải đặt bước vào thực thể đang được xác thực hoặc bằng cách nào đó chỉ để xác thực.

0

Bạn có thể làm điều này bằng cách này:

try 
{ 
    //write code 

} 
catch (System.Data.Entity.Validation.DbEntityValidationException ex) 
      { 
       var outputLines = new List<string>(); 
       foreach (var eve in ex.EntityValidationErrors) 
       { 
        outputLines.Add(string.Format(
         "{0}: Entity of type \"{1}\" in state \"{2}\" has the following validation errors:", 
         DateTime.Now, eve.Entry.Entity.GetType().Name, eve.Entry.State)); 
        foreach (var ve in eve.ValidationErrors) 
        { 
         outputLines.Add(string.Format(
          "- Property: \"{0}\", Error: \"{1}\"", 
          ve.PropertyName, ve.ErrorMessage)); 
        } 
       } 
       System.IO.File.AppendAllLines(@"c:\temp\errors.txt", outputLines); 
      } 
0

Chỉ cần chia sẻ giải pháp của tôi cho Validate MVC:

public class TestController:Controller 
{ 
    public ActionResult Action1(MyModel data) 
    { 
     try 
     { 
      if (!ModelState.IsValid) 
      { 
       var errors = ModelState.Values.Where(c => c.Errors.Count > 0).SelectMany(c => c.Errors.Select(o => o.ErrorMessage)); 
       var errorMsg = String.Join("<br/>", errors); 
       return Json(new 
       { 
        IsSuccess = false, 
        Message = errorMsg 
       }); 
      } 
      //deal business 
      return Json(new { IsSuccess = true, Message = "success" }); 
     } 
     catch (Exception ex) 
     { 
      return Json(new { IsSuccess = false, Message = "fail" }); 
     } 
    } 
} 
public class MyModel : IValidatableObject 
{ 
    [Required(ErrorMessage = "{0} is required")] 
    public decimal TotalPrice { get; set; } 
    [Required(ErrorMessage = "{0} is required")] 
    public decimal TotalPriceWithoutCoupon { get; set; } 
    public ContactStruct Contact { get; set; } 
    public bool Condition{ get; set; } 

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) 
    { 
     var instance = validationContext.ObjectInstance as MyModel; 
     if (instance == null) 
     { 
      yield break; 
     } 
     if (instance.Condition) 
     { 
      if (instance.Contact == null) 
      { 
       yield return new ValidationResult("contact is required", new string[] { "Contact" }); 
      } 
      else 
      { 
       if (string.IsNullOrEmpty(instance.Contact.phone)) 
       { 
        yield return new ValidationResult("the phone of contact is required", new string[] { "Contact.phone" }); 
       } 
      } 
     } 
    } 
} 
Các vấn đề liên quan