Rewiring

Proper OData nextLink and context urls when running through a proxy

Using the standard ASP.NET ForwardedHeadersMiddleware allows easy management of the @odata.nextlink and @odata.context urls without having to actually touch OData at all!

Assume that you have an OData running in an internal server at https://intraservices/apis/apiname and you're calling it via a proxy from https://org.com/apiname.

In a setup like this, OData responses will by default have inaccessible urls in the metadata:

{
  "@odata.context": "https://intraservices/apis/apiname/$metadata#Entity",
  "value": [
    {...}
  ],
  "@odata.nextLink": "https://intraservices/apis/apiname/entity?$skip=200"
}

This is, of course, a classic issue of proxies, but there's a lot of older guides pointing to things like ODataOutputFormatter which get unnecessarily complex. In the newer ASP.NET versions (at least from 8 onwards) it's very easy to use the built in mechanism of ForwardedHeadersMiddleware which OData, for once, respects.

Setup

First, from your proxy, set the following headers:

X-Forwarded-Host: org.com
X-Forwarded-Prefix: /apiname

Note, that the Prefix needs to begin with /. This is only required if the url root differs between the actual api and the proxy address.

Then, in your API configuration:

builder.Services.Configure<ForwardedHeadersOptions>(options => {
        options.ForwardedHeaders =
            ForwardedHeaders.XForwardedHost | ForwardedHeaders.XForwardedPrefix;
        options.KnownProxies.Add(IPAddress.Parse("127.0.0.1"));
    });
...

var app = builder.Build();
app.UseForwardedHeaders();

So we configure the headers to process in the middleware (in this case X-Forwarded-Host and X-Forwarded-Prefix are enough), allow these from only our trusted proxy server IP (hardcoded here - read from config in actual use), and then setup the middleware (note the middleware order).

Now the OData response is populated with urls that the caller can access:

{
  "@odata.context": "https://org.com/apiname/$metadata#Entity",
  "value": [
    {...}
  ],
  "@odata.nextLink": "https://org.com/apiname/entity?$skip=200"
}

OData working well with the other infrastructure is a rare Pokémon indeed, but in this case it was delightfully easy.

Thoughts, comments? Send me an email!

#aspnet #odata #tech