14

I'm using C# and MVC3.

I have a page, for example a Student list, that displays the list of students, which is database driven. At the same time my menu is database driven, so I also have to send it to the view.

How can I send both models to a single view?

Hannele
  • 9,301
  • 6
  • 48
  • 68
czetsuya
  • 4,773
  • 13
  • 53
  • 99

6 Answers6

14

You should always create separate ViewModels for your views. There should be an abstraction from your Views to your Domain Models. In the demos/tutorials they show it all pretty and easy by simply strongly typing the Views to Domain Models but that's not a good strategy. The views should not be dependent on the business objects.

You should implement David Glenn's proposed solution for your current scenario and also for all other views even if requires mapping the domain model to to another view model class.

EDIT:

If you have lets say a top Menu > TopMenu.aspx And you have multiple partial views inside it > StudentMenu.ascx, ResultMenu.ascx

You will create a View Model for Top Menu > TopMenuViewModel.cs And you will also create view models for partial views > StudentMenuViewModel , ResultMenuViewModel etc.

and your TopMenuViewModel will have both >

class TopMenuViewModel 
{
   //all the stuff required in TopMenu.aspx
   StudentMenuViewModel studentvm;
   ResultMenuViewModel resultvm;
}

and in TopMenu.aspx when rendering the partial you will pass the relevant view model >

Html.RenderPartial('StudentView', Model.studentvm)

Hope it makes sense

neebz
  • 11,465
  • 7
  • 47
  • 64
  • Hi, Thanks for your response. Do you mean here: http://www.asp.net/? demos/tutorials? – czetsuya Apr 23 '11 at 12:18
  • Yup mostly but if you are building a large project, you should always be abstracting your domain objects and view models. – neebz Apr 23 '11 at 12:22
  • Hi thanks for the tip, that's the plan from the start to separate the domain models. But I get confuse on how will I send the model objects from the controller, specially when I divided my page into several partial views. And some partial views depend on certain models. For example in my partial view menu, assuming I have a MenuViewModel. Add the MenuViewModel in a match bigger model CompilationViewModel (menu, students, etc), how will I access that particular view model in a given view? Or much worse access 2 view models in a single view? Thanks. – czetsuya Apr 23 '11 at 12:31
  • Hi, thanks for the sample code. It sure is helpful, I just thought I could get away without passing the model from the main view, just access the view model directly from the partial view. – czetsuya Apr 23 '11 at 12:46
  • I am afraid I don't get your point. You need to pass an object to your PartialView whether you render it from a main page or a controller. (also would be nice for other users with the same problem if you select an answer from the replies you get on your questions) – neebz Apr 23 '11 at 12:51
  • Btw, what if I want to pass a list? Example MenuViewModel is actually a list? – czetsuya Apr 23 '11 at 12:56
  • You could pass `IEnumerable` instead of a single `MenuViewModel` – neebz Apr 23 '11 at 13:36
10

You can create a ViewModel which is a representation of your view and not your business model

public class StudentPage {

  public IEnumerable<Student> Students { get; set; }

  public Menu Menu { get; set; }

}

Your controller then returns the ViewModel to your view

public ViewResult Students() {

   var menu = GetMenu();
   var students = Repository.Students();

   var model = new StudentPage {
     Menu = menu,
     Students = students
   }

   return View(model);

}

I'm assuming the menu is a reoccurring feature on your pages so you probably want to break it down a bit like

public class BasePage {

  public Menu Menu { get; set; }

}

public class StudentPage : BasePage {

  public IEnumerable<Student> Students { get; set; }

}

and you could also create a base controller that has the GetMenu() functionality to re-use across multiple controllers.

David Glenn
  • 24,412
  • 19
  • 74
  • 94
  • Hi, Thanks for the reply. I'm actually doing that having a base class controller that have the Menu definition (on OnActionExecuting). My question is do I always have to define this? var model = new StudentPage { Menu = menu, Students = students } return View(model); The problem is, I might have another entity say Teacher then I'll have the TeacherPage which is (teacher + menu)? Btw, how to I distinguish the 2 models in the view? Sorry newbie here. – czetsuya Apr 23 '11 at 12:11
  • @czetsuya: It does end up being the most flexible if you have [one view model per view](http://lostechies.com/jimmybogard/2009/06/30/how-we-do-mvc-view-models/). So in the case of having a teacher page, yes, you would also have a `TeacherPage` class as your view model which you would instantiate in the controller action for that view, and return as your model. Hope that helps. – ataddeini Apr 23 '11 at 12:51
  • Can we use two partial views that contains one model in the main page? And What is difference from the above?Thanks – AliRıza Adıyahşi Apr 18 '12 at 08:06
3

Note: Classes below should be used in .net 3.5 and lower, because .net 4 introduced as similar class called Tuple and should be used instead.

MultiObject<O1, O2, ..> and MultiList<L1, L2, ...>

This is how I write such controller actions and views:

public ActionResult MultiModel()
{
    MultiList<User, Company> result = MultiList.New(
        this.repository.GetUsers(),
        this.repository.GetCompanies()
    );
    return View(result);
}

And my view is of type:

ViewPage<MultiList<User, Company>>

I'm using this reusable convenience class:

#region MultiObject static helper class

/// <summary>
/// Provides static methods for creating multi objects with type inference.
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Multi")]
public static class MultiObject
{
    /// <summary>
    /// Creates a new <see cref="MultiObject{T1, T2}"/> object instance.
    /// </summary>
    /// <typeparam name="T1">The type of the first object.</typeparam>
    /// <typeparam name="T2">The type of the second object.</typeparam>
    /// <param name="first"><typeparamref name="T1"/> object instance.</param>
    /// <param name="second"><typeparamref name="T2"/> object instance.</param>
    /// <returns>
    /// Returns a <see cref="MultiObject{T1, T2}"/> of <typeparamref name="T1"/> and <typeparamref name="T2"/> object instances.
    /// </returns>
    public static MultiObject<T1, T2> New<T1, T2>(T1 first, T2 second)
    {
        return new MultiObject<T1, T2>(first, second);
    }

    /// <summary>
    /// Creates a new <see cref="MultiObject{T1, T2, T3}"/> object instance.
    /// </summary>
    /// <typeparam name="T1">The type of the first object.</typeparam>
    /// <typeparam name="T2">The type of the second object.</typeparam>
    /// <typeparam name="T3">The type of the third object.</typeparam>
    /// <param name="first"><typeparamref name="T1"/> object instance.</param>
    /// <param name="second"><typeparamref name="T2"/> object instance.</param>
    /// <param name="third"><typeparamref name="T3"/> object instance.</param>
    /// <returns>
    /// Returns a <see cref="MultiObject{T1, T2, T3}"/> of <typeparamref name="T1"/>, <typeparamref name="T2"/> and <typeparamref name="T3"/> objects instances.
    /// </returns>
    public static MultiObject<T1, T2, T3> New<T1, T2, T3>(T1 first, T2 second, T3 third)
    {
        return new MultiObject<T1, T2, T3>(first, second, third);
    }
}

#endregion

#region MultiObject<T1, T2>

/// <summary>
/// Represents a 2-multi object, or pair.
/// </summary>
/// <typeparam name="T1">The type of the multi object's first component.</typeparam>
/// <typeparam name="T2">The type of the multi object's second component.</typeparam>
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Multi")]
public class MultiObject<T1, T2>
{
    /// <summary>
    /// Gets or sets the value of the first multi object component.
    /// </summary>
    /// <value>The first.</value>
    public T1 First { get; set; }

    /// <summary>
    /// Gets or sets the value of the second multi object component.
    /// </summary>
    /// <value>The second multi object component value.</value>
    public T2 Second { get; set; }

    /// <summary>
    /// Initializes a new instance of the <see cref="MultiObject{T1, T2}"/> class.
    /// </summary>
    /// <param name="first">Multi object's first component value.</param>
    /// <param name="second">Multi object's second component value.</param>
    public MultiObject(T1 first, T2 second)
    {
        this.First = first;
        this.Second = second;
    }
}

#endregion

#region MultiObject<T1, T2, T3>

/// <summary>
/// Creates a new 3-multi object, or triple.
/// </summary>
/// <typeparam name="T1">The value of the first component of the multi object.</typeparam>
/// <typeparam name="T2">The value of the second component of the multi object.</typeparam>
/// <typeparam name="T3">The value of the third component of the multi object.</typeparam>
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Multi")]
[SuppressMessage("Microsoft.Design", "CA1005:AvoidExcessiveParametersOnGenericTypes")]
public class MultiObject<T1, T2, T3> : MultiObject<T1, T2>
{
    /// <summary>
    /// Gets or sets the value of the third multi object component.
    /// </summary>
    /// <value>The third multi object component value.</value>
    public T3 Third { get; set; }

    /// <summary>
    /// Initializes a new instance of the <see cref="MultiObject{T1, T2, T3}"/> class.
    /// </summary>
    /// <param name="first">Multi object's first component value.</param>
    /// <param name="second">Multi object's second component value.</param>
    /// <param name="third">Multi object's third component value.</param>
    public MultiObject(T1 first, T2 second, T3 third)
        : base(first, second)
    {
        this.Third = third;
    }
}

#endregion

Any when I have to pass multiple lists

#region MultiObject static helper class

/// <summary>
/// Provides static methods for creating multi objects with type inference.
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Multi")]
public static class MultiList
{
    /// <summary>
    /// Creates a new <see cref="MultiObject{T1, T2}"/> object instance.
    /// </summary>
    /// <typeparam name="T1">The type of the first object.</typeparam>
    /// <typeparam name="T2">The type of the second object.</typeparam>
    /// <param name="first"><typeparamref name="T1"/> object instance.</param>
    /// <param name="second"><typeparamref name="T2"/> object instance.</param>
    /// <returns>
    /// Returns a <see cref="MultiObject{T1, T2}"/> of <typeparamref name="T1"/> and <typeparamref name="T2"/> object instances.
    /// </returns>
    public static MultiList<T1, T2> New<T1, T2>(IList<T1> first, IList<T2> second)
    {
        return new MultiList<T1, T2>(first, second);
    }

    /// <summary>
    /// Creates a new <see cref="MultiObject{T1, T2, T3}"/> object instance.
    /// </summary>
    /// <typeparam name="T1">The type of the first object.</typeparam>
    /// <typeparam name="T2">The type of the second object.</typeparam>
    /// <typeparam name="T3">The type of the third object.</typeparam>
    /// <param name="first"><typeparamref name="T1"/> object instance.</param>
    /// <param name="second"><typeparamref name="T2"/> object instance.</param>
    /// <param name="third"><typeparamref name="T3"/> object instance.</param>
    /// <returns>
    /// Returns a <see cref="MultiObject{T1, T2, T3}"/> of <typeparamref name="T1"/>, <typeparamref name="T2"/> and <typeparamref name="T3"/> objects instances.
    /// </returns>
    public static MultiList<T1, T2, T3> New<T1, T2, T3>(IList<T1> first, IList<T2> second, IList<T3> third)
    {
        return new MultiList<T1, T2, T3>(first, second, third);
    }
}

#endregion

#region MultiList<T1, T2>

/// <summary>
/// Represents a 2-multi object, or pair.
/// </summary>
/// <typeparam name="T1">The type of the multi object's first component.</typeparam>
/// <typeparam name="T2">The type of the multi object's second component.</typeparam>
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Multi")]
public class MultiList<T1, T2> : MultiObject<IList<T1>, IList<T2>>
{
    /// <summary>
    /// Initializes a new instance of the <see cref="MultiList&lt;T1, T2&gt;"/> class.
    /// </summary>
    /// <param name="first">The first.</param>
    /// <param name="second">The second.</param>
    public MultiList(IList<T1> first, IList<T2> second) : base(first, second) { }
}

#endregion

#region MultiList<T1, T2, T3>

/// <summary>
/// Creates a new 3-multi object, or triple.
/// </summary>
/// <typeparam name="T1">The value of the first component of the multi object.</typeparam>
/// <typeparam name="T2">The value of the second component of the multi object.</typeparam>
/// <typeparam name="T3">The value of the third component of the multi object.</typeparam>
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Multi")]
[SuppressMessage("Microsoft.Design", "CA1005:AvoidExcessiveParametersOnGenericTypes")]
public class MultiList<T1, T2, T3> : MultiObject<IList<T1>, IList<T2>, IList<T3>>
{
    /// <summary>
    /// Initializes a new instance of the <see cref="MultiList&lt;T1, T2, T3&gt;"/> class.
    /// </summary>
    /// <param name="first">The first.</param>
    /// <param name="second">The second.</param>
    /// <param name="third">The third.</param>
    public MultiList(IList<T1> first, IList<T2> second, IList<T3> third) : base(first, second, third) { }
}

#endregion

Data for every view

But in your case where you want to pass a menu it's the best to have a base page class that all your pages inherit from and that page class provides all common properties (menu data being one).

Robert Koritnik
  • 103,639
  • 52
  • 277
  • 404
  • Hi Robert, Is there benefit of your MultiObject over Tuple and your MultiList over Tuple,List... – Surjit Samra Nov 13 '11 at 20:43
  • @SurjitSamra: The main reason I wrote these classes was because I needed them in .net 3.5. `Tuple` came in .net 4 so... But apart from that, `MultiObject` has no advantage, but `MultiList` has brevity reasons. In .net 4 I wouldn't use them anyway... – Robert Koritnik Aug 09 '12 at 08:09
2

To handle multiple model in a single view, I personally use ViewBag.

Please note that if you use ViewBag, all the help you get from the compiler is disabled and runtime errors/bugs will occur more likely than if the property has been on a "normal" object and typos would be catched by the compiler.

That is the disadvantage of using dynamic objects, however, there are many other advantages. In your controller, you just need to pass the data/model into the ViewBag:

public ActionResult Index() {
            ViewBag.TopMenu = TopMenu();
            ViewBag.Student = Student();
            return View();
        }

Then in the view, call them out:

@{
    ViewBag.Title = "Index_ViewBag";
}

<h2>Index View Bag</h2>

<table>
   <tr>
   @foreach (var menu in ViewBag.TopMenu) 
   {
      <td>
      <a href="@menu.URL">@menu.Name</a>
      </td>
   }
   </tr>
</table>

<p>
 <ul>
  @foreach (var student in ViewBag.Student) 
  {
   <li>
    <a href="@student.URL">@student.Name</a>
   </li>   
  }
 </ul>
</p>
onedevteam.com
  • 3,838
  • 10
  • 41
  • 74
DragonZelda
  • 168
  • 1
  • 2
  • 12
0

There is another option for this, that gets slated by some MVC purists, but I find that it works well for me. Instead of including the two models on every page where you have a 'menu', (which I am assuming is nearly all the pages), you can display your menu like so, from within your student view:

@Html.RenderAction("Menu");

Which will call to it's own action, generating the menu viewmodel and 'Menu' partial view.

To me, this makes sense, but I know a lot of folk don't like it.

Paddy
  • 33,309
  • 15
  • 79
  • 114
0

In the .Net Framework 4.0 you can use dynamic models.

Roughly:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        dynamic viewmodel = new ExpandoObject();
        viewmodel.Students = MyStudent();
        viewmodel.MenuItems = MyMenuItems();
        return View(mymodel);
    }
}

How to access in view code:

@model dynamic

@foreach (Student student in Model.Students)
    }
        <h1>@student.Name</h1>
    }
@foreach (MenuItem menuItem in Model.MenuItems)
    {
        <h1>@menuItem.menuname</h1>
    }
Lars
  • 9,976
  • 4
  • 34
  • 40