ASP.NET MVC ViewModel: Make your ViewModels More Functional
How functional are your ViewModels? Do they just deliver data or do they provide additional functionality to assist your views?
One day, I was talking to a developer who wanted to learn about ASP.NET MVC and how it compared to other languages. While describing the basics of how to pass data to a View, the developer decided to focus on placing additional functionality in the ViewModel.
They wanted to place business rules/logic inside of a ViewModel.
Yikes! Hold on there. While on the surface that may seem like a good idea, but there are two reasons why you shouldn't do that:
- What if your web services need the business rules? You wouldn't go through a ViewModel to get the rules. Keep the business logic as close to the server as possible and keep it in the middle or database tier. Can you imagine changing the business logic in ALL of your ViewModels and not your business entities? Ugghh.
- The ViewModel should be a POCO object with one job: deliver the data with the optional job of formatting it for the View.
The ViewModel's primary job, whether it's the ViewBag, ViewData, TempData, or ViewModel, is to deliver data to the view in a raw or formatted form. Period.
We Need a Formatter
If you're wondering what I mean by formatted data, let me explain with an example.
Let's use a simple Customer/Order ViewModel. The View will show a customer and a list of their orders. The orders will be in a dropdown list.
Here's your basic ViewModel:
using System.Collections.Generic; namespace ThinController.Models { public class CustomerOrderViewModel { public Customer Customer { get; set; } public IEnumerable<Order> Orders { get; set; } } }
So my question is: how would you add an IEnumerable<SelectListItem> into a View using your existing list of Orders?
One possible solution would be to create a SelectList on the View like below?
<div class="col-md-4"> <h2>Customer Orders</h2> @using (Html.BeginForm()) { <div class="customer"> <label>Customer: </label><span>@Model.Customer.CustomerName</span> </div> <div class="order-list"> <label>Orders: </label> <span> @Html.DropDownListFor(model => model.Orders, Model.Orders.Select( order => new SelectListItem { Value = order.OrderId.ToString(), Text = String.Format("{0} ({1} items)", order.OrderDate.ToShortDateString(), order.TotalItems) })) </span> </div> } </div>
A little messy, isn't it? It muddies up your Views. General rule is to make your HTML/Views as clean as possible.
Yeah...umm...I would have to say no to this approach.
An easier way would be to let the ViewModel format and return a SelectList.
using System.Collections.Generic; using System.Web.Mvc; using ThinController.Extensions; namespace ThinController.Models { public class CustomerOrderViewModel { public Customer Customer { get; set; } public IEnumerable<Order> Orders { get; set; } public IEnumerable<SelectListItem> OrderSelectList(string defaultId = "") { return Orders.ToSelectList( e => string.Format("{0} ({1} items)", e.OrderDate.ToShortDateString(), e.TotalItems.ToString()), e => e.OrderId.ToString(), defaultId); } } }
If you were paying attention, I'm also using an extension method to convert the list into a IEnumerable<SelectListItem> to make our life a little easier.
using System; using System.Collections.Generic; using System.Linq; using System.Web.Mvc; namespace ThinController.Extensions { public static class ListExtensions { public static IEnumerable<SelectListItem> ToSelectList<T>( this IEnumerable<T> list, Func<T, string> dataField, Func<T, string> valueField, string defaultValue) { var result = new List<SelectListItem>(); if (list.Any()) result.AddRange( list.Select( resultItem => new SelectListItem { Value = valueField(resultItem), Text = dataField(resultItem), Selected = defaultValue == valueField(resultItem) })); return result; } } }
It is true that your ViewModel should be as clean as possible, but why not provide some assistance to your Views and create (and I emphasize) SMALL pieces of code to provide a better maintenance path.
Conclusion
In this post, we explained how you can increase the use of your ViewModels by giving them a little more functionality.
Instead of placing DropDownLists all over the place or creating custom HtmlHelpers, we've created a generic ToSelectList that will work with every list requiring a dropdownlist.
Extension Methods can go a long way when used properly.
How was this post? Did it make sense? Post your comments below!