覆盖Webapi OData链接的主机

Eiv*_*eth 2 asp.net odata asp.net-web-api odata-v4

我正在使用WebAPI 2.2和Microsoft.AspNet.OData 5.7.0创建支持分页的OData服务。

当托管在生产环境中时,WebAPI驻留在未对外公开的服务器上,因此OData响应中返回的各种链接(例如@odata.context@odata.nextLink指向内部IP地址,例如,http://192.168.X.X/<AccountName>/api/...等等)。

Request.ODataProperties().NextLink通过在每个ODataController方法中实现一些逻辑以将内部URL替换为外部URL(例如)https://account-name.domain.com/api/...,我已经能够修改,但这非常不便,并且只能修复NextLinks。

有什么方法可以在OData服务的配置时设置外部主机名?我已经看过一个属性,Request.ODataProperties().Path并且想知道是否可以在config.MapODataServiceRoute("odata", "odata", GetModel());调用时或在GetModel()使用例如ODataConventionModelBuilder?的实现中设置基本路径?


更新:到目前为止,我想出的最好的解决方案是创建一个BaseODataController重写该Initialize方法并检查是否存在Request.RequestUri.Host.StartsWith("beginning-of-known-internal-IP-address"),然后像下面这样重写RequestUri:

var externalAddress = ConfigClient.Get().ExternalAddress;  // e.g. https://account-name.domain.com
var account = ConfigClient.Get().Id;  // e.g. AccountName
var uriToReplace = new Uri(new Uri("http://" + Request.RequestUri.Host), account);
string originalUri = Request.RequestUri.AbsoluteUri;
Request.RequestUri = new Uri(Request.RequestUri.AbsoluteUri.Replace(uriToReplace.AbsoluteUri, externalAddress));
string newUri = Request.RequestUri.AbsoluteUri;
this.GetLogger().Info($"Request URI was rewritten from {originalUri} to {newUri}");
Run Code Online (Sandbox Code Playgroud)

这可以完美地修复@odata.nextLink所有控制器的URL,但是由于某些原因,@odata.contextURL仍然可以发挥AccountName作用(例如 https://account-name.domain.com/AccountName/api/odata/ $ metadata#ControllerName),因此它们仍然没有工作。

Sco*_*son 6

lencharest 的回答很有希望,但我发现他的方法有所改进。我没有使用 UrlHelper,而是创建了一个派生自 System.Net.Http.DelegatingHandler 的类。这个类(首先)插入到消息处理管道中,因此在改变传入的 HttpRequestMessage 方面有一个裂缝。这是对上述解决方案的改进,因为除了更改特定于控制器的 URL(如 UrlHelper 所做的,例如https://data.contoso.com/odata/MyController),它还更改显示为的 URL OData 服务文档(例如,https ://data.contoso.com/odata)中的 xml:base 。

我的特定应用程序是在代理服务器后面托管 OData 服务,我希望服务器提供的所有 URL 都是外部可见的 URL,而不是内部可见的 URL。而且,我不想为此依赖注释;我希望它是全自动的。

消息处理程序如下所示:

    public class BehindProxyMessageHandler : DelegatingHandler
    {
        protected async override Task<HttpResponseMessage> SendAsync(
            HttpRequestMessage request, CancellationToken cancellationToken)
        {
            var builder = new UriBuilder(request.RequestUri);
            var visibleHost = builder.Host;
            var visibleScheme = builder.Scheme;
            var visiblePort = builder.Port;

            if (request.Headers.Contains("X-Forwarded-Host"))
            {
                string[] forwardedHosts = request.Headers.GetValues("X-Forwarded-Host").First().Split(new char[] { ',' });
                visibleHost = forwardedHosts[0].Trim();
            }

            if (request.Headers.Contains("X-Forwarded-Proto"))
            {
                visibleScheme = request.Headers.GetValues("X-Forwarded-Proto").First();
            }

            if (request.Headers.Contains("X-Forwarded-Port"))
            {
                try
                {
                    visiblePort = int.Parse(request.Headers.GetValues("X-Forwarded-Port").First());
                }
                catch (Exception)
                { }
            }

            builder.Host = visibleHost;
            builder.Scheme = visibleScheme;
            builder.Port = visiblePort;

            request.RequestUri = builder.Uri;
            var response = await base.SendAsync(request, cancellationToken);
            return response;
        }
    }
Run Code Online (Sandbox Code Playgroud)

您在 WebApiConfig.cs 中连接处理程序:

    config.Routes.MapODataServiceRoute(
        routeName: "odata",
        routePrefix: "odata",
        model: builder.GetEdmModel(),
        pathHandler: new DefaultODataPathHandler(),
        routingConventions: ODataRoutingConventions.CreateDefault()
    );
    config.MessageHandlers.Insert(0, new BehindProxyMessageHandler());
Run Code Online (Sandbox Code Playgroud)


len*_*est 5

重写RequestUri足以影响@odata.nextLink值,因为计算下一个链接代码RequestUri直接取决于。其他@odata.xxx链接是通过来计算的UrlHelper,它以某种方式引用了来自原始请求URI的路径。(因此,AccountName您在@odata.context链接中看到的内容。我已经在代码中看到此行为,但是我无法跟踪缓存的URI路径的来源。)

除了重写之外RequestUri,我们还可以通过创建一个CustomUrlHelper类来实时重写OData链接来解决问题。新GetNextPageLink方法将处理@odata.nextLink重写,而Link方法重写将处理所有其他重写。

public class CustomUrlHelper : System.Web.Http.Routing.UrlHelper
{
    public CustomUrlHelper(HttpRequestMessage request) : base(request)
    { }

    // Change these strings to suit your specific needs.
    private static readonly string ODataRouteName = "ODataRoute"; // Must be the same as used in api config
    private static readonly string TargetPrefix = "http://localhost:8080/somePathPrefix"; 
    private static readonly int TargetPrefixLength = TargetPrefix.Length;
    private static readonly string ReplacementPrefix = "http://www.contoso.com"; // Do not end with slash

    // Helper method.
    protected string ReplaceTargetPrefix(string link)
    {
        if (link.StartsWith(TargetPrefix))
        {
            if (link.Length == TargetPrefixLength)
            {
                link = ReplacementPrefix;
            }
            else if (link[TargetPrefixLength] == '/')
            {
                link = ReplacementPrefix + link.Substring(TargetPrefixLength);
            }
        }

        return link;
    }

    public override string Link(string routeName, IDictionary<string, object> routeValues)
    {
        var link = base.Link(routeName, routeValues);

        if (routeName == ODataRouteName)
        {
            link = this.ReplaceTargetPrefix(link);
        }

        return link;
    }

    public Uri GetNextPageLink(int pageSize)
    {
        return new Uri(this.ReplaceTargetPrefix(this.Request.GetNextPageLink(pageSize).ToString()));
    }
}
Run Code Online (Sandbox Code Playgroud)

连接基本控制器类CustomUrlHelperInitialize方法。

public abstract class BaseODataController : ODataController
{
    protected abstract int DefaultPageSize { get; }

    protected override void Initialize(System.Web.Http.Controllers.HttpControllerContext controllerContext)
    {
        base.Initialize(controllerContext);

        var helper = new CustomUrlHelper(controllerContext.Request);
        controllerContext.RequestContext.Url = helper;
        controllerContext.Request.ODataProperties().NextLink = helper.GetNextPageLink(this.DefaultPageSize);
    }
Run Code Online (Sandbox Code Playgroud)

在上面请注意,页面大小对于给定控制器类中的所有操作都是相同的。您可以通过将对指定ODataProperties().NextLink操作方法的主体的分配移动如下来解决此限制:

var helper = this.RequestContext.Url as CustomUrlHelper;
this.Request.ODataProperties().NextLink = helper.GetNextPageLink(otherPageSize);
Run Code Online (Sandbox Code Playgroud)