遵循视图模型模式的最佳实践

时间:2011-11-07 07:53:46

标签: asp.net-mvc

我现在正在学习ASP.NET MVC一段时间了。我遵循在互联网或书籍中找到的一些指导原则,我希望确保我遵循关于视图模型模式的开发中的良好实践。以下是博客实施的简单示例。能否请你确认我是在正确的方式。假设我想在视图上显示帖子标题+描述,并在此帖子上显示评论。所以我有一个关于它的2个部分视图的视图。

在我的控制器中:

    public ActionResult DetailPost(int postID)
    {
        // retrieve infos
        Post postModel = repository.Posts.FirstOrDefault(p => p.PostID == postID);
        IEnumerable<Comments> commentsModel = postModel.Comments;

        // prepare view model
        DetailPostViewModel detailPostViewModel = new DetailPostViewModel
                                                        {
                                                            Post = postModel,
                                                            Comments = commentsModel,
                                                        };

        return View(detailPostViewModel);
    }

所以在这里,我会看到一个由两件事组成的视图模型:

  • 帖子信息
  • 评论的枚举。

视图模型将传递给视图以跟随视图模型模式。

在我的详情视图中

@model WebUI.ViewModels.DetailPostViewModel

@{
    ViewBag.Title = "Detail";
}

<h2>Detail</h2>

@Html.Partial("_DetailPost", Model.Post)
@Html.Partial("_Comments",  Model.Comments)

所以在这里,我使用了我的DetailPostViewModel。对于我的部分观点,我传递了必要的信息,即实体!?我不知道这是正确的方式,还是我必须在这里通过视图模型呢?

在我的_DetailPost视图中

@model Domain.Entities.Post

@Html.DisplayForModel()
...

所以在我的部分视图中,我使用en实体作为模型。是好还是坏?

在我的_Comments视图中

@model IEnumerable<Domain.Entities.Comment>

@foreach (var item in Model)
{
    @Html.DisplayFor(Model => item.Title)
    @Html.DisplayFor(Model => item.Username)
}

所以在我的部分视图中,我使用en实体作为模型。是好还是坏?

感谢您的建议/建议。

修改

我尝试在Automapper的帮助下实现我的视图模型模式,但是我遇到了一些问题,如下所述。我理解将一个对象映射到另一个对象的原理。在基本情况下,它运作良好。现在,让我们看一个更复杂的情况:我有一个由一些属性(id,title,...)组成的帖子,每个帖子可以附加一些注释(对象),每个注释只能附加一个用户(对象) 。我希望得到一个包含我所有帖子的视图模型,并获取所有评论,并为每个评论获得用户附加。

以下是域名实体:

public class Post
{
    public int    PostID    { get; set; }
    public string Title     { get; set; }
    ...
    public virtual ICollection<Comment> Comments { get; set; }
}

public class Comment
{
    public int    CommentID   { get; set; }
    public string CommentText { get; set; }
    ...
    public virtual <User> User { get; set; }
}

public class User
{
    public int UserID      { get; set; }
    public string Username { get; set; }
    ...
}

以下是视图模型:

public class PostVM
{
    public int    PostID    { get; set; }
    public string Title     { get; set; }
    ...
    public virtual ICollection<CommentVM> CommentsVM { get; set; }
}

public class CommentVM
{
    public int    CommentID   { get; set; }
    public string CommentText { get; set; }
    ...
    public virtual <UserVM> UserVM { get; set; }
}

public class UserVM
{
    public int UserID      { get; set; }
    public string Username { get; set; }
    ...
}

这是我的控制器:

// I would like to list all posts in the DB.
public ViewResult Index()
{
    PostVM viewModel = new PostVM 
                           {
                               ...
                           }

    return View(viewModel);
}

如何将我的(实体)帖子对象列表映射到我的视图模型对象和所有子对象(注释,用户)?

谢谢!

2 个答案:

答案 0 :(得分:5)

你很接近,但对我而言,这不是视图模型的正确用法。您有以下内容:

public class DetailPostViewModel
{
    public Post Post { get; set; }
    public IEnumerable<Comment> Comments { get; set; }
}

这是某种混合伪视图模型。真实视图模型不应该引用任何域模型。

更正确的例子如下:

public class DetailPostViewModel
{
    public PostViewModel Post { get; set; }
    public IEnumerable<CommentViewModel> Comments { get; set; }
}

当然PostViewModel和CommentViewModel是各自域实体的相应视图模型。

我也会使用显示模板代替部分:

@model WebUI.ViewModels.DetailPostViewModel

@{
    ViewBag.Title = "Detail";
}

<h2>Detail</h2>

@Html.DisplayFor(x => x.Post)
@Html.DisplayFor(x => x.Comments)

然后您将拥有相应的显示模板(~/Views/Shared/DisplayTemplates/PostViewModel.cshtml):

@model WebUI.ViewModels.PostViewModel
... some properties of a post

和(~/Views/Shared/DisplayTemplates/CommentViewModel.cshtml):

@model WebUI.ViewModels.CommentViewModel
@Html.DisplayFor(x => x.Title)
@Html.DisplayFor(x => x.Username)

显然,由于Comments是一个枚举,因此将自动为集合的每个元素呈现显示模板。这样您就不需要在视图中编写任何循环。

就控制器逻辑而言,我个人喜欢使用AutoMapper在域实体和视图模型之间进行映射。

所以看起来像这样:

public ActionResult DetailPost(int postID)
{
    // retrieve infos
    var postModel = repository.Posts.FirstOrDefault(p => p.PostID == postID);

    var viewModel = new DetailPostViewModel
    {
        Post = Mapper.Map<Post, PostViewModel>(postModel),
        Comments = Mapper.Map<IEnumerable<Comment>, IEnumerable<CommentViewModel>>(postModel.Comments)
    };
    return View(viewModel);
}

甚至更好:

public class PostViewModel
{
    ... some properties of a Post
    public IEnumerable<CommentViewModel> Comments { get; set; }
}

然后:

public ActionResult DetailPost(int postID)
{
    // retrieve infos
    var postModel = repository.Posts.FirstOrDefault(p => p.PostID == postID);
    var viewModel = Mapper.Map<Post, PostViewModel>(postModel);
    return View(viewModel);
}

然后您的视图将强烈输入PostViewModel:

@model WebUI.ViewModels.PostViewModel

@{
    ViewBag.Title = "Detail";
}

<h2>Detail</h2>

... some properties of a post

@Html.DisplayFor(x => x.Comments)

根据评论部分的要求,域模型和视图模型之间的映射如下所示:

Mapper.CreateMap<User, UserVM>();
Mapper
    .CreateMap<Comment, CommentVM>()
    .ForMember(
        dest => dest.UserVM, 
        opt => opt.MapFrom(src => src.User)
    );
Mapper
    .CreateMap<Post, PostVM>()
    .ForMember(
        dest => dest.CommentsVM, 
        opt => opt.MapFrom(src => src.Comments)
);

备注:如果在视图模型中以相同的方式命名属性,则不需要进行两次ForMemeber调用:UserComments。 AutoMapper依赖于标准命名约定,您也可以编写自己的自定义约定策略,以避免单独映射每个成员。然后,您只需要在定义视图模型时保持一致并遵循惯例。

然后在控制器中:

IEnumerable<Post> posts = ...
IEnumerable<PostVm> postVms = Mapper.Map<IEnumerable<Post>, IEnumerable<PostVM>>(posts);
return View(postVms);

答案 1 :(得分:2)

您确实将自己的域模型用于视图中。您的视图模型只是域模型的容器。

就我个人而言,在我的视图中使用实体或域模型没有任何问题。我通常从他们开始。当域/实体模型没有给我我想要的东西时(而不是在我的视图中引入逻辑),我切换到查看模型。

没有其他代码依赖于您的视图,因此可以轻松地重构它们并随时切换模型。