Доступ к экземпляру контроллера из представления в .net core 5

Я обращался к экземпляру базового контроллера из представления через следующую строку, ASP.NET

BaseController baseController = ViewContext.Controller as BaseController;

У меня новый проект в ASP.NET Core 5.0. По какой-то причине я хотел бы получить доступ к базовому контроллеру, но теперь он не похож на предыдущую версию MVC.

Есть ли какое-либо решение или альтернатива для достижения того же?

ПРИМЕЧАНИЕ. Я хочу получить доступ к полностью инициализированному экземпляру контроллера. Я попытался получить экземпляр через внедрение зависимостей, используя метод GetService(). Он дает новый экземпляр контроллера, но не полностью инициализированный, например, свойства HttpContext, User и т. д. имеют значение null.


person Aftab Ahmed Kalhoro    schedule 18.03.2021    source источник
comment
как насчет того, чтобы просто отправить базу контроллера через ViewModel с контроллера на представление? Таким образом, вы сэкономите много процессов литья и прочего.   -  person IbraHim M. Nada    schedule 18.03.2021
comment
Вероятно, стоит отметить, что, хотя @King-King answer предлагает решение вашего вопроса, обычно это запах кода, требующий доступа контроллеру из представления и почти наверняка нарушает основные принципы проектирования шаблона MVC. Лучшее решение — взять всю необходимую информацию из вашего контроллера и внедрить ее в модель представления, которую вы возвращаете в свое представление.   -  person Jeremy Caney    schedule 21.03.2021


Ответы (1)


ПРИМЕЧАНИЕ. это первое решение, основанное на ControllerActionInvokerCache, было протестировано в asp.net core 2.2 и работало нормально. Однако в более поздних версиях похоже, что этот класс сделан internal и больше недоступен, это решение не поможет. Попробуйте второе решение, представленное позже.

Вызванный контроллер предварительно кэшируется в ControllerActionInvokerCache (который доступен через DI). Ключ кеша — это экземпляр ControllerContext, который не сравнивается одинаково по ссылке (поэтому вы можете создать новый экземпляр, пока обернутый ActionContext является текущим). На самом деле ControllerActionInvokerCache - это составной кеш.

Вы можете увидеть код ниже для деталей:

public static class RazorViewExtensions
{
    public static Controller GetInvokedController(this RazorPage view)
    {
        var serviceProvider = view.Context.RequestServices;
        var controllerCache = serviceProvider.GetRequiredService<ControllerActionInvokerCache>();         
        //ViewContext here is also an ActionContext   
        var controllerContext = new ControllerContext(view.ViewContext);
        var cacheEntry = controllerCache.GetCachedResult(controllerContext).cacheEntry;
        return cacheEntry == null ? null : cacheEntry.ControllerFactory(controllerContext) as Controller;
    }
}    

Для удобства мы объявляем метод расширения, как указано выше. Чтобы использовать его в представлении вашего контроллера:

var controller = this.GetInvokedController();

Вы можете основываться на этом, чтобы написать аналогичный метод расширения для использования внутри страницы Razor (базовая страница — Page вместо RazorPage).

На самом деле ControllerActionInvokerCacheEntry передается ControllerActionDescriptor.CacheEntry. Однако это свойство CacheEntry является внутренним (и, конечно же, не задокументировано). Мы можем видеть это в исходном коде. Таким образом, вы можете использовать reflection для получения этой записи в кэше. Однако это требует отражения, поэтому блок кода даже длиннее, чем первое решение, которое мы использовали выше.

Вот еще одно решение путем извлечения экземпляра контроллера из файла ActionExecutingContext.Controller. Это может быть немного быстрее, чем первое решение, но нам нужен отдельный класс для пользовательского IActionFilter, чтобы захватить экземпляр контроллера в функцию, совместно используемую через HttpContext.Features. Код, конечно, немного длиннее, например:

//define the feature types
public interface IInvokedControllerFeature
{
    Controller Controller { get; }
}
public class InvokedControllerFeature : IInvokedControllerFeature
{
    public InvokedControllerFeature(Controller controller)
    {
        Controller = controller;
    }
    public Controller Controller { get; }
}

//an extension class to contain several convenient extension methods
//to setup the feature and get the controller instance later
public static class InvokedControllerFeatureExtensions
{
    public static Controller GetInvokedController(this HttpContext httpContext)
    {
        return httpContext.Features.Get<IInvokedControllerFeature>()?.Controller;
    }
    public static Controller GetInvokedController(this RazorPage view)
    {
        return view.Context.GetInvokedController();
    }
    public static IServiceCollection AddInvokedControllerFeature(this IServiceCollection services)
    {
        return services.Configure<MvcOptions>(o => {
            o.Filters.Add<InvokedControllerFeatureActionFilter>();
        });
    }
    class InvokedControllerFeatureActionFilter : IActionFilter
    {
        public void OnActionExecuted(ActionExecutedContext context) {}

        public void OnActionExecuting(ActionExecutingContext context)
        {
            //share the controller instance via a feature
            context.HttpContext.Features.Set<IInvokedControllerFeature>(new InvokedControllerFeature(context.Controller as Controller));
        }
    }
}

//register the feature inside Startup.ConfigureServices
services.AddInvokedControllerFeature();

Использование внутри представления контроллера точно такое же, как и в первом решении.

person King King    schedule 18.03.2021
comment
Эта первая часть, расширение Razor View (общедоступный статический контроллер GetInvokedController (это представление RazorPage)) не работала, но позже заработала. Большое спасибо - person Aftab Ahmed Kalhoro; 19.03.2021
comment
@AftabAhmedKalhoro, пожалуйста, на самом деле я тестировал оба решения, и оба работали на моей стороне. Не могли бы вы поделиться дополнительной информацией о том, как это не работает для первого решения? есть ли какие-либо исключения или вы просто получаете null экземпляр контроллера? - person King King; 19.03.2021
comment
@AftabAhmedKalhoro Я полагаю, что ошибка, с которой вы столкнулись в первом решении, связана с IActionContextAccessor, который не зарегистрирован на вашей стороне, однако я только что узнал, что ViewContext также является ActionContext, поэтому мы можем удобно использовать это вместо использования IActionContextAccessor , это также сокращает код :) - person King King; 19.03.2021
comment
Были ошибки: ControllerActionInvokerCache недоступен из-за его уровня защиты 'ControllerActionInvokerCache.GetCachedResult(ControllerContext)' недоступен из-за его уровня защиты 'ControllerActionInvokerCacheEntry.ControllerFactory' недоступен из-за его уровня защиты - person Aftab Ahmed Kalhoro; 20.03.2021
comment
@AftabAhmedKalhoro спасибо за ваш ответ, похоже, он был изменен на internal в более поздних версиях asp.net core, то, что я тестировал, находится в asp.net core 2.2. Я обновлю ответ, чтобы отразить это. - person King King; 21.03.2021
comment
как я уже говорил, я использую .NET Core 5.0. Так что, возможно, структура изменена. В любом случае, большое спасибо за вашу поддержку. - person Aftab Ahmed Kalhoro; 21.03.2021
comment
Меня также удивляет, когда я использую var controller = this.GetInvokedController() в качестве BaseController; я все еще получаю экземпляр контроллера (т.е. HomeController), а не BaseController - person Aftab Ahmed Kalhoro; 23.03.2021
comment
@AftabAhmedKalhoro ваш фактический класс контроллера наследуется от BaseController, поэтому он должен наследовать все общедоступные и защищенные члены этого базового контроллера. Вы можете получить доступ ко всем общедоступным членам BaseController через полученный вами экземпляр контроллера, так что это должно работать. Другими словами, экземпляр контроллера, который вы получаете, на самом деле является BaseController, а также, более конкретно, более производным контроллером (например: HomeController, ...). - person King King; 23.03.2021