33

我有一个标准的域层实体:

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

    public string Name { get; set; }

    public decimal Price { get; set;}
}

它应用了某种验证属性:

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

    [NotEmpty, NotShorterThan10Characters, NotLongerThan100Characters]
    public string Name { get; set; }

    [NotLessThan0]
    public decimal Price { get; set;}
}

如您所见,我已经完全弥补了这些属性。这里使用哪个验证框架(NHibernate Validator、DataAnnotations、ValidationApplicationBlock、Castle Validator 等)并不重要。

在我的客户端层中,我还有一个标准设置,我不使用域实体本身,而是将它们映射到我的视图层使用的 ViewModels(又名 DTO):

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

    public string Name { get; set; }

    public decimal Price { get; set;}
}

然后假设我希望我的客户端/视图能够执行一些基本的属性级验证。

我看到我能做到这一点的唯一方法是在 ViewModel 对象中重复验证定义:

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

    // validation attributes copied from Domain entity
    [NotEmpty, NotShorterThan10Characters, NotLongerThan100Characters]
    public string Name { get; set; }

    // validation attributes copied from Domain entity
    [NotLessThan0]
    public decimal Price { get; set;}
}

这显然不能令人满意,因为我现在在 ViewModel (DTO) 层中重复了业务逻辑(属性级验证)。

那么可以做些什么呢?

假设我使用像 AutoMapper 这样的自动化工具将我的域实体映射到我的 ViewModel DTO,那么以某种方式将映射属性的验证逻辑也传输到 ViewModel 不是很酷吗?

问题是:

1)这是个好主意吗?

2)如果可以,可以吗?如果没有,有什么替代方案(如果有的话)?

提前感谢您的任何意见!

4

8 回答 8

11

如果您使用支持 DataAnnotations 的东西,您应该能够使用元数据类来包含您的验证属性:

public class ProductMetadata 
{
    [NotEmpty, NotShorterThan10Characters, NotLongerThan100Characters]
    public string Name { get; set; }

    [NotLessThan0]
    public decimal Price { get; set;}
}

并将其添加到域实体和 DTO 的 MetadataTypeAttribute 中:

[MetadataType(typeof(ProductMetadata))]
public class Product

[MetadataType(typeof(ProductMetadata))]
public class ProductViewModel

这不适用于所有验证器 - 您可能需要扩展您选择的验证框架以实现类似的方法。

于 2010-02-11T08:31:34.047 回答
9

验证的目的是确保进入您的应用程序的数据符合某些标准,考虑到这一点,验证属性约束(如您在此处确定的那些)唯一有意义的地方是您接受来自不受信任的来源(即用户)。

您可以使用“金钱模式”之类的东西将验证提升到您的域类型系统中,并在有意义的视图模型中使用这些域类型。如果您有更复杂的验证(即,您正在表达的业务规则需要比单个属性中表达的知识更多的知识),这些都属于应用更改的域模型上的方法。

简而言之,将数据验证属性放在您的视图模型上,并将它们从您的域模型中移除。

于 2010-02-10T00:43:47.493 回答
4

为什么不使用界面来表达您的意图?例如:

public interface IProductValidationAttributes {
    [NotEmpty, NotShorterThan10Characters, NotLongerThan100Characters]
    string Name { get; set; }

    [NotLessThan0]
    decimal Price { get; set;}
}
于 2010-01-16T01:52:17.293 回答
4

事实证明,AutoMapper 或许能够自动为我们做这件事,这是最好的情况。

AutoMapper-users:将验证属性转移到视图模型?
http://groups.google.com/group/automapper-users/browse_thread/thread/efa1d551e498311c/db4e7f6c93a77302?lnk=gst&q=validation#db4e7f6c93a77302

我还没有开始尝试那里提出的解决方案,但打算很快尝试。

于 2010-02-20T11:06:23.087 回答
1

如果您使用手写的域实体,为什么不将您的域实体放在它们自己的程序集中并在客户端和服务器上使用相同的程序集。您可以重复使用相同的验证。

于 2010-02-16T15:55:42.120 回答
1

我也考虑了一段时间。我完全理解布拉德的回答。但是,假设我想使用另一个适用于注释域实体和视图模型的验证框架。

我可以在纸上提出的唯一仍然适用于属性的解决方案是创建另一个“指向”您在视图模型中镜像的域实体属性的属性。这是一个例子:

// In UI as a view model.
public class UserRegistration {
  [ValidationDependency<Person>(x => x.FirstName)]
  public string FirstName { get; set; }

  [ValidationDependency<Person>(x => x.LastName)]
  public string LastName { get; set; }

  [ValidationDependency<Membership>(x => x.Username)]
  public string Username { get; set; }

  [ValidationDependency<Membership>(x => x.Password)]
  public string Password { get; set; }
}

可以扩展像 xVal 这样的框架来处理这个新属性并在依赖类的属性上运行验证属性,但使用您的视图模型的属性值。我只是没有时间进一步充实这一点。

有什么想法吗?

于 2010-02-17T23:45:01.923 回答
0

首先,没有“标准”域实体的概念。对我来说,标准域实体没有任何设置器。如果您采用这种方法,您可以拥有更有意义的 api,它实际上传达了有关您的域的某些信息。因此,您可以拥有处理 DTO 的应用程序服务,创建可以直接针对域对象执行的命令,例如 SetContactInfo、ChangePrice 等。其中的每一个都可以引发 ValidationException,而您可以在服务中收集并呈现给用户。您仍然可以将属性保留在 dto 的属性上,以进行简单的属性/属性级别验证。对于其他任何事情,请咨询您的域。即使这是 CRUD 应用程序,我也会避免将我的域实体暴露给表示层。

于 2010-01-25T13:57:27.027 回答
0

免责声明:我知道这是一个古老的讨论,但它最接近我正在寻找的内容:通过重用验证属性保持 DRY。我希望它离最初的问题不会太远。

在我的情况下,我想让错误消息在 .NET 视图和其他视图模型中可用。我们的实体几乎没有业务逻辑,主要针对数据存储。相反,如果我想重用错误消息,我们有一个带有验证和业务逻辑的大型视图模型。由于用户只关心错误消息,我发现这是相关的,因为这是易于维护的重要内容。

I could not find a feasible way to remove logic from the partial ViewModels, but I found a way to convey the same ErrorMessage, such that it can be maintained from a single point. Since ErrorMessages are tied to the view, it can just as well be part of the ViewModel. Consts are considered static members, so defining the error messages as public string constants, we can access them outside the class.

public class LargeViewModel
{
    public const string TopicIdsErrorMessage = "My error message";

    [Required(ErrorMessage = TopicIdsErrorMessage)]
    [MinimumCount(1, ErrorMessage = TopicIdsErrorMessage)]
    [WithValidIndex(ErrorMessage = TopicIdsErrorMessage)]
    public List<int> TopicIds { get; set; }
}

public class PartialViewModel
{
    [Required(ErrorMessage = LargeViewModel.TopicIdsErrorMessage]
    public List<int> TopicIds { get; set; }
}

In our project we were using custom html for dropdownlists, such that we could not use @Html.EditorFor helper in razor, thus we could not use unobtrusive validation. With the error message availible we could now apply the necessary attributes:

    @(Html.Kendo().DropDownList()
        .Name("TopicIds")
        .HtmlAttributes(new {
            @class = "form-control",
            data_val = "true",
            data_val_required = SupervisionViewModel.TopicIdsErrorMessage
        })
    )

Warning: You might need to recompile all related projects that rely on const values...

于 2019-06-12T07:50:01.433 回答