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!