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 in Model) 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.

Date Posted: Monday, May 11, 2009 7:06 AM
Last Updated: Friday, October 10, 2014 9:08 PM
Posted by: Mikesdotnetting
Total Views to date: 262514

27 Comments

Thursday, May 21, 2009 9:04 AM - hoangdung, le

Good effort!

But I prefer uptyped view page, it's easy to use and cost me less time to implement.

Thank you anyway!

Monday, May 25, 2009 7:22 PM - Mary

I was just playing around with this myself and discovered that instead of creating a new class to contain your strongly typed classes, i.e. your HomePageViewModel class, you can just reference your strongly typed partial views directly from your untyped Index view like so:

<%@ 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", this.ViewData["mostpopular"]); %>

<% Html.RenderPartial("ArticleSummaryPartial", this.ViewData["summaries"]); %>

</asp:Content>


Monday, May 25, 2009 8:10 PM - Mike

@Mary,

Yes, but you are using a weakly typed ViewDataDictionery. If you mis-spell "mostpopular" or "summaries", you will get errors. That's part of the point of the article. Granted, on a site like mine, weakly typed ViewData collections are probably fine because there is only me working on it, and there's not much code. However, if you have a team specifically working on the UI (Views) and another one working on the models, that's where the strongly typed approach comes to the fore.

Wednesday, July 8, 2009 9:29 PM - Andrew

Excellent post, this really helped me clarify the separation of concerns between data and the model. The custom classes for summary information makes the code a lot easier to manage when you have large EF classes.

Would you consider adding in a web service wrapper around your model?

Thursday, July 9, 2009 8:47 AM - Mike

@Andrew

I can't think of any good reason to add web services into the mix. MVC is a design pattern that is mainly about the UI. Web Services have no UI. I wouldn't use web services generally unless I was exposing services to third parties - although they do also have an application in automatically generating JSON response within a web forms app for javascript to consume.

Friday, July 31, 2009 3:14 PM - Gaz

Very good explanation, I use strongly typed view models because they are easier to work with in the long run and much more organized. You also get full intellisense in your view with a strongly typed model.

Wednesday, September 2, 2009 12:01 PM - abhinav

Thanks ! very helpful

Monday, October 5, 2009 9:25 AM - donna

hi. can i get the full source code for creating a tag cloud? would appreciate if you could give it to me. as i am not good at IT-related stuffs, and am doing my final year project on tag cloud, silverlight application (dynamic silverlight carousel) and mobile view. i am using visual studio and sharepoint to do all these. i've actually got a code sample for tagcloud-silverlight application, however, it cannot run in my sharepoint site. it is static.

Monday, October 5, 2009 3:53 PM - Mike

Wednesday, November 25, 2009 5:41 AM - C.T.

i found this pattern.

http://stackoverflow.com/questions/768236/how-to-create-a-strongly-typed-master-page-using-a-base-controller-in-asp-net-mvc/1794863#1794863

Friday, December 25, 2009 9:13 AM - Jasurbek

This article is very usefull for me. Thanks

Wednesday, March 3, 2010 3:13 AM - lamvapnguyen

This post is very useful, please sent for me this code!

Thursday, March 4, 2010 10:42 PM - Lucian Maran

The best article on that subject!

Tuesday, April 27, 2010 2:45 PM - Pit

How does the ArticleTitle class is visible in ArticleTitles partial view - as IEnumerable generic?

Friday, June 18, 2010 2:39 AM - Mark

Nice post! I am doing something very similar, but I've been struggling with problem. Like you, my Index view renders two partial views. The first view has a form that collects filter parameters so that when the user clicks 'Get Data' the parameters are passed to the second view which displays a grid of data. Rather than use the RenderPartial method, however, I'm using the RenderAction method. The action takes the filter parameters and gets the appropriate data used to render the grid view.

The problem that I've been having has been keeping the URL in sync with the filter parameters. Although both forms work, the URL in the address bar doesn't always contain the latest filter parameters. This is a problem because the page with all of it's parameters can't be reliably bookmarked.

Saturday, June 19, 2010 8:48 AM - Mike

@Mark

As a general point, if you want your filter criteria to appear within the URL so that the result can be bookmarked, you will need to create the right URL and redirect to it. As to your specific problem, I would suggest posting it with a bit more detail on the forums as www.asp.net.

Sunday, June 27, 2010 8:29 PM - David

Many thanks for this article - very clear and concise.

I am also exploring the architecture options mode available with EF4, MVC2 etc...

My largest headache, is whether to create custom Entities in the Entity Model, or seperate classes in a ViewDataModel as shown here.

Have you encountered any situations where the Partial Views, bound to ViewDataModel classes are actually not only displaying data, but also being used for edits too? Do you feel that is a viable approach? Interested in hearing your thoughts!

Wednesday, November 3, 2010 5:21 PM - Demetrios

In skimming through the comments, I did not see a mention of this so I thought I would share ... one of the greatest benefits of type safety (to me, especially when dealing with code embedded in markup and large projects) is not having to worry about missing any references to an untyped property key (e.g., "summaries") anywhere in your code. If you make a code-breaking change to a key, you won't know the code is broken until runtime. With strong-typed properties, you will know instantly.

Thanks for the article, Mike!

Thursday, November 18, 2010 3:18 PM - Muneeb

Hello,

I just wanted to know one thing. Here we passed IEnumerable objects to the partial views. What would you suggest if we had to pass a custom object? Creating a custom view model class for each partial view? This means a CustomViewModel class for each partial view ( + one custom vm class for the master page itself) ?

Is that the way to go? I may end up with dozen classes :/

Thursday, November 18, 2010 8:13 PM - Mike

@Muneeb

That is the common approach.

Friday, December 10, 2010 4:22 AM - Rob

Hey Mike,
I Love this article. Pretty much this article alone covers 50 % of MVC :)

Would you be able to write an article on rendering parent child data using partial views? In other words If I want to use ArticleSummary Model to display some of the articles group by Category, along with the main content.

Cat 1
=====
Art 1
Art 2
Art 3

Cat 2
===
Art 7
Art 8

To be precise...let's say home page divided into 2 sections, one for main content [ display all articles by date ] and right side display categorized articles ( as shown above )


Friday, May 6, 2011 8:49 AM - vinay

nice article

Tuesday, August 21, 2012 7:01 AM - salman

Good article

Tuesday, September 25, 2012 9:45 AM - manoj

Good One.

Wednesday, September 26, 2012 11:40 PM - Aaron

great article! are the class definitions for Article, Category, etc. available somewhere? Thanks!

Monday, January 7, 2013 10:50 AM - Martinez

Question regarding first part of article (weakly typed models):

How asp engine knows that calling this line:

<% Html.RenderPartial("ArticleTitlePartial"); %>

it should call appriopriate method from ArticleController? If I would change name of this controller to XXXControler, where would I have to make corresponsing change for this to work correctly?

Thursday, January 17, 2013 8:11 AM - Mike

@Martinez

The RenderPartial doesn't result in a call to a controller action. The controller action has already been called and passed data to the view, either as ViewData/ViewBag or as a strongly typed ViewModel. That data is then made available to the Partial by the parent View.
Add your comment

If you have any comments to make about this article, please use this form to do so. Make sure that your comment relates specifically to the article above. More general comments can be posted through the form on the Contact page.

Please note, all comments are moderated, and some may not be published. The kind of things that will ensure your comment is deleted without ever seeing the light of day are as follows:

  • Not relevant to the article
  • Gratuitous links to your own site or product
  • Anything abusive or libellous
  • Spam
  • Anything in a language I don't understand including gibberish.

I do not pass email addresses on to spammers, so a valid one will assist me in responding to you personally if required.