Reputation: 9362
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 regarding the view model pattern. Below is a simple example of implementation of a blog. Could you please confirm me that I'm on the right way. Let's say I would like to display a post title + description on a view and below that comments on this post. So I have a view with 2 partial views on it.
In my controller:
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);
}
So here, I perpare a view model composed of 2 things:
The view model will be passed to the view for following the view model pattern.
In my Detail view:
@model WebUI.ViewModels.DetailPostViewModel
@{
ViewBag.Title = "Detail";
}
<h2>Detail</h2>
@Html.Partial("_DetailPost", Model.Post)
@Html.Partial("_Comments", Model.Comments)
So here, I used my DetailPostViewModel. For my partial views, I passed the necessary information which are entities!? I don't know if this is the right way or do I have to pass exclusively view models here too?
In my _DetailPost view:
@model Domain.Entities.Post
@Html.DisplayForModel()
...
So here in my partial view, I used en entity as model. Is it good or bad?
In my _Comments view:
@model IEnumerable<Domain.Entities.Comment>
@foreach (var item in Model)
{
@Html.DisplayFor(Model => item.Title)
@Html.DisplayFor(Model => item.Username)
}
So here in my partial view, I used en entity as model. Is it good or bad?
Thanks for your advises/suggestions.
EDIT
I try to implement my view model pattern with the help of Automapper but I have some problems as described below. I understand the principe of mapping one object to another. In a basic situation it works very well. Now, let's see the a more complex situation: I have a post composed of some attributes (id, title, ...) AND each post can have some comments (object) attached AND each comment can have only one user (object) attached. I would like to get a view model with all my posts and for each one, get all the comments and for each comment get the user attached.
Here are the domain entities:
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; }
...
}
Here are the view models:
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; }
...
}
Here is my controller:
// I would like to list all posts in the DB.
public ViewResult Index()
{
PostVM viewModel = new PostVM
{
...
}
return View(viewModel);
}
How do I map my list of (entity) posts objects into my view model object and all sub objects (comments, users)?
Thanks!
Upvotes: 4
Views: 1696
Reputation: 1039418
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);
Upvotes: 5
Reputation: 101194
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.
Upvotes: 2