我正在使用Simple Injector进行依赖项注入任务的ASP.NET Core应用程序。我正在寻找一种将IUrlHelper
注入控制器的方法。我了解IUrlHelperFactory
,但是更喜欢直接注入IUrlHelper
来使事情变得更整洁和易于模拟。
以下问题对于通过标准ASP.Net依赖项注入注入IUrlHelper
有用的答案:
Injection of IUrlHelper in ASP.NET Core
MVC 6 IUrlHelper Dependency Injection
根据这些答案,我想出了类似的SimpleInjector注册:
container.Register<IUrlHelper>(
() => container.GetInstance<IUrlHelperFactory>().GetUrlHelper(
container.GetInstance<IActionContextAccessor>().ActionContext));
它确实起作用,但是由于在没有活动的HTTP请求时
IActionContextAccessor.ActionContext
返回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)。我提出的注册确实掩盖了这种依赖性,但是并没有从根本上改变它-如果无法创建一个助手,那么我们可能只会抛出一种异常。我是否错过了使这件事比我意识到的风险更大的事情? 最佳答案
根据这些答案,我想出了类似的SimpleInjector注册:
container.Register<IUrlHelper>(
() => container.GetInstance<IUrlHelperFactory>().GetUrlHelper(
container.GetInstance<IActionContextAccessor>().ActionContext));
它确实可以工作,但是由于在没有活动的HTTP请求时
IActionContextAccessor.ActionContext
返回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
的HttpContext
在该请求期间缓存创建的IUrlHelper
。因此,对同一请求多次调用factory.GetUrlHelper
将导致在该请求期间返回同一IUrlHelper
。这就是为什么您不需要在代理中缓存IUrlHelper
的原因。还要注意,我现在在MS.DI中注册了
IActionContextAccessor
和IUrlHelper
而不是Simple Injector。这表明该解决方案在使用内置容器或任何其他DI容器时同样有效-不仅适用于Simple Injector。这只是一个坏主意吗?
绝对不。我什至想知道为什么ASP.NET Core团队没有立即定义此类代理实现。
MVC架构师显然希望我们注入IUrlHelperFactory,而不是IUrlHelper。
这是因为那些架构师意识到不应使用运行时数据来构建对象图。实际上,我过去也和他们一起discussed。
我在这里看到的唯一风险是,您可以在没有Web请求的上下文中插入
IUrlHelper
,这将导致使用代理引发异常。但是,当您注入IActionContextAccessor
时,该问题也存在,因此我认为这没什么大不了的。