使用添加的ValidationContext项进行MVC和EF验证

Ale*_*lex 9 c# validation asp.net-mvc entity-framework

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

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

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

在我的自定义环境中:

public WizardStep Step { get; set; }

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

设置实体的服务:

public void SaveChanges(WizardStep step)
{
    _context.Step = step;
    _context.SaveChanges();
}
Run Code Online (Sandbox Code Playgroud)

在我的实体

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" });
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

控制器:

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

Rit*_*ieD 3

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

\n\n

理论上,应该可以有一个自定义的 MVC ModelValidator 并拦截 Validate 方法来设置 ValidationContext 项。但是,我不知道该怎么做。然而,我确实找到了一个稍微不同的适合我的解决方案。也许可以对其进行调整以满足您的需求。

\n\n

就我而言,我希望 EF DbContext(在我的代码中名为 CmsEntities)可用于验证方法,以便我可以查询数据库(并执行丰富的复杂业务逻辑验证)。控制器具有 DbContext,但模型验证由 ModelBinder 在将其传递给controller\xe2\x80\x99s 操作之前调用。

\n\n

我的解决方案是:

\n\n

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

\n\n

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

\n\n

3)在Application_Start()中注册自定义ModelBinder

\n\n

现在,在任何验证方法内,模型都将具有填充的 DbContext。\xef\x81\x8a

\n\n

自定义模型绑定器

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

全局.asax.cs

\n\n
    protected void Application_Start()\n    {\n        ...\n        ModelBinders.Binders.DefaultBinder = new CmsModelBinder();\n    }\n
Run Code Online (Sandbox Code Playgroud)\n