4

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.

enter image description here

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/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384- EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/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);
        }
Serge
  • 40,935
  • 4
  • 18
  • 45
Sinado123
  • 93
  • 2
  • 6
  • 1
    I believe this link might be applicable. Please review, and let us know the results: https://stackoverflow.com/a/47065308/421195. ALSO: please copy/paste text whenever possible. Screenshots are discouraged on SO: https://meta.stackoverflow.com/questions/303812/ – paulsm4 Aug 06 '21 at 19:06
  • The successor of ASP.NET MVC (not Core) Html.Action and Html.RenderAction are View Components, see https://learn.microsoft.com/en-us/aspnet/core/mvc/views/view-components?view=aspnetcore-5.0 – Michael Aug 06 '21 at 19:09
  • Well noted @paulsm4. I used the screenshot as a way to show what the error message was. A copy of the code was given as well. I am working on it and will update when I implement a possible solution. – Sinado123 Aug 06 '21 at 23:37

3 Answers3

3

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.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Loupeznik
  • 318
  • 5
  • 8
2

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.

Dharman
  • 30,962
  • 25
  • 85
  • 135
Sinado123
  • 93
  • 2
  • 6
1

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;}
}
Serge
  • 40,935
  • 4
  • 18
  • 45