@Html.Asp.Net 核心行动

本文关键字:核心 Net Asp @Html | 更新日期: 2023-09-27 18:30:14

@Html.Action Asp.net 核心在哪里?我可以看到@Html.ActionLink,但不像以前那样直接调用操作。

它被ViewComponents取代了吗?

@Html.Asp.Net 核心行动

是的,ViewComponents 将是执行此操作的新方法,但它们与@Html.Action以前所做的并不完全相同......例如,在 MVC5 及更早版本中,调用"子操作"还将执行任何过滤器(例如,如果控制器在其上装饰了过滤器(,使它们看起来像常规操作......但对于 ViewComponents 来说并非如此,它们是在实际请求的上下文中执行的......

有关视图组件的详细信息:https://learn.microsoft.com/en-us/aspnet/core/mvc/views/view-components

更新

:从 2.2.2 开始,HttpContextAccessor 将上下文保留在一个对象中(据说是为了防止请求间混淆(,它会影响当前的解决方案...... 因此,您需要为 IHttpContextAccessor(旧版本(提供以下实现,并将其注册为单例:

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

对于 asp.net 核心 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));
                }
            }
        }
    }
}

它基于白羊座的回应。 我纠正了没有为 2.0 编译的内容,并添加了一些调整。 当前 httpcontext 和当前操作上下文有 2 个美化的静态值。 httpcontext的一个是在IHttpContextFactory.Create中设置的,我在代码中为actioncontext设置了一个。 请注意,根据您使用的功能IActionContextAccessorIHttpContextAccessor可能默认未注册,因此您可能需要在启动中添加它们:

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

HttpContext只是HttpContext.Features的包装器,所以如果你在一个中更改某些内容,它也会在另一个中更改...我在尝试/捕获的最后重置了我所知道的。

我从项目缓存中删除了IUrlHelper,因为即使构建 urlHelper 的操作上下文不同(IUrlHelperFactory.GetUrlHelper(,此值也会被重用。

Asp.net Core 2.0 假设您不会这样做,但很有可能还有其他缓存的东西,所以我建议在使用它时要小心,如果您不需要,请不要这样做。

ViewComponents 很棒,但对于 Ajax 来说不是那么好。

如果你真的错过了@Html.RenderAction方法,那么这是我为AspNetCore拼凑的一个快速实现。

using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Routing;
namespace Microsoft.AspNetCore.Mvc.Rendering    {
    public static class HtmlHelperViewExtensions
    {
        public static IHtmlContent RenderAction(this IHtmlHelper helper, string action, object parameters = null)
        {
            var controller =  (string)helper.ViewContext.RouteData.Values["controller"];
            return RenderAction(helper, action, controller, parameters);
        }
        public static IHtmlContent RenderAction(this IHtmlHelper helper, string action, string controller, object parameters = null)
        {
            var area = (string)helper.ViewContext.RouteData.Values["area"];
            return RenderAction(helper, action, controller, area, parameters);
        }
        public static IHtmlContent RenderAction(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");
            if (area == null)
                throw new ArgumentNullException("area");
            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 currentHttpContext = helper.ViewContext?.HttpContext;
            var httpContextFactory = GetServiceOrFail<IHttpContextFactory>(currentHttpContext);
            var actionInvokerFactory = GetServiceOrFail<IActionInvokerFactory>(currentHttpContext);
            var actionSelector = GetServiceOrFail<IActionSelectorDecisionTreeProvider>(currentHttpContext);
            // creating new action invocation context
            var routeData = new RouteData();
            var routeParams = new RouteValueDictionary(parameters ?? new { });
            var routeValues = new RouteValueDictionary(new { area = area, controller = controller, action = action });
            var newHttpContext = httpContextFactory.Create(currentHttpContext.Features);
            newHttpContext.Response.Body = new MemoryStream();
            foreach (var router in helper.ViewContext.RouteData.Routers)
                routeData.PushState(router, null, null);
            routeData.PushState(null, routeValues, null);
            routeData.PushState(null, routeParams, null);
            var actionDescriptor = actionSelector.DecisionTree.Select(routeValues).First();
            var actionContext = new ActionContext(newHttpContext, routeData, actionDescriptor);
            // invoke action and retreive the response body
            var invoker = actionInvokerFactory.CreateInvoker(actionContext);
            string content = null;
            await invoker.InvokeAsync().ContinueWith(task => {
                if (task.IsFaulted)
                {
                    content = task.Exception.Message;
                }
                else if (task.IsCompleted)
                {
                    newHttpContext.Response.Body.Position = 0;
                    using (var reader = new StreamReader(newHttpContext.Response.Body))
                        content = reader.ReadToEnd();
                }
            });
            return new HtmlString(content);
        }
        private static TService GetServiceOrFail<TService>(HttpContext httpContext)
        {
            if (httpContext == null)
                throw new ArgumentNullException(nameof(httpContext));
            var service = httpContext.RequestServices.GetService(typeof(TService));
            if (service == null)
                throw new InvalidOperationException($"Could not locate service: {nameof(TService)}");
            return (TService)service;
        }
    }
}

您可以使用以下方法之一从视图中调用:

@Html.RenderAction("action", "controller", "area", new { id = 1})
@Html.RenderAction("action", "controller", new { id = 1})
@Html.RenderAction("action", new { id = 1})

注意:

如果未提供控制器名称和可选的区域名称,将默认为操作上下文中的相应值。

@Html.Action 被 ViewComponents 取代。我不喜欢ViewComponents有多种原因。

但是我正在使用替代模式来@Html.Action

首先,我在控制器上创建操作,该操作返回部分视图,其中包含我想在页面中显示的内容,即

    [HttpGet]
    public async Task<IActionResult> GetFoo()
    {
        return PartialView("_Foo", new Foo());
    }

然后我将div 放在应该加载 foo 视图的页面上,并在该页面底部包含 IIFE。即下面的代码将加载 GetFoo 视图,然后将该 html 插入到div 与 id foo-view。

<div class="page">
    <div id="foo-view" data-url="@Url.Action(action: "GetFoo", controller: "Home")"></div>
</div>
<script>
    $(document).ready(function () {
        (function () {
            var url = $("#foo-view").data("url");
            $("#foo-view").load(url);
        })();
    });
</script>

您可能还希望在从服务器获取视图时显示微调器。

对于网络核心 2.0

using Microsoft.AspNetCore.Mvc.Infrastructure;

取代

// var actionSelector = GetServiceOrFail<IActionSelectorDecisionTreeProvider>(currentHttpContext); 
var actionSelector = GetServiceOrFail<IActionDescriptorCollectionProvider>(currentHttpContext); 

// var actionDescriptor = actionSelector.DecisionTree.Select(routeValues).First(); 
var actionDescriptor = actionSelector.ActionDescriptors.Items.Where(i => i.RouteValues["Controller"] == controller && i.RouteValues["Action"] == action).First();

我使用此页面上的人员代码来获得正确的结果。

https://stackoverflow.com/a/39951006/6778726

https://stackoverflow.com/a/46859170/6778726

例如,在旧类中,执行以下代码时,显示错误

@Html.RenderAction("About", "Home")

修复了以下代码

using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Routing;
namespace Microsoft.AspNetCore.Mvc.Rendering
{
    public static class HtmlHelperViewExtensions
    {
        public static IHtmlContent RenderAction(this IHtmlHelper helper, string action, object parameters = null)
        {
            var controller = (string)helper.ViewContext.RouteData.Values["controller"];
            return RenderAction(helper, action, controller, parameters);
        }
        public static IHtmlContent RenderAction(this IHtmlHelper helper, string action, string controller, object parameters = null)
        {
            var area = (string)helper.ViewContext.RouteData.Values["area"];
            return RenderAction(helper, action, controller, area, parameters);
        }
        public static IHtmlContent RenderAction(this IHtmlHelper helper, string action, string controller, string area, object parameters = null)
        {
            if (action == null)
                throw new ArgumentNullException(nameof(controller));
            if (controller == null)
                throw new ArgumentNullException(nameof(action));
            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 currentHttpContext = helper.ViewContext.HttpContext;
            var httpContextFactory = GetServiceOrFail<IHttpContextFactory>(currentHttpContext);
            var actionInvokerFactory = GetServiceOrFail<IActionInvokerFactory>(currentHttpContext);
            var actionSelector = GetServiceOrFail<IActionDescriptorCollectionProvider>(currentHttpContext);
            // creating new action invocation context
            var routeData = new RouteData();
            var routeParams = new RouteValueDictionary(parameters ?? new { });
            var routeValues = new RouteValueDictionary(new { area, controller, action });
            var newHttpContext = httpContextFactory.Create(currentHttpContext.Features);
            newHttpContext.Response.Body = new MemoryStream();
            foreach (var router in helper.ViewContext.RouteData.Routers)
                routeData.PushState(router, null, null);
            routeData.PushState(null, routeValues, null);
            routeData.PushState(null, routeParams, null);
            var actionDescriptor = actionSelector.ActionDescriptors.Items.First(i => i.RouteValues["Controller"] == controller && i.RouteValues["Action"] == action);
            var actionContext = new ActionContext(newHttpContext, routeData, actionDescriptor);
            // invoke action and retreive the response body
            var invoker = actionInvokerFactory.CreateInvoker(actionContext);
            string content = null;
            await invoker.InvokeAsync().ContinueWith(task =>
            {
                if (task.IsFaulted)
                {
                    content = task.Exception.Message;
                }
                else if (task.IsCompleted)
                {
                    newHttpContext.Response.Body.Position = 0;
                    using (var reader = new StreamReader(newHttpContext.Response.Body))
                        content = reader.ReadToEnd();
                }
            });
            return new HtmlString(content);
        }
        private static TService GetServiceOrFail<TService>(HttpContext httpContext)
        {
            if (httpContext == null)
                throw new ArgumentNullException(nameof(httpContext));
            var service = httpContext.RequestServices.GetService(typeof(TService));
            if (service == null)
                throw new InvalidOperationException($"Could not locate service: {nameof(TService)}");
            return (TService)service;
        }
    }
}

已成功测试以下示例:

@Html.RenderAction("About")
@Html.RenderAction("About", "Home")
@Html.RenderAction("About", new { data1 = "test1", data2 = "test2" })
@Html.RenderAction("About", "Home", new { data1 = "test1", data2 = "test2" })

Aries对帮助程序扩展的解决方法对于Net Core 2.0不再可行,因为IActionSelectorDecisionTreeProvider已从较新版本中删除。请参阅下面的链接。

https://github.com/Microsoft/aspnet-api-versioning/issues/154

对于Yepeekai提供的 asp.net 核心2解决方案,请在您的Startup.cs中添加以下内容:

services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();

默认情况下,这在 2.0 中不再注册。

我的解决方案是基于 Yepeekai 基于白羊座解决方案的大修代码。

由于 actionSelector.SelectCandidate 没有返回所有路由,我不得不更改为 IActionDescriptorCollectionProvider。这导致为所需操作获取多个路由,这使得 actionSelector.SelectBestCandidate 失败。所以我手动对可能的路由进行了控制器和操作过滤。

请记住,对于路由筛选,您可能需要考虑更多值,例如"区域"。

<小时 />

完整代码:

在你的 StartUp.cs:(记得使用你自己的 HttpContextAccessor 实现,没有命名空间,将使用框架版本!

builder.Services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
builder.Services.AddSingleton<IHttpContextAccessor, Helper.HttpContextAccessor>();

来自Yepeekai的Custom HttpContextAccessor:

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

html 帮助程序的扩展:

public static class HtmlHelperViewExtensions
{
    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 result = RenderActionAsync(helper, action, controller, area, parameters).GetAwaiter().GetResult();
        return 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 = serviceProvider.GetRequiredService<IActionContextAccessor>();
        var httpContextAccessor = serviceProvider.GetRequiredService<IHttpContextAccessor>();
        var actionSelector = serviceProvider.GetRequiredService<IActionSelector>();
        var actionCollectionProvider = serviceProvider.GetRequiredService<IActionDescriptorCollectionProvider>();
        // 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 originalActionContext = actionContextAccessor.ActionContext;
        var originalhttpContext = httpContextAccessor.HttpContext;
        try
        {
            IReadOnlyList<ActionDescriptor> candidates = actionCollectionProvider.ActionDescriptors.Items.Select(x => x as ControllerActionDescriptor)
                .Where(x =>
                string.Equals(x.ControllerName, controller, StringComparison.CurrentCultureIgnoreCase) &&
                string.Equals(x.ActionName, action, StringComparison.CurrentCultureIgnoreCase)).ToList();
            var actionDescriptor = actionSelector.SelectBestCandidate(routeContext, candidates);
            var newHttpContext = serviceProvider.GetRequiredService<IHttpContextFactory>().Create(helper.ViewContext.HttpContext.Features);
            if (newHttpContext.Items.ContainsKey(typeof(IUrlHelper)))
            {
                newHttpContext.Items.Remove(typeof(IUrlHelper));
            }
            using (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().ConfigureAwait(false);
                newHttpContext.Response.Body.Position = 0;
                using (var reader = new StreamReader(newHttpContext.Response.Body))
                {
                    return new HtmlString(reader.ReadToEnd());
                }
            };
        }
        catch (Exception ex)
        {
            logger.Error($"Failed to invoke action {action} on controller {controller}.", 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));
            }
        }
    }
}
<小时 />

重要节点

我在访问时遇到了MemoryStream被处理的奇怪问题。问题的根源似乎是布局和实际索引 html 中使用的多个部分。确保您正在调用的操作返回此内容。视图((但是这个。PartialView(( 否则你会遇到同样的问题或更糟。

如果有什么需要改进的,请告诉我。

M.R.T2017 说 : ...以下示例已成功 测试:。。。

首先感谢您的分享。

但是这种重载方法可能会导致"HTTP错误500":

@Html.RenderAction("About")
因为"控制器">

可能是小写的控制器名称,例如"home","grid"等:

helper.ViewContext.RouteData.Values["controller"]

您需要将控制器名称大写,例如"grid"->"Grid",因为控制器类名在程序集中区分大小写,操作名称相同。

*Visual Studio 2019/NET Core 2.2。

我在Umbraco V11(.NET 7.0(上尝试了一个复杂的控制器构造函数,它运行良好:

XXController(ILogger<XXController> logger, ICompositeViewEngine compositeViewEngine, IUmbracoContextAccessor umbracoContextAccessor,
                IPublishedValueFallback publishedValueFallback, IExamineManager ExamineManager, IPublishedContentQuery contentquery, IContentService contentService,
                IWebHostEnvironment? webHost, IMemoryCache memoryCache, IMemberService memberService, IUserService userService, IHttpContextAccessor contextAccessor)
            : base(logger, compositeViewEngine, umbracoContextAccessor)