Search code examples
c#asp.net-core-mvcslugurlhelper

How to add the slug to all Link generation in an asp.net core website?


I need to be able to control the links being generated by my Url.Content("~") call to be able to accept a Slug in the beginning of the link. Basically the hosting URL will be behind a Load-balancer and may be at the root level or behind a friendlier Url...

As an example: The site is configured to run under http://localhost:5001, so Url.Content("~/scripts/site.js") will generate "/scripts/site.js"

this is fine if the browser is coming directly to that url or even to an alias such as www.mysite.com.

But i want o be able to have the flexibility to host the site under www.mysite.com/Slug (think certs and such)...

now my link that was generated goes to www.mysite.com/scripts.site.js which resolves to a 404.

Ideally, the slug can be configured in a custom IUrlHelper, or even a custom LinkGenerator, but i cannot seem to inject those and overwrite the current ones.

I've tried:

services.AddScoped<IUrlHelper>(x =>
            {
                var actionContext = x.GetService<IActionContextAccessor>().ActionContext;
                return new MyCustomUrlHelper(actionContext);
            });

but was unable to get that injected. When i tried debugging, I noticed that if you call the same command in a controller, you get an instance of Microsoft.AspNetCore.Mvc.Routing.EndpointRoutingUrlHelper instead.

Is there a way to change that without creating a custom helper (because that will be missed in some areas and will make debugging near impossible to find the misused helper)


Solution

  • Binding IUrlHelper directly has no effect, as MVC internally resolves the instance using a factory. To get an instance of your own custom URL helper in your controllers and razor views, you need to provide a custom implementation of IUrlHelperFactory in your startup class.

    The following code snippets allow you to decorate the original URL helper with your own functionality:

    In your Startup class, you need to add the custom implementation for IUrlHelperFactory with singleton scope after AddMvc:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
        services.AddSingleton<IUrlHelperFactory, CustomUrlHelperFactory>();
    }
    

    And the custom implementation could look like this:

    public class CustomUrlHelper : IUrlHelper
    {
        private IUrlHelper _originalUrlHelper;
    
        public ActionContext ActionContext { get; private set; }
    
        public CustomUrlHelper(ActionContext actionContext, IUrlHelper originalUrlHelper)
        {
            this.ActionContext = actionContext;
            this._originalUrlHelper = originalUrlHelper;
        }
    
        public string Action(UrlActionContext urlActionContext)
        {
            return _originalUrlHelper.Action(urlActionContext);
        }
    
        public string Content(string contentPath)
        {
            return _originalUrlHelper.Content(contentPath);
        }
    
        public bool IsLocalUrl(string url)
        {
            return _originalUrlHelper.IsLocalUrl(url);
        }
    
        public string Link(string routeName, object values)
        {
            return _originalUrlHelper.Link(routeName, values);
        }
    
        public string RouteUrl(UrlRouteContext routeContext)
        {
            return _originalUrlHelper.RouteUrl(routeContext);
        }
    }
    
    public class CustomUrlHelperFactory : IUrlHelperFactory
    {
        public IUrlHelper GetUrlHelper(ActionContext context)
        {
            var originalUrlHelperFactory = new UrlHelperFactory();
            var originalUrlHelper = originalUrlHelperFactory.GetUrlHelper(context);
            return new CustomUrlHelper(context, originalUrlHelper);
        }
    }