Roll Your Own REST-ful WCF Router
Here is the universal service contract for a routing service that can accept requests that are formatted as SOAP, POX or JSON.
[ServiceContract(Namespace = "urn:example:routing")]
public interface IRoutingService
{
[WebInvoke(UriTemplate = "")]
[OperationContract(AsyncPattern = true, Action = "*", ReplyAction = "*")]
IAsyncResult BeginProcessRequest(Message requestMessage, AsyncCallback asyncCallback, object asyncState);
Message EndProcessRequest(IAsyncResult asyncResult);
}
Here is the implementation of the IRoutingService interface.
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall,
AddressFilterMode = AddressFilterMode.Any, ValidateMustUnderstand = false)]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class RoutingService : IRoutingService, IDisposable
{
private IRoutingService _client;
public IAsyncResult BeginProcessRequest(Message requestMessage, AsyncCallback asyncCallback, object asyncState)
{
// Select soap or rest client endpoint
string endpoint = "service-basic";
if (requestMessage.Version == MessageVersion.None)
endpoint = "service-web";
// Create channel factory
var factory = new ChannelFactory<IRoutingService>(endpoint);
// Set message address
requestMessage.Headers.To = new Uri(factory.Endpoint.Address);
// Create client channel
_client = factory.CreateChannel();
// Begin request
return _client.BeginProcessRequest(requestMessage, asyncCallback, asyncState);
}
public Message EndProcessRequest(IAsyncResult asyncResult)
{
return _client.EndProcessRequest(asyncResult);
}
public void Dispose()
{
if (_client != null)
{
var channel = (IClientChannel)_client;
if (channel.State != CommunicationState.Closed)
{
try
channel.Close();
catch
channel.Abort();
}
}
}
}
For soap-based messages, the natural place for routing instructions is the header. Here is part of a method that reads routing instructions from the incoming message header.
public Dictionary<string, string> GetRoutingHeaders(Message requestMessage)
{
// Set routing namespace
var routingHeaders = new Dictionary<string, string>();
// Get soap routing headers
if (requestMessage.Version != MessageVersion.None)
{
foreach (var header in requestMessage.Headers)
{
if (header.Namespace.ToLower() == _routingNamespace)
{
int headerIndex = requestMessage.Headers.FindHeader
(header.Name, _routingNamespace);
if (headerIndex != -1)
{
var headerValue = requestMessage.Headers.GetHeader<string>
(header.Name, _routingNamespace);
requestMessage.Headers.RemoveAt(headerIndex);
if (!string.IsNullOrWhiteSpace(headerValue))
routingHeaders.Add(header.Name, headerValue);
}
}
}
}
WebHeaderCollection httpHeaders = WebOperationContext.Current.IncomingRequest.Headers;
foreach (string headerName in httpHeaders)
{
if (headerName.ToLower().StartsWith(routingNamespace))
{
string name = headerName.Substring(routingNamespace.Length + 1);
string value = httpHeaders.Get(headerName);
routingHeaders.Add(name, value);
}
}
if (routingHeaders.Count == 0)
{
var queryParams = WebOperationContext.Current.IncomingRequest.UriTemplateMatch.QueryParameters;
foreach (string paramKey in queryParams.AllKeys)
{
string name = paramKey.Substring(_routingNamespace.Length + 1);
string value = queryParams[paramKey];
routingHeaders.Add(name, value);
}
}
Armed with routing metadata, you can look up the destination’s address in a routing table of some kind. This method selects GreetingService2 if the routing instructions specify the “western” region.
public string GetServiceAddress(Dictionary<string, string> routingHeaders,
string defaultServiceAddress)
{
// Select service address based on region
string serviceAddress = defaultServiceAddress;
var region = (from rh in routingHeaders
where rh.Key.ToLower() == "region"
select rh.Value).FirstOrDefault();
if (region != null)
{
if (region.ToLower() == "western")
serviceAddress = defaultServiceAddress
.Replace("GreetingService1", "GreetingService2");
}
return serviceAddress;
}
<system.serviceModel>
<services>
<service name="RoutingPrototype.Services.GreetingService1">
<endpoint address="Soap"
binding="basicHttpBinding"
contract="RoutingPrototype.Interfaces.IGreetingService"
name="service-basic"/>
<endpoint address="Rest"
binding="webHttpBinding"
behaviorConfiguration="web"
contract="RoutingPrototype.Interfaces.IGreetingService"
name="service-web"/>
<host>
<baseAddresses>
<add baseAddress="http://localhost:8000/GreetingService1" />
</baseAddresses>
</host>
</service>
<behaviors>
<endpointBehaviors>
<behavior name="web">
<webHttp helpEnabled="true"
automaticFormatSelectionEnabled="true"
faultExceptionEnabled="true"/>
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
The client-side code for sending Soap-based messages looks like this:
private static string SendSoapMessage(string name, string addressType, string region, bool useFiddler)
{
var factory = new ChannelFactory<IGreetingService>(addressType);
string address = factory.Endpoint.Address.ToString()
.Replace("localhost", ConfigurationManager.AppSettings["MachineName"]);
if (useFiddler) factory.Endpoint.Address = new EndpointAddress(address);
IGreetingService client = factory.CreateChannel();
using ((IDisposable)client)
{
using (var contextScope = new OperationContextScope((IContextChannel)client))
{
if (region != null)
{
MessageHeader regionHeader = MessageHeader
.CreateHeader("region", _routingNamespace, region);
OperationContext.Current.OutgoingMessageHeaders.Add(regionHeader);
}
return client.Hello(name);
}
}
}
The client-side code for sending Rest-ful messages looks like this:
private static string SendRestMessage(string name, string addressType, string region, bool useFiddler)
{
string address = ConfigurationManager.AppSettings[addressType];
if (useFiddler) address = address.Replace("localhost", ConfigurationManager.AppSettings["MachineName"]);
var client = new WebClient {BaseAddress = address};
// Set format
string format = GetFormat();
if (format == null) return null;
string requestString;
if (format == "xml")
{
requestString = SerializationHelper.SerializeXml(name);
client.Headers.Add(HttpRequestHeader.ContentType, "application/xml");
}
else if (format == "json")
{
requestString = SerializationHelper.SerializeJson(name);
client.Headers.Add(HttpRequestHeader.ContentType, "application/json");
}
// Set region header
string addressParameters = string.Empty;
if (region != null)
{
bool? useHeaders = UseHttpHeaders();
if (useHeaders == null) return null;
if ((bool)useHeaders)
{
string regionHeader = string.Format("{0}:{1}", _routingNamespace, "region");
regionHeader = regionHeader.Replace(":", "-");
client.Headers.Add(regionHeader, region);
}
else
{
addressParameters = string.Format
("?{0}:region={1}", _routingNamespace, region);
}
}
// Send message
string responseString = client.UploadString(addressParameters, requestString);
// Deserialize response
string response = null;
if (format == "xml")
response = SerializationHelper.DeserializeXml<string>(responseString);
else if (format == "json")
response = SerializationHelper.DeserializeJson<string>(responseString);
return response;
}
WCF Addressing In Depth (MSDN Magazine June 2007)
WCF Messaging Fundamentals (MSDN Magazine April 2007)
Building a WCF Router, Part 1 (MSDN Magazine April 2008)
Building a WCF Router, Part 2 (MSDN Magazine June 2008)
You can download the code for this post here. Enjoy.
Reference: Roll Your Own REST-ful WCF Router from our NCG partner Tony Sneed at the Tony Sneed’s Blog blog.
Hi Tony,
Great post, it’s really useful. I’m not able to download the source code from your other website – do you still have it?
Thanks again!
Mark
have to got any solutions ? .im also looking for that