Create a WCF service and client, using Msmq, programmatically

by geoffrey 17. November 2009 06:00

  download source code

Most of the WCF examples on the web uses the config file to setup the address, binding and contract of a WCF service. I dislike this way of configuring client and service in WCF because most of the time it leads to config files that are full of crab and that becomes unmanageable. I prefer to construct my WCF services programmatically. Therefore I’ve create a bunch of helper classes that contains most of the configuration settings that are applicable in a particular domain.

In this article I discuss one of these classes  I use to create local queues. With local I mean queues used by components making part of the same application. This class can be used as a factory to create the service (serviceHost) as well as the client part (Channel) for components communicating through Msmq.

This article provides a small tutorial on how to create a simple console app. I demonstrate how to create the client and service host without any configuration by using a helper factory class: WCFMsmqFactory

Prerequisite:

- This article assumes you’ve installed Msmq on your box.  If this isn’t the case install Msmq see->  here for Xp & Win server 2003 or here for Vista & Win 2008 & Seven.

- If you’re a vista user you need to register your namespace for by typing in the command (in administrator mode):
netsh http add urlacl url=http://+:8000/BackToOwner/gatewayservice/sms user=[your username]

1) Create a new console application project: ‘WCFMsmqFactory’ and add a reference to

  • System.ServiceModel
  • System.Messaging
  • System.Transactions

 

2) Create a the WcfMsmqFactory class by copying the source code here beneath:

   1:  public class WcfMsmqFactory<I, T> where T : I
   2:     {
   3:         private string _queueAddress;
   4:         public WcfMsmqFactory(string queueAddress)
   5:         {
   6:             _queueAddress = queueAddress; 
   7:   
   8:             if (!MessageQueue.Exists(queueAddress))
   9:                 MessageQueue.Create(queueAddress, true);
  10:         } 
  11:   
  12:         public ServiceHost CreateMsmqServiceHost(string metadataAddress, string namespaceName)
  13:         {
  14:             var sHost = new ServiceHost(typeof(T));
  15:             var binding = new MsmqIntegrationBinding(MsmqIntegrationSecurityMode.None);
  16:             binding.DeadLetterQueue = DeadLetterQueue.System;
  17:             binding.Namespace = namespaceName;
  18:             sHost.AddServiceEndpoint(typeof(I),
  19:                                            binding,
  20:                                            new Uri(String.Format(
  21:                                                        @"msmq.formatname:DIRECT=OS:{0}",
  22:                                                        _queueAddress)
  23:                                                )
  24:                 ); 
  25:   
  26:             // Expose the service metadata on the metadataAddress
  27:             var smb = new ServiceMetadataBehavior();
  28:             smb.HttpGetEnabled = true;
  29:             smb.HttpGetUrl = new Uri(metadataAddress);
  30:             sHost.Description.Behaviors.Add(smb); 
  31:   
  32:             return sHost;
  33:         } 
  34:   
  35:         public I CreateChannel()
  36:         {
  37:             var binding = new MsmqIntegrationBinding(MsmqIntegrationSecurityMode.None);
  38:             var address = new EndpointAddress(String.Format("msmq.formatname:DIRECT=OS:{0}", _queueAddress));
  39:             var channelFactory = new ChannelFactory<I>(binding, address);
  40:             return channelFactory.CreateChannel();
  41:         }
  42:     }
  43:   

This class is perfectly reusable in any project and provide an abstraction on how to create programaticaly WCF services using the msmq binding. To instantiate the class we’ve to pass the interface and his actual implementation. The interface type we’ll be used to create the client & server part. The implementation type (service) will only be used to create the server.
The constructor creates a queue if it’s not already available. Note that the example don’t use security as the transport level security would demand to use active directory. As my pc does not connect to an Active Directory server I had to instantiate the binding without security:

var binding = new MsmqIntegrationBinding(MsmqIntegrationSecurityMode.None);

Not setting this setting resulted to the following error message: ” Binding validation failed because the binding's MsmqAuthenticationMode property is set to WindowsDomain but MSMQ is installed with Active Directory integration disabled. The channel factory or service host cannot be opened”

For the dead letter queue I use the default dead letter queue (see comment) settings:

binding.DeadLetterQueue = DeadLetterQueue.System;

To prevent the service to display the namespace as Tempuri.org it’s important to provide the same namespace settings as in your service contract & behavior directive.

binding.Namespace = namespaceName;

The following code exposes the metadata information (wsdl) on the provided metadata address:

var smb = new ServiceMetadataBehavior();
smb.HttpGetEnabled = true;
smb.HttpGetUrl = new Uri(metadataAddress);
sHost.Description.Behaviors.Add(smb);

3) Now lets create the message definition.
Add a new class SmsMessage:

public class SmsMessage
{
        public string Number { get; set; }
        public string Body { get; set; }
}

4) Define the service definition:
Add the file SmsService.cs:

   1:  [ServiceContract(Namespace = "http://My.Domain.Services.WCF")]
   2:  [ServiceKnownType(typeof(SmsMessage))]
   3:  public interface ISmsService
   4:  {
   5:      [OperationContract(IsOneWay = true, Action = "*")]
   6:      void SubmitSms(MsmqMessage<SmsMessage> msg);
   7:  } 
   8:   
   9:  [ServiceBehavior(Namespace = "http://My.Domain.Services.WCF")]
  10:  public class SmsService : ISmsService
  11:  {
  12:      public void SubmitSms(MsmqMessage<SmsMessage> msg)
  13:      {
  14:          var sms = msg.Body;
  15:          Console.WriteLine(string.Format(
  16:                                "SMS send to {0} with body='{1}'",
  17:                                sms.Number,
  18:                                sms.Body)
  19:              ); 
  20:   
  21:      }
  22:  }
  23:   

The SmsService class contains the real implementation code. As the purpose of this article is not to demonstrate how to actually send sms messages I didn’t provide the real code here. This demo only output the message on the console.

5) Finally let’s put all the parts together.
Add the following code to the main part of the program:

   1:   static void Main(string[] args)
   2:      {
   3:          //define variables
   4:          const string queueAddress = @".\private$\sms";
   5:          const string metadataAddress = "http://localhost:8000/BackToOwner/gatewayservice/sms";
   6:          const string nameSpaceName = "http://My.Domain.Services.WCF";
   7:          var message = new SmsMessage()
   8:                            {
   9:                                Number = "+322678821",
  10:                                Body = "This is a sample sms message!"
  11:                            };
  12:          var msmqMessage = new MsmqMessage<SmsMessage>(message); 
  13:   
  14:          //define factory 
  15:          var factory = new WcfMsmqFactory<ISmsService, SmsService>(queueAddress); 
  16:   
  17:          //Do the work
  18:          using(var serviceHost = factory.CreateMsmqServiceHost(metadataAddress,nameSpaceName))
  19:          {
  20:              Console.WriteLine("Starting the server...");
  21:              serviceHost.Open();
  22:              Console.WriteLine("Instantiating the client channel...");
  23:              var channel = factory.CreateChannel();
  24:              using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
  25:              {
  26:                  Console.WriteLine("Sending the message...");
  27:                  channel.SubmitSms(msmqMessage);
  28:                  scope.Complete();
  29:              }
  30:              Thread.Sleep(1000);
  31:              Console.WriteLine("Closing the server...");
  32:              serviceHost.Close();
  33:          }
  34:          Console.ReadLine();
  35:      }
  36:  }
  37:   
 

   Running the application should display a console with:
Starting the server...
Instantiating the client channel...
Sending the message...
SMS send to +322678821 with body='This is a sample sms message!'
Closing the server...

The real magic happens here:

var factory = new WcfMsmqFactory<ISmsService, SmsService>(queueAddress);

With this few line of code we’ve instantiated a factory that is able to create the client as the server of our service. The only thing we’ve to pass is the queue address – the rest of the configuration is encapsulated in our Factory class and can be easily reused.   We can provide our factory classes to all the enterprise and create a framework on top of WCF that will standardize and facilitate how WCF is used inside our company.

GVD

kick it on DotNetKicks.com

Comments

4/9/2010 11:08:44 PM #

trackback

Create a WCF service and client, using Msmq, programmatically

You've been kicked (a good thing) - Trackback from DotNetKicks.com

DotNetKicks.com | Reply

5/31/2010 11:40:39 PM #

Yuri

Nice example! Your notes are however not very elaborate on the subject of error handling, especially the deadletter queue. AFAIK the DLQ works as follows: specify a custom DLQ (property CustomDeadLetterQueue on the MsmqIntegrationBinding) and specify what the name is of that DLQ. If you don't, the default DLQ is used, but only when property ReceiveErrorHandling is set to 'Reject'. In your examples there is no reference to both, so I would suppose the default values are chosen, which is probably 'Fail' and no custom DLQ, which means that when an error occurs the whole service will get shot down by an exception (which I would not prefer when I'm writing an SMS service handler, for example).
Would you mind elaborating a bit about your views on this? Does your example actually work with a custom DLQ, and why isn't this custom DLQ specified anywhere?

Yuri Netherlands | Reply

6/1/2010 12:08:41 AM #

Yuri

Actually I just saw it was 'Fault' instead of 'Fail', but the rest of the story still remains the same. Why isn't the CustomDeadLetterQueue and ReceiveErrorHandling property set in your example even though you specify 'Custom' as the DeadLetterQueue property?

Yuri Netherlands | Reply

6/1/2010 4:41:10 AM #

geoffrey

You’re right there’s a bug in this example.
It makes no sense to specify “Custom” as the value for the property DeadLetterQueue without specifying an URI for the queue.   The method CreateMsmqServiceHost should get an extra parameter or (deadLetterQueueAddress) or use DeadLetterQueue.System (what is the default value).
Thanks for your review.

geoffrey Belgium | Reply

6/2/2010 6:19:03 PM #

Yuri

Just noticed I missed a detail in your comment. Actually, if you would allow such an extra parameter, it should be added to the CreateChannel method, not the CreateMsmqServiceHost. TimeToLive and CustomDeadLetterQueue are client properties, not server properties. The client is the one that sends the message and gives the message it's destination and TimeToLive, not the server. This bit of wisdom was exactly what I was looking for when I came here, as I couldn't get the custom deadletter queue to work. Finally I figured out that I had to add these properties to the client and then it worked. The MSDN docs are not very clear on this subject.

Yuri Netherlands | Reply

6/1/2010 6:26:01 PM #

Yuri

Thanks for the reply. I like the idea, though. I'm trying to get your code into a shape I can use for my project. I've noticed two more issues:
- you can't instantiate a client without a reference to the service implementation (which is kinda silly, as a client doesn't *need* to know about the service implementation - which could just as well be in a different class library)
- you can't close the channelfactory used for the client (which could result in wasting resources)

I've refactored your class so that <I,T> is replaced with <I> and changed the CreateMsmqServiceHost method to a generic method CreateMsmqServiceHost<T>. That way you can instantiate a client without a reference to the service implementation.
Also, I've refactored CreateChannel to return the channelfactory, instead of the channel itself. That way, in your client you can use channelfactory.Close().

Yuri Netherlands | Reply

6/2/2010 5:11:26 AM #

geoffrey

Thanks for your comment; this is the type of feedback that helps us improve the knowledge of our community.  Don’t hesitate to link to your code example.
Bye

geoffrey Belgium | Reply

6/2/2010 5:47:35 PM #

Yuri

here you go:
http://snippets.dzone.com/posts/show/11513

Yuri Netherlands | Reply

Add comment


(Will show your Gravatar icon)

  Country flag

biuquote
  • Comment
  • Preview
Loading



About the author

Geoffrey Vandiest

contact

View Geoffrey Vandiest's profile on LinkedIn

Follow me on twitter

Geoffrey Vandiest is a technical fellow that learned the art of programming at the age of 10 on a Philips MSX computer. He's skilled in the architecture and development on the Microsoft platform and started experimenting with the Microsoft .Net framework as from the Beta 1 in 2001. Since nearly a decade Geoffrey coaches development teams and base his management style on Agile principles.