I'm working on an ASP.NET Core app using Simple Injector for dependency injection duties. I'm looking for a way to inject IUrlHelper into controllers. I know about IUrlHelperFactory, but would prefer to directly inject IUrlHelper to keep things a little tidier and simpler to mock.
The following questions have useful answers for injecting IUrlHelper through the standard ASP.Net Dependency Injection:
Based on those answers, I came up with a comparable SimpleInjector registration:
container.Register<IUrlHelper>(
() => container.GetInstance<IUrlHelperFactory>().GetUrlHelper(
container.GetInstance<IActionContextAccessor>().ActionContext));
It does work, but because IActionContextAccessor.ActionContext returns null when there's no active HTTP request, this binding causes container.Verify() to fail when called during app startup.
(It's worth noting that the ASP.Net DI registrations from the linked questions also work through cross-wiring, but suffer the same problem.)
As a workaround, I've designed a proxy class...
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;
}
Which then has a standard registration...
container.Register<IUrlHelper, UrlHelperProxy>(Lifestyle.Scoped);
This works, but leaves me with the following questions:
- Is there a better/simpler way?
- Is this just a bad idea?
To the second point: The MVC architects clearly wanted us to inject IUrlHelperFactory, and not IUrlHelper. That's because of the need for an HTTP request when creating a URL Helper (see here and here). The registrations I've come up with do obscure that dependency, but don't fundamentally change it--if a helper can't be created, we're probably just going to throw an exception either way. Am I missing something that makes this more risky than I realize?