I'm using WebAPI 2.2 and Microsoft.AspNet.OData 5.7.0 to create an OData service that supports paging.
When hosted in the production environment, the WebAPI lives on a server that is not exposed externally, hence the various links returned in the OData response such as the @odata.context
and @odata.nextLink
point to the internal IP address e.g. http://192.168.X.X/<AccountName>/api/...
etc.
I've been able to modify the Request.ODataProperties().NextLink
by implementing some logic in each and every ODataController method to replace the internal URL with an external URL like https://account-name.domain.com/api/...
, but this is very inconvenient and it only fixes the NextLinks.
Is there some way to set an external host name at configuration time of the OData service? I've seen a property Request.ODataProperties().Path
and wonder if it's possible to set a base path at the config.MapODataServiceRoute("odata", "odata", GetModel());
call, or in the GetModel()
implementation using for instance the ODataConventionModelBuilder
?
UPDATE: The best solution I've come up with so far, is to create a BaseODataController
that overrides the Initialize
method and checks whether the Request.RequestUri.Host.StartsWith("beginning-of-known-internal-IP-address")
and then do a RequestUri rewrite like so:
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}");
This perfectly fixes the @odata.nextLink
URLs for all controllers, but for some reason the @odata.context
URLs still get the AccountName
part (e.g. https://account-name.domain.com/AccountName/api/odata/$metadata#ControllerName) so they still don't work.
There is another solution, but it overrides url for the entire context. What I'd like to suggest is:
Here is an example of middleware
ProxyHost is the host you want to have. Example: test.com
ProxyScheme is the scheme you want: Example: https
Example of middleware registration
Rewriting the
RequestUri
is sufficient to affect@odata.nextLink
values because the code that computes the next link depends on theRequestUri
directly. The other@odata.xxx
links are computed via aUrlHelper
, which is somehow referencing the path from the original request URI. (Hence theAccountName
you see in your@odata.context
link. I've seen this behavior in my code, but I haven't been able to track down the source of the cached URI path.)Rather than rewrite the
RequestUri
, we can solve the problem by creating aCustomUrlHelper
class to rewrite OData links on the fly. The newGetNextPageLink
method will handle@odata.nextLink
rewrites, and theLink
method override will handle all other rewrites.Wire-up the
CustomUrlHelper
in theInitialize
method of a base controller class.Note in the above that the page size will be the same for all actions in a given controller class. You can work around this limitation by moving the assignment of
ODataProperties().NextLink
to the body of a specific action method as follows:Using system.web.odata 6.0.0.0.
Setting the NextLink property too soon is problematic. Every reply will then have a nextLink in it. The last page should of course be free of such decorations.
http://docs.oasis-open.org/odata/odata-json-format/v4.0/os/odata-json-format-v4.0-os.html#_Toc372793048 says:
One way that I hope will work is to override EnableQueryAttribute:
ApplyQuery()
is where the "overflow" is detected. It basically asks forpagesize+1
rows and will setNextLink
if the result set contains more thanpagesize
rows.At this point it is relatively easy to rewrite
NextLink
to a relative URL.The downside is that every odata method must now be adorned with the new myEnableQuery attribute:
and other URLs embedded elsewhere remains problematic. odata.context remains a problem. I want to avoid playing with the request URL, because I fail to see how that is maintainable over time.
Your question boils down to controlling the service root URI from within the service itself. My first thought was to look for a hook on the media type formatters used to serialize responses.
ODataMediaTypeFormatter.MessageWriterSettings.PayloadBaseUri
andODataMediaTypeFormatter.MessageWriterSettings.ODataUri.ServiceRoot
are both settable properties that suggest a solution. Unfortunately,ODataMediaTypeFormatter
resets these properties on every call toWriteToStreamAsync
.The work-around is not obvious, but if you dig through the source code you'll eventually reach a call to
IODataPathHandler.Link
. A path handler is an OData extension point, so you can create a custom path handler that always returns an absolute URI which begins with the service root you desire.And then register that path handler during service configuration.