添加了ValidationContext项的MVC和EFvalidation

我有一个场景,我想在ValidationContext中添加一个项目,并在EF触发实体validation中检查它。 我在向导中执行此操作,因此我只能在特定步骤上validation某些内容。 (如果有一个好的模式,请分享它)。

问题是在控制器操作被击中之前,实际上会触发两次validation。 我希望我理解为什么。 在发生这种情况之前我不确定如何在ValidationContext中获取该项,因此我无法告诉validation我正在进行哪一步。

此外,如果我通过在下面的代码中检查项目来触发保存更改时仅执行自定义validation,那么当页面刷新时,我不会显示自动模型validation错误。

在我的自定义环境中:

public WizardStep Step { get; set; } protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary items) { items.Add("ValidationStep", Step); return base.ValidateEntity(entityEntry, items); } 

设置实体的服务:

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

在我的实体中

 public IEnumerable 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" }); } } } } 

控制器:

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

第一个validation是在MVC创建的模型上传递给控制器​​。 MVC使用ModelBinder类来构造,填充和validation客户端http表单数据到模型中。 任何失败的validation都将返回给客户端。 然后,控制器可以更改有效模型,因此在保存时由EF完成第二次validation。 我相信在保存时,只有当属性是新属性或者具有与原始值不同的数据时才会触发EFvalidation。

从理论上讲,应该可以使用自定义MVC ModelValidator并拦截Validate方法来设置ValidationContext项。 但是,我无法弄清楚如何做到这一点。 然而,我找到了一个对我有用的略有不同的解决方案。 也许它可以适应您的需求。

在我的例子中,我希望validation方法可以使用EF DbContext(在我的代码中命名为CmsEntities),这样我就可以查询数据库(并进行丰富的复杂业务逻辑validation)。 控制器具有DbContext,但模型validation在将其传递给控制器​​的操作之前由ModelBinder调用。

我的解决方案是:

1)向我的实体添加DbContext属性(使用部分类,或在所有实体inheritance的基本实体中)

2)创建一个Custom ModelBinder,它将从控制器获取DbContext并将其填充到模型中

3)在Application_Start()中注册Custom ModelBinder

现在,在任何validation方法中,模型将具有填充的DbContext。 

自定义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(); } 

首先,您应该考虑WizardStep是否属于上下文或在单独步骤中修改的对象? 其他的是为什么不使用ie。 在不同步骤处理validation逻辑的策略?

关于validation,我看到你混合了两件事。

一个是对上下文的validation,您应该在上下文中处理每个实体类型的validation逻辑。

另一个是IValidatableObject.Validate的实现,应该在SaveChanges上为实体自动调用。

我会决定并选择一种方法,从你给我们的信息来看,我认为只有IValidatableObject.Validate更有意义,但是你必须要将步骤放入正在validation的实体中,或者以某种方式注入该步骤仅用于validation。

你可以这样做:

 try { //write code } catch (System.Data.Entity.Validation.DbEntityValidationException ex) { var outputLines = new List(); 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); } 

只需分享我的mvcvalidation解决方案:

 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("
", 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 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" }); } } } } }