Search code examples
c#asp.net-mvcasp.net-mvc-4viewmodelasp.net-mvc-viewmodel

PartialView with a ViewModel on _Layout.cshtml


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.


Solution

  • 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); }