@Html.Action in ASP.NET Core

@Html.Action in Asp.Net Core

Update: As of 2.2.2 HttpContextAccessor keep the context in an object (supposedly to prevent inter request mix up) and it impacts the current solution... So you need to provide the following implementation for IHttpContextAccessor (an old version) and register it as a singleton:

public class HttpContextAccessor : IHttpContextAccessor
{
private static AsyncLocal<HttpContext> _httpContextCurrent = new AsyncLocal<HttpContext>();
HttpContext IHttpContextAccessor.HttpContext { get => _httpContextCurrent.Value; set => _httpContextCurrent.Value = value; }
}

For asp.net core 2

using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.IO;
using System.Threading.Tasks;

namespace Microsoft.AspNetCore.Mvc.Rendering
{
public static class HtmlHelperViewExtensions
{
public static IHtmlContent Action(this IHtmlHelper helper, string action, object parameters = null)
{
var controller = (string)helper.ViewContext.RouteData.Values["controller"];

return Action(helper, action, controller, parameters);
}

public static IHtmlContent Action(this IHtmlHelper helper, string action, string controller, object parameters = null)
{
var area = (string)helper.ViewContext.RouteData.Values["area"];

return Action(helper, action, controller, area, parameters);
}

public static IHtmlContent Action(this IHtmlHelper helper, string action, string controller, string area, object parameters = null)
{
if (action == null)
throw new ArgumentNullException("action");

if (controller == null)
throw new ArgumentNullException("controller");

var task = RenderActionAsync(helper, action, controller, area, parameters);

return task.Result;
}

private static async Task<IHtmlContent> RenderActionAsync(this IHtmlHelper helper, string action, string controller, string area, object parameters = null)
{
// fetching required services for invocation
var serviceProvider = helper.ViewContext.HttpContext.RequestServices;
var actionContextAccessor = helper.ViewContext.HttpContext.RequestServices.GetRequiredService<IActionContextAccessor>();
var httpContextAccessor = helper.ViewContext.HttpContext.RequestServices.GetRequiredService<IHttpContextAccessor>();
var actionSelector = serviceProvider.GetRequiredService<IActionSelector>();

// creating new action invocation context
var routeData = new RouteData();
foreach (var router in helper.ViewContext.RouteData.Routers)
{
routeData.PushState(router, null, null);
}
routeData.PushState(null, new RouteValueDictionary(new { controller = controller, action = action, area = area }), null);
routeData.PushState(null, new RouteValueDictionary(parameters ?? new { }), null);

//get the actiondescriptor
RouteContext routeContext = new RouteContext(helper.ViewContext.HttpContext) { RouteData = routeData };
var candidates = actionSelector.SelectCandidates(routeContext);
var actionDescriptor = actionSelector.SelectBestCandidate(routeContext, candidates);

var originalActionContext = actionContextAccessor.ActionContext;
var originalhttpContext = httpContextAccessor.HttpContext;
try
{
var newHttpContext = serviceProvider.GetRequiredService<IHttpContextFactory>().Create(helper.ViewContext.HttpContext.Features);
if (newHttpContext.Items.ContainsKey(typeof(IUrlHelper)))
{
newHttpContext.Items.Remove(typeof(IUrlHelper));
}
newHttpContext.Response.Body = new MemoryStream();
var actionContext = new ActionContext(newHttpContext, routeData, actionDescriptor);
actionContextAccessor.ActionContext = actionContext;
var invoker = serviceProvider.GetRequiredService<IActionInvokerFactory>().CreateInvoker(actionContext);
await invoker.InvokeAsync();
newHttpContext.Response.Body.Position = 0;
using (var reader = new StreamReader(newHttpContext.Response.Body))
{
return new HtmlString(reader.ReadToEnd());
}
}
catch (Exception ex)
{
return new HtmlString(ex.Message);
}
finally
{
actionContextAccessor.ActionContext = originalActionContext;
httpContextAccessor.HttpContext = originalhttpContext;
if (helper.ViewContext.HttpContext.Items.ContainsKey(typeof(IUrlHelper)))
{
helper.ViewContext.HttpContext.Items.Remove(typeof(IUrlHelper));
}
}
}
}
}

It is based on Aries response. I corrected what wasn't compiling for 2.0 and I added a couple of tweaks. There are 2 glorified static values for the current httpcontext and the current actioncontext. The one for httpcontext is set in IHttpContextFactory.Create and I set the one for actioncontext in the code. Note that depending on the features you use IActionContextAccessor and IHttpContextAccessor may not be registered by default, so you may need to add them in your startup:

services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

HttpContext is just a wrapper around HttpContext.Features, so if you change something in one, it also changes in the other... I reset what I know about in the finally of the try/catch.

I removed the IUrlHelper from the Items cache since this value will be reused even if the actionContext to build the urlHelper is different(IUrlHelperFactory.GetUrlHelper).

Asp.net core 2.0 assumes you won't do this, there is a good chance there are other cached things, so I recommend to be careful when using this and just don't if you don't need to.

The @Html.Action() is not available

ChildActionOnlyAttribute, @Html.Action and @Html.RenderAction are not available in .NET Core MVC. These are available as part of ASP.NET MVC framework. ChildActionOnlyAttribute can be used from the namespace System.Web.Mvc.

Alternatively, in .NET Core MVC, you can use the View Components. Here is the link to the MS Document.

Html Helper 'Action' is Undefined, Asp.NET Core 2.2

"Posting as an answer".

Like I said, Html.Action was removed from ASP.NET Core and was replaced for ViewComponents.

There is a good tutorial in Microsoft Docs, you can see here. Explaining about the advantages, the place when you can put the View for your ViewComponent, how to use parameters and so on.

Equivalent of Html.RenderAction in ASP.NET Core

I was finally able to do it with ViewComponent. So, instead of RenderAction(), I did:

@section xyz{
@await Component.InvokeAsync("abc")
}

Where abc is a class as abcViewComponent. The ViewComponent looks like:

public class abcViewComponent : ViewComponent
{
private DbContextOptions<MyContext> db = new DbContextOptions<MyContext>();
public async Task<IViewComponentResult> InvokeAsync()
{
MyContext context = new MyContext(db);
IEnumerable<tableRowClass> mc = await context.tableRows.ToListAsync();
return View(mc);
}
}

Then, I created a view under a new folder 'abc' as Views/Home/Components/abc/Default.cshtml

It is to be noted that the view name is Default.cshtml and that is how it worked. If anyone has any better solution, please let me know.

Thanks for pointing me in the direction of ViewComponent.

ASP.NET Core 5.0 MVC : CS1061 error using Html.RenderAction

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.

MVC Core 2.0 HTML.Action vs Component.InvokeAsync

There's two parts to a view component: your class that derives from ViewComponent and a view that component should return. By convention, your view component classes actually go in /ViewComponents, while again by convention, your view goes into Views/Shared/Components/{component name}/Default.cshtml.

That's just convention, though. The same as how Razor automatically finds views based on controller/action name. In truth, the classes can go wherever you like. They are brought in via reflection, so as long as it's somewhere in the assembly or a referenced assembly, it will be found. The views likewise can be moved wherever you like, you just have to tell it where to find them, since it won't be able to do it automatically based on convention anymore. See: https://learn.microsoft.com/en-us/aspnet/core/mvc/views/view-components#view-search-path

Unable to Retain ActionContext When Running HTML.Action in .Net Core 3.1

the UserProfile was set to null

I did a test and can reproduce same issue. In your code we can find that the UserProfile property of your PeoplePickerViewModel class is a complex type, which seems cause this issue.

To fix it, you can try the following workaround.

routeData.PushState(null, new RouteValueDictionary(new { controller = controller, action = action, area = area }), null);

if (parameters == null)
{
routeData.PushState(null, new RouteValueDictionary(new { }), null);
}
else
{
var type = parameters.GetType();

if (parameters.GetType() == typeof(PeoplePickerViewModel))
{
//dynamically generate and populate values based on your model class

var mdata = parameters as PeoplePickerViewModel;

var routeValDict = new RouteValueDictionary();
routeValDict.Add("PickerId", mdata.PickerId);
routeValDict.Add("UserProfile.Id", mdata.UserProfile.Id);
routeValDict.Add("UserProfile.Name", mdata.UserProfile.Name);

routeData.PushState(null, routeValDict, null);
}
else
{
routeData.PushState(null, new RouteValueDictionary(parameters), null);
}
}

Testing code of UserModel class

public class UserModel
{
public int Id { get; set; }
public string Name { get; set; }
}

Difference between Html.RenderAction and Html.Action

Html.Action() – Outputs string

Html.RenderAction() – Renders directly to response stream

If the action returns a large amount of HTML, then rendering directly to the response stream provides better performance than outputting a string.



Related Topics



Leave a reply



Submit