1

I've got a View rendering two dropdownlists. The controllers for the dropdownlists work fine. They call methods in a repository class for the DB selections. Below the dropdownlists I'm trying to render a table of data in a partial view, in response to the dropdownlist selections.

The dropdowns in the View use a single model:

@model BudgetDemo.Models.BudgetsActualsViewModel

The Partial View displaying the table data uses IEnumerable:

@model IEnumerable<BudgetDemo.Models.BudgetsActualsViewModel>

View (GetBudgetsActuals.cshtml):

@using (Html.BeginForm("GetBudgetsActuals", "BudgetsActuals", FormMethod.Post))
{
    ... DropDownLists and Submit button

    @if (Model.SelectedDepartment != null && Model.SelectedYear != null)
    {
        // Table headers
        @if (Model != null)
        {
            Html.RenderPartial("_BudgetsActuals", Model);
        }
    }
}

Partial View (_BudgetsActuals.cshtml):

@model IEnumerable<BudgetDemo.Models.BudgetsActualsViewModel>
@foreach (var item in Model)
{
    <tr>
        <td>
            @Html.DisplayFor(modelItem => item.Account)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.CostCenter)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.TotalCurrentMonthActual)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.TotalCurrentMonthBudget)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.TotalYTDActual)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.TotalYTDBudget)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.TotalVariance)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.TotalETCBudget)
        </td>
        <td>
             @Html.DisplayFor(modelItem => item.TotalEAC)
        </td>
    </tr>
}

Controllers:

// GET: Render view with dropdowns
public ActionResult GetBudgetsActuals()
{
    try
    {
        // Populate Department dropdown and Year dropdown here
        repo = new BudgetDemoRepository();
        ModelState.Clear();

        return View(repo.GetBudgetsActuals());
    }
    catch
    {
        return View("Error");
    }
}

// POST: Grab data for department and year
[HttpPost]
public ActionResult GetBudgetsActuals(BudgetsActualsViewModel model)
{
    repo = new BudgetDemoRepository();
    if (ModelState.IsValid)
    {
        return View(repo.GetBudgetsActuals(model));
    }
    else
    {
        model.Departments = repo.GetBudgetsActuals().Departments;
        model.Years = repo.GetBudgetsActuals().Years;
        return View(model);    
    }
}

[ChildActionOnly]
public ActionResult 
    GetBudgetsActualsPartialData(BudgetsActualsViewModel model)
{
    repo = new BudgetDemoRepository();
    List<BudgetsActualsViewModel> dataVM = 
        repo.GetBudgetsActualsData(model);
    
    // RETURNING CORRECT DATA
    return PartialView("GetBudgetsActuals", dataVM);
}

What I'm trying to figure out is how to hook this all together. It's blowing up here:

@if (Model != null)
{
    Html.RenderPartial("_BudgetsActuals", Model);
}

This is the error error message:

The model item passed into the dictionary is of type
'System.Collections.Generic.List`1[BudgetDemo.Models.BudgetsActualsViewModel]', 
but this dictionary requires a model item of type
'BudgetDemo.Models.BudgetsActualsViewModel'.

enter image description here enter image description here

UPDATE

There seems to be a known issue with partial view if you're passing a model to the RenderPartial helper method and that model is null - it will default to the model of the containing view. I have debugged this and from what I can see the model being passed to the helper method is not null, so I am at a loss.

UPDATE 2

Apparently I was not alone regarding the issue of the model of the calling view being passed to the partial view. This problem is addressed in .NET Core with the introduction of View Components :)

IrishChieftain
  • 15,108
  • 7
  • 50
  • 91
  • instead of returning a `List` for your table, why not make a single view model object that has properties for your drop down lists and then another property of type `List` for your table data? – mituw16 Dec 02 '20 at 18:57
  • So what happens when you change dropdown selection? Does it go to the `GetBudgetsActuals()` and return a view If you need to just update the tables on a page, you need to do an ajax call to action method and return `PartialView(viewName, model)` and populate html using javascript. – Pirate Dec 02 '20 at 18:59
  • @Dave, I don't want to use AJAX to solve this. Will update the question. – IrishChieftain Dec 02 '20 at 21:20
  • may be this would help [here](https://www.c-sharpcorner.com/article/different-ways-of-render-partial-view-in-mvc/) – Pirate Dec 02 '20 at 21:44
  • 1
    So you have a controller that pulls some data from a repository in response to a dropdown selection, stores the data in an `IEnumerable`, and delivers that data to a view. The view then relays your model to a partial view to handle the actual rendering. That all sounds pretty standard. What part isn't working? Is the issue with relaying the model to the partial? Or are you trying to e.g. dynamically select the partial based on the input? I'm assuming you're using e.g. `@html.Partial()` or `@html.RenderPartial()` to call the actual partial view. Could you include your relevant razor code? – Jeremy Caney Dec 02 '20 at 21:52
  • @JeremyCaney, no the view with the dropdowns currently has a single model, not an ienumerable. The partial view I want to render with the table data needs an ienumerable. – IrishChieftain Dec 02 '20 at 21:59
  • I have updated the question and the code snippets in an effort to narrow things down. I think I'm doing something wrong with between the RenderAction call and the child controller. Any suggestions appreciated :) – IrishChieftain Dec 03 '20 at 16:48
  • @IrishChieftain what is the type Model in `Html.RenderPartial("_BudgetsActuals", Model);`. your error says wrong model was passed. Partial view is expecting something else. – Pirate Dec 04 '20 at 14:53
  • @Pirate, BudgetsActualsViewModel is being passed and that is what the child action expects. I have confirmed it has values for department and year. The partial view iteself expects to get back IEnumerable. But we're not getting that far; it's blowing up on submit. – IrishChieftain Dec 04 '20 at 15:15
  • @Pirate You are correct though. It seems like the model being passed is wrong, but for the life of me I can't see why. I'm new to MVC and am wondering at this point if there is a better way to have two dropdown values being used to populate a table without resorting to a partial view for the table? – IrishChieftain Dec 04 '20 at 17:05
  • @IrishChieftain It's a broad question. There are few possibilities, but one would be to use jQuery datatables. It's robust and feature reach library for table needs. See [here]. However, all solutions are ajax based which is not something you want. I would suggest, redirect to another page with dropdown values, gather data and return a full view. – Pirate Dec 04 '20 at 17:13
  • @IrishChieftain I've not used partial view like this with child action, so not sure what happens behind the scene. You've a view in the screenshot you posted. What is that view? My guess is, that view accepting a viewmodel of other type and you're passing the same viewmodel `_BugestActual` partial view. What does `_BugestActual` expect and what does the view in screenshot expect? are they same? – Pirate Dec 04 '20 at 17:15
  • @Pirate, the main view has the dropdowns (single instance of model). Below that I'm injecting a partial view to display the table data (IEnumerable). I'm passing two params from the single model to the partial child action method. Then returning an IEnumerable of model values for the table. I think I may try doing everything in the view with an IEnumerable and toggle the table visibility somehow. – IrishChieftain Dec 04 '20 at 17:22
  • @IrishChieftain yeah, i understood the flow you have. Where is `Html.RenderPartial("_BudgetsActuals", Model);` code? in which view it is? In your main view, I see with dropdowns, you're calling a child action. Child action returns a partial view `GetBudgetsActuals` which takes `IEnumerable`. Is above `Html.RenderPartial` code in `GetBudgetsActuals` partial view? Can you post full code in `GetBudgetsActuals ` and `_BudgetsActuals`? – Pirate Dec 04 '20 at 17:30
  • @Pirate, Html.RenderPartial("_BudgetsActuals", Model); is in the main view, GetBudgetsActuals.cshtml. I'll post the partial view markup/code. – IrishChieftain Dec 04 '20 at 19:29
  • @IrishChieftain but in child action, you're returning the main view. shouldn't it return `_BudgetsActuals` from `GetBudgetsActualsPartialData` action? `GetBudgetsActuals` doesn't take the collections as a model. Try updating `return PartialView("GetBudgetsActuals", dataVM);` to `return PartialView("_BudgetsActuals", dataVM);` in child action. – Pirate Dec 04 '20 at 19:47
  • @Pirate, you are correct, I was returning the wrong view from the child action. I fixed that but am still getting the same error. On form submission, I am not getting past the error in the RenderPartial call in the main view. – IrishChieftain Dec 04 '20 at 20:21
  • 1
    @IrishChieftain i don't see `RenderPartial` in main view. perhaps update your question with error, main view code, partial view code and controller. Remove all other code including model definition and repo code. – Pirate Dec 04 '20 at 20:27
  • @Pirate, thanks for the positive feedback. I'm shortened the question and removed unnecessary code. I'm throwing out a bounty to whoever can nail this down. – IrishChieftain Dec 04 '20 at 20:59
  • @IrishChieftain ok, now it makes some sense. I've added an answer with explanation, let me know if it still doesn't work. – Pirate Dec 04 '20 at 21:16

1 Answers1

2

Well, what happens is, when you do Html.RenderPartial("_BudgetsActuals", Model); , it takes the model from the view in which the code is written, and tries to pass it as is to the partial view. So here, based on the error screenshot in the question and the behavior of .net, BudgetDemo.Models.BudgetsActualsViewModel was passed to the partial view because GetBudgetsActuals.cshtml view takes that as a model. But, that is not right, because your partial view requires IEnumerable<BudgetDemo.Models.BudgetsActualsViewModel> as model. So, you need to actually store an instance of IEnumerable<BudgetDemo.Models.BudgetsActualsViewModel> in your BudgetDemo.Models.BudgetsActualsViewModel

Model

public class BudgetsActualsViewModel 
{
   // other properties

   public IEnumerable<BudgetDemo.Models.BudgetsActualsViewModel> BudgetActualCollection {get;set;}
}

note: make sure to initialize it with the data on server or with a new instance when there is no data. Otherwise, it will throw null reference error. You can initialize it in the constructor as well.

Controller (updated post method, this is just for example, you can simplify or update to your needs)

// GET: Render view with dropdowns
public ActionResult GetBudgetsActuals()
{
    try
    {
        // Populate Department dropdown and Year dropdown here
        repo = new BudgetDemoRepository();
        ModelState.Clear();

        return View(repo.GetBudgetsActuals());
    }
    catch
    {
        return View("Error");
    }
}

// POST: Grab data for department and year
[HttpPost]
public ActionResult GetBudgetsActuals(BudgetsActualsViewModel model)
{
    var repo = new BudgetDemoRepository();
    model.Departments = repo.GetBudgetsActuals().Departments;
    model.Years = repo.GetBudgetsActuals().Years;
    
    if (ModelState.IsValid)
    {
         model.BudgetActualCollection = repo.GetBudgetsActualsData(model);
    }
    else
    {
        model.BudgetActualCollection = new List<BudgetDemo.Models.BudgetsActualsViewModel>();
    }
    return View(model);
}

Then do Html.RenderPartial("_BudgetsActuals", Model.BudgetActualCollection ). Now proper model will be passed to the partial view.

Doing Html.RenderPartial("_BudgetsActuals", Model); and (as op tried in comment below)

Html.RenderPartial("_BudgetsActuals", new BudgetDemo.Models.BudgetsActualsViewModel
{ 
   SelectedDepartment = Model.SelectedDepartment, 
   SelectedYear = Model.SelectedYear 
})

are essentially the same with one difference. In first one, the model from main view will be passed whereas second will pass a new instance of the model.

Pirate
  • 1,167
  • 1
  • 9
  • 19
  • Haven't had a chance to check it yet. But my understanding is that the partial view does not take the model from the view. And a bug in MVC meant that it only does this if the model being passed to the RenderPartial helper is null. Check out the links? Away from my desk now but will give this a shot. Thanks. – IrishChieftain Dec 05 '20 at 00:27
  • Have you tried passing a valid instance (non-null model)? What happens then? – Pirate Dec 05 '20 at 01:04
  • Same error: Html.RenderPartial("_BudgetsActuals", new BudgetDemo.Models.BudgetsActualsViewModel { SelectedDepartment = Model.SelectedDepartment, SelectedYear = Model.SelectedYear }); – IrishChieftain Dec 06 '20 at 08:21
  • This actually gets past the error but there's no data in the partial view: Html.RenderPartial("_BudgetsActuals", new List { }); – IrishChieftain Dec 06 '20 at 13:18
  • Yeah, the later is correct i.e. passing `List`. But here you passed a new instance of a `List`, so it doesn't have any data. I would suggest, populate the property (as suggested in answer) in main viewmodel on server side with actual data and pass it to the partial view. As mentioned in note in my answer, please be sure to initialize the `BudgetActualCollection` with real data or a new `List` object as you did in comment above. – Pirate Dec 06 '20 at 15:26
  • So when you populate main virwmodel with the data you need, also populate the collection with real data or a new instance, so it is always valid and safe. – Pirate Dec 06 '20 at 15:27
  • `Html.RenderPartial("_BudgetsActuals", new BudgetDemo.Models.BudgetsActualsViewModel { SelectedDepartment = Model.SelectedDepartment, SelectedYear = Model.SelectedYear });` this one is still wrong because you're passing `BudgetsActualsViewModel` but it requires a `List`. – Pirate Dec 06 '20 at 15:34
  • @IrishChieftain i've updated my answer to make it more clear. So in controller POST request, get list data and pass it back to view and pass it to partial view from main viewmodel. – Pirate Dec 06 '20 at 16:07
  • I've tried your code and the table headers appear but no data appears because the child action "GetBudgetsActualsPartialData" never gets called. – IrishChieftain Dec 07 '20 at 18:46
  • Have you added `BudgetActualCollection` in your main viewmodel? If so, when you make post request, assign the collection with the data `model.BudgetActualCollection = repo.GetBudgetsActualsData(model);` and send the view back. Now your collection will have data and partial view will be rendered using that data. You don't need child action with `Html.RenderPartial("_BudgetsActuals", Model.BudgetActualCollection)` – Pirate Dec 07 '20 at 18:49
  • I did that. When I change return View(model); to return PartialView("_BudgetsActuals", model.BudgetActualCollection); in the HTTP action, I get correct raw data (no site template showing). When I change return View(model); to return View(model.BudgetActualCollection); it blows up as before with "model item passed into the dictionary is of type 'System.Collections.Generic.List`1[BudgetDemo.Models.BudgetsActualsViewModel]', but this dictionary requires a model item of type 'BudgetDemo.Models.BudgetsActualsViewModel'" – IrishChieftain Dec 07 '20 at 18:55
  • No, you cannot do `return View(model.BudgetActualCollection)` because it will return a main view `GetBudgetsActuals.cshtml`. You still need to do `return View(model)`. That is the correct way. The question is, in the `HttpPost` action in my answer, when you do `model.BudgetActualCollection = repo.GetBudgetsActualsData(model);`, does `model.BudgetActualCollection` even have data? Can you verify that by setting a breakpoint there? – Pirate Dec 07 '20 at 18:58
  • When you make a post request, we need to return the same view with dropdowns with addition of table data stored in `model.BudgetActualCollection`. That's why we need to populate the property with actual data in HttpPost before returning the view back. – Pirate Dec 07 '20 at 19:00
  • model.BudgetActualCollection has all the data coming back from the server. When it hits the RenderPartial call in the view next time around, it just renders the headers and nothing else. – IrishChieftain Dec 07 '20 at 19:12
  • 1
    Okay, so it is coming close. You've got rid of the original issue of passing incorrect model item to partial view. For data, try setting breakpoint on `Html.RenderPartial("_BudgetsActuals", Model.BudgetActualCollection )` and `@foreach (var item in Model)` in parital view and see if there is any data. – Pirate Dec 07 '20 at 19:16
  • When it hits the foreach in the partial, there's nothing. I could be wrong but I think when we add "new List { }" to the RenderPartial method, we're zapping the data that came back? – IrishChieftain Dec 07 '20 at 19:25
  • 1
    That's right, but that's not what we're doing. We pass `Model.BudgetActualCollection` in `Html.RenderPartial("_BudgetsActuals", Model.BudgetActualCollection)` – Pirate Dec 07 '20 at 19:27
  • I corrected the RenderPartial call and it's working! Can't thank you enough @Pirate! – IrishChieftain Dec 07 '20 at 19:31
  • @IrishChieftain glad its working. Please consider accepting the answer, so others can be benefitted. – Pirate Dec 07 '20 at 19:33
  • Now the validation is broken. If I don't select either of the required dropdowns, I get There is no ViewData item of type 'IEnumerable' that has the key 'SelectedDepartment'. If I select a department but not a year, I get The ViewData item that has the key 'SelectedDepartment' is of type 'System.String' but must be of type 'IEnumerable'. – IrishChieftain Dec 07 '20 at 23:25
  • 1
    @IrishChieftain please open a new question as it's going beyond the scope of the original one. Include the complete model definition, main view, validation, and how it gets submitted to the controller. – Pirate Dec 07 '20 at 23:30
  • Done. If you think I need to add anything else, let me know? – IrishChieftain Dec 07 '20 at 23:56
  • 1
    @IrishChieftain thanks, I am away from the desktop. I'll take a look at it tomorrow. – Pirate Dec 08 '20 at 00:45