Невозможно сохранить ActionContext при запуске HTML.Action в .Net Core 3.1

#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 работает во всех других аспектах своей функциональности. Это просто не работает, когда пользовательский профиль передается в начале.

Итак, вот мои вопросы:

  1. Почему он вызывает модель более одного раза?
  2. Есть ли способ устранить неполадки, отличные от того, что я сделал до этого момента?
  3. Есть ли лучший способ сделать это в ядре 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. Это ИМЕННО то, что мне было нужно!!!