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

3

9

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)

Sleuth answered 17/2, 2019 at 2:50 Comment(0)
H
6

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);
    }
}
Haswell answered 21/2, 2019 at 19:48 Comment(1)
Thank you, that was exactly what was needed. I was able to use my original UrlHelper in the UrlHelperFactory.Sleuth
D
2

The IUrlHelper is not injectable by default.

You will have to modify your startup.cs code a bit as explained in this blog.

You will have to first register IActionContextAccessor.

Then with the help of UrlHelperFactory, you can inject your custom implementation as shown below:

services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
services.AddScoped<IUrlHelper>(x => {
    var actionContext = x.GetRequiredService<IActionContextAccessor>().ActionContext;
    var factory = x.GetRequiredService<IUrlHelperFactory>();
    return factory.GetUrlHelper(actionContext);
});

Both IActionContextAccessor and IUrlHelperFactory live in the Microsoft.AspNetCore.Mvc.Core package.

If you're using the Microsoft.AspNetCore.All metapackage you should have this referenced already.

This should help you to resolve your problem.

Dupin answered 21/2, 2019 at 18:23 Comment(5)
Yes, that snippet in the question is from that same post and did not work. Do you have a running example?Sleuth
Where do you want the new instance to be available ? Inside controller ? or In view ? Or in some other service ?Dupin
I need this anywhere the Url helper will be invoked. I would like a consistent behavior across the controller, view and wherever else you can use these helpersSleuth
The .NetCore framework if you see, the iUrlHelper in from controllerBase class is not injected. It is a property. When we create new controller , if we explicitly inject IUrlHelper then the custom url helper is injected properly (i.e. the above code works).Dupin
Did you try using extension method instead ? That may be easier.Dupin
F
-1

Can you not just brute force some flexibility into your solution with string concatenation like this:

public string SlugUrl(string slug, string url, bool tilde = false)
{
    if (tilde) then
    {
        return Url.Content("~" + slug + url);
    }
    else
    {
        return Url.Content(slug + url);
    }
}

[...]

string slug1 = "www.mysite.com";
string slug2 = "www.mysite.com/Slug";
string trailUrl = "/scripts/site.js";
string result1 = SomeClass.SlugUrl(slug1, trailUrl);
string result2 = SomeClass.SlugUrl(slug2, trailUrl);
string result3 = SomeClass.SlugUrl(slug1, trailUrl, true);
string result4 = SomeClass.SlugUrl(slug2, trailUrl, true);

etc...

Fibrosis answered 21/2, 2019 at 19:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.