Custom MVC Blog Part 3 - ViewModels and a Service Layer
7/11/2012I will do my best to make this post a lot shorter. That last two posts we went over the general project structure and how we use Entity Framework Code First to generate a database. We also took a brief look at using the Package Manager Console to manage you database changes. In this post I will show you how I separated my logic out of my Controllers and into a class library that will be my Service Layer. I will also go over my ViewModels and why they are important in many cases.
To start off I added a folder to my solution in the root directory called ViewModels (clever I know), for the sake of brevity I will go over the PostDetailViewModel.cs file. In one of my views I needed to return a single Post based off of a key, a list of Comments based off of the Post, and have a seperate single Comment object in order to allow a user to add a new comment. If you have ever worked with MVC before and are familiar with passing data to your View, then you can understand how this can be a giant pain and require some very clever coding. Honestly I am not even sure how you would do this without a ViewModel, but I am sure it is possible. Either way, using a ViewModel allows my to "bundle" these three things together. Take a look:
public class PostDetailViewModel { public Post Post { get; set; } public ListComments { get; set; } public Comment Comment { get; set; } }
To some this may look pretty self-explanatory, but let me tell you this: it took me forever to finally wrap my mind around this concept. And who knows, I may have it completely wrong, but this works and as of now that is good enough for me. Purists be damned! So to explain the logic here a bit in the way that I understand it, we have two sets of data we are working with, from separate tables in our database, Posts and Comments. The View that these will be served up to will show a blog post on a stand-alone page, along with any comments on the post, and an area to add a new comment. So with those requirements in mind I added the three properties: a Post, a Comment, and a List of Comments. Deceptively simple... makes me wonder why this didn't click sooner with me. It is also my understanding that you can also just serve up the particular fields you want from a Post or a Comment, but I haven't implemented this yet, and I don't know if this would be better suited here or in a Service Layer (you will see why in a minute).
One the the Service Class that handles getting the right data. Our ViewModel simply tells us the kind of data we are getting, the Service Class will tell us what the data contains. In the root directory again I added a folder called Services (yes yes, clever) and added a class called PostsServices.cs. Here is what you will find in there:
public PostDetailViewModel GetPost(bool? isRedirect, string blogTitle, int id = 0) { var viewModel = new PostDetailViewModel(); viewModel.Post = db.Posts.First(x => x.Id == id); IListcomments = db.Comments.ToList (); var query = from c in comments where c.PostId == id && c.Moderated == true select c; IList result = query.ToList (); viewModel.Comments = query.ToList(); viewModel.Comment = new Comment(); return viewModel; }
I wont go into detail here on what I am doing. Basic Linq stuff. I grab a new instance of the ViewModel we made. Then I grab the singular Post based off of the Id I passed in, grab the Comments based off of the post and whether it has been moderated or not, and then for the single Comment I instantiate a new Comment.
No we are finally back to the controller. The code here is prone to to get messy fast, but with the ViewModels and Service Layer it makes the Controller and subsequent ActionResults very clean and readable.
private HomeServices service = new HomeServices(); public ActionResult Post(bool? isRedirect, string blogTitle, int id = 0) { var result = service.GetPost(isRedirect, blogTitle, id); return View(result); }
Now there is more you can add to the controller here, like error handling and whatnot. I also have the optional bool of isRedirect in there, which I am in the process of implementing so I wont go into that. So we instantiate a new HomeServices and call the method we just created and put it in a variable called result. Then we return the view and pass in all that result data.
That is it! Pretty cool, and easier to debug in my opinion. Let me know if you have any questions, or if you think my code is garbage!
{David Stanley}