7

I've read similar questions on SO but can't seem to figure out this issue, which is specific to DBContext objects (I think). Here's some dummy code to illustrate.

I have the following code in my Index() action:

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

        return View(db.Dogs);
    }

I have the following code for my models:

    public class Dog
    {
        public int ID { get; set; }
        public string name { get; set; }
        public string breed { get; set; }
    }

    public class AnimalDBContext : DbContext
    {
        public DbSet<Dog> Dogs { get; set; }
    }

In my view I have the following:

@model IEnumerable<AnimalProject.Models.Dog>
    @foreach (var d in Model)
    {
    <h3>@d.name</h3>
    <h2>@d.breed</h2>
    }

Everything works great, the view will loop through every dog in my database. However, I have another set of DBContext data from another table that I want in the same view. I want to be able to enumerate each item in the database for that table as well.

This is what I want, if you catch my drift:

@model IEnumerable<AnimalProject.Models.Dog>
@model IEnumerable<AnimalProject.Models.Cat>
    @foreach (var d in Dog)
    {
    <h3>@d.name</h3>
    <h2>@d.breed</h2>
    }
    @foreach (var c in Cat)
    {
    <h3>@c.name</h3>
    <h2>@c.breed</h2>
    }

I have tried grouping the classes together and using a partial view, but apparently you can't have a different model in a partial view, because I always get the error message:

"The model item passed into the dictionary is of type 'System.Data.Entity.DbSet1[AnimalProject.Models.Dog]', but this dictionary requires a model item of type 'System.Collections.Generic.IEnumerable1[AnimalProject.Models.Cat]'."

So, how can I use multiple models in my view that both get the data I want from separate tables in the database?

Nuri YILMAZ
  • 4,291
  • 5
  • 37
  • 43
Monochrome
  • 420
  • 1
  • 5
  • 15

3 Answers3

14

What about creating custom view model class:

public AnimalModel
{
    public IEnumerable<Dog> Dogs { get; set; }
    public IEnumerable<Cat> Cats { get; set; }
} 

Fill this model in Index and pass it to the view which will expect AnimalModel instead of enumerables.

Edit:

Filling the model:

public ActionResult Index()
{
    using (var db = new AnimalDBContext())
    {
        var model = new AnimalModel 
        {
            Dogs = db.Dogs.ToList(),
            Cats = db.Cats.ToList()
        };

        return View(model);
    }
}

View (I have never used Razor so I hope this is correct):

@model AnimalProject.Models.AnimalModel
@foreach (var d in Model.Dogs)
{
  <h3>@d.name</h3>
  <h2>@d.breed</h2>
}
@foreach (var c in Model.Cats)
{
  <h3>@c.name</h3>
  <h2>@c.breed</h2>
}
Ladislav Mrnka
  • 360,892
  • 59
  • 660
  • 670
  • I just tried this, perhaps you can elaborate on what to do. If I specify the AnimalModel class in the @model at the top of my view, populate the AnimalModel class in my Index, when I try @foreach (var d in Model) I can't go three levels deep. So, if I write @d.Dogs, that's all I get, I can't do @d.Dogs.name, because the Dogs have to be enumerated as well. Does that make sense? – Monochrome Apr 29 '11 at 07:58
  • 1
    You must iterate over properties of the model: `foreach(var d in Model.Dogs)` – Ladislav Mrnka Apr 29 '11 at 08:08
  • When I try that, and add a period (Model.), Dogs is not an option in the view. It only shows Aggregate<>, All<>, Any<>... etc. Perhaps I'm not filling in the model properly. For some reason in MVC I haven't been able to iterate any class' properties using foreach. – Monochrome Apr 29 '11 at 08:29
  • How did you registered AnimalModel in the view? It looks like you have registered collection of animal models. – Ladislav Mrnka Apr 29 '11 at 08:34
  • Like this: @model IEnumerable – Monochrome Apr 29 '11 at 08:36
  • You should use only `@model AnimalProject.Models.AnimalModel` – Ladislav Mrnka Apr 29 '11 at 08:37
  • So happy right now, this is great. Thanks again for your help and patience. I need to go learn about IEnumerable now, because I clearly don't understand the interface. I've spent hours trying to figure this out, and you saved me hours more. Thanks!! – Monochrome Apr 29 '11 at 08:46
  • `IEnumerable` says that you are passing collection of records but in this case you want to pass just single record which wraps two collections. – Ladislav Mrnka Apr 29 '11 at 08:48
3

A model is just an object like any other object in C# (or VB)

So you can pass any object you want to the view, even complex ones. If you need to present a lot of things, then create a view model (a model just for the view's needs) which contains all the things you want to present and pass this one to the view. Then, from within the view, you access the model's individual properties on the different parts of the view, to present them. An example of this approach is the Animal Model Ladislav Mrnka talked about. You put in there the dogs and cats enumerables and then pass it to the view. You tell the view that your model now is this AnimalModel type etc etc...

So, since a model is just an object, and if you are lazy enough to not want to create another custom view model, then you can pass your whole dbcontext as a model to the view like this.

Controller

public ActionResult Index()
{
   return View(new AnimalDBContext()); // We pass a new instance of the dbcontext to the view
}

View

@model AnimalProject.AnimalDBContext // we declare that our model is of type AnimalDBContext 
@foreach (var d in Model.Dogs) // We reference the dbcontext's sets directly
{
  <h3>@d.name</h3>
  <h2>@d.breed</h2>
}
@foreach (var c in Model.Cats) // We reference the dbcontext's sets directly
{
  <h3>@c.name</h3>
  <h2>@c.breed</h2>
}

Now you have declared to your view that your "Model" object will be an AnimalDBContext So you can access all the AnimalDBContext's properties, sets etc directly from within your view. It may not be the best abstraction for the data access, but look how simple your controller has become! :) You just throw the whole world into the view and let it decide which parts to pick and present... Of course this is for simple scenarios. If you wanted more complex stuff you would have to fall back to custom view models eventually.

Thanasis Ioannidis
  • 2,981
  • 1
  • 30
  • 50
0

The only solution to this problem I've found so far is to chuck both models in the ViewBag, and then render partial views for parts that use only one model. Of course, it only works if the partial views are separable - as is in your case. This allows strongly typed partial views compared to accessing the contents of the ViewBag directly.

The solution is similar to Ufuk's answer here. First put data in ViewBag from controller:

ViewBag.Dogs = db.Dogs;
ViewBag.Cats = db.Cats;

Then render the partial View from the view:

@Html.Partial("_ListDogs", ViewBag.Dogs)
@Html.Partial("_ListCats", ViewBag.Cats)

And each partial view would be along the lines of the following (example is _ListDogs.cshtml):

@model IEnumerable<AnimalProject.Models.Dog>
@foreach (var d in Model)
{
    <h3>@d.name</h3>
    <h2>@d.breed</h2>
}
vhallac
  • 13,301
  • 3
  • 25
  • 36
  • This seems promising, everything is set up properly but when I specify @Html.Partial("_ListDogs", ViewBag.Dogs) I get this error: System.Web.Mvc.HtmlHelper' has no applicable method named 'Partial' but appears to have an extension method by that name. Extension methods cannot be dynamically dispatched. Consider casting the dynamic arguments or calling the extension method without the extension method syntax. – Monochrome Apr 29 '11 at 08:37
  • Have a look at http://stackoverflow.com/questions/4047543/render-partial-view-with-dynamic-model-in-razor-view-engine-and-asp-net-mvc-3 and the SO link in the first answer too. Casting the `ViewBag.Dogs` to `object` made the problem go away for me, but I am not sure if it should be cast to `IEnumerable` instead. – vhallac Apr 29 '11 at 18:08