注意:第一个基于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();
控制器视图内的用法与第一个解决方案相同。