C# 在控制器内使用视图模型时,如何将验证规则保留在实体内?

C# 在控制器内使用视图模型时,如何将验证规则保留在实体内?,c#,asp.net-mvc,entity-framework,validation,asp.net-mvc-5,C#,Asp.net Mvc,Entity Framework,Validation,Asp.net Mvc 5,因此,在我的Models目录中有一个实体: public class Event { public int Id { get; set; } [Required, MaxLength(50), MinLength(3)] public string Name { get; set; } [Required, MaxLength(2000)] public string Description { get; set; } } 我想使用viewModel

因此,在我的
Models
目录中有一个实体:

public class Event
{
    public int Id { get; set; }

    [Required, MaxLength(50), MinLength(3)]
    public string Name { get; set; }

    [Required, MaxLength(2000)]
    public string Description { get; set; }
}
我想使用viewModel将其公开到视图中:

public class BaseEventViewModel
{
    public string Name { get; set; }

    [DataType(DataType.MultilineText)]
    public string Description { get; set; }
}

public class EventCreateViewModel : BaseEventViewModel
{

}
我的推理是,我希望所有的数据验证都在实体上完成,所有的表示内容(比如渲染文本区域)都在视图模型上完成。然后,我可以使用任意多个视图模型来表示实体,同时保持数据完整性

因此,我更改了控制器以使用新的视图模型:

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create(EventCreateViewModel viewModel)
    {
        if (ModelState.IsValid)
        {
            db.Events.Add(new Event
            {
                Name = viewModel.Name,
                Description = viewModel.Description
            });
            db.SaveChanges();
            return RedirectToAction("Index");
        }

        return View(viewModel);
    }
但是,没有一个实体验证完成,我可以提交一个空白表单,它会引发
DbEntityValidationException
异常


这大概是因为
ModelState.IsValid
正在处理视图模型,而不是视图模型表示的实体。如何捕获这些验证错误?

在这种情况下,您的验证规则应该位于视图模型上。您所做的工作是验证用户输入,这就是您用来接收输入的内容


在写入数据库之前,可以对实体执行单独的验证步骤。在这种情况下,您将使用单独的验证机制来处理实体上的规则。

模型是通过操作方法参数传递的全部内容。抱歉-实现这一点的唯一方法是在
ViewModels
中添加数据注释

编辑:它可以在运行时附加反射(因此,
实体
验证中的更改可以在
视图模型
上自动“看到”)-但是这需要大量的工作。您必须从
DataAnnotationsModelValidatorProvider
继承,从appropiate
Entity
类属性中获取所有属性,并将它们添加到viewmodels。我认为最好的方法是为
Entity
Viewmodels
验证规则编写单元测试(将相同的属性附加到
Entity
ViewModel
中的字段中),以避免不同的验证程序错误


解决此类问题的第二个好方法(也是最快的方法)是使用
AOP
框架,如
PostSharp
。创建方面,如:
EntityNameValidatorAspect
(为具有正确属性值的属性添加数据注释)。然后在
实体
视图模型
中的name属性之前添加此方面(
[EntityNameValidatorAspect]
),依此类推。这类似于将重复的代码重构为方法——您只需将几个常见属性“重构”为一个属性。

我在正确的方向上得到了答案。如果将此注释添加到视图模型中,它将继承应用于实体上属性的所有注释:

[MetadataType(typeof(Event))]
public class BaseEventViewModel
{
    public int Id { get; set; }

    public string Name { get; set; }

    [DataType(DataType.MultilineText)]
    public string Description { get; set; }
}
现在,当我提交一个空白表单时,我会像往常一样看到验证错误


这确实需要在我的视图模型中重新定义每个属性,这有点违背了视图模型仅包含所需属性的观点,但是它适用于我的情况。

我一直在使用这个惊人的nuget,它使用AOP类似模式进行动态注释

您可以验证您梦寐以求的任何逻辑:

public string Email { get; set; }
public string Phone { get; set; }
[RequiredIf("Email != null")]
[RequiredIf("Phone != null")]
[AssertThat("AgreeToContact == true")]
public bool? AgreeToContact { get; set; }

我曾希望有一个更干净的解决方案,因为现在我将在多个视图模型中散布相同的数据注释。如果我换了一个,我必须记住把它们全部换掉/不一定。如果您没有对从ViewModel接收的数据执行任何操作,则没有理由再次验证。您只需验证您的程序是否要更改用户输入。@kingisac-view不应信任用户,controller不应信任view,database不应信任调用者。对每个步骤(甚至是数据库)进行验证是个好主意,不应该跳过它。@Kingisac:验证应该在所有层上进行,数据层可以被其他程序集使用,类似于批量导入或非web前端。
这需要注意的是,必须在我的视图模型中重新定义每个属性
您可以在视图模型中放置一个
事件
对象。这将解决您的两个问题,并且它将触发一些可怕的异常,例如ORM lazyloading@fex当然可以,但您始终可以根据传递的id查找新实体。真的不是问题。