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