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")
Partial and @Html. RenderPartial methods are not meant to call the Controller's Action method, these methods will directly populate the Partial View from Model and render it. In order to call the Controller's Action method of the Partial View, the @Html. Action method needs to be used in ASP.Net MVC Razor.
For rendering a partial view using Html. Action, we required the Controller Action method which returns PartialViewResult like Html. RenderAction. There are 6 overloaded versions available for the Action HTML Helper method in ASP.NET MVC Framework as shown in the below image.
The Partial, RenderPartial, RenderAction helper methods are used to render partial view in mvc3 razor. The main difference between above two methods is that the Partial helper method renders a partial view into a string while RenderPartial method writes directly into the response stream instead of returning a string.
Within a markup file, there are two ways to reference a partial view: Asynchronous HTML Helper. Synchronous HTML Helper.
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 its InvokeAsync
method, and it could take any number of parameters. In your case InvokeAsync
takes no parameters, will load the popular posts from the db and will render the view component view:
public class PopularPostsViewComponent: ViewComponent
{
private readonly ApplicationDbContext _context;
public PopularPostsViewComponent(ApplicationDbContext context)
{
_context = context;
}
public async Task<IViewComponentResult> InvokeAsync()
{
var posts = await _context.Posts
.Where(p => p.IsActive == true)
.OrderByDescending(p => p.Id)
.Take(5)
.ToListAsync();
return View(posts);
}
}
Now you need to create the view for that view component. The default view name Default.cshtml
(as when you do return View()
without specifying a view name) and it should be located in Views/Shared/Components/PopularPosts/Default.cshtml
. This will basically be the same as your current partial view:
@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>
}
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:
@await Component.InvokeAsync("PopularPosts")
Finally, if you are in .Net Core 1.1 or higher, you can also invoke it as a tag helper:
<vc:popular-posts></vc:popular-posts>
PS. I wrote an article describing usages for partials, tag helpers and view components that you might find interesting.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With