Asp.Net Core 中的@Html.Action

Posted

技术标签:

【中文标题】Asp.Net Core 中的@Html.Action【英文标题】:@Html.Action in Asp.Net Core 【发布时间】:2015-01-11 01:23:14 【问题描述】:

@html.Action 在 Asp.net Core 中的什么位置? 我可以看到@Html.ActionLink,但不能像以前那样直接调用 Action。

被 ViewComponents 取代了吗?

【问题讨论】:

【参考方案1】:

是的,ViewComponents 将是执行此操作的新方法,但它们@Html.Action 之前所做的完全相同...例如,在 MVC5 和之前的版本中,调用“子操作”也将执行任何过滤器(例如,如果控制器在其上装饰了过滤器),使它们看起来像常规操作......但 ViewComponents 并非如此,它们是在实际请求的上下文中执行的。 ..

关于视图组件的更多信息: https://docs.microsoft.com/en-us/aspnet/core/mvc/views/view-components

【讨论】:

优秀。现在我们可以在调用“部分视图”(现在是 ViewComponents)时使用异步调用【参考方案2】:

更新:从 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 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));
                
            
        
    

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

services.AddSingleton&lt;IActionContextAccessor, ActionContextAccessor&gt;(); services.AddSingleton&lt;IHttpContextAccessor, HttpContextAccessor&gt;();

HttpContext 只是HttpContext.Features 的一个包装器,所以如果你在一个中改变某些东西,它也会在另一个中改变......我在try/catch 的finally 中重置了我所知道的。

我从 Items 缓存中删除了 IUrlHelper,因为即使构建 urlHelper 的 actionContext 不同(IUrlHelperFactory.GetUrlHelper),该值也会被重用。

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

【讨论】:

确保在 Startup.cs 中包含 services.AddSingleton&lt;IActionContextAccessor, ActionContextAccessor&gt;();,因为默认情况下 2.0 不这样做。 好点!我碰巧已经通过剑道间接拥有它 在 2.1 中,您需要注册 HttpContextAccessor。在 Startup.cs 中添加 services.AddHttpContextAccessor();services.AddSingleton&lt;IHttpContextAccessor, HttpContextAccessor&gt;(); 这不会在 HTTP POST 之后呈现视图,任何指针表示赞赏 另外,响应主体流在使用后被释放,我有一个中间件在请求结束前对主体进行更改,使用此扩展后失败;(【参考方案3】:

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)

注意:

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

【讨论】:

从技术上讲,此代码是 Html.Action() 的替代,后者将操作作为字符串值返回。 Html.RenderAction() 直接将action写入响应流,并返回void。 我尝试使用此代码,但是当我多次调用 Html.RenderAction 时,我收到一条错误消息“发生一个或多个错误。(已添加具有相同键的项目. 键:System.Object)”。它发生在 ControllerActionInvoker.Next 中,最终出现在“System.ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException”中你知道什么对象被用作键以及如何更改代码以允许在同一个请求中多次使用吗? 遇到同样的错误,找不到解决方法:(。发生了一个或多个错误。(已添加具有相同键的项目。键:System.Object) 我解决了。在 startup.cs 中更改它: services.AddMvc();到 services.AddMvc(opts => opts.Filters.RemoveAt(1));它删除了过滤器“TempDataFilter”。我不知道它的用途,但它解决了我的问题,并且通过禁用它我没有遇到任何其他问题。 我也尝试过使用此代码,但是当我有两个同名操作(一个用于获取另一个用于发布)时,actionInvoker 会选择该操作的发布版本。如何强制获取版本?【参考方案4】:

对于网络核心 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();

【讨论】:

我不清楚您在哪里进行了此更改?您能否将这些信息添加到我们这些无法读心的人的答案中? 我相信这是针对下面的自定义@Html.RenderAction 实现。 我相信 t 不是当前的答案。 否 @Daniel Santos ,这是正确答案。而且比其他的实用得多。【参考方案5】:

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

但是我使用 alternative 模式来@Html.Action

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

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

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

<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>

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

【讨论】:

谢谢!!您是否发现使用它的一些问题或限制?【参考方案6】:

我使用了这个页面上的人的代码来得到正确的结果。

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

https://***.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" )

【讨论】:

【参考方案7】:

Aries 的 Helper Extension 解决方法不再适用于 Net Core 2.0,因为 IActionSelectorDecisionTreeProvider 已从较新版本中删除。请参阅下面的链接。

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

【讨论】:

【参考方案8】:

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

services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();

这在 2.0 中不再默认注册。

【讨论】:

【参考方案9】:

M.R.T2017 说:……下面的例子都成功了 测试:...

首先感谢您的分享。

但是这种重载方法可能会导致“HTTP ERROR 500”:

@Html.RenderAction("About")

因为“控制器”可能是小写的控制器名称,例如。 “家”、“网格”等:

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

您需要将控制器名称大写,例如。 "grid" -> "Grid",因为控制器类名在Assembly中是区分大小写的,所以动作名是一样的。

*Visual Studio 2019/NET Core 2.2。

【讨论】:

如何在没有 View 的情况下直接返回 HtmlString ?有谁知道 ?因为没有视图,Html.Render/RenderAction 会抛出 HTTP ERROR 500。 抱歉,HTTP ERROR 500 是由剃须刀代码 (@ Layout = ...; ... ) 而不是 Html.Render/RenderAction 引起的,这些方法可以正常工作。但是Html.Render/RenderAction总是会返回空字符串,而action返回的是HtmlString(没有对应的view),有大神知道怎么解决吗?

以上是关于Asp.Net Core 中的@Html.Action的主要内容,如果未能解决你的问题,请参考以下文章

Asp.Net Core 中的静态文件

ASP.NET Core 2.0中的HttpContext

Asp.Net Core 中的静态文件

详解Asp.Net Core中的Cookies

ASP.NET Web API 与 ASP.NET Core 中的 URL 匹配差异

如何仅为 ASP.NET 5 (ASP.NET Core) 中的受保护操作添加令牌验证