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