Best practice for following the view model pattern

前端 未结 2 1473
孤独总比滥情好
孤独总比滥情好 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:02

    You do use your domain model into your views. Your view model is just a container of domain models.

    Personally I have no problems with using entity or domain models in my views. I usually start with them. I switch to view models when the domain/entity model doesn't give me exactly what I want (instead of introducing logic into my views).

    No other code is dependent of your views, which makes it easy to refactor them and switch models at anytime.

    0 讨论(0)
  • 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<Comment> 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<CommentViewModel> 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";
    }
    
    <h2>Detail</h2>
    
    @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<Post, PostViewModel>(postModel),
            Comments = Mapper.Map<IEnumerable<Comment>, IEnumerable<CommentViewModel>>(postModel.Comments)
        };
        return View(viewModel);
    }
    

    Or even better:

    public class PostViewModel
    {
        ... some properties of a Post
        public IEnumerable<CommentViewModel> 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<Post, PostViewModel>(postModel);
        return View(viewModel);
    }
    

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

    @model WebUI.ViewModels.PostViewModel
    
    @{
        ViewBag.Title = "Detail";
    }
    
    <h2>Detail</h2>
    
    ... 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<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)
    );
    

    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<Post> posts = ...
    IEnumerable<PostVm> postVms = Mapper.Map<IEnumerable<Post>, IEnumerable<PostVM>>(posts);
    return View(postVms);
    
    0 讨论(0)
提交回复
热议问题