Внедрение IUrlHelper с помощью простого инжектора - PullRequest
0 голосов
/ 17 февраля 2019

Я работаю над приложением ASP.NET Core, используя Simple Injector для выполнения функций внедрения зависимостей.Я ищу способ ввести IUrlHelper в контроллеры.Я знаю о IUrlHelperFactory, но предпочел бы напрямую вводить IUrlHelper, чтобы сделать вещи немного опрятнее и проще издеваться.

На следующие вопросы есть полезные ответы для введения IUrlHelper через стандартный ASP.NetВнедрение зависимостей:

Основываясь на этих ответах, я придумал сопоставимую регистрацию SimpleInjector:

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

Это работает, но потому что IActionContextAccessor.ActionContext возвращает null, когда нет активного HTTP-запроса, эта привязка вызывает container.Verify() сбой при вызове во время запуска приложения.

(Стоит отметить, что регистрации DI ASP.Net по связанным вопросам также работают через перекрестное соединение, но страдают той же проблемой.)

В качестве обходного пути я разработал прокси-класс ...

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);

Это работает,но оставляет меня со следующимestions:

  • Есть ли лучший / более простой способ?
  • Это просто плохая идея?

Ко второму пункту: MVC явно проектируетхотел, чтобы мы вводили IUrlHelperFactory, а не IUrlHelper.Это связано с необходимостью HTTP-запроса при создании URL Helper (см. здесь и здесь ).Регистрации, которые я придумала, затеняют эту зависимость, но не меняют ее принципиально - если помощник не может быть создан, мы, вероятно, просто собираемся вызвать исключение в любом случае.Я что-то упускаю, что делает это более рискованным, чем я понимаю?

1 Ответ

0 голосов
/ 09 марта 2019

На основании этих ответов я придумал сопоставимую регистрацию SimpleInjector:

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

Это работает, но поскольку IActionContextAccessor.ActionContext возвращает ноль, когда нет активного запроса HTTP, это связывание вызываетСбой container.Verify() при вызове во время запуска приложения.

Основная проблема здесь заключается в том, что для построения IUrlHelper требуются данные времени выполнения, и данные времени выполнения не должны использоваться при построении графов объектов.Это очень похоже на запах кода Внедрение данных времени выполнения в компоненты .

В качестве обходного пути я разработал прокси-класс ...

Я не считаю прокси обходным путем вообще .Как я понимаю, ты в значительной степени прибил это.Прокси (или адаптер) - это способ отложить создание данных времени выполнения.Я бы обычно использовал адаптер и определял абстракцию для конкретного приложения, но это было бы неэффективно в этом случае, так как ASP.NET Core определяет много методов расширения для 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);
}

И IActionContextAccessor, и IUrlHelperFactory являются синглетонами (или, по крайней мере, ихреализации могут быть зарегистрированы как singleton), поэтому вы можете зарегистрировать UrlHelperProxy также как singleton:

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

ActionContextAccessor использует AsyncLocal хранилище для хранения ActionContext на время запроса,И IUrlHelperFactory использует ActionContext HttpContext для кэширования созданного IUrlHelper на время этого запроса.Следовательно, многократный вызов factory.GetUrlHelper для одного и того же запроса приведет к тому, что один и тот же IUrlHelper будет возвращен в течение этого запроса.Вот почему вам не нужно кэшировать IUrlHelper внутри прокси.

Также обратите внимание, что теперь я зарегистрировал IActionContextAccessor и IUrlHelper в MS.DI вместо Simple Injector.Это показывает, что это решение работает так же хорошо при использовании встроенного контейнера или любого другого DI-контейнера - не только с Simple Injector.

Это просто плохая идея?

Абсолютно нет.Я даже удивляюсь, почему такая реализация прокси-сервера не определена из коробки командой ASP.NET Core.

Архитекторы MVC явно хотели, чтобы мы вводили IUrlHelperFactory, а не IUrlHelper.

Это потому, что эти архитекторы поняли, что графы объектов не должны строиться с использованием данных времени выполнения.На самом деле это то, что я обсуждал с ними и в прошлом.

Единственный риск, который я вижу здесь, заключается в том, что вы можете ввести IUrlHelper в контексте, где нет веб-запроса, что приведет к использованию прокси для исключения.Эта проблема, однако, также существует, когда вы вводите IActionContextAccessor, поэтому я не нахожу это большим делом.

...