-1

I have a layout page which has a partial view. The partial view needs to loop through a property on the view model to show a list of categories. When a category is displayed I need to show a list of documents in that category. /Home/Index works, but when I try to view /Documents/Category/{id}, I get an error:

Additional information: The model item passed into the dictionary is of type 'System.Collections.Generic.List`1[ViewModels.DocumentViewModel]', but this dictionary requires a model item of type 'ViewModels.HomeViewModel'.

_Layout.cshtml

... 
<body>

@Html.Partial("_CategoryViewModel")

<div class="content">
    @RenderBody()
</div>

HomeViewModel.cs

public class HomeViewModel {
    ...
    public ICollection<DocumentCategory> Categories { get; set; }
    public ICollection<Documents> Documents { get; set; }
    ...
}

_CategoryViewModel.cshtml (this should show a list of all categories)

@model ViewModels.HomeViewModel
...
@foreach (DocumentCategory item in Model.Categories)
{
    <li>
        <a href="@Url.Action("Category", "Documents", new { @id = @item.CategoryId })" title="View documents in the @item.Name category">
            <span class="fa fa-files-o"></span>&nbsp;@item.Name
        </a>
    </li>
}

DocumentsController.cs

public ActionResult Category(int id)
{
    var thisCategory = _ctx.Categories.Get(c => c.CategoryId == id).FirstOrDefault();
    IEnumerable<DocumentViewModel> docs = null;
    if(thisCategory == null)
    {
        TempData.Add("errorMessage", "Invalid category");
    } else {
        docs = thisCategory.Documents.ToList();
    }

    return View("Category", docs);
}

What's happening kind of makes sense - the PartialView on the Layout page needs to enumerate over a collection which isn't present in the ViewModel I'm using. I have no idea how to achieve this - the only way would seem to be to add a Categories property to every ViewModel in my site.

Echilon
  • 10,064
  • 33
  • 131
  • 217

2 Answers2

1

By default, using @Html.Partial() will pass the current model to the partial view, and because your Category.cshtml view uses @model List<DocumentViewModel>, then List<DocumentViewModel> is passed to a partial that expects HomeViewModel.

If you want to render a partial view for HomeViewModel on every page, then use @Html.Action() to call a ChildActionOnly method that returns the partial

[ChildActionOnly]
public ActionResult Categories
{
    var model = new HomeViewModel()
    {
        .... // initialize properties
    }
    return PartialView("_CategoryViewModel", model)
}

and in the layout

@Html.Action("Categories", yourControllerName)
// or
@{ Html.RenderAction("Categories", yourControllerName); }
  • This worked perfectly. It seems 'cleaner' than having everything inherit from a base ViewModel. Thanks for your help. – Echilon Aug 24 '16 at 10:34
0

As I see it you have a few different alternatives.

1. Use Html.Action and create an Action that returns your view.

@Html.Action("Index", "Category") // Or your controller name.

I believe that there are some performance draw-backs with this approach because the whole MVC lifecycle will run again in order to render the result of the action. But then you can render the result of an action without having the correct model in the view that called it.

One may also argue that this breaks the MVC pattern, but it might be worth it.


2. Use a generic model (or an interface) in your _Layout.cshtml, and let your viewmodels inherit from that model.

In your _Layout.cshtml:

@model IBaseViewModel

And let all your viewmodels implement this interface.

public interface IBaseViewModel
{
    ICollection<DocumentCategory> Categories { get; set; }
}

public interface IBaseViewModel<T> : IBaseViewModel
{
    T ViewModel {get; set;}
}

Since you're placing @Html.Partial("_CategoryViewModel") in _Layout.cshtml I assume that it should be visible in all pages, so I think it's logical that all the controllers that are using _Layout.cshtml make sure that it gets the information it needs, and thus adding Categories to the model.

I use this approach all the time for stuff like breadcrumbs and menu-information (stuff that is used in all pages). Then I have a basecontroller that makes sure that Categories is populated with the correct info.

smoksnes
  • 10,509
  • 4
  • 49
  • 74