【问题标题】:Unable to Retain ActionContext When Running HTML.Action in .Net Core 3.1在 .Net Core 3.1 中运行 HTML.Action 时无法保留 ActionContext
【发布时间】:2021-01-03 20:26:53
【问题描述】:

我了解在 .Net Core 3.1 中删除了 html.action 以支持 ViewComponents。不幸的是,我拥有的代码不适用于 ViewComponent,因为它是一个自定义的 PeoplePicker 控件,可以进行用户交互。请记住,此 PeoplePicker 控件在 .Net 4.7.2 中可以正常工作。我在网上查看并找到了有关如何重新实现 html.action 功能的方法。我遇到的问题是,当代码到达 await invoker.InvokeAsync();在代码行中,设置的 ActionContext 会被后续调用底层模型的 get/set 属性覆盖。我将介绍代码以及正在发生的事情。这是调用 PeoplePicker 的行:

 @Html.Action("PeoplePicker", "PeoplePicker", new EDAD.Models.PeoplePickerViewModel { PickerId = 20, UserProfile = Model.CurrentUser })

发生的下一步是我实现的 HTMLHelperViewExtensions 以允许调用 html.Action:

        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));
                        }
                    }
                }
            }
        }

此时一切正常。代码到达以下行,然后调用 People Picker 模型

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

这会进入模型并正确获取带有传入数据的 2 个变量:

public class PeoplePickerViewModel 
{
    public int? PickerId { get; set; }
    public  UserModel UserProfile { get; set; }
}

代码通过 HTMLHelper 代码继续。在 await invoker.InvokeAsync() 之前的行上,我可以查看两个变量(PickerID 和 UserProfile)中的数据。这就是问题发生的地方。当它到达 await invoker.InvokeAsync() 时,它返回模型并获取 UserProfile(现在为 NULL),获取保留该值的 PickerID,然后第三次再次获取 UserProfile(它仍然为 null )。然后它将信息传递给 PeoplePicker 控制器,其中“模型”变量用于设置 PeoplePicker。由于第二次/第三次调用将 UserProfile 设置为 null,因此 model.UserProfile 被设置为 new UserModel() 而不是使用开始时的那个。

    public PartialViewResult PeoplePicker(PeoplePickerViewModel model)
    {
        model.UserProfile = model.UserProfile ?? new UserModel();
        model.PickerId = model.PickerId ?? 0;
        return PartialView(model);
    }

让我补充一点,PeoplePicker 在其功能的所有其他方面都有效。当一开始就传入用户配置文件时,它就不起作用了。

所以这是我的问题:

  1. 为什么不止一次调用模型?
  2. 除了我目前所做的之外,还有其他方法可以解决此问题吗?
  3. 在 Core 3.1 中是否有更好的方法来做到这一点?

【问题讨论】:

    标签: c# asp.net-core .net-core asp.net-core-3.1


    【解决方案1】:

    UserProfile 设置为 null

    我做了一个测试,可以重现同样的问题。在您的代码中,我们可以发现您的PeoplePickerViewModel 类的UserProfile 属性是一个复杂类型,这似乎是导致此问题的原因。

    要修复它,您可以尝试以下解决方法。

    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);
        }
    }
    

    UserModel类的测试代码

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

    【讨论】:

    • RouteValueDictionary 不包含“ToRouteValueDictionaryWithCollection”的定义。我错过了什么?
    • 嗨@FlyFish,请移除.ToRouteValueDictionaryWithCollection(),我将它用于另一个测试用例。
    • 谢谢@FeiHan。这正是我需要的!!!
    猜你喜欢
    • 1970-01-01
    • 2022-01-19
    • 1970-01-01
    • 2020-05-10
    • 2020-11-27
    • 2020-05-22
    • 2020-12-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多