如何使 ASP.NET 核心 MVC 路由生成相对?
How to make ASP.NET Core MVC routes generation relative?
上下文 - 应用程序
我正在使用 netcoreapp2.2
(dotnet core 2.2) 开发 ASP.NET 核心应用程序。此应用程序作为 Docker 图像分发,并且运行良好。它是 HASS.IO 的 Add-On,Home Assistant 的自动化环境,基于docker。一切正常。
我的应用中缺少的功能:HASS.IO 的入口[=52=]
但是...我想使用一个名为 Ingress 的 HASS.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));
- 重要提示:您需要为 https://www.nuget.org/packages/Scrutor/.
安装 nuget 包 Scrutor
终于...
我在那里发布了我的解决方案作为 PR:https://github.com/yllibed/Zigbee2MqttAssistant/pull/2
上下文 - 应用程序
我正在使用 netcoreapp2.2
(dotnet core 2.2) 开发 ASP.NET 核心应用程序。此应用程序作为 Docker 图像分发,并且运行良好。它是 HASS.IO 的 Add-On,Home Assistant 的自动化环境,基于docker。一切正常。
我的应用中缺少的功能:HASS.IO 的入口[=52=]
但是...我想使用一个名为 Ingress 的 HASS.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));
- 重要提示:您需要为 https://www.nuget.org/packages/Scrutor/. 安装 nuget 包
Scrutor
终于...
我在那里发布了我的解决方案作为 PR:https://github.com/yllibed/Zigbee2MqttAssistant/pull/2