将验证属性从域实体映射到DTO

时间:2010-01-15 22:53:21

标签: asp.net-mvc validation domain-driven-design viewmodel dto

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

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之类的自动化工具将我的Domain实体映射到我的ViewModel DTO,那么以某种方式将映射属性的验证逻辑传递给ViewModel也不是很酷吗?

问题是:

1)这是个好主意吗?

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

提前感谢您的任何意见!

8 个答案:

答案 0 :(得分:11)

如果您正在使用支持DataAnnotations的内容,则应该能够使用元数据类来包含验证属性:

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

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

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

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

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

这不会与所有验证器一起开箱即用 - 您可能需要扩展您选择的验证框架以实现类似的方法。

答案 1 :(得分:9)

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

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

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

答案 2 :(得分:4)

事实证明,AutoMapper可以自动为我们这样做,这是最好的情况。

AutoMapper-users:将验证属性传递给viewmodel?
http://groups.google.com/group/automapper-users/browse_thread/thread/efa1d551e498311c/db4e7f6c93a77302?lnk=gst&q=validation#db4e7f6c93a77302

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

答案 3 :(得分:3)

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

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

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

答案 4 :(得分:1)

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

答案 5 :(得分: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这样的框架可以扩展为处理这个新属性并在依赖类的属性上运行验证属性,但是使用视图模型的属性值。我没有时间更多地充实它。

有什么想法吗?

答案 6 :(得分:0)

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

答案 7 :(得分:0)

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

在我的情况下,我想使错误消息在.NET视图和其他视图模型中可用。我们的实体几乎没有业务逻辑,主要针对数据存储。相反,我们有一个带有验证和业务逻辑的大型视图模型,我想重用错误消息。由于用户只了解错误消息,因此我认为这很重要,因为这对于轻松维护很重要。

我找不到从部分ViewModel中删除逻辑的可行方法,但是我找到了一种传递相同ErrorMessage的方法,以便可以从单个点进行维护。由于ErrorMessages绑定到视图,因此它也可以成为ViewModel的一部分。 const 被视为静态成员,因此将错误消息定义为公共字符串 const ant,我们可以在类外部访问它们。

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

在我们的项目中,我们将自定义html用于下拉列表,这样我们就无法在剃刀中使用@ Html.EditorFor helper,因此,我们不能使用不显眼的验证。有了错误消息,我们现在可以应用必要的属性:

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

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