我正在使用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请求(请参见herehere)。我提出的注册确实掩盖了这种依赖性,但是并没有从根本上改变它-如果无法创建一个助手,那么我们可能只会抛出一种异常。我是否错过了使这件事比我意识到的风险更大的事情?

最佳答案

根据这些答案,我想出了类似的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);
}


IActionContextAccessorIUrlHelperFactory都是单例(或者至少可以将其实现注册为单例),因此您也可以将UrlHelperProxy注册为单例:

services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
services.AddSingleton<IUrlHelper, UrlHelperProxy>();


ActionContextAccessor使用AsyncLocal存储器在请求期间存储ActionContext。并且IUrlHelperFactory使用ActionContextHttpContext在该请求期间缓存创建的IUrlHelper。因此,对同一请求多次调用factory.GetUrlHelper将导致在该请求期间返回同一IUrlHelper。这就是为什么您不需要在代理中缓存IUrlHelper的原因。

还要注意,我现在在MS.DI中注册了IActionContextAccessorIUrlHelper而不是Simple Injector。这表明该解决方案在使用内置容器或任何其他DI容器时同样有效-不仅适用于Simple Injector。


  这只是一个坏主意吗?


绝对不。我什至想知道为什么ASP.NET Core团队没有立即定义此类代理实现。


  MVC架构师显然希望我们注入IUrlHelperFactory,而不是IUrlHelper。


这是因为那些架构师意识到不应使用运行时数据来构建对象图。实际上,我过去也和他们一起discussed

我在这里看到的唯一风险是,您可以在没有Web请求的上下文中插入IUrlHelper,这将导致使用代理引发异常。但是,当您注入IActionContextAccessor时,该问题也存在,因此我认为这没什么大不了的。

10-04 19:44