#c# #asp.net-core #.net-core #asp.net-core-3.1
#c# #asp.net-core #.net-core #asp.net-core-3.1
Вопрос:
Я понимаю, что в .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.Вызываются действия:
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.Resu<
}
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));
}
}
}
}
}
На данный момент все работает. Код переходит к следующей строке, где затем вызывается модель выбора людей
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() Я могу просматривать данные в обеих из 2 переменных (PickerID и UserProfile). Вот где возникает проблема. Когда он попадает в await invoker.InvokeAsync() возвращается к модели и получает пользовательский профиль (который теперь равен нулю), получает идентификатор выбора, который сохранил значение, затем снова получает пользовательский профиль в третий раз (он по-прежнему равен нулю). Затем он передает информацию контроллеру PeoplePicker, где переменная «model» используется для установки PeoplePicker. Поскольку для пользовательского профиля было установлено значение null при втором / третьем вызове модели.Для UserProfile установлена новая UserModel() вместо той, с которой была запущена.
public PartialViewResult PeoplePicker(PeoplePickerViewModel model)
{
model.UserProfile = model.UserProfile ?? new UserModel();
model.PickerId = model.PickerId ?? 0;
return PartialView(model);
}
Позвольте мне добавить, что PeoplePicker работает во всех других аспектах своей функциональности. Это просто не работает, когда пользовательский профиль передается в начале.
Итак, вот мои вопросы:
- Почему он вызывает модель более одного раза?
- Есть ли способ устранить неполадки, отличные от того, что я сделал до этого момента?
- Есть ли лучший способ сделать это в ядре 3.1?
Ответ №1:
для пользовательского профиля было установлено значение null
Я провел тест и могу воспроизвести ту же проблему. В вашем коде мы можем обнаружить, что UserProfile
свойство вашего PeoplePickerViewModel
класса является сложным типом, который, по-видимому, вызывает эту проблему.
Чтобы исправить это, вы можете попробовать следующий обходной путь.
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; }
}
Комментарии:
1. RouteValueDictionary не содержит определения для «ToRouteValueDictionaryWithCollection». Чего мне не хватает?
2. Привет @FlyFish, просто удалите
.ToRouteValueDictionaryWithCollection()
, я использовал его для другого примера тестирования.3. Спасибо @FeiHan. Это ИМЕННО то, что мне было нужно!!!