[英]Injecting IUrlHelper with Simple Injector
I'm working on an ASP.NET Core app using Simple Injector for dependency injection duties. 我正在使用Simple Injector开发一个ASP.NET Core应用程序,用于依赖注入任务。 I'm looking for a way to inject
IUrlHelper
into controllers. 我正在寻找一种方法将
IUrlHelper
注入控制器。 I know about IUrlHelperFactory
, but would prefer to directly inject IUrlHelper
to keep things a little tidier and simpler to mock. 我知道
IUrlHelperFactory
,但更喜欢直接注入IUrlHelper
来保持模拟更简单和更简单。
The following questions have useful answers for injecting IUrlHelper
through the standard ASP.Net Dependency Injection: 以下问题为通过标准ASP.Net依赖注入注入
IUrlHelper
提供了有用的答案:
Based on those answers, I came up with a comparable SimpleInjector registration: 根据这些答案,我想出了一个类似的SimpleInjector注册:
container.Register<IUrlHelper>(
() => container.GetInstance<IUrlHelperFactory>().GetUrlHelper(
container.GetInstance<IActionContextAccessor>().ActionContext));
It does work, but because IActionContextAccessor.ActionContext
returns null
when there's no active HTTP request, this binding causes container.Verify()
to fail when called during app startup. 它确实有效,但由于
IActionContextAccessor.ActionContext
在没有活动的HTTP请求时返回null
,因此在app启动期间调用此绑定会导致container.Verify()
失败。
(It's worth noting that the ASP.Net DI registrations from the linked questions also work through cross-wiring, but suffer the same problem.) (值得注意的是,来自链接问题的ASP.Net DI注册也可以通过交叉布线工作,但遇到同样的问题。)
As a workaround, I've designed a proxy class... 作为一种解决方法,我设计了一个代理类......
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;
}
Which then has a standard registration... 然后有标准注册...
container.Register<IUrlHelper, UrlHelperProxy>(Lifestyle.Scoped);
This works, but leaves me with the following questions: 这有效,但让我有以下问题:
To the second point: The MVC architects clearly wanted us to inject IUrlHelperFactory
, and not IUrlHelper
. 第二点:MVC架构师显然希望我们注入
IUrlHelperFactory
,而不是IUrlHelper
。 That's because of the need for an HTTP request when creating a URL Helper (see here and here ). 这是因为在创建URL Helper时需要HTTP请求(请参阅此处和此处 )。 The registrations I've come up with do obscure that dependency, but don't fundamentally change it--if a helper can't be created, we're probably just going to throw an exception either way.
我提出的注册确实掩盖了这种依赖关系,但是没有从根本上改变它 - 如果无法创建一个帮助器,我们可能只会抛出异常。 Am I missing something that makes this more risky than I realize?
我错过了一些比我意识到的更危险的东西吗?
Based on those answers, I came up with a comparable SimpleInjector registration:
根据这些答案,我想出了一个类似的SimpleInjector注册:
container.Register<IUrlHelper>( () => container.GetInstance<IUrlHelperFactory>().GetUrlHelper( container.GetInstance<IActionContextAccessor>().ActionContext));
It does work, but because
IActionContextAccessor.ActionContext
returns null when there's no active HTTP request, this binding causescontainer.Verify()
to fail when called during app startup.它确实有效,但由于
IActionContextAccessor.ActionContext
在没有活动的HTTP请求时返回null,因此在app启动期间调用此绑定会导致container.Verify()
失败。
The underlying problem here is that the construction of the IUrlHelper
requires runtime data, and runtime data should not be used while constructing object graphs. 这里的根本问题是
IUrlHelper
的构造需要运行时数据,并且在构造对象图时不应使用运行时数据。 This is very similar to the code smell of Injecting runtime data into components . 这与将运行时数据注入组件的代码气味非常相似。
As a workaround, I've designed a proxy class...
作为一种解决方法,我设计了一个代理类......
I don't consider the proxy to be a workaround at all . 我不考虑代理是一个解决办法都没有 。 As I see it, you pretty much nailed it.
就像我看到的那样,你几乎把它钉了起来。 A proxy (or adapter) is the way to defer the creation of runtime data.
代理(或适配器)是推迟创建运行时数据的方法。 I would typically go with an adapter and define an application-specific abstraction, but that would be counter-productive in this case, as ASP.NET Core defines many extension methods on
IUrlHelper
. 我通常会使用适配器并定义特定于应用程序的抽象,但在这种情况下这会适得其反,因为ASP.NET Core在
IUrlHelper
上定义了许多扩展方法。 Defining your own abstraction would probably mean having to create many new methods, as you are likely to require several of them. 定义自己的抽象可能意味着必须创建许多新方法,因为您可能需要其中的几个。
Is there a better/simpler way?
有更好/更简单的方法吗?
I don't think there is, although your proxy implementation can work as well without the use of a Lazy<T>
: 我不认为有,虽然您的代理实现可以在不使用
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);
}
Both IActionContextAccessor
and IUrlHelperFactory
are singletons (or at least, their implementations can be registered as singleton), so you can register the UrlHelperProxy
as singleton as well: IActionContextAccessor
和IUrlHelperFactory
都是单例(或者至少,它们的实现可以注册为singleton),因此您也可以将UrlHelperProxy
注册为singleton:
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
services.AddSingleton<IUrlHelper, UrlHelperProxy>();
ActionContextAccessor
uses AsyncLocal
storage to store the ActionContext
for the duration of the request. ActionContextAccessor
使用AsyncLocal
存储在请求期间存储ActionContext
。 And IUrlHelperFactory
uses the ActionContext
's HttpContext
to cache a created IUrlHelper
for the duration of that request. IUrlHelperFactory
使用ActionContext
的HttpContext
在该请求期间缓存创建的IUrlHelper
。 Calling factory.GetUrlHelper
multiple times for the same request will, therefore, result in the same IUrlHelper
to be returned for the duration of that request. 因此,对同一请求多次调用
factory.GetUrlHelper
会导致在该请求期间返回相同的IUrlHelper
。 That's why you don't need to cache the IUrlHelper
inside the proxy. 这就是您不需要在代理中缓存
IUrlHelper
的原因。
Also notice that I now registered both IActionContextAccessor
and IUrlHelper
in MS.DI instead of Simple Injector. 另请注意,我现在在
IActionContextAccessor
注册了IActionContextAccessor
和IUrlHelper
,而不是Simple Injector。 This is to show that this solution works just as well when using the built-in container or any other DI Container--not just with Simple Injector. 这表明当使用内置容器或任何其他DI容器时,此解决方案也可以正常工作 - 而不仅仅是使用Simple Injector。
Is this just a bad idea?
这只是一个坏主意吗?
Absolutely not. 绝对不。 I'm even wondering why such proxy implementation is not defined out-of-the-box by the ASP.NET Core team.
我甚至想知道为什么这样的代理实现没有被ASP.NET核心团队开箱即用。
The MVC architects clearly wanted us to inject IUrlHelperFactory, and not IUrlHelper.
MVC架构师显然希望我们注入IUrlHelperFactory,而不是IUrlHelper。
This is because those architects realized that object graphs should not be built using runtime data. 这是因为那些架构师意识到不应该使用运行时数据构建对象图。 This is actually something I discussed with them in the past as well.
这实际上也是我过去与他们讨论过的事情。
The only risk I see here is that you can inject an IUrlHelper
in a context where there is no web request, which will cause the use of the proxy to throw an exception. 我在这里看到的唯一风险是你可以在没有web请求的上下文中注入
IUrlHelper
,这将导致使用代理抛出异常。 That problem, however, exists as well when you inject an IActionContextAccessor
, so I don't find this a big deal. 但是,当你注入一个
IActionContextAccessor
时,这个问题也存在,所以我觉得这没什么大不了的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.