从 .net core 5 中的视图访问控制器实例

Accessing Controller instance from View in .net core 5

我一直在通过以下行从视图访问基本控制器实例,ASP.NET

BaseController baseController = ViewContext.Controller as BaseController;

我在 ASP.NET Core 5.0 中有一个新项目。出于某种原因,我想访问基本控制器,但现在看来它与以前版本的 MVC 不相似。

是否有任何解决方案或替代方案可以实现同样的目标?

注意:我想访问控制器的完全初始化实例。我尝试使用 GetService() 方法通过依赖注入获取实例。它给出了控制器的新实例,但没有完全初始化,例如 HttpContextUser 等属性为空。

注意: 第一个基于 ControllerActionInvokerCache 的解决方案在 asp.net core 2.2 中进行了测试并且运行良好。但是在以后的版本中,看起来 class 变成了 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 中提取控制器实例的另一种解决方案。这可能 比第一个解决方案快 一点点,但我们需要一个单独的 class 用于自定义 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();

控制器视图中的用法与第一个解决方案相同。