views:

488

answers:

6

My terminology is probably way off here but basically I'm trying to learn the ropes of ASP.NET MVC and so far so good (largely thanks to Phil Haack and Scott Hanselman's tutorials). One thing which I haven't seen addressed yet is the 'right' or most elegant way to pass multiple data models to a view. To help put the question in context, take this example:

Say I was making a blog. When I log in I want the home screen to display a list of all new unapproved comments, as well as a list of recently registered users, and a list of the most recently submitted blog posts.

Most discussions I've seen suggest strongly-typing the view page so it can be called with something like "return View(RecentComments)" and iterate through the comments in the view, or to cast the data model like "var NewUsers = (MembershipUserCollection) ViewData.Model". What I'm ideally after is the 'right', or at least a 'right-enough', way of passing multiple models while still maintaining appropriate logic separation.

+1  A: 

Sadly, the only way to accomplish passing multiple objects is either by creating an object with both objects as fields/properties, or by using a weakly typed array.

David Pfeffer
+2  A: 

The way to pass multiple models to a view is to create what we call a Form View Model which has your other models within it.

Then in your view you can pass the individual models contained in your Form View Model to the Partial Views responsible for rendering the data in said models.

Makes sense?

edit

btw: a form view model is simply a class. it's not a special type as may have been suggested by my answer.

griegs
The term is `ViewModel` He called it a `FormViewModel` because the user was filling out a `Form`. If you're not filling out a form, then it's just a `ViewModel` and that allows for strongly typed objects to be passed around instead of magic strings.
George Stocker
Nice one @George.
griegs
+6  A: 

One way is to create a new type that encapsulates both pieces of model data:

public class MyBigViewData {
    public SubData1 SubData1 { get; set; }
    public SubData2 SubData2 { get; set; }
}

public class SubData1 {
    ... more properties here ...
}

public class SubData2 {
    ... more properties here ...
}

Another way is to store the "main" model data as the strongly-typed data and store other data in the view data as dictionary items:

ViewData["username"] = "joe"; // "other" data
ViewData["something"] = "whatever"; // "other" data
ViewData["subdata1"] = new SubData1(...);
return View(myRealModelData);

The advantage of the second approach is that you don't need to do anything special at all: It works right out of the box.

Eilon
I would do the simplest thing that could work here - so store the data in the ViewData collection. Easy.
Jaco Pretorius
The problem with this approach is that you might end up with a proliferation of view-models for each view. Some argue that this is how it should be, and in a perfect world I agree, but often times this degree of customization exceeds the requirements and can actually make the project more difficult to comprehend. A more rapid, but **potentially risky** approach, would be to use my answers below with your domain entities. In some environments this is unacceptable. For example, if you outsource your view development you might not want to make your entities visible or invokable from the view.
gWiz
+3  A: 

What I've done in the past is written a class that contained instances of both the classes I will need on the view.

ie

public class City{
public Mall TheMall;
public School TheSchool;
}

Then your view will be strongly typed as City, and you will use Model.TheMall.Property and Model.TheSchool.Property to access what you need

EDIT

This is an example of what other posters mean by creating an object with both objects as fields/properties

splatto
+1  A: 

After working on a large ASP.NET MVC app, I found the approach that was most productive while minimizing runtime casting was based on using generics to mimic the nested structure of views. Essentially views get their own data type. Typically these are either domain objects, or collections of domain objects that include metadata. Generic versions of these types are available across all possible master pages, taking a type parameter that defines data relevant to the master page.

public class Car {
  // can be used as a model
} 

public class CarCollection: Collection<Car> {
  public BodyTypes BodyType {get;set;}
  public Colors Color {get;set;}
  // can also be used as a model
}

public interface ILayoutModel<TLayout> {
  TLayout LayoutModel {get;set;}
}

public class CarView<TLayout>: Car, ILayoutModel<TLayout>  {
  // model that can be used with strongly-typed master page
}

public class CarCollection<TLayout> : CarCollection, ILayoutModel<TLayout> {
  // model that can be used with strongly-typed master page
}

public class LayoutAData {
  // model for LayoutA.master
}

public class LayoutBData {
  // model for LayoutB.master
}

It's also possible to invert the generic-ness, but since the view dictates the layout, the view data should dominate over the layout data in my opinion. LayoutA.master would derive from ViewMasterPage<ILayoutModel<LayoutAData>> and LayoutB.master would derive from ViewMasterPage<ILayoutModel<LayoutBData>>. This keeps the view data and the layout data separate, in a consistent, strongly-typed and flexible way.

gWiz
+1  A: 

In addition to my other answer, another way to do this would be not to strongly-type the view and master pages in the page directive, but instead make use of the generic type-based ViewData extensions from MVC Contrib. These extensions basically use the fully-qualified type name as the ViewData dictionary key. Effectively, the typing benefits are the same as the strongly-typed page approach, with less class overhead in terms of the number of view model classes required. Then in your actions you do

ViewData.Add<Car>(car);
ViewData.Add<LayoutAData>(layoutAData);

and in the views you do

<%= ViewData.Get<Car>().Color %>

and in the master page you do

<%= ViewData.Get<LayoutAData>().Username %>

You could cache these Get<> calls inline in the views to mitigate the cost of casting multiple times.

gWiz