BloggingAbout.NET
Thoughts of developers
WCF Simple Header v1

The following is an example of passing information in WCF's operation context message headers. 

In this scenario, multiple clients will be connecting to a single service to retrieve membership numbers for a supplied name.  The service will log each request before returning the membership number. 

I built the example solution to consist of a host console application, a shared common library and two client console applications.  One client application will pass a string in the header while the other client application will pass a typed object, ServiceContext, defined in the common library (i.e., common being that it is referenced by both the host and client application).

You can find the source code here.

WCFHost console application

First, let's examine the host's service contract that will simply receive a member name and based on the name, return a membership number.

   1: using System;
   2: using System.ServiceModel;
   3: using WCFCommon;
   4:  
   5: namespace WCFHost
   6: {
   7:     [ServiceContract(SessionMode = SessionMode.Allowed)]
   8:     class MemberMaster
   9:     {
  10:         /// <summary>
  11:         /// Gets the membership number
  12:         /// </summary>
  13:         /// <param name="name">The name.</param>
  14:         /// <returns></returns>
  15:         [OperationContract]
  16:         string GetMembership(string name)
  17:         {
  18:             // log the source of the request
  19:             LogRequest(name);
  20:  
  21:             switch (name)
  22:             {
  23:                 case "Jim": return "J9992";
  24:                 case "Sally": return "S9992";
  25:                 case "Henry": return "H9992";
  26:                 case "Gertrude": return "G9992";
  27:                 case "Barry": return "B9992";
  28:                 default: return "Unknown";
  29:             }            
  30:         }
  31:  
  32:         /// <summary>
  33:         /// Logs the member name and the source of the request
  34:         /// </summary>
  35:         /// <param name="name">The name.</param>
  36:         private static void LogRequest(string name)
  37:         {
  38:             // retrieve the source from the header
  39:             string source = ServiceContext.Current.SourceApplication;
  40:  
  41:             Console.WriteLine("Received message from {0} requesting {1}'s membership.", source, name);
  42:         }
  43:     }
  44: }

For completeness, the host program is shown below (For a walk-through of a simple WCF application, see the post by Dennis):

   1: using System;
   2: using System.ServiceModel;
   3: using System.ServiceModel.Description;
   4:  
   5: namespace WCFHost
   6: {
   7:     class Program
   8:     {
   9:         static void Main(string[] args)
  10:         {
  11:             // We did not separate contract from implementation.    
  12:             // Therefor service and contract are the same in this example.    
  13:             Type serviceType = typeof(MemberMaster);    
  14:             
  15:             ServiceHost host = new ServiceHost(serviceType, new Uri[] { new Uri("http://localhost:9090/") } );    
  16:             
  17:             // Add behavior for our MEX endpoint   
  18:             ServiceMetadataBehavior behavior = new ServiceMetadataBehavior();   
  19:             behavior.HttpGetEnabled = true;   
  20:             host.Description.Behaviors.Add(behavior);  
  21:  
  22:             // Create basicHttpBinding endpoint at http://localhost:9090/MemberMaster/   
  23:             host.AddServiceEndpoint(serviceType, new BasicHttpBinding(), "MemberMaster");   
  24:  
  25:             // Add MEX endpoint at http://localhost:9090/MEX/   
  26:             host.AddServiceEndpoint(typeof(IMetadataExchange), new BasicHttpBinding(), "MEX");   
  27:  
  28:             host.Open();   
  29:             
  30:             Console.WriteLine("Service is ready, press any key to terminate.");   
  31:             Console.ReadKey();
  32:         }
  33:     }
  34: }

WCFClient1 console application

The first client will send its identity as part of the ServiceContext class in the outgoing header of the WCF client channel.  The client proxy was created using the SvcUtil.exe on the host url.  The details of the ServiceContext class will be explained later, but the following is just to illustrate that nothing unusual is being performed in the basic client.

   1: using System;
   2: using System.ServiceModel;
   3: using WCFCommon;
   4:  
   5: namespace WCFClient1
   6: {
   7:     class Program
   8:     {
   9:         static void Main(string[] args)
  10:         {
  11:             using (MemberMasterClient proxy = new MemberMasterClient())
  12:             {
  13:                 using (OperationContextScope scope = new OperationContextScope(proxy.InnerChannel))
  14:                 {
  15:                     // The servicecontext is set and will exist for the duration of the operationscope
  16:                     ServiceContext.Current = new ServiceContext("WCFClient Application 1");
  17:  
  18:                     Console.WriteLine("Membership Details");
  19:                     Console.WriteLine("Jim's - {0}", proxy.GetMembership("Jim"));
  20:                     Console.WriteLine("Sally's - {0}", proxy.GetMembership("Sally"));  
  21:                 }
  22:             }
  23:             Console.WriteLine("Enter to end client.");
  24:             Console.ReadLine();
  25:         }
  26:     }
  27: }

WCFClient2 console application

The second client will send its identity as a string value in the outgoing header of the current WCF channel.  In order to do this, a untyped MessageHeader object is created and added to the OutgoingMessageHeaders of the current OperationContext.

   1: using System;
   2: using System.ServiceModel;
   3: using System.ServiceModel.Channels;
   4:  
   5: namespace WCFClient2
   6: {
   7:     class Program
   8:     {
   9:         static void Main(string[] args)
  10:         {
  11:             using (MemberMasterClient proxy = new MemberMasterClient())
  12:             {
  13:                 using (OperationContextScope scope = new OperationContextScope(proxy.InnerChannel))
  14:                 {
  15:                     // set the message in header
  16:                     MessageHeader header = MessageHeader.CreateHeader("SourceApplication", "urn:spike.WCFHeaderExample:v1", "WCFClient Application 2");
  17:                     OperationContext.Current.OutgoingMessageHeaders.Add(header); 
  18:  
  19:                     Console.WriteLine("Membership Details");
  20:                     Console.WriteLine("Henry's - {0}", proxy.GetMembership("Henry"));
  21:                     Console.WriteLine("Gertrude's - {0}", proxy.GetMembership("Gertrude"));
  22:                     Console.WriteLine("Greg's - {0}", proxy.GetMembership("Greg"));
  23:                 }
  24:             }
  25:             Console.WriteLine("Enter to end client.");
  26:             Console.ReadLine();
  27:         }
  28:     }
  29: }

ServiceContext class

The ServiceContext class is a wrapper to simplify using the message headers.  The class is used to hold the information that we want to pass in the headers of the WCF message.  The class also has a static property for setting a single instance of the class in the outgoing header (used by the client) and retrieving the instance from the header (used by the host).  As an illustration for this example, I allowed the SourceApplication value to be passed as a string or as part of an instance of ServiceContext.

   1: using System;
   2: using System.Runtime.Serialization;
   3: using System.ServiceModel;
   4:  
   5: namespace WCFCommon
   6: {    
   7:     [DataContract(Namespace="urn:spike.WCFHeaderExample:v1", Name="ServiceContext")]
   8:     public class ServiceContext
   9:     {
  10:         public ServiceContext() { }
  11:  
  12:         public ServiceContext(string source)
  13:         {
  14:             _source = source;
  15:         }
  16:  
  17:         private string _source = string.Empty;
  18:         [DataMember(IsRequired = true, Name = "SourceApplication")]
  19:         public string SourceApplication
  20:         {   
  21:             get { return _source; }
  22:             set { _source = value; }
  23:         }
  24:  
  25:         /// <summary>
  26:         /// Unique name of object stored in headers
  27:         /// </summary>
  28:         internal const string HeaderServiceContext = "ServiceContext";
  29:  
  30:         /// <summary>
  31:         /// Unique name of object stored in headers
  32:         /// </summary>
  33:         internal const string HeaderSourceApplication = "SourceApplication";
  34:  
  35:         /// <summary>
  36:         /// namespace of object stored in headers
  37:         /// </summary>
  38:         internal const string HeaderNamespace = "urn:spike.WCFHeaderExample:v1";
  39:  
  40:         /// <summary>
  41:         /// Gets or sets the current context a service call is running on
  42:         /// </summary>
  43:         /// <remarks>This is set on the client and retrieved on the host</remarks>
  44:         /// <value>The current.</value>
  45:         public static ServiceContext Current
  46:         {
  47:             set
  48:             {
  49:                 MessageHeader<ServiceContext> header = new MessageHeader<ServiceContext>(value);
  50:                 OperationContext.Current.OutgoingMessageHeaders.Add(header.GetUntypedHeader(ServiceContext.HeaderServiceContext, ServiceContext.HeaderNamespace));
  51:             }
  52:             get
  53:             {
  54:                 // determine if servicecontext was sent in headers
  55:                 int indexOfHeader = OperationContext.Current.IncomingMessageHeaders.FindHeader(HeaderServiceContext, HeaderNamespace);
  56:  
  57:                 if (indexOfHeader == -1)
  58:                 {
  59:                     // determine if the name of the source application was sent in the headers
  60:                     indexOfHeader = OperationContext.Current.IncomingMessageHeaders.FindHeader(HeaderSourceApplication, HeaderNamespace);
  61:                     
  62:                     if(indexOfHeader==-1)
  63:                         throw new ApplicationException("Client Application not found!"); 
  64:                     {
  65:                         // retrieve the name of the client application and return a within a new service context
  66:                         string source = OperationContext.Current.IncomingMessageHeaders.GetHeader<string>(HeaderSourceApplication, HeaderNamespace);
  67:  
  68:                         return new ServiceContext(source);
  69:                     }
  70:                 }
  71:  
  72:                 // return the sent servicecontext
  73:                 return OperationContext.Current.IncomingMessageHeaders.GetHeader<ServiceContext>(HeaderServiceContext, HeaderNamespace);                
  74:             }
  75:         }
  76:     }
  77: }

Running the solution

To run the project, set the solution to startup the WCFClient1, WCFClient2 and WCFHost project. 

The output of the WCFClient1 application:

Membership Details
Jim's - J9992
Sally's - S9992
Enter to end client.

The output of the WCFClient2 application:

Membership Details
Henry's - H9992
Gertrude's - G9992
Greg's - Unknown
Enter to end client.

And, the output of the WCFHost application:

Service is ready, press any key to terminate.
Received message from WCFClient Application 2 requesting Henry's membership.
Received message from WCFClient Application 2 requesting Gertrude's membership.
Received message from WCFClient Application 2 requesting Greg's membership.
Received message from WCFClient Application 1 requesting Jim's membership.
Received message from WCFClient Application 1 requesting Sally's membership.

Summary

In the current project I am working on, I chose to pass user id, source, and message id (a.k.a., correlation id) in the header for all services.  I feel this is a clean way of having a consistent pattern to all services.  Of course, in my situation I have control and/or influence of the client and host solutions so deploying a shared class library was an option.


Posted Fri, Jul 27 2007 12:16 PM by chilberto

Comments

Jeffrey's Blog wrote WCF Service Trace Viewer
on Tue, Aug 7 2007 6:48 AM

WCF Service Trace Viewer

Max wrote re: WCF Simple Header v1
on Tue, Jun 16 2009 9:50 PM

This is the best of all solution for WCF SOAP Header!

Ken wrote re: WCF Simple Header v1
on Wed, Jun 24 2009 10:58 PM

Hi,

Just wondering if there is supposed to be an "else" between line 63 and 64 of the ServiceContext class?

dan wrote re: WCF Simple Header v1
on Mon, Nov 16 2009 3:16 PM

excelent post

Add a Comment

(required)  
(optional)
(required)  
Remember Me?

Please add 7 and 7 and type the answer here:
Copyright © 2003-2008 BloggingAbout.NET
Powered by Community Server (Commercial Edition), by Telligent Systems