MVC 3应用程序中的模型,ViewModel,DTO

时间:2011-05-13 16:42:26

标签: asp.net asp.net-mvc asp.net-mvc-3 architecture

我有一个带有两个子项目的网络解决方案(在VS2010中):

  1. Domain包含Model类(通过实体框架映射到数据库表)和Services(除了其他东西)负责CRUD操作

  2. WebUI引用了域项目

  3. 对于我创建的第一个页面,我在我的强类型视图中直接使用Domain项目中的Model类作为Model,因为类很小,我想显示和修改所有属性

    现在我有一个页面,它只适用于相应域模型的所有属性的一小部分。我通过在Service类中使用查询结果的投影来检索这些属性。但我需要投射到一个类型 - 这里有我能想到的解决方案的问题:

    1. 我介绍ViewModels项目中存在的WebUI,并将IQueryablesEF data context从服务公开给WebUI项目。然后我可以直接投射到那些ViewModels。

    2. 如果我不想公开IQueryables和EF数据上下文,我将ViewModel类放在Domain项目中,那么我可以直接返回ViewModels作为查询的结果和服务类的预测。

    3. 除了ViewModels项目中的WebUI之外,我还介绍了Data transfer objects,它将数据从服务类中的查询移动到ViewModels。< / p>

    4. 解决方案1和2看起来像是相同的工作量,我更倾向于选择解决方案2来将所有数据库问题保存在单独的项目中。但不知何故,在域项目中使用 View -Models听起来是错误的。

      解决方案3听起来更像是工作,因为我有更多的类可以创建并关心Model-DTO-ViewModel映射。我也不明白DTO和ViewModels之间会有什么区别。 ViewModels不是我想要显示的Model类的所选属性的集合吗?他们不会包含与DTO相同的成员吗?为什么我要区分ViewModels和DTO?

      这三种解决方案中的哪一种更为可取,有哪些优点和缺点?还有其他选择吗?

      提前感谢您的反馈!

      修改(因为我的文字可能太长了,并且被要求提供代码)

      示例:我有一个Customer实体...

      public class Customer
      {
          public int ID { get; set; }
          public string Name { get; set; }
          public City { get; set; }
          // ... and many more properties
      }
      

      ...并希望创建一个仅显示(并且可能允许编辑)列表中Name个客户的视图。在Service类中,我通过投影提取View所需的数据:

      public class CustomerService
      {
          public List<SomeClass1> GetCustomerNameList()
          {
              using (var dbContext = new MyDbContext())
              {
                  return dbContext.Customers
                      .Select(c => new SomeClass1
                                   {
                                       ID = c.ID,
                                       Name = c.Name
                                   })
                      .ToList();
              }
          }
      }
      

      然后有一个带有动作方法的CustomerController。这应该怎么样?

      这种方式(a)......

      public ActionResult Index()
      {
          List<SomeClass1> list = _service.GetCustomerNameList();
          return View(list);
      }
      

      ...或更好的方式(b):

      public ActionResult Index()
      {
          List<SomeClass1> list = _service.GetCustomerNameList();
      
          List<SomeClass2> newList = CreateNewList(list);
      
          return View(newList);
      }
      

      关于上面的选项3,我要说:SomeClass1(住在Domain项目中)是 DTO SomeClass2(住在{{} 1}} project)是 ViewModel

      我想知道区分这两个类是否合理。为什么我不总是为控制器动作选择选项(a)(因为它更容易)?是否有理由在 DTO WebUI)之外引入 ViewModel SomeClass2)?

3 个答案:

答案 0 :(得分:9)

我会通过使用自动映射工具(如AutoMapper)为您完成映射来解决您的问题。在映射很容易的情况下(例如,如果一个类的所有属性都应映射到另一个类上具有相同名称的属性),AutoMapper将能够为您完成所有连接工作,并且您必须给出几行代码,注意两者之间应该有一个映射。

这样,您可以将Domain中的实体和WebUI中的几个视图模型类放在某个位置(最好放在WebUI中,或者放在同一个子命名空间中) )定义它们之间的映射。您的视图模型实际上 DTO,但您不必担心域和DTO类之间的转换过程。

注意:我强烈建议强烈建议直接将您的域实体提供给MVC Web UI的视图。你不希望EF一直“粘贴”到前端层,以防你以后想要使用EF以外的东西。

答案 1 :(得分:6)

  

介绍住在里面的ViewModels   WebUI项目并公开IQueryables   来自的EF数据上下文   服务到WebUI项目。然后我   可以直接投射到那些   的ViewModels。

问题是你很快就会遇到使用EF试图“压扁”模型的问题。当我有一个CommentViewModel类时,我遇到了类似的东西:

public class CommentViewModel
{
    public string Content { get; set; }
    public string DateCreated { get; set; }
}

以下针对CommentViewModel的EF4查询预测无法用作couldn't translate the ToString() method into SQL

var comments = from c in DbSet where c.PostId == postId 
               select new CommentViewModel() 
               { 
                   Content = c.Content,
                   DateCreated = c.DateCreated.ToShortTimeString() 
               };

使用像Automapper这样的东西是一个不错的选择,特别是如果你要进行大量的转换。但是,您也可以创建自己的转换器,基本上将您的域模型转换为您的视图模型。在我的情况下,我创建了自己的扩展方法,将我的Comment域模型转换为我的CommentViewModel,如下所示:

public static class ViewModelConverters
{
    public static CommentViewModel ToCommentViewModel(this Comment comment)
    {
        return new CommentViewModel() 
        { 
            Content = comment.Content,
            DateCreated = comment.DateCreated.ToShortDateString() 
        };
    }

    public static IEnumerable<CommentViewModel> ToCommentViewModelList(this IEnumerable<Comment> comments)
    {
        List<CommentViewModel> commentModels = new List<CommentViewModel>(comments.Count());

        foreach (var c in comments)
        {
            commentModels.Add(c.ToCommentViewModel());
        }

        return commentModels;
    }
}

基本上我所做的是执行标准EF查询以恢复域模型,然后使用扩展方法将结果转换为视图模型。例如,以下方法说明了用法:

public Comment GetComment(int commentId)
{
    return CommentRepository.GetById(commentId);
}

public CommentViewModel GetCommentViewModel(int commentId)
{
    return CommentRepository.GetById(commentId).ToCommentViewModel();
}

public IEnumerable<Comment> GetCommentsForPost(int postId)
{
    return CommentRepository.GetCommentsForPost(postId);
}

public IEnumerable<CommentViewModel> GetCommentViewModelsForPost(int postId)
{
    return CommentRepository.GetCommentsForPost(postId).ToCommentViewModelList();
}

答案 2 :(得分:1)

谈论模型,ViewModels和DTO令人困惑,我个人不喜欢使用这些术语。我更喜欢谈论域实体域名服务操作输入/结果(又称DTO)。所有这些类型都存在于Domain层中。操作是实体和服务的行为。除非您正在构建纯CRUD应用程序,否则表示层仅处理输入/结果类型,而不是实体。您不需要其他ViewModel类型,这些是ViewModel(换句话说,View的模型)。 View用于将“操作结果”转换为HTML,但相同的Result可以序列化为XML或JSON。您用作ViewModel的是域的一部分,而不是表示层。