На основании этих ответов я придумал сопоставимую регистрацию 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
, поэтому я не нахожу это большим делом.