Overwriting the service root URI in Wcf Data Service

Suppose you are hosting a WCF Data Service on a particular URL, but for some reason you’d like the consumers of your service to hit another URI – perhaps an IIS re-write, or a cookieless session. You can do this via setting one of the custom properties in the OperationContext:

OperationContext.Current.IncomingMessageProperties["MicrosoftDataServicesRootUri"] = serviceUri;

You need to do this per instance of the data service, so it makes a lot of sense to do it in the service class’ constructor. Normally, WCF data service will try to resolve the absolute service URI by asking the underlying host. However, when there is a “MicrosoftDataServicesRootUri” key present, then the property will take precedence and become the service root URI. By default, the service root uri is used to generate identities and links in the content (such as edit links).

Doing just this, however, is not quite enough. Some of the links are generated by looking at the request uri, rather than the service root uri. For example, server driven paging’s next page link is constructed this way. Therefore, you also need to override request uri (and you need to do it per request, obviously). You do this in the “MicrosoftDataServicesRequestUri” property:

OperationContext.Current.IncomingMessageProperties["MicrosoftDataServicesRequestUri"] = requestUri;

As a real world example, let’s take a look at the OData demo service located at https://services.odata.org/(S(readwrite))/OData/OData.svc/. This service utilizes ASP.net cookieless session to allow multiple users reading/writing to the same structural data. If you follow this link, you’ll notice that the “readwrite” token inside the session ID will get replaced by an actual session ID, and from that point on all of the service content will be referred with this session key inside. For example:

<id>https://services.odata.org/(S(v43ws2r0y00yx42zc220o2ci))/ OData/OData.svc/Products(0)</id>

<title type="text">Bread</title>

<summary type="text">Whole grain bread</summary>

By default, ASP.net will strip the session part of the URI out, leaving the service to think the host root is actually unsessioned. Therefore we need to utilize the above technique and “reconstruct” the sessioned uri (based on session IDs). We can construct the service root once per session, but the request URI must be reconstructed per request. Therefore, we ended up with the following code:

if (WebOperationContext.Current.IncomingRequest.UriTemplateMatch != null)
{
Uri serviceUri = HttpContext.Current.Session["ServiceUri"] as Uri;
Uri requestUri = null;

    UriTemplateMatch match = WebOperationContext.Current.IncomingRequest.UriTemplateMatch;

    if (serviceUri == null)
{
// create a service Uri that represent the current session
UriBuilder serviceUriBuilder = new UriBuilder(match.BaseUri);
serviceUriBuilder.Path = "(S(" + HttpContext.Current.Session.SessionID + "))" + serviceUriBuilder.Path;

        serviceUri = serviceUriBuilder.Uri;
HttpContext.Current.Session["ServiceUri"] = serviceUri;
}

    if (serviceUri != null)
{
OperationContext.Current.IncomingMessageProperties["MicrosoftDataServicesRootUri"] = serviceUri;

        UriBuilder requestUriBuilder = new UriBuilder(match.RequestUri);
requestUriBuilder.Path = "(S(" + HttpContext.Current.Session.SessionID + "))" + requestUriBuilder.Path;
// because we have overwritten the Root URI, we need to make sure the request URI shares the same host
// (sometimes we have request URI resolving to a different host, if there are firewall re-directs
requestUriBuilder.Host = serviceUri.Host;

        requestUri = requestUriBuilder.Uri;
OperationContext.Current.IncomingMessageProperties["MicrosoftDataServicesRequestUri"] = requestUri;
}
}

NOTE: the above source code is from the OData service sample code library, located at https://www.odata.org/developers/odata-sdk and under the MPL license.