使用简单注入器注入IUrlHelper

时间:2019-02-16 21:17:42

标签: c# asp.net-mvc asp.net-core dependency-injection simple-injector

我正在使用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请求(请参见herehere)。我提出的注册确实掩盖了这种依赖关系,但是并没有从根本上改变它-如果无法创建一个助手,我们可能只会以任何一种方式抛出异常。我是否错过了使事情变得比我想象中更危险的事情?

1 个答案:

答案 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);
}

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

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

ActionContextAccessor使用AsyncLocal存储在请求期间存储ActionContextIUrlHelperFactory使用ActionContext的{​​{1}}在请求期间缓存创建的HttpContext。因此,对同一请求多次调用IUrlHelper将导致在该请求期间返回相同的factory.GetUrlHelper。这就是为什么您不需要在代理内部缓存IUrlHelper的原因。

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

  

这只是个坏主意吗?

绝对不是。我什至想知道为什么ASP.NET Core团队没有开箱即用地定义这种代理实现。

  

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

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

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