Pass Multiple Models And Anonymous Types To View in ASP.NET MVC

Developers new to ASP.NET MVC often ask these questions:

  • How do I pass multiple models to a view?
  • How do I pass anonymous objects to a view?

This article provides a solution to both of these questions. Before we discuss the solution, let's quickly understand these questions and possible solutions in brief.

How do I pass multiple models to a view?

Consider a case where you have two model classes - Customer and Order. ASP.NET MVC allows you to pass only ONE model to a view. You typically do that by passing the model as a View() method parameter. This means a view can be bound with one and only one model. So, in the preceding example a view can either display Customer data or Order data. What if a view requires to display both? You can tackle such a situation as follows:

  • Create a new view model class, say CustomerOrder, that has two properties. One for holding Customer data and other for holding Order data.
    public class CustomerOrder
    {
      public List<Customer> Customers{get; set;}
      public List<Order> Orders{get; set;}
    }
  • Pass one of the model as the View() method parameter and other(s) through ViewData or ViewBag.
  • Use ExpandoObject to create a dynamic model.

The first two approaches are quite straightforward and hence I won't discuss them here. Out of these three the first solution (custom view model class) is a recommended approach to deal with the situation. However, it calls for creating of a new view model class (POCO). The second one does the job but the view can't be strongly typed because models passed through ViewData or ViewBag are not part of the Model property of the view. The third approach is the topic of this article and is discussed in detail in the later part of this article.

 How do I pass anonymous objects to a view?

Sometimes you need to write LINQ to Entities queries that return data filled in an anonymous type. Consider the following example:

var query = from c in db.Customers
            join o in db.Orders
            on c.CustomerID equals o.CustomerID
            orderby c.CustomerID ascending
            select new { 
                c.CustomerID,
                c.CompanyName,
                o.OrderID,
                o.OrderDate 
            };

Here, you are not selecting Customer objects (select c). You are creating an anonymous type that has four properties - CustomerID, CompanyName, OrderID and OrderDate. Although this LINQ query works without any problem, the problem is you can't pass anonymous types to an ASP.NET MVC view. If you try to do so, you will see a runtime error. Of course, just like previous case you can create a new view model (POCO with these four properties) and project your data in it. You can then pass the collection to the View() method. As before this is a recommended solution but again you need create an additional class. If you wish to avoid doing that for some reason, here also you can resort to ExpandoObject. You can store the realized data in an ExpandoObject and then pass it to the view.

ExpandoObject to the rescue

The ExpandoObject class resides in System.Dynamic namespace and represents an object whose members can be dynamically added and removed at run time. In our specific cases an ExpandoObject can act as a container for multiple pieces of data that you wish to pass as a model to a view. So, in a way ExpandoObject is a "dynamic" alternative to a manually created view model.

Storing data in an ExpandoObject is straightforward.

dynamic model = new ExpandoObject();
model.Customers = <some_data_here>;
model.Orders = <some_data_here>;

The above code creates an instance of ExpandoObject class. Notice that the instance is stored in a variable of type dynamic. The code then assigns Customers and Orders properties of the ExpandoObject to some data. You should be familiar with this usage because ViewBag uses the same "dynamic" mechanism to store values.

Ok. Now that you know what an ExpandoObject is and how to use it, let's put it to use in our specific situations.

Let's assume that you have two model classes - Customers and Orders - as a part of Entity Framework data model. You wish to pass both of them to a view. So, you can write something like this in an action method:

public ActionResult Index()
{
    NorthwindEntities db=new NorthwindEntities();

    dynamic model = new ExpandoObject();
    model.Customers = db.Customers.ToList();
    model.Orders = db.Orders.ToList();
    return View(model);
}

Notice the code marked in bold letters. The code creates an ExpandoObject and assigns two properties on it - Customers and Orders. These properties store a generic List of the respective model objects. The ExpandoObject is then passed to the View() method.

You can use  the ExpandoObject in the view as shown below:

@model dynamic

...
<h1>List of Customers</h1>
<div>
  @foreach(var item in Model.Customers)
  {
    <h2>@item.CustomerID</h2>
  }
</div>

Notice that the view is marked to use a dynamic model using the @model dynamic line. This line is optional. If you don't write any @model then Razor defaults to dynamic. The foreach loop then iterates through Model.Customers property and displays all the CustomerIDs. Note that since Model points to an ExpandoObject, you won't get any IntelliSense help for the property names. You will need to key them in yourself. On the same lines you can display Model.Orders data.

Store anonymous objects in an ExpandoObject

In the preceding example you stored List of Customer and Order objects respectively in the model ExpandoObject. Now let's see how to store an anonymous object in an ExpandoObject.

var addtionalInfo = new { 
                    CustomersNote="This is a customer note",
                    OrdersNote="This is an order note" };
IDictionary<string, object> 
expandoAddInfo = new ExpandoObject();
foreach (PropertyDescriptor property 
         in 
         TypeDescriptor.GetProperties(addtionalInfo.GetType()))
{
    expandoAddInfo.Add(property.Name, 
           property.GetValue(addtionalInfo));
}
model.AdditionalInfo = expandoAddInfo;

The code creates an anonymous object with two properties - CustomersNote and OrdersNote - and stores it in additionalInfo variable. Then an ExpandoObject is created. This time, however, it is captured as an IDictionary<string, object>. Then a foreach loop iterates through all the properties of additionalInfo object. This is done using PropertyDescriptor and TypeDescriptor classes from System.ComponentModel namespace. These classes allow you to reflect upon the properties of any object (additionalInfo in this case). All the properties from the anonymous object are transferred to the ExpandoObject by adding them to the expandoAddInfo variable. Finally, AdditionalInfo property of model (which itself is an ExpandoObject) is set to expandoAddInfo ExpandoObject.

You can pass the model object to the view exactly as before. Once received in the view you can access the AdditionalInfo property as shown below:

<h1>Additional Information</h1>
<h2>@Model.AdditionalInfo.CustomersNote</h2>
<h2>@Model.AdditionalInfo.OrdersNote</h2>

What if you wish to convert a collection of anonymous objects to an ExpandoObject? The following code shows how this can be done:

var query = from c in db.Customers
            join o in db.Orders
            on c.CustomerID equals o.CustomerID
            orderby c.CustomerID ascending
            select new { 
                c.CustomerID,
                c.CompanyName,
                o.OrderID,
                o.OrderDate 
            };

List<ExpandoObject> joinData=new List<ExpandoObject>();

foreach(var item in query)
{
    IDictionary<string, object> itemExpando = new ExpandoObject();
    foreach (PropertyDescriptor property 
             in 
             TypeDescriptor.GetProperties(item.GetType()))
    {
        itemExpando.Add(property.Name, property.GetValue(item));
    }
    joinData.Add(itemExpando as ExpandoObject);
}

model.JoinData = joinData;

The above code is similar to the previous case but deals with a List of ExpandoObject (joinData variable). Each ExpandoObject (itemExpando variable) holds data belonging to a single anonymous object. The joinData List is then assigned to the JoinData property of the model ExpandoObject.

You can access JoinData inside a view as shown below:

<h1>Customer Orders Join Data</h1>
<div>
    @foreach (var item in Model.JoinData)
    {
        <h2>@item.CustomerID (@item.OrderID on @item.OrderDate)</h2>
    }
</div>

A note of caution about using ExpandoObject as shown above - Although ExpandoObject solves our problem, one should carefully evaluate whether there is any flaw in the model creation process. You should give a thought to creating POCOs for holding the multiple pieces instead of using an ExpandoObject. Since ExpandoObject instances are dynamic Visual Studio IntelliSense won't help much. POCO containers, on the other hand, give all the benefits of strongly typed view.

That's it for today! Keep coding!!


Bipin Joshi is an independent software consultant and trainer by profession specializing in Microsoft web development technologies. Having embraced the Yoga way of life he is also a meditation teacher and spiritual guide to his students. He is a prolific author and writes regularly about software development and yoga on his websites. He is programming, meditating, writing, and teaching for over 27 years. To know more about his ASP.NET online courses go here. More details about his Kriya and Meditation online course are available here.

Posted On : 18 February 2015


Tags : ASP.NET LINQ MVC C#