Reputation: 93
I am working on a project using ASP.NET Core 5.0 to build a web app (MVC). I tried to use the Html.RenderAction
extension method but I get the following error.
COMPILE ERROR:
Html.RenderAction CS1061: IHtmlHelper<dynamic> does not contain a definition for 'RenderAction'
What I'm I not doing right?
To give more context, I want to render a partial view in _Layout.cshtml
using data loaded directly from the database.
This is the content of my _Layout.cshtml
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384- EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
<link rel="stylesheet" href="~/dist/css/main.css" media="all" />
<title>@ViewData["Title"]: my webpage</title>
</head>
<body id="body">
<!-- Header -->
<header id="header">
<!-- Navigation -->
<nav id="nav">
<partial name="../Shared/Components/_navBar.cshtml" />
<partial name="../Shared/Components/_headerSearchBar.cshtml" />
</nav>
@{ Html.RenderAction("GetCategories"); }
</header>
<!-- Main -->
<main role="main" class="main-wrapper">
<div class="home main-content">
@RenderBody()
</div>
</main>
<!-- Footer -->
<footer class="footer">
<partial name="../Shared/Components/_footer.cshtml" />
</footer>
<script src="~/js/script.js" asp-append-version="true"></script>
@*<script src="~/js/scriptanim.js" asp-append-version="true"></script>*@
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>
This is the action method in the controller that loads data to the partial view
public async Task<IActionResult> GetCategories()
{
var categories = await _fashionBuyRepository.GetAllCategoriesAsync();
return PartialView("_productCategoriesHeader", categories);
}
Upvotes: 4
Views: 4425
Reputation: 93
I took the ViewComponent method as suggested by @Michael to solve this problem and was the most optimal for me.
I created a CategoriesViewComponent in a Components folder as follows:
using eFashionBuy.Repository;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace eFashionBuy.Components
{
public class CategoriesViewComponent : ViewComponent
{
private readonly IFashionBuyRepository _fashionBuyRepository;
public CategoriesViewComponent(IFashionBuyRepository fashionBuyRepository)
{
_fashionBuyRepository = fashionBuyRepository;
}
public async Task<IViewComponentResult> InvokeAsync()
{
var categories = await _fashionBuyRepository.GetAllCategoriesAsync();
return View(categories);
}
}
}
The view associated with this component is called Default.cshtml (a partial view) in the location /Shared/Components/Categories/Default.cshtml as follows:
@model IEnumerable<Category>;
@* Product Categories mobile menu *@
@foreach (var category in Model)
{
<li class="categories__item">
<a class="categories-link"
asp-controller="Catgeories"
asp-action="Category"
asp-route-id="@category.CategoryID"
asp-route-category="@category.CategoryName">
<span>@category.CategoryName</span>
</a>
</li>
}
The component is now ready for use. I simply called it as follows where I want to use it
<ul class="categories__items">
<!-- This is how I call the component using TagHelper -->
<vc:categories />
</ul>
This helped me avoid most of the nuances with RenderAction which was my initial approach. I will use this knowledge to simplify future designs.
Upvotes: 2
Reputation: 43860
There are several ways to do this. One of them to use component. But it will need some javascript code to use it
Another, more simple for a novice way, you have to create a base view model that you will have to use for ALL your views that are using this layout
using Microsoft.AspNetCore.Mvc.Rendering;
public interface IBaseViewModel
{
int CategoryId { get; set; }
List<Category> Categories{ get; set; }
}
public class BaseViewModel : IBaseViewModel
{
public int CategoryId { get; set; }
public List<Category> Categories { get; set; }
}
action
public async Task<IActionResult> Index()
{
var baseViewModel=new BaseViewModel();
baseViewModel.Categories= await _fashionBuyRepository.GetAllCategoriesAsync();
return View(baseViewModel);
}
use this model inside of the partial view
@model BaseViewModel
layout
@model IBaseViewModel // you can omit it but I like to have it explicitly
@if(Model!=null && Model.Categories!=null && Model.Categories.Count > 0)
{
<partial name="_productCategoriesHeader" />
}
for another view you can create this action code
public IActionResult MyAction()
var myActionViewModel= new MyActionViewModel {
..... your init code
}
InitBaseViewModel(myActionViewModel);
return View(myActionViewModel)
}
public class MyActionViewModel : BaseViewModel
//or
public class MyActionViewModel : IBaseViewModel
{
public .... {get; set;}
}
Upvotes: 1
Reputation: 313
In .NET 5, @Html.RenderAction
no longer works as to my knowledge.
I believe you can use
@await Html.PartialAsync("GetCategories")
or
@await Html.RenderPartialAsync("GetCategories")
instead of it. There may be other options, check the .NET documentation.
Upvotes: 3