Consider the following two WCF 4.0 REST services:
[ServiceContract]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class WorkspaceService
{
[WebInvoke(UriTemplate = "{id}/documents/{name}", Method = "POST")]
public Document CreateWorkspaceDocument(Stream stream, string id, string name)
{
/* CreateDocument is omitted as it isn't relevant to the question */
Document response = CreateDocument(id, name, stream);
/* set the location header */
SetLocationHeader(response.Id);
}
private void SetLocationHeader(string id)
{
Uri uri = new Uri("https://example.com/documents/" + id);
WebOperationContext.Current.OutgoingResponse.SetStatusAsCreated(uri);
}
/* methods to delete, update etc */
}
[ServiceContract]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class DocumentService
{
[WebGet(UriTemplate = "{id}")]
public Document GetDocument(string id)
{
}
/* methods to delete, update etc */
}
In essence, when someone creates a document in a workspace, the Location header is set to the location of the document, which is essentially is the same as invoking the DocumentService.GetDocument operation.
My global.asax looks as follows:
public class Global : HttpApplication
{
private void Application_Start(object sender, EventArgs e)
{
RegisterRoutes();
}
private void RegisterRoutes()
{
var webServiceHostFactory = new WebServiceHostFactory();
RouteTable.Routes.Add(new ServiceRoute("workspaces", webServiceHostFactory, typeof (WorkspaceService)));
RouteTable.Routes.Add(new ServiceRoute("documents", webServiceHostFactory, typeof (DocumentService)));
/* other services */
}
}
The implementation of WorkspaceService.SetLocationHeader as it makes some assumptions about how routing was setup. If I was to change the route of DocumentService then the resulting Uri will be incorrect. If I changed the UriTemplate of DocumentService.GetDocument, then the resulting Uri will be incorrect too.
If WorkspaceService and DocumentService was merged into one service, I could have written SetLocationHeader as follows:
var itemTemplate = WebOperationContext.Current.GetUriTemplate("GetDocument");
var uri = itemTemplate.BindByPosition(WebOperationContext.Current.IncomingRequest.UriTemplateMatch.BaseUri, id);
WebOperationContext.Current.OutgoingResponse.SetStatusAsCreated(uri);
How would one write WorkspaceService.SetLocationHeader such that it will use the routing table defined in Global.asax and UriTemplates to return the Uri for the GetDocument operation of the DocumentService?
I’m using plain old WCF 4.0 (not the WCF Web API).
By accident, I found the an article written by José F. Romaniello which shows how to do it for the WCF Web API and adapted it. The source code is at the end of the answer.
Assuming I have four services, the routing registration changes to use a subclass of ServiceRoute which we later use to “evaluate” when scanning the routing table.
The
WorkspaceService.SetLocationHeadernow looks as follows:The same code snippet can be used to set the uri of a workspace from other services, such as
DocumentService.GetWith this approach there are no magic strings and its unlikely that a change to a method name, service name, routing table prefix will break the system.
Here is the implementation adapted from the article :
The default constructor of ResourceLinker requires some changes to pick up the base uri of the web application, taking into account that HTTPS may be terminated at the load balancer. That falls outside of this answer.