Multiple Models in a Single View (C# MVC3)

2019-01-11 04:45发布

问题:

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?

回答1:

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



回答2:

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.



回答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).



回答4:

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>


回答5:

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.



回答6:

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>
    }