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.
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.
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>
[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!
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;
}
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);
}
}
IUrlHelper
in DI/IoCPut this in the ConfigureServices()
of the Startup.cs
file:
services.Decorate<IUrlHelperFactory>((previous, _) => new RelativeUrlHelperFactory(previous));
Scrutor
for that https://www.nuget.org/packages/Scrutor/.I posted my solution as a PR there: https://github.com/yllibed/Zigbee2MqttAssistant/pull/2