Create ViewModel for Navigation
Asked Answered
M

2

4

I have an MVC 4 application with several views. I.e. Products, Recipes, Distrubutors & Stores.

Each view is based around a model.

Let's keep it simple and say that all my controllers pass a similar view-model that looks something like my Product action:

public ActionResult Index()
{
    return View(db.Ingredients.ToList());
}

Ok so this is fine, no problems. But now that all of my pages work I want to change my navigation (which has dropdowns for each view) to load the items in that model.

So I would have a navigation with 4 Buttons (Products, Recipes, Distrubutors & Stores).

When you roll over each button (let's say we roll over the products button) then a dropdown would have the Products listed.

To do this I need to create some type of ViewModel that has all 4 of those models combined. Obviously I can't just cut out a PartialView for each navigation element and use

@model IEnumerable<GranSabanaUS.Models.Products>

And repeat out the Products for that dropdown, because then that navigation would only work in the Product View and nowhere else.

(After the solution) AND YES ROWAN You are correct in the type of nav I am creating, see here: Navigation I am creating

Menendez answered 25/11, 2013 at 3:48 Comment(4)
Are you using jQuery to populate the div that will be used as your dropdown?Fluxmeter
Yes, to be more specific I am using Foundation 4's top-bar navigation elementMenendez
I only need help creating the viewModel to pass the multiple models to one view, Don't need help with the jQuery end of thingsMenendez
If I understand you correctly, you aren't creating a specific class for you models, right? It would seem like you'd want to create a "MenuContents" class that contains public db.Ingredients { get; set; } (along with your other db items). I typically create a method called "PopulateModel" that will read the DB, and fill in all the member variables. In your controller, you will create a new instance of this class and pass it into your partial view. Then your cshtml file can use each of the lists to fill in their dropdowns.Fluxmeter
F
1
public class MenuContents
{
    public IEnumerable<Products> AllProducts { get; set; }
    public IEnumerable<Recepies> AllRecepies { get; set; }
    public IEnumerable<Distributors> AllDistributors { get; set; }
    public IEnumerable<Stores> AllStores { get; set; }

    private XXXDb db = new XXXUSDb();

    public void PopulateModel()
    {
        AllProducts = db.Products.ToList();
        AllRecepies = db.Recepies.ToList();
        AllDistributors = db.Distributors.ToList();
        AllStores = db.Stores.ToList();
    }
}

Then in your controller

public ActionResult PartialWhatever()
{
    MenuContents model = new MenuContents();
    model.PopulateModel();

    return PartialView("PartialViewName", model);
}

Then in your partial view

@Model MenuContents

... do whatever here
Fluxmeter answered 25/11, 2013 at 4:29 Comment(4)
I was so close. I didnt have the PopulateModel part and was implementing it in my controller slightly differently. THis looks right now that I see it all in action. Thanks. I'll 1 up you once I get it working! Thanks Scottie! Sometimes us Noobs just need a push in the right direction and I have not seen one QUestion on here that lays it out this easily for this type of application of ViewModel.Menendez
So in this case I do not pass it through my normal view. Instead in that view I create a partial view and bring it in through that?Menendez
You had mentioned in your original post that you were using a partial view, so that's why I used a partial view. It will work the same in a regular view, just change it to return View(model); The only diff between return View and return PartialView is that PartialView won't return any of the _layout.cshtml stuff.Fluxmeter
For some reason the implementation that Rowan had given worked only in MVC 4, I have been beating myself up ever since moving to MVC 5 because it broke my ViewModels. I ditched everything and used your implementation and aside from figuring out how to structure the for loop it worked perfectly for me. Thanks and I have changed the Correct answer in this Question to yours. I will update your for loop with the right info because for a newbie that is important.Menendez
H
13

Introduction

I'm going to be making a few assumptions because I don't have all the information.

I suspect you want to create something like this:

Dropdown menu

Separating views

When you run into the issue of "How do I put everything into a single controller/viewmodel" it's possible that it's doing too much and needs to be divided up.

Don't treat your a final page as one big view - divide the views up into smaller views so they are doing 'one thing'.

For example, the navigation is just one part of your layout. You could go even further to say that each dropdown menu is a single view that are part of the navigation, and so on.

Navigation overview

Suppose you have a _Layout.cshtml that looks like this:

<body>
    <div class="navbar">
        <ul class="nav">
            <li><a href="#">Products</a></li>
            <li><a href="#">Recipes</a></li>
        </ul>
    </div>

    @RenderBody()
</body>

As you can see we have a simple navigation system and then the main body is rendered. The problem that we face is: How do we extract this navigation out and give it the models it needs to render everything?

Extracting the navigation

Let's extract the navigation into it's own view. Grab the navigation HTML and paste it into a new view called __Navigation.cshtml_ and put it under ~/Views/Partials.

_Navigation.cshtml

<div class="navbar">
    <ul class="nav">
        <li><a href="#">Products</a></li>
        <li><a href="#">Recipes</a></li>
    </ul>
</div>

Create a new controller called PartialsController. Create a new action to call our navigation.

PartialsController.cs

[ChildActionOnly]
public class PartialsController : Controller
{
    public ActionResult Navigation()
    {
        return PartialView("_Navigation");
    }

}

Update our Layout to call the navigation.

_Layout.cshtml

<body>
    @Html.Action("Navigation", "Partials")

    @RenderBody()
</body>

Now our navigation is separated out into its own partial view. It's more independent and modular and now it's much easier to give it model data to work with.

Injecting model data

Suppose we have a few models such as the ones you mentioned.

public class Product { //... }
public class Recipe { //... }

Let's create a view-model:

NavigationViewModel.cs

public class NavigationViewModel
{
    public IEnumerable<Product> Products { get; set; }
    public IEnumerable<Recipe> Recipes { get; set; }
}

Let's fix up our action:

PartialsController.cs

public ActionResult Navigation()
{
    NavigationViewModel viewModel;

    viewModel = new NavigationViewModel();
    viewModel.Products = db.Products;
    viewModel.Recipes = db.Recipes;

    return PartialView("_Navigation", viewModel);
}

Finally, update our view:

_Navigation.cshtml

@model NavigationViewModel

<div class="navbar">
    <ul class="nav">

        @foreach (Product product in Model.Products)
        {
            @<li>product.Name</li>
        }

        @foreach (Recipe recipe in Model.Recipes)
        {
            @<li>recipe.Name</li>
        }
    </ul>
</div>
Hallucinogen answered 25/11, 2013 at 4:47 Comment(5)
Damn. I appreciate your help and Scotties so much. I have taken huge things away from both of your pieces of code. This answers my question and beyond that, you took the time to explain it to someone like me who needs the explanation. Again I thank both of you! Let me chew on this for a few and I might have a question, but I doubt it.Menendez
BTW I just added an image of the actual navigation I was creating. You nailed it! Much love to Scottie too. Again I can't say how much I appreciate your help. Both of you!Menendez
Works Beautifully. :)Menendez
I just updated to MVC 5 and this implementation no longer works, I get the following error: The type or namespace name 'Recipe' could not be found (are you missing a using directive or an assembly reference?)Menendez
I had to switch the answer. This implementation will not work in MVC 5Menendez
F
1
public class MenuContents
{
    public IEnumerable<Products> AllProducts { get; set; }
    public IEnumerable<Recepies> AllRecepies { get; set; }
    public IEnumerable<Distributors> AllDistributors { get; set; }
    public IEnumerable<Stores> AllStores { get; set; }

    private XXXDb db = new XXXUSDb();

    public void PopulateModel()
    {
        AllProducts = db.Products.ToList();
        AllRecepies = db.Recepies.ToList();
        AllDistributors = db.Distributors.ToList();
        AllStores = db.Stores.ToList();
    }
}

Then in your controller

public ActionResult PartialWhatever()
{
    MenuContents model = new MenuContents();
    model.PopulateModel();

    return PartialView("PartialViewName", model);
}

Then in your partial view

@Model MenuContents

... do whatever here
Fluxmeter answered 25/11, 2013 at 4:29 Comment(4)
I was so close. I didnt have the PopulateModel part and was implementing it in my controller slightly differently. THis looks right now that I see it all in action. Thanks. I'll 1 up you once I get it working! Thanks Scottie! Sometimes us Noobs just need a push in the right direction and I have not seen one QUestion on here that lays it out this easily for this type of application of ViewModel.Menendez
So in this case I do not pass it through my normal view. Instead in that view I create a partial view and bring it in through that?Menendez
You had mentioned in your original post that you were using a partial view, so that's why I used a partial view. It will work the same in a regular view, just change it to return View(model); The only diff between return View and return PartialView is that PartialView won't return any of the _layout.cshtml stuff.Fluxmeter
For some reason the implementation that Rowan had given worked only in MVC 4, I have been beating myself up ever since moving to MVC 5 because it broke my ViewModels. I ditched everything and used your implementation and aside from figuring out how to structure the for loop it worked perfectly for me. Thanks and I have changed the Correct answer in this Question to yours. I will update your for loop with the right info because for a newbie that is important.Menendez

© 2022 - 2024 — McMap. All rights reserved.