ASP.NET MVC ViewModel usage and pick your best pattern

In this post, I will start with typical usage of view model in view that you often see in ASP.NET MVC Applications with full of tag soup, then I refactor it incrementally to correct the implementation and finally show you a better implementation which promotes better OOP and encapsulation, makes the view fully logic-less by delegating the presentation logic to a separate class. Lets see the initial implementation, consider a User Profile view which contains the following code:

Typical ViewModel Usage

              @model User
              
              @{ ViewBag.Title = "User"; }
              
              <div id="profile">
                  @{
                      var avatarImage = string.IsNullOrWhiteSpace(Model.Avatar) ?
                                        "default.png" :
                                        Model.Avatar;
                      var avatarUrl = Url.Content("~/Content/images/avatars/" + avatarImage);
                  }
                  @if (!string.IsNullOrWhiteSpace(Model.Website)) {
                      <a href="@Model.Website">
                          <img src="@avatarUrl" alt="@Model.UserName" class="avatar"/>
                      </a>
                  } else {
                      <img src="@avatarUrl" alt="@Model.UserName" class="avatar"/>
                  }
              
                  <h1>
                      @{
                          var name = string.IsNullOrWhiteSpace(Model.FullName) ?
                                     Model.UserName :
                                     Model.FullName;
                      }
                      @if (!string.IsNullOrWhiteSpace(Model.Website)) {
                          <a href="@Model.Website">@name</a>
                      } else {
                          <text>@name</text>
                      }
                  </h1>
              
                  <dl>
                      <dt>Username:</dt>
                      <dd>@Model.UserName</dd>
              
                      <dt>Website:</dt>
                      <dd>
                          @if (!string.IsNullOrWhiteSpace(Model.Website)) {
                              <a href="@Model.Website">@Model.Website</a>
                          } else {
                              <span class="none">None given</span>
                          }
                      </dd>
              
                      <dt>Twitter:</dt>
                      <dd>
                          @if (!string.IsNullOrWhiteSpace(Model.Twitter)) {
                              <a href="http://twitter.com/@Model.Twitter">@Model.Twitter</a>
                          } else {
                              <span class="none">None given</span>
                          }
                      </dd>
              
                      <dt>Bio:</dt>
                      <dd>
                          @if (!string.IsNullOrWhiteSpace(Model.Bio)) {
                              @Html.Raw(Model.Bio)
                          } else {
                              <span class="none">None given</span>
                          }
                      </dd>
                  </dl>
              </div>
              

nothing complex, but there are too many conditional logics what to show when, lets remove the duplicate markups first which makes  it slightly optimized:

Slightly optimized

              @model User
              
              @{ ViewBag.Title = "User"; }
              
              @functions {
                  IHtmlString Avatar() {
                      var image = string.IsNullOrWhiteSpace(Model.Avatar) ?
                                   "default.png" :
                                   Model.Avatar;
              
                      var url = Url.Content("~/Content/images/avatars/" + image);
                      
                      var html = "<img src=\"" +
                                 url + "\" alt=\"" +
                                 Model.UserName +
                                 "\" class=\"avatar\"/>";
              
                      return Html.Raw(html);
                  }
              
                  IHtmlString None() {
                      return Html.Raw("<span class=\"none\">None given</span>");
                  }
              }
              <div id="profile">
                  @if (!string.IsNullOrWhiteSpace(Model.Website)) {
                      <a href="@Model.Website">@Avatar()</a>
                  } else {
                      @Avatar()
                  }
              
                  <h1>
                      @{
                          var name = string.IsNullOrWhiteSpace(Model.FullName) ?
                                     Model.UserName :
                                     Model.FullName;
                      }
                      @if (!string.IsNullOrWhiteSpace(Model.Website)) {
                          <a href="@Model.Website">@name</a>
                      } else {
                          <text>@name</text>
                      }
                  </h1>
              
                  <dl>
                      <dt>Username:</dt>
                      <dd>@Model.UserName</dd>
              
                      <dt>Website:</dt>
                      <dd>
                          @if (!string.IsNullOrWhiteSpace(Model.Website)) {
                              <a href="@Model.Website">@Model.Website</a>
                          } else {
                              @None()
                          }
                      </dd>
              
                      <dt>Twitter:</dt>
                      <dd>
                          @if (!string.IsNullOrWhiteSpace(Model.Twitter)) {
                              <a href="http://twitter.com/@Model.Twitter">@Model.Twitter</a>
                          } else {
                              @None()
                          }
                      </dd>
              
                      <dt>Bio:</dt>
                      <dd>
                          @if (!string.IsNullOrWhiteSpace(Model.Bio)) {
                              @Html.Raw(Model.Bio)
                          } else {
                              @None()
                          }
                      </dd>
                  </dl>
              </div>
              

I just moved the duplicate avatar image and no content in helpers methods, but the condition checkings are still there, may be we can inline it like this:

Inlining condition checking and markup

              @model User
              
              @{ ViewBag.Title = "User"; }
              
              @functions {
                  IHtmlString Avatar()
                  {
                      var image = string.IsNullOrWhiteSpace(Model.Avatar) ?
                                   "default.png" :
                                   Model.Avatar;
              
                      var url = Url.Content("~/Content/images/avatars/" + image);
              
                      var html = "<img src=\"" +
                                 url + "\" alt=\"" +
                                 Model.UserName +
                                 "\" class=\"avatar\"/>";
              
                      return Html.Raw(html);
                  }
              
                  IHtmlString None() {
                      return Html.Raw("<span class=\"none\">None given</span>");
                  }
              
                  HelperResult When(
                      bool condition,
                      Func<dynamic, HelperResult> trueNugget,
                      Func<dynamic, HelperResult> falseNugget)
                  {
                      var nugget = condition ?
                                   trueNugget :
                                   falseNugget;
              
                      return new HelperResult(w => nugget(null).WriteTo(w));
                  }
              }
              <div id="profile">
                  @When(
                      string.IsNullOrWhiteSpace(Model.Website),
                      @<text>@Avatar()</text>,
                      @<a href="@Model.Website">@Avatar()</a>)
                  <h1>
                      @{
                          var name = string.IsNullOrWhiteSpace(Model.FullName) ?
                                     Model.UserName :
                                     Model.FullName;
                      }
                      @When(
                          string.IsNullOrWhiteSpace(Model.Website),
                          @<text>@name</text>,
                          @<a href="@Model.Website">@name</a>)
                  </h1>
                  <dl>
                      <dt>Username:</dt>
                      <dd>@Model.UserName</dd>
              
                      <dt>Website:</dt>
                      <dd>
                          @When(
                              string.IsNullOrEmpty(Model.Website),
                              @<text>@None()</text>,
                              @<a href="@Model.Website">@Model.Website</a>)
                      </dd>
              
                      <dt>Twitter:</dt>
                      <dd>
                          @When(
                              string.IsNullOrWhiteSpace(Model.Twitter),
                              @<text>@None()</text>,
                              @<a href="http://twitter.com/@Model.Twitter">@Model.Twitter</a>)
                      </dd>
              
                      <dt>Bio:</dt>
                      <dd>
                          @When(
                              string.IsNullOrWhiteSpace(Model.Bio),
                              @<text>@None()</text>,
                              @<text>@Html.Raw(Model.Bio)</text>)
                      </dd>
                  </dl>
              </div>
              

I just introduced a helper method When which takes a boolean as first argument, if it is true it renders the first code nugget and if false renders the second. Now, we know that any kind of code branching is bad in view because it is hard to test and that is the reason all the ASP.NET MVC gurus suggest to move it to helper class like the following:

HTMLHelper based optimization

              @model User
              
              @{ ViewBag.Title = "User"; }
              
              <div id="profile">
                  @Html.Avatar(Model)
                  <h1>@Html.LinkedName(Model)</h1>
                  <dl>
                      <dt>Username:</dt>
                      <dd>@Model.UserName</dd>
              
                      <dt>Website:</dt>
                      <dd>@Html.Website(Model)</dd>
              
                      <dt>Twitter:</dt>
                      <dd>@Html.Twitter(Model)</dd>
              
                      <dt>Bio:</dt>
                      <dd>@Html.Bio(Model)</dd>
                  </dl>
              </div>
              

The code looks a lot better comparing than those above version, but I do not like the “Helper” based approach, the name “Helper” always sounds design/code smell to me, it tells me that my “actual thing” do not have the ability to solve the issue, that is why it needs a “Helper”. A much better approach would be to create a separate class which decorates the ViewModel and has it own set of methods to handle the presentation and view would use these decorated object instead of doing the code branching by itself. Here, is the decorated class which handles the presentation logic:

DecoratedUser

              public class DecoratedUser : Decorator<User>
              {
                  private const string None = "<span class=\"none\">None given</span>";
              
                  public DecoratedUser(ControllerContext controllerContext, User model) :
                      base(controllerContext, model)
                  {
                  }
              
                  public IHtmlString Avatar
                  {
                      get
                      {
                          var image = string.IsNullOrWhiteSpace(Model.Avatar) ?
                                      "default.png" :
                                      Model.Avatar;
              
                          var url = Url.Content("~/Content/images/avatars/" + image);
              
                          var html = "<img src=\"" +
                                      url +
                                      "\" alt=\"" +
                                      Model.UserName +
                                      "\" class=\"avatar\"/>";
              
                          if (!string.IsNullOrWhiteSpace(Model.Website))
                          {
                              html = "<a href=\"" + Model.Website + "\">" +
                                      html +
                                      "</a>";
                          }
              
                          return Html.Raw(html);
                      }
                  }
              
                  public IHtmlString LinkedName
                  {
                      get
                      {
                          var name = string.IsNullOrWhiteSpace(Model.FullName) ?
                                      Model.UserName :
                                      Model.FullName;
              
                          var html = string.IsNullOrWhiteSpace(Model.Website) ?
                                      name :
                                      "<a href=\"" + Model.Website + "\">" + name + "</a>";
              
                          return Html.Raw(html);
                      }
                  }
              
                  public IHtmlString Website
                  {
                      get
                      {
                          var html = string.IsNullOrWhiteSpace(Model.Website) ?
                                      None :
                                      "<a href=\"" +
                                      Model.Website +
                                      "\">" +
                                      Model.Website +
                                      "</a>";
              
                          return Html.Raw(html);
                      }
                  }
              
                  public IHtmlString Twitter
                  {
                      get
                      {
                          var html = string.IsNullOrWhiteSpace(Model.Twitter) ?
                                      None :
                                      "<a href=\"http://twitter.com/" +
                                      Model.Twitter +
                                      "\">" +
                                      Model.Twitter +
                                      "</a>";
              
                          return Html.Raw(html);
                      }
                  }
              
                  public IHtmlString Bio
                  {
                      get
                      {
                          var html = string.IsNullOrWhiteSpace(Model.Bio) ?
                                      None :
                                      Model.Bio;
              
                          return Html.Raw(html);
                      }
                  }
              }
              

This decorated class does have both UrlHelper, HtmlHelper along with the original ViewModel, to make it yourself more clear here the abstract decorator from which the DecoratedUser class inherits, I have removed all those helper creation methods just for the sake of brevity:

Decorator

              public abstract class Decorator<TModel>
              {
                  private readonly TModel model;
                  private readonly ControllerContext controllerContext;
              
                  private ViewContext viewContext;
                  private UrlHelper url;
                  private HtmlHelper html;
              
                  protected Decorator(
                      ControllerContext controllerContext,
                      TModel model)
                  {
                      if (controllerContext == null)
                      {
                          throw new ArgumentNullException("controllerContext");
                      }
              
                      this.model = model;
                      this.controllerContext = controllerContext;
                  }
              
                  public virtual TModel Model
                  {
                      get { return model; }
                  }
              
                  protected virtual ViewContext ViewContext
                  {
                      get { return viewContext ?? (viewContext = CreateViewContext()); }
                  }
              
                  protected virtual UrlHelper Url
                  {
                      get { return url ?? (url = CreateUrlHelper()); }
                  }
              
                  protected virtual HtmlHelper Html
                  {
                      get { return html ?? (html = CreateHtmlHelper()); }
                  }
              }
              

Now, we can change our view to this:

Decorator View

              @model DecoratedUser
              
              @{ ViewBag.Title = "User"; }
              
              <div id="profile">
                  @Model.Avatar
                  <h1>@Model.LinkedName</h1>
                  <dl>
                      <dt>Username:</dt>
                      <dd>@Model.Model.UserName</dd>
              
                      <dt>Website:</dt>
                      <dd>@Model.Website</dd>
              
                      <dt>Twitter:</dt>
                      <dd>@Model.Twitter</dd>
              
                      <dt>Bio:</dt>
                      <dd>@Model.Bio</dd>
                  </dl>
              </div>
              

To me, it looks way better than the HtmlHelper based approach. But I am still not yet convinced, there are still two things that are bothering me, check the line 10, whenever I need to access the real Model, I have to use double Model dot and in the controller I have to create this decorated instance which is somewhat painful:

Creating Decorator in the Controller

              public ActionResult Details5(int id)
              {
                  var user = users.SingleOrDefault(u => u.Id == id);
              
                  if (user == null)
                  {
                      return HttpNotFound();
                  }
              
                  return View(new DecoratedUser(ControllerContext, user));
              }
              

Lets address the first one, what if, if our decorator has the ability to forward the calls to real model if the decorator does not have it defined, yes with some C# 4 dynamic magic we can add this feature,  the idea is when in the view a member is accessed it would first check whether it has this member defined. if it is defined it calls it otherwise it would invoke the model member. With this dynamic feature we can write our view like this:

Dynamic Member Access

              @model dynamic
              
              @{ ViewBag.Title = "User"; }
              
              <div id="profile">
                  @Model.Avatar
                  <h1>@Model.LinkedName</h1>
                  <dl>
                      <dt>Username:</dt>
                      <dd>@Model.UserName</dd>
              
                      <dt>Website:</dt>
                      <dd>@Model.Website</dd>
              
                      <dt>Twitter:</dt>
                      <dd>@Model.Twitter</dd>
              
                      <dt>Bio:</dt>
                      <dd>@Model.Bio</dd>
                  </dl>
              </div>
              

Now, there is double Model dots like the previous, when the UserName is accessed it checks whether the decorator has this defined and since it has not, it invokes the Model UserName. This applies not only for properties but also for methods. Adding this dynamic features just needs to implement the IDynamicMetaObjectProvider interface which our Decorator<TModel> do and create your own implementation of DynamicMetaObject. I am skipping the discussion of our version of DynamicMetaObject (the only thing it does is mangling with C# Expression tree) but you will find the full source code in the zip file. The next issue was decorator creation which we do not like to do in the controller, instead we can create an action filter which does this job automatically for us and in the controller we will only set the model like we regularly do, so our controller action would become something like the following:

Decorate FilterAttribute

              [Decorator(typeof(DecoratedUser))]
              public ActionResult Details6(int id)
              {
                  //.....//
              }
              

The filter attribute itself is not a complex things, main action happens in the executing method. Here is the code:

DecoratorAttribute

              public class DecoratorAttribute : FilterAttribute, IResultFilter
              {
                  public DecoratorAttribute(Type decoratorType)
                  {
                      if (decoratorType == null)
                      {
                          throw new ArgumentNullException("decoratorType");
                      }
              
                      DecoratorType = decoratorType;
                  }
              
                  public Type DecoratorType { get; private set; }
              
                  public void OnResultExecuting(ResultExecutingContext filterContext)
                  {
                      if (filterContext == null)
                      {
                          throw new ArgumentNullException("filterContext");
                      }
              
                      // Only consider view result
                      if (!typeof(ViewResultBase).IsAssignableFrom(
                          filterContext.Result.GetType()))
                      {
                          return;
                      }
              
                      var model = filterContext.Controller.ViewData.Model;
              
                      // No need to decorate if model is null
                      if (model == null)
                      {
                          return;
                      }
              
                      var decorator = Activator.CreateInstance(
                          DecoratorType,
                          filterContext,
                          model);
              
                      // Now replace the model with decorator
                      filterContext.Controller.ViewData.Model = decorator;
                  }
              
                  public virtual void OnResultExecuted(ResultExecutedContext filterContext)
                  {
                      // Do Nothing
                  }
              }
              

Great, you are still with me, the above decorator pattern for View is not my idea, the Rails developers are using it for some times and it is known as Presenter pattern. In fact, the example that I have used is taken directly from RailsCasts episode # 286 on Drapper, I just ported it to ASP.NET MVC and C#. So what do you think, is it better than we are used to do it?

That’s it for Today.

Download: ViewModelDecorator.zip

Shout it

Comments

blog comments powered by Disqus