Wednesday, October 19, 2011

ASP.NET MVC Partial Views and Strongly Typed Custom ViewModels


I'm in the process of rewriting mikesdotnetting.com using the ASP.NET MVC framework. It's an interesting experience, in that this is my first exposure to MVC. The first stumbling block I encountered was how to pass data in a satisfactory way to Partial Views which only represent part of the data that is to be displayed in the View. Since I struggled to find clear guidance on this, I thought I would share a way to do it.

There are a fair number of articles and blog pieces about the difference between ViewDataDictionery and ViewDataDictionery<TModel>, but there's no harm in taking the opportunity to recap - especially as the MSDN documentation is (at the moment) pretty thin on notes in the MVC Reference section. The ViewDataDictionery is a container which is used to pass data from the Controller to the View. It is then referenced via the ViewPage.ViewData property.
The first type of ViewDataDictionery is an untyped collection consisting of a string and an object. The string represents the key, and the object holds the actual data. Like any untyped collection, the object can be of any type, but must be cast to the correct type before it is used. Items are referenced via their key in the ViewData property of the ViewUserControl (in the case of a PartialView). The second is a strongly typed collection, where items are properties of the ViewUserControl.Model property.
Now, if you look at the front page of my site, you will see a listing of Article Summaries (the most recent 15 that I have written) and a small box containing the 10 Most Popular Article Titles. Both of these are candidates for PartialViews. The Article Summaries layout is also used on any page that features a listing, such as the Articles By Category page, and the Articles By Type. If I ever want to change the format or the content of the listing, I would only want to do it in one place. The Most Popular only actually appears on the home page at the moment, but I may want to add a listing of linked Article Titles to other pages, so I may as well encapsulate the logic that creates this display so that I can just plug it in wherever I like in future.
I am using the Entity Framework to generate the business objects and manage Data Access within the application. As a result, I already have a class called Article which is auto-generated from the database table by EF. It features all the fields in the database table as properties of the class.

I don't want to use this class for the listing of Article Titles. It would be wasteful as EF will bring back all information related to each article that features in the list - including the article text AND the complete set of comments and replies to each article. All I need is the article title (or headline) and the ArticleID. So I create a separate class that just holds these properties:
namespace MikesDotnetting.Models
{
/// <summary>
///
The Article Headline and ID for linking and listing purposes
/// </summary>
public class ArticleTitle
{
public int ID { get; set; }
public string Head { get; set; }
}
}

The same holds true for the summaries listing. I only want part of the article class exposed, so I create an ArticleSummary class for objects that will be used in this listing:
namespace MikesDotnetting.Models
{
/// <summary>
///
Returns summary information for an Article for listing purposes
/// </summary>
public class ArticleSummary
{
public int ID { get; set; }
public string Head { get; set; }
public string Intro { get; set; }
public string ArticleType { get; set; }
public IEnumerable<Category> CategoryList { get; set; }
public string PostBy { get; set; }
public DateTime CreatedDate { get; set; }
}
}

Both of these classes are added to the Models folder in the application, as you can probably guess from the namespaces they have. Now, I'm using the same approach as described in the Contact Manager tutorials, and the NerdDinner sample application, which involves the use of the Repository Pattern to decouple the data access from the rest of the application. So I have my Interface, which now needs to have some methods added to it to retrieve ArticleSummary and ArticleTitle collections:
using System.Collections.Generic;

namespace MikesDotnetting.Models
{
public interface IArticleRepository
{
IEnumerable<ArticleSummary> GetFrontPageSummaries();
IEnumerable<ArticleSummary> GetSummariesByCategory(int id);
IEnumerable<ArticleSummary> GetSummariesByArticleType(int id);
IEnumerable<ArticleTitle> GetMostPopular();

}
}

And then the Repository class that implements the Interface, and actually does the work of obtaining the data from the database:
using System.Collections.Generic;
using System.Linq;

namespace MikesDotnetting.Models
{
public class ArticleRepository : IArticleRepository
{
private DotnettingEntities de = new DotnettingEntities();



/// <summary>
///
Gets the Front Page Listing
/// </summary>
/// <returns>
A Collection of ArticleSummary objects</returns>
public IEnumerable<ArticleSummary> GetFrontPageSummaries()
{
return (from a in de.ArticleSet
orderby a.DateCreated descending
select new
ArticleSummary
{
ID = a.ArticleID,
Head = a.Headline,
Intro = a.Abstract,
ArticleType = a.ArticleTypes.ArticleTypeName,
CategoryList = a.Categories,
PostBy = a.PostedBy,
CreatedDate = a.DateCreated
})
.Take(15).ToList();
}

/// <summary>
///
Gets the listing for the Most Popular
/// </summary>
/// <returns>
A Collection of ArticleTitle objects</returns>
public IEnumerable<ArticleTitle> GetMostPopular()
{
return (from a in de.ArticleSet
orderby a.Views descending
select new
ArticleTitle
{
ID = a.ArticleID,
Head = a.Headline
})
.Take(10).ToList();
}
}
}

Untyped ViewPages

The Controller for the Articles (ArticleController.cs) is responsible for returning the ArticleSummary collection and the ArticleTitle collection to the home page, or the Index View in my case. The code for this is as follows and shows how the untyped ViewDataDictionery is put to work:
using System.Web.Mvc;
using MikesDotnetting.Models;

namespace MikesDotnetting.Controllers
{
public class ArticleController : BaseController
{
private IArticleRepository repository;

public ArticleController()
: this(new ArticleRepository())
{ }

public ArticleController(IArticleRepository rep)
{
repository = rep;
}

// GET: /Article/

public ActionResult Index()
{
ViewData["summaries"] = repository.GetFrontPageSummaries();
ViewData["mostpopular"] = repository.GetMostPopular();
return View(ViewData);
}
}
}

As you can see, the ViewData is a collection to which I have added two items: the key "summaries" plus its related object - a collection of ArticleSummaries, and the key "mostpopular" plus its related object - a collection of ArticleTitles. The ViewData object is then passed to the Index View. Now I need something to show the data, which is where my PartialViews come in. I'll show the code for the ArticleTitles Partial View as an example because it's nice and short:
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>


<%{
foreach (var item in ViewData["mostpopular"] as IEnumerable<ArticleTitle>)
{%>
<p>
<%=Html.RouteLink(
item.Head,
"Details",
new
{
controller = "Article",
action = "Details",
id = item.ID
})%>
</p>
<% }
}%>


Notice how items are referenced within the foreach iterator? First, the correct key within the ViewData collection has to be referenced, and then each item within that particular collection (remember, it's of ArticleTitle objects) needs to be cast to the correct type. This kind of untyped collection is fine to work with on smallish projects, and is pretty easy to get to grips with. But as projects grow, the potential for errors increases. The key is a string, and that is somewhere that typos can creep in which will not be caught at compile time. Nevertheless, adding the PartialViews to the main View is easy:
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" 
Inherits
="System.Web.Mvc.ViewPage" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Mikesdotnetting: on ASP.NET and Web Development
</asp:Content>

<
asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<% Html.RenderPartial("ArticleTitlePartial"); %>
<h2>Latest Entries</h2>
<% Html.RenderPartial("ArticleSummaryPartial"); %>

</asp:Content>



And running the page gives the desired result:

Just a little aside - you might wonder why the page above looks little like the current page on my site. The reason for that is that I trimmed the css file right back to purely positional rules, removing all styling. I might add the old style back in at a later stage, or I might change the look altogether. But when developing a site, I always add the style aspects last.

Strongly Typed Views

At the moment, there are two entities being passed to the Index View - one for each PartialView. What we could really do with is one entity or type which comprises the two parts. This can be accomplished by creating a custom ViewModel. Typically, these are named after the View that will make use of them. In my case. I called it HomePageViewModel, and the code for it is as follows:
using System.Collections.Generic;
using MikesDotnetting.Models;

namespace MikesDotnetting.ViewModels
{
public class HomePageViewModel
{
public HomePageViewModel(IEnumerable<ArticleSummary> summaries,
IEnumerable<ArticleTitle> mostPopular)
{
this.ArticleSummaries = summaries;
this.MostPopular = mostPopular;
}
public IEnumerable<ArticleSummary> ArticleSummaries { get; private set; }
public IEnumerable<ArticleTitle> MostPopular { get; private set; }
}
}

I've placed this in a folder called ViewModels, although I found that people have different approaches. Principally, the HomePageViewModel class has one purpose, and that is to combine an ArticleSummary collection and an ArticleTitle collection into one entity for display on the Home Page. I may at some stage look to add properties to this class which will take care of database drive navigation too. However, for the purposes of demonstration, this is sufficient.
Some additional changes are needed to the Controller and to the PartialViews, as well as the Index View itself. I'll start with the revised method in the Controller:
using System.Web.Mvc;
using MikesDotnetting.Models;

namespace MikesDotnetting.Controllers
{
public class ArticleController : BaseController
{
private IArticleRepository repository;

public ArticleController()
: this(new ArticleRepository())
{ }

public ArticleController(IArticleRepository rep)
{
repository = rep;
}


// GET: /Article/

public ActionResult Index()
{
HomePageViewModel viewdata = new HomePageViewModel(repository.GetFrontPageSummaries(),
repository.GetMostPopular());
return View(viewdata);
}
}
}

You can see that a new instance of HomePageViewModel is instantiated by passing in two types: a collection of ArticleSummary objects, and a collection of ArticleTitle objects. This is then passed into the Index View as a strongly-typed model. To allow the Index View to play with the strogly typed view model, the @ Page directive in the Index View needs to be changed to inherit from the ViewPage<T> class, specifying the correct type, which is HomePageViewModel:
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" 
Inherits="System.Web.Mvc.ViewPage<HomePageViewModel>" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Mikesdotnetting: on ASP.NET and Web Development
</asp:Content>

<
asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">


</asp:Content>

Both of the PartialView controls need to be changed to make sure they inherit from their respective ViewUserControl<T> types:
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<ArticleTitle>>" %>

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<ArticleSummary>>" %>

The first one will hold the ArticleTitle collection (the Top Stories) and the second one will feature the ArticleSummary collection. Now it's simply a case of referecning the PartialView controls within the Index View, and then to work within each PartialView to display the contents of their respective models:
Index View:
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" 
Inherits="System.Web.Mvc.ViewPage<HomePageViewModel>" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Mikesdotnetting: on ASP.NET and Web Development
</asp:Content>

<
asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

<% Html.RenderPartial("ArticleTitlePartial", this.ViewData.Model.MostPopular); %>
<h2>Latest Entries</h2>
<% Html.RenderPartial("ArticleSummaryPartial", this.ViewData.Model.ArticleSummaries); %>


</asp:Content>

The Html.RenderPartial() method in this case takes a couple of parameters - the PartialView to render, and the model to apply to the PartialView. If you recall, the Index View possesses a HomePageViewModel object as its Model. This itself has two properties - ArticleSummaries and MostPopular. this.ViewData.Model.MostPopular refers to the MostPopular property and passes it as the model to the Partial View. The same thing happens with the ArticleSummaries property and its Partial View. Since the code in the ArticleTitlePartial is shorter, and therefore clearer. I shall only review that below:
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<ArticleTitle>>" %>
<div id="popular">
<
h2>Most Popular</h2>

<%{
foreach (var item in Model)
{%>
<p>
<%=Html.RouteLink(
item.Head,
"Details",
new
{
controller = "Article",
action = "Details",
id = item.ID
})%>
</p>
<% }
}%>
</div>

Each item in the Model is iterated over and turned into an Html.RouteLink. The arguments in this case are the text for the link, the name of the route and the items that build up the link itself, which will render as Article/Detals/{id}. Hovering over Model in var in foreach (var item inModel) reveals it to be of type IEnumerable<ArticleTitle>.

Summary

We constructed a custom ViewModel to hold the data for the Index View. It comprises two strongly typed collections. Each collection is passed to its PartialView, which in turn has been set up to expect a collection of that type. If I wanted to add more data to the Index View, all I need to do is to add further properties to the HomePageViewModel class to cater for the new data. Using strongly typed Custom ViewModels means that I do not have to cast the models to the correct type when working with them, and I do not have to use a string-based dictionary key to reference parts.