【发布时间】: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 在其功能的所有其他方面都有效。当一开始就传入用户配置文件时,它就不起作用了。
所以这是我的问题:
- 为什么不止一次调用模型?
- 除了我目前所做的之外,还有其他方法可以解决此问题吗?
- 在 Core 3.1 中是否有更好的方法来做到这一点?
【问题讨论】:
标签: c# asp.net-core .net-core asp.net-core-3.1