如何使 ASP.NET 核心 MVC 路由生成相对?

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

上下文 - 应用程序

我正在使用 netcoreapp2.2 (dotnet core 2.2) 开发 ASP.NET 核心应用程序。此应用程序作为 Docker 图像分发,并且运行良好。它是 HASS.IOAdd-OnHome Assistant 的自动化环境,基于docker。一切正常。

我的应用中缺少的功能:HASS.IO 的入口[=52​​=]

但是...我想使用一个名为 IngressHASS.IO 功能:https://developers.home-assistant.io/docs/en/next/hassio_addon_presentation.html#ingress

此功能的目标是允许 Home Assistant 将 http 流量路由到附加组件,而无需管理身份验证部分,也不需要系统所有者进行设置用于通信的防火墙上的端口映射。所以这是一个非常好的功能。

MVC 路由路径是绝对的

要使用 HASS.IO ingress,应用程序需要提供导航的相对路径。例如,当用户正在加载 url https://my.hass.io/a0a0a0a0_myaddon/ 时,add-on 容器将收到一个 / http 请求。这意味着应用程序中的所有导航都必须是相对的。

例如,在根页面上(https://my.hass.io/a0a0a0a0_myaddon/ 转换为 HTTP GET / 容器),我们添加以下 razor 代码:

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

我们会得到这样的结果 html,在这种情况下这是错误的:

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

这是错误的,因为它被浏览器翻译成 https://my.hass.io/Home/myAction/123,而正确的地址应该是 https://my.hass.io/a0a0a0a0_myaddon/Home/myAction/123

要解决这个问题,我需要生成的 html 是这样的:

<!-- 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>

要解决的问题

[选项 A]

有没有办法设置 MVC 的路由引擎输出相对路径而不是绝对路径?那会解决我的问题。

这也意味着当你在https://my.hass.io/a0a0a0a0_myaddon/Home/myAction/123并且你想去时,结果应该是

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

---或---

[选项 B]

另一种方法是找到一种方法来发现实际的绝对路径,并找到一种方法将其添加到 MVC 的路由机制中。

我找到了我自己的问题的解决方案。我不知道这是否是最好的方法,但它奏效了!

1。为现有 IUrlHelper

创建一个包装器

这个将绝对路径转换为相对路径...

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。为 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。将 IUrlHelper 包裹在 DI/IoC

将其放入 Startup.cs 文件的 ConfigureServices() 中:

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

终于...

我在那里发布了我的解决方案作为 PR:https://github.com/yllibed/Zigbee2MqttAssistant/pull/2