我正在使用Simple Injector来执行ASP.NET Core应用程序的依赖项注入任务。我正在寻找一种将IUrlHelper
注入控制器的方法。我了解IUrlHelperFactory
,但希望直接注入IUrlHelper
,以使事情更整洁和易于模拟。
以下问题为通过标准ASP.Net依赖注入注入IUrlHelper
提供了有用的答案:
基于这些答案,我想出了类似的SimpleInjector注册:
container.Register<IUrlHelper>(
() => container.GetInstance<IUrlHelperFactory>().GetUrlHelper(
container.GetInstance<IActionContextAccessor>().ActionContext));
它确实有效,但是由于IActionContextAccessor.ActionContext
在没有活动的HTTP请求时返回null
,因此在应用启动期间调用该绑定会导致container.Verify()
失败。
(值得注意的是,链接问题中的ASP.Net DI注册也可以通过交叉布线工作,但是会遇到相同的问题。)
作为一种解决方法,我设计了一个代理类...
class UrlHelperProxy : IUrlHelper
{
// Lazy-load because an IUrlHelper can only be created within an HTTP request scope,
// and will fail during container validation.
private readonly Lazy<IUrlHelper> realUrlHelper;
public UrlHelperProxy(IActionContextAccessor accessor, IUrlHelperFactory factory)
{
realUrlHelper = new Lazy<IUrlHelper>(
() => factory.GetUrlHelper(accessor.ActionContext));
}
public ActionContext ActionContext => UrlHelper.ActionContext;
public string Action(UrlActionContext context) => UrlHelper.Action(context);
public string Content(string contentPath) => UrlHelper.Content(contentPath);
public bool IsLocalUrl(string url) => UrlHelper.IsLocalUrl(url);
public string Link(string name, object values) => UrlHelper.Link(name, values);
public string RouteUrl(UrlRouteContext context) => UrlHelper.RouteUrl(context);
private IUrlHelper UrlHelper => realUrlHelper.Value;
}
然后拥有标准注册...
container.Register<IUrlHelper, UrlHelperProxy>(Lifestyle.Scoped);
这可行,但是给我留下以下问题:
第二点:MVC架构师显然希望我们注入IUrlHelperFactory
,而不是IUrlHelper
。这是因为在创建URL Helper时需要HTTP请求(请参见here和here)。我提出的注册确实掩盖了这种依赖关系,但是并没有从根本上改变它-如果无法创建一个助手,我们可能只会以任何一种方式抛出异常。我是否错过了使事情变得比我想象中更危险的事情?
答案 0 :(得分:2)
基于这些答案,我想出了类似的SimpleInjector注册:
container.Register<IUrlHelper>( () => container.GetInstance<IUrlHelperFactory>().GetUrlHelper( container.GetInstance<IActionContextAccessor>().ActionContext));
它确实可以工作,但是由于
IActionContextAccessor.ActionContext
在没有活动的HTTP请求时返回null,因此在应用启动期间调用该绑定会导致container.Verify()
失败。
这里的潜在问题是IUrlHelper
的构造需要运行时数据,并且在构造对象图时不应使用运行时数据。这与Injecting runtime data into components的代码气味非常相似。
作为一种解决方法,我设计了一个代理类...
我完全不认为代理是一种替代方法 。正如我所看到的,您几乎钉牢了它。代理(或适配器)是推迟创建运行时数据的方法。我通常会使用适配器并定义特定于应用程序的抽象,但是在这种情况下,这会适得其反,因为ASP.NET Core在IUrlHelper
上定义了many扩展方法。定义自己的抽象可能意味着必须创建许多新方法,因为您可能需要其中的几种。
有没有更好/更简单的方法?
我认为没有,尽管您的代理实现无需使用Lazy<T>
也可以正常工作:
class UrlHelperProxy : IUrlHelper
{
private readonly IActionContextAccessor accessor;
private readonly IUrlHelperFactory factory;
public UrlHelperProxy(IActionContextAccessor accessor, IUrlHelperFactory factory)
{
this.accessor = accessor;
this.factory = factory;
}
public ActionContext ActionContext => UrlHelper.ActionContext;
public string Action(UrlActionContext context) => UrlHelper.Action(context);
public string Content(string contentPath) => UrlHelper.Content(contentPath);
public bool IsLocalUrl(string url) => UrlHelper.IsLocalUrl(url);
public string Link(string name, object values) => UrlHelper.Link(name, values);
public string RouteUrl(UrlRouteContext context) => UrlHelper.RouteUrl(context);
private IUrlHelper UrlHelper => factory.GetUrlHelper(accessor.ActionContext);
}
IActionContextAccessor
和IUrlHelperFactory
都是单例(或者至少可以将其实现注册为单例),因此您也可以将UrlHelperProxy
注册为单例:
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
services.AddSingleton<IUrlHelper, UrlHelperProxy>();
ActionContextAccessor
使用AsyncLocal
存储在请求期间存储ActionContext
。 IUrlHelperFactory
使用ActionContext
的{{1}}在请求期间缓存创建的HttpContext
。因此,对同一请求多次调用IUrlHelper
将导致在该请求期间返回相同的factory.GetUrlHelper
。这就是为什么您不需要在代理内部缓存IUrlHelper
的原因。
还要注意,我现在在MS.DI中注册了IUrlHelper
和IActionContextAccessor
,而不是Simple Injector。这表明该解决方案在使用内置容器或任何其他DI容器时同样有效-不仅仅是与Simple Injector一起使用。
这只是个坏主意吗?
绝对不是。我什至想知道为什么ASP.NET Core团队没有开箱即用地定义这种代理实现。
MVC架构师显然希望我们注入IUrlHelperFactory,而不是IUrlHelper。
这是因为那些架构师意识到不应使用运行时数据来构建对象图。实际上,我过去也discussed和他们在一起。
我在这里看到的唯一风险是,您可以在没有Web请求的情况下注入IUrlHelper
,这将导致使用代理引发异常。但是,当您注入IUrlHelper
时也会存在该问题,所以我认为这没什么大不了的。