简体   繁体   中英

How to make ASP.NET Core MVC routes generation relative?

Context - the application

I'm developing an ASP.NET Core application using netcoreapp2.2 (dotnet core 2.2). This application is distributed as a Docker image and it's working well. It's an Add-On for HASS.IO , an automated environment for Home Assistant based on docker. Everything works well.

The missing feature in my app: HASS.IO's ingress

But... I want to make use of a HASS.IO feature called Ingress : https://developers.home-assistant.io/docs/en/next/hassio_addon_presentation.html#ingress

The goal of this feature is to allow Home Assistant to route the http traffic to the add-on without having to manage the authentication part and without requiring the system owner to setup a port mapping on its firewall for the communication. So it's a very nice feature.

MVC routing paths are absolute

To use HASS.IO ingress, the application needs to provide relative paths for navigation. By example, when the user is loading the url https://my.hass.io/a0a0a0a0_myaddon/ , the add-on container will receive a / http request. It means all navigation in the app must be relative.

By example, while on the root page ( https://my.hass.io/a0a0a0a0_myaddon/ translated to a HTTP GET / for the container), we add the following razor code:

<a asp-action="myAction" asp-route-id="123">this is a link</a>

We'll get a resulting html like this, which is wrong in this case:

<a href="/Home/myAction/123">this is a link</a> <!-- THIS IS A WRONG LINK! -->

It's wrong because it's getting translated to https://my.hass.io/Home/myAction/123 by the browser while the correct address would be https://my.hass.io/a0a0a0a0_myaddon/Home/myAction/123 .

To fix this, I need the resulting html to be like that:

<!-- THIS WOULD BE THE RIGHT LINK [option A] -->
<a href="Home/myAction/123">this is a link</a>

<!-- THIS WOULD BE GOOD TOO [option B] -->
<a href="/a0a0a0a0_myaddon/Home/myAction/123">this is a link</a>

The problem to solve

[option A]

Is there a way to setup the MVC's routing engine to output relative paths instead of absolute ones? That would solve my problem.

It also means when you're on https://my.hass.io/a0a0a0a0_myaddon/Home/myAction/123 and you want to go home , the result should be

<a href="../..">Return home</a>

---OR---

[option B]

Another approach would be to find a way to discover the actual absolute path and find a way to prepend it in the MVC's routing mechanism.

I found the solution to my own question. I don't know if it's the best way to do it, but it worked!

1. Create a wrapper for the existing IUrlHelper

This one converts absolute paths to relative ones...

private class RelativeUrlHelper : IUrlHelper
{
    private readonly IUrlHelper _inner;
    private readonly HttpContext _contextHttpContext;

    public RelativeUrlHelper(IUrlHelper inner, HttpContext contextHttpContext)
    {
        _inner = inner;
        _contextHttpContext = contextHttpContext;
    }

    private string MakeUrlRelative(string url)
    {
        if (url.Length == 0 || url[0] != '/')
        {
            return url; // that's an url going elsewhere: no need to be relative
        }

        if (url.Length > 2 && url[1] == '/')
        {
            return url; // That's a "//" url, means it's like an absolute one using the same scheme
        }

        // This is not a well-optimized algorithm, but it works!
        // You're welcome to improve it.
        var deepness = _contextHttpContext.Request.Path.Value.Split('/').Length - 2;
        if (deepness == 0)
        {
            return url.Substring(1);
        }
        else
        {
            for (var i = 0; i < deepness; i++)
            {
                url = i == 0 ? ".." + url : "../" + url;
            }
        }

        return url;
    }

    public string Action(UrlActionContext actionContext)
    {
        return MakeUrlRelative(_inner.Action(actionContext));
    }

    public string Content(string contentPath)
    {
        return MakeUrlRelative(_inner.Content(contentPath));
    }

    public bool IsLocalUrl(string url)
    {
        if (url?.StartsWith("../") ?? false)
        {
            return true;
        }

        return _inner.IsLocalUrl(url);
    }

    public string RouteUrl(UrlRouteContext routeContext) => _inner.RouteUrl(routeContext);

    public string Link(string routeName, object values) => _inner.Link(routeName, values);

    public ActionContext ActionContext => _inner.ActionContext;
}

2. Create a wrapper for IUrlHelperFactory

public class RelativeUrlHelperFactory : IUrlHelperFactory
{
    private readonly IUrlHelperFactory _previous;

    public RelativeUrlHelperFactory(IUrlHelperFactory previous)
    {
        _previous = previous;
    }

    public IUrlHelper GetUrlHelper(ActionContext context)
    {
        var inner = _previous.GetUrlHelper(context);

        return new RelativeUrlHelper(inner, context.HttpContext);
    }
}

3. Wrap the IUrlHelper in DI/IoC

Put this in the ConfigureServices() of the Startup.cs file:

services.Decorate<IUrlHelperFactory>((previous, _) => new RelativeUrlHelperFactory(previous));

Finally...

I posted my solution as a PR there: https://github.com/yllibed/Zigbee2MqttAssistant/pull/2

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM