简体   繁体   English

使用Simple Injector注入IUrlHelper

[英]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: 这有效,但让我有以下问题:

  • Is there a better/simpler way? 有更好/更简单的方法吗?
  • Is this just a bad idea? 这只是一个坏主意吗?

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 causes container.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: IActionContextAccessorIUrlHelperFactory都是单例(或者至少,它们的实现可以注册为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使用ActionContextHttpContext在该请求期间缓存创建的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注册了IActionContextAccessorIUrlHelper ,而不是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.

相关问题 根据程序集,使用Simple Injector注入不同的依赖项 - Injecting different dependencies with Simple Injector depending on assembly 使用简单注入器将配置类注入构造函数 - Injecting a configuration class into a constructor using Simple Injector 使用Simple Injector在运行时注入数据解析的模型 - Injecting data resolved models at runtime with Simple Injector Simple Injector:将导航属性注入存储库 - Simple Injector: injecting navigation properties into repositories 使用简单注射器将 controller 注入 SignalR 集线器 - Injecting controller into SignalR Hubs using Simple Injector 简单注入器:在基础 class 中注入属性 - Simple Injector: Injecting a property in a base class 将依赖项注入到OWIN中间件中,并使用Simple Injector根据每个Web请求 - Injecting a dependency into OWIN Middleware and per web-request with Simple Injector 使用ASP.NET Core 2.0将简单的Injector组件注入IHostedService - Injecting Simple Injector components into IHostedService with ASP.NET Core 2.0 使用ResolveUnregisteredType事件注入可能在Simple Injector中按请求更改的数据库配置值 - Injecting database configuration values that might change per request in Simple Injector using ResolveUnregisteredType event 简单的Injector IsRegistered方法? - Simple Injector IsRegistered method?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM