There was previously some complexity in hosting the web services and ensuring that they were functioning correctly. This complexity manifested itself in the form of a background Thread to service requests, a ManualResetEvent for synchronisation, two AppDomains at runtime and a few helper classes. Since we are no longer using web services, all of this can be deleted.

Creating a Service Contract

Often some thought is need around the exact operations to be exposed and their various signatures. However, since I wasn't aiming to change functionality at all, I didn't see any point in revisiting the operation signatures we had previously. So all that was involved in creating the service contract was setting up an IMessengerService interface and decorating it with the appropriate attributes:

The old web service methods were thin wrappers around calls to QueueManager methods. These wrapper methods now live in the QueueManager class which implements IMessengerService. So all that's needed to expose this singleton instance over WCF is the following code:

Strangely enough, while the move to WCF was a simplification of the service code there is now more code in the MSBuild client. This is due to one particular quirk of code running as an MSBuild task - there is no config file. WCF is (rightly so) tailored to expressing service endpoints in config files.

As a result of this, the MsnNotification task has to programmatically construct the full endpoint (including binding, service behaviours, etc) based on parameters passed in from the build script. To avoid excessive complexity, the code deduces the transport protocol from the service URL and then uses the default binding.

So aside from the use of technology, have we gained anything from using WCF instead of the web services? Sure we have. Here's the list as I see it:

Reduced code complexity - a lot of the complex stuff around the hosting of the web services is now taken care of by the System.ServiceModel.ServiceHost class.

Better scalability - the sharp-eyed reader will notice that previously we only had a single thread to process incoming requests. The ServiceHost class does a better job of servicing clients.

A wider range of protocols - we are no longer limited to plain SOAP messages. We can declaratively add in things like security, transactions, reliable messaging, etc, etc (although the MSBuild task doesn't support these right now). Additionally, we can expose the service over MSMQ, TCP sockets, etc, etc.

UPDATE

Howard took one look at my code and immediately started refactoring it. The updated version is attached to this post. The main change he made was to improve the access to the configuration file - creating a class that derives from ConfigurationSection is much more of a .Net 2.0 way of doing things.

The network we were on had a lot of congestion. So sometimes the sign-in process to Messenger would time out and return an error to MSBuild. This would break the CC.Net build [:(]

Every time a message was sent, the code would sign-in to Messenger. This meant that if you were a recipient you would see the following:

This would work a lot better if there was a Windows Service which signed in to Messenger and held onto the connection. It could then accept messages from the CC.Net build process and forward them on to the Messenger network. And this is what I've written. The service exposes some web services, puts the messages into an in-memory queue and then has a timer which polls this queue and pushes message out over Messenger. It was then fairly trivial to write a custom MSBuild task which is a proxy for these web services. You can download the whole lot from here.

Below are some details on installation and configuration, along with some discussions around the technically more interesting bits.

Installation and Configuration

Installation is pretty straightforward:

Copy the entire contents (files and subdirectories) of MessengerSvc\build to where you want them

Run "installutil MessengerSvc.exe" (installutil.exe is part of the .Net SDK)

Open the Services control panel and configure whatever service account, startup properties, etc that you like

A word of warning - I haven't done much testing with different privileges on the service account. I can't tell you exactly what the minimum required privileges are. If you do install and run this service then I'd be very ineterested to hear how you got on.

Configuration settings are all contained in the MessengerSvc.exe.config file. There are comments in there, so I'm not going to describe each setting here.

Exposing the Web Services

This turned out to be fiddly but not overly complex. We use the ApplicationHost class in the System.Web.Hosting namespace to start it all off. This creates a new AppDomain and loads an instance of HttpListenerWrapper into it. This class primarily does what it says on the tin, and wraps the HttpListener class. Our service then creates a background thread which makes a blocking call to HttpListenerWrapper.ProcessRequest which uses the HttpListener class to receive an HTTP request and calls HttpRuntime.ProcessRequest to perform the heavy-lifting of processing the request.

Incoming message requests accepted via web service are recorded in a singleton instance of QueueManager. The HttpListenerWrapper class has a method which retrieves all the values held in the instance of the QueueManager and returns them. This method allows us to transfer the requests from the secondary AppDomain (created by ApplicationHost) into our primary AppDomain. It is called from the background thread created by the service, once the call to HttpListenerWrapper.ProcessRequest has completed.

Sending Messages Over Messenger

Given that I started with the code described by Russell (which comes from Dr.NETjes) which already includes the DotMSN library (available here), I was really standing on the shoulders of giants. And so the code for actually sending the message over MSN Messenger deals more with time outs, validity periods, exception conditions and retry counts than it does with networking. All this logic is contained in the QueueManager.PollMessages method.

It's worth pointing out that I did modify the MessengerWrapper class a bit to make it (a) thread-safe; and (b) more aware of the current state of its connection. A good example of this is in the SendMessage method, where if a login operation is underway then we attempt to wait for it to complete. And if there isn't a login operation in progress but we aren't logged in, then we start a login operation. Logic like this is a lot more important in a service that in a straightforward single-threaded executable.

Service Logging

I've used log4net within the service. There isn't a config file in the download, but you can find out how to write one by going to the official log4net site. I've tried to aim for some consistency, so you may want to consider logging Error messages from the MessengerSvc.HostService logger into the event log, or some other important place.

Using the Service From MSBuild

Creating a custom build task which makes web service calls was really quite trivial. If you're looking for a good primer on how to create a custom task, try this one from Waaargh.Net. There is a basic MSBuild script in the download, which looks like this:

One of the
benefits of using a service is that you don't need to specify the Messenger login details in your build script. They are located in the service's config file instead.

Future Enhancements

It occurred to me after writing this code (once I learnt a bit about WCF) that we can replace the entire web service bit with a few choice WCF calls. If I get time to do this, then I'll make another post about it.

When writing the code to push the messages out onto Messenger, it started to become clear how you could write a Messenger bot, i.e. an application that used Messenger as its UI. Users could then connect via Messenger to send commands / queries to the service. This is similar to something we already have at Conchango for accessing the corporate phonebook.

A professional installer is a nice-to-have. Something that would prompt the user for various details, update the config file with these values and install the service automatically.

If you do make use this service, then please drop me a line (or a comment) to let me know how it works for you.