Best practice for following the view model pattern

前端 未结 2 1500
孤独总比滥情好
孤独总比滥情好 2021-02-04 21:11

I\'m learning ASP.NET MVC for some times now. I follow some guidelines found on internet or in books and I would like to be sure I follow good practices in my developments regar

2条回答
  •  长发绾君心
    2021-02-04 22:13

    You are close but for me this is not a correct usage of view models. You have the following:

    public class DetailPostViewModel
    {
        public Post Post { get; set; }
        public IEnumerable Comments { get; set; }
    }
    

    That's some sort of a hybrid pseudo view model. A real view model should not reference any domain model whatsoever.

    A more correct example would be the following:

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

    where of course PostViewModel and CommentViewModel are the corresponding view models for their respective domain entities.

    Also instead of partials I would use display templates:

    @model WebUI.ViewModels.DetailPostViewModel
    
    @{
        ViewBag.Title = "Detail";
    }
    
    

    Detail

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

    and then you will have the respective display templates (~/Views/Shared/DisplayTemplates/PostViewModel.cshtml):

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

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

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

    Obviously since Comments is an enumeration the display template will be automatically rendered for each element of the collection. This way you don't need to write any loop in your views.

    As far as the controller logic is concerned, personally I like to use AutoMapper to map between the domain entities and the view models.

    So it might look like this:

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

    Or even better:

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

    and then:

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

    and then your view will be strongly typed to a PostViewModel:

    @model WebUI.ViewModels.PostViewModel
    
    @{
        ViewBag.Title = "Detail";
    }
    
    

    Detail

    ... some properties of a post @Html.DisplayFor(x => x.Comments)

    As requested in the comments section here's how the mapping between the domain models and the view models would look like:

    Mapper.CreateMap();
    Mapper
        .CreateMap()
        .ForMember(
            dest => dest.UserVM, 
            opt => opt.MapFrom(src => src.User)
        );
    Mapper
        .CreateMap()
        .ForMember(
            dest => dest.CommentsVM, 
            opt => opt.MapFrom(src => src.Comments)
    );
    

    Remark: The two ForMemeber calls wouldn't have been necessary if the properties were named the same way in the view model: User and Comments. AutoMapper relies on standard naming conventions and also you could write your own custom conventions strategy to avoid mapping each member individually. Then all you need is to be consistent and follow conventions when defining the view models.

    and then in the controller:

    IEnumerable posts = ...
    IEnumerable postVms = Mapper.Map, IEnumerable>(posts);
    return View(postVms);
    

提交回复
热议问题