从 .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() 方法通过依赖注入获取实例。它给出了控制器的新实例,但没有完全初始化,例如 HttpContext、User 等属性为空。
注意: 第一个基于 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();
控制器视图中的用法与第一个解决方案相同。
我一直在通过以下行从视图访问基本控制器实例,ASP.NET
BaseController baseController = ViewContext.Controller as BaseController;
我在 ASP.NET Core 5.0 中有一个新项目。出于某种原因,我想访问基本控制器,但现在看来它与以前版本的 MVC 不相似。
是否有任何解决方案或替代方案可以实现同样的目标?
注意:我想访问控制器的完全初始化实例。我尝试使用 GetService() 方法通过依赖注入获取实例。它给出了控制器的新实例,但没有完全初始化,例如 HttpContext、User 等属性为空。
注意: 第一个基于 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();
控制器视图中的用法与第一个解决方案相同。