使用Simple Injector注入IUrlHelper

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用Simple Injector注入IUrlHelper相关的知识,希望对你有一定的参考价值。

我正在使用Simple Injector进行依赖注入任务的ASP.NET核心应用程序。我正在寻找一种方法将IUrlHelper注入控制器。我知道IUrlHelperFactory,但更愿意直接注入IUrlHelper以保持一些更整洁和更简单的模拟。

以下问题通过标准ASP.Net依赖注入注入IUrlHelper有用的答案:

根据这些答案,我想出了一个类似的SimpleInjector注册:

container.Register<IUrlHelper>(
    () => container.GetInstance<IUrlHelperFactory>().GetUrlHelper(
        container.GetInstance<IActionContextAccessor>().ActionContext));

它确实有效,但是因为当没有活动的HTTP请求时IActionContextAccessor.ActionContext返回null,这个绑定会导致container.Verify()在app启动时调用失败。

(值得注意的是,来自链接问题的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));

它确实有效,但由于IActionContextAccessor.ActionContext在没有活动的HTTP请求时返回null,因此在app启动期间调用时,此绑定会导致container.Verify()失败。

这里的根本问题是IUrlHelper的构造需要运行时数据,并且在构造对象图时不应使用运行时数据。这与Injecting runtime data into components的代码气味非常相似。

作为一种解决方法,我设计了一个代理类......

我不认为代理是一种解决方法。就像我看到的那样,你几乎把它钉了起来。代理(或适配器)是推迟创建运行时数据的方法。我通常会使用适配器并定义特定于应用程序的抽象,但在这种情况下这会适得其反,因为ASP.NET Core在many上定义了IUrlHelper扩展方法。定义自己的抽象可能意味着必须创建许多新方法,因为您可能需要其中的几个。

有更好/更简单的方法吗?

我不认为有,虽然您的代理实现可以在不使用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核心团队开箱即用。

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

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

我在这里看到的唯一风险是你可以在没有web请求的上下文中注入discussed,这将导致使用代理抛出异常。然而,当你注射IUrlHelper时,这个问题也存在,所以我觉得这没什么大不了的。

以上是关于使用Simple Injector注入IUrlHelper的主要内容,如果未能解决你的问题,请参考以下文章

Simple Injector 无法在 Web API 控制器中注入依赖项

如何将 Blazor 中的所有注册服务添加到 Simple Injector?

您正在尝试解决跨线服务,但在使用 Simple Injector 的活动(Async Scoped)范围之外执行此操作

Angular 使用 Injector API 人工获取依赖注入的实例

angularJs $injector

注入(injector)