添加ValidationContext项目的MVC和EF验证 [英] MVC and EF Validation with added ValidationContext item

查看:128
本文介绍了添加ValidationContext项目的MVC和EF验证的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个场景,我想在ValidationContext中添加一个项目,并在EF触发的实体验证中检查它。我在一个向导中这样做,所以我只能在特定的步骤验证某些事情。 (如果有一个很好的模式,请分享它)。



问题是在控制器动作甚至被触发之前,验证被触发,实际上是两次。我希望我明白为什么。我不知道在发生之前如何获取ValidationContext中的项目,所以我无法确定我的步骤。



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



在我的自定义上下文中:

  public WizardStep Step {get;组; 

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

设置实体的服务:

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

在我的实体中

  public IEnumerable< ValidationResult>验证(ValidationContext validationContext)
{
//从保存更改中调用时,步骤只会出现。来自模型状态验证的调用将不会有
if(validationContext.Items.ContainsKey(ValidationStep))
{
var validationStep =(WizardStep)validationContext.Items [ValidationStep] ;
if(validationStep == WizardStep.Introduction)
{
if(criteria)
{
yield返回新的ValidationResult($错误消息,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);
}


解决方案

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



理论上应该有一个自定义的MVC ModelValidator和拦截Validate方法设置ValidationContext项。但是,我不知道如何做到这一点。我找到了一个稍微不同的解决方案,适用于我。也许它可以适应你的需要。



在我的情况下,我想要EF DbContext(在我的代码中,它的命名为CmsEntities)可用于验证方法,所以我可以查询数据库(并做复杂的业务逻辑验证)。控制器具有DbContext,但模型验证在将其传递给控制器​​的操作之前由ModelBinder调用。



我的解决方案是:



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



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



3)在Application_Start()中注册Custom ModelBinder



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



自定义ModelBinder

  public class CmsModelBinder:DefaultModelBinder 
{
protected override bool OnModelUpdating(ControllerContext controllerContext,ModelBindingContext bindingContext)
{
//将CmsEntities从Controller复制到模型(在我们更新和验证模型之前)
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();
}


I have a scenario where I'd like to add an item to the ValidationContext and check for it in the EF triggered entity validation. I'm doing this in a wizard so I can only validate certain things on specific steps. (If there's a good pattern for that please do share it).

The problem is that the validation is triggered, twice actually, before the controller action is even hit. I wish I understood why. I'm not sure how to get the item in ValidationContext before that happens, so I can't tell the validation what step I'm on.

Furthermore, if I only do the custom validation when save changes is triggered by checking for the item as I have in my code below, then I get no automatic model validation errors displayed when the page refreshes.

In my custom context:

public WizardStep Step { get; set; }

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

Service that sets the entity:

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

In my entity

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

Controller:

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

解决方案

The first validation is on the MVC created model that is passed to the controller. MVC uses a ModelBinder class to construct, populate and validate the client http form data into the model. Any failed validation will be returned to the client. A valid model may then be changed by the controller, so a second validation is done by EF when saved. I believe when saved, EF validation is only triggered if the property is new or has different data the original value.

Theoretically it should be possible to have a custom MVC ModelValidator and intercept the Validate method to set ValidationContext items. However, I could NOT figure out how to do that. I did however find a slightly different solution that works for me. Perhaps it can be adapted to fit your needs.

In my case, I wanted the EF DbContext (In my code its named CmsEntities) available to the Validation methods so I can querying the database (and do rich complex business logic validation). The controller has the DbContext, but the model validation is called by the ModelBinder before passing it to the controller’s action.

My solution is to:

1) Add a DbContext property to my Entity (Using partial class, or in Base Entity that all entities inherit from)

2) Create a Custom ModelBinder that will get the DbContext from the controller and populate it to the model

3) Register the Custom ModelBinder in the Application_Start()

Now, inside any validation method, the model will have a populated DbContext. 

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

这篇关于添加ValidationContext项目的MVC和EF验证的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆