I am creating a partial view for a sidebar that will show the most popular posts in my site. How can I create a separated controller for loading the model required by the partial view? (The IEnumerable<Post>
with the popular posts)
Currently I have created a controller class that loads the popular posts but I keep getting errors when rendering the partial as I cannot call the controller and load the partial model. For example, if I call it from a view where I render a single post, the model types won't match (Post
vs IEnumerable<Post>
)
This is my SidebarController
:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using GoBaron.Front.Models;
namespace GoBaron.Front.Controllers
{
public class SidebarController : Controller
{
private readonly ApplicationDbContext _context;
public SidebarController(ApplicationDbContext context)
{
_context = context;
}
public async Task<IActionResult> PopularPosts()
{
return PartialView(await _context.Posts
.Where(p => p.IsActive == true)
.OrderByDescending(p => p.Id)
.Take(5)
.ToListAsync());
}
}
}
And this is my partial view PopularPosts.cshtml
:
@model IEnumerable<GoBaron.Front.Models.Post>
@using GoBaron.Front.Data.Extensions
@if (Model.Any())
{
<div class="sidebar-item" id="topTrending">
<header class="sidebar-item__header">
<h1>Najpopularniejsze</h1>
</header>
<div class="sidebar-item__content">
@foreach (var item in Model)
{
<a asp-controller="Post" asp-action="Details" asp-route-id="@item.Id" asp-route-slug="@item.Title.ConvertToSlug()" title="@item.Title">
<div class="sidebar-post-item" style="background-image:url('@item.PosterUrl')">
<span class="sidebar-post-item__counter">+5</span>
<span class="sidebar-post-item__title">@item.Title</span>
</div>
</a>
}
</div>
</div>
}
When I want to include the popular posts sidebar, for example in the layout, I just add:
@await Html.PartialAsync("~/Views/Sidebar/PopularPosts.cshtml")
This is a good use case for a View Component instead of a partial view. You not only need to render a view, you also need to execute that bit of logic that loads the Popular Posts from the db.
Instead of a Controller, create a new
ViewComponent
class. This contains the logic that prepares the model for the view component in itsInvokeAsync
method, and it could take any number of parameters. In your caseInvokeAsync
takes no parameters, will load the popular posts from the db and will render the view component view:Now you need to create the view for that view component. The default view name
Default.cshtml
(as when you doreturn View()
without specifying a view name) and it should be located inViews/Shared/Components/PopularPosts/Default.cshtml
. This will basically be the same as your current partial view:Then from any other view, you would render your view component instead of a partial, passing any parameters required by
InvokeAsync
. In your case you take no parameters so it will be:Finally, if you are in .Net Core 1.1 or higher, you can also invoke it as a tag helper:
PS. I wrote an article describing usages for partials, tag helpers and view components that you might find interesting.