Living Without Biztalk by doyleits


In a world where disconnected systems are the norm, and guaranteed delivery is crucial, asynchronous processing is a godsend. Your ecommerce system can accept an order online, fire off a message, forget about it, and send the customer on his merry way. Your billing system can be notified, along with your inventory system, as well as your delivery system, all from a single message or file. Life is good.

OK, so life is good if you have a way to capture that message or file and process it. I can remember doing this with my VB6 ActiveX DLL in COM+, using MSMQ Triggers. That worked (well, kind of). Other than doing exactly what was coded in your DLL, you didn’t have much control over anything else.

Then you have BizTalk Server, which was designed for disconnected systems, heterogenous data, complex orchestrations, scheduled and event-driven processing, and so much more. But let’s face it, BizTalk is a little overkill for a lot of tasks, the setup and administration can be a nightmare, and it is very cost-prohibitive for smaller organizations and individuals.

A Simple Solution

The .NET Framework offers powerful classes and features to help you mimic the functionality of BizTalk, or any other application you may have written or worked with. What we’ll discuss here is how to implement three features of BizTalk, running in the context of a Windows Service.

  • Receiving a message off of a queue, using the System.Message.MessageQueue class
  • Watching the file system for new files using the System.IO.FileSystemWatcher class
  • Performing actions after a specified time interval using the System.Timers.Timer class

Keep in mind that these examples are only for demonstration. They could work in a live, production environment, but do not offer the flexibility and reusability desired in the enterprise. Some comments on what could be done are offered in the closing statements of this article.

We are going to create a Windows Service to mimic BizTalk, becuase it gives us a long-running, self-contained process. The service will accept a message from a message queue we’ve created (this assumes you are developing this example on a Windows 2000 / 2003 / XP machine with the Message Queuing component installed), and write the contents of that message to a file. That file will be saved in a directory being watched by our file watcher, and moved to some other directory. After a certain period of time, all files in that final directory will be deleted.

Create a message queue

Open the Computer Management console (Start – All Programs – Administrative Tools – Computer Management), and navigate to the Message Queuing node (Services and Applications – Message Queuing). Expand the node, right-click Public Queues, and select New – Public Queue [see Image 1]. I’ll give the name “MyQueue,” and make it transactional [see Image 2].

Drop messages into the queue

Next we need to write a program that will drop messages into our message queue, both before and while our soon-to-be-created Windows Service runs. In the Visual Studio IDE, create a new Console Application project, and we’ll leave the name “ConsoleApplication1.” Since we’ll be interacting with a MSMQ message queue, be sure to add a reference to the System.Messaging library. [see Image 3]

Next we will add the code to create five messages in our queue. You can copy the code in Figure 1.

Figure 1
static void Main(string[] args)
{
// Create a new message queue instance
System.Messaging.MessageQueue messageQueue = new
System.Messaging.MessageQueue(@"FormatName:DIRECT=OS:localhostMyQueue");

for (int i = 1; i <= 5; i++)
{
// Create new message instance
System.Messaging.Message message = new System.Messaging.Message();

// Set formatter to allow ASCII text
message.Formatter = new System.Messaging.ActiveXMessageFormatter();

// Give message a label and body
message.Label = string.Format("Message{0}", i.ToString());
message.Body = "This is the message body. It can be text or XML";

// Create a message queue transaction
System.Messaging.MessageQueueTransaction txn =
new System.Messaging.MessageQueueTransaction();

// Send message
txn.Begin();
messageQueue.Send(message, txn);
txn.Commit();

// Clean up
message.Dispose();
message = null;
}

// Clean up
messageQueue.Close();
messageQueue = null;
}

You’ll notice a couple of things in this example. First, I’ve specified the format of the message as an ActiveXMessageFormatter. A message formatted thusly is compatible with the MSMQ COM control, as well as messages sent by or received from VB6 or ASP applications. Also, the serialization is very compact. Secondly, you’ll notice I’ve used a MessageQueueTransaction. This demonstrates that, using proper exception handling, you could Abort or Commit the transaction accordingly.

Run the application. This will place five messages in your message queue [see Image 4]. We will run it again when our service has been started.

Creating the Windows Service

In the Visual Studio IDE, create a new Windows Service project. We’ll use the default project name “WindowsService1,” and the default service class name “Service1.” Again, be sure to add a reference to the System.Messaging library. By default, the service name as appears in the Services management console will be “Service1,” but this can be changed in the InitializeComponent() method [see Figure 2].

Figure 2
private void InitializeComponent()
{
...
this.ServiceName = "Service1";
...
}

First, let’s write some code in the service’s OnStart() method that will create some directories, which will be used later [see Figure 3].

Figure 3
...
private string newMessageDirectory = null;
private string processedMessageDirectory = null;
...
protected override void OnStart(string[] args)
{
...
// Set directories
this.newMessageDirectory = @"C:TempWindowsService1New";
this.processedMessageDirectory = @"C:TempWindowsService1Processed";

// Create directories if needed
if (!System.IO.Directory.Exists(this.newMessageDirectory))
System.IO.Directory.CreateDirectory(this.newMessageDirectory);
if (!System.IO.Directory.Exists(this.processedMessageDirectory))
System.IO.Directory.CreateDirectory(this.processedMessageDirectory);
...
}

Next, let’s set up the message queue handler. We’ll declare the queue, establish the listening process in the service’s OnStart() method, and write the code to handle when the service is stopped. We’ll also create the messageQueue_ReceiveCompleted() method, which will actually retrieve the message, and write the contents to a file [see Figure 4].

Figure 4
...
private System.Messaging.MessageQueue messageQueue = null;
...
protected override void OnStart(string[] args)
{
...
// Create new message queue instance
this.messageQueue =
new System.Messaging.MessageQueue(@"FormatName:DIRECT=OS:localhostMyQueue");
// Set formatter to allow ASCII text
this.messageQueue.Formatter = new System.Messaging.ActiveXMessageFormatter();
// Assign event handler when message is received
this.messageQueue.ReceiveCompleted +=
new System.Messaging.ReceiveCompletedEventHandler(messageQueue_ReceiveCompleted);
// Start listening
this.messageQueue.BeginReceive();
...
}
...
private void messageQueue_ReceiveCompleted(object sender, System.Messaging.ReceiveCompletedEventArgs e)
{
System.Messaging.Message completeMessage = null;
System.IO.FileStream fileStream = null;
System.IO.StreamWriter streamWriter = null;

try
{
// Receive the message
completeMessage = this.messageQueue.EndReceive(e.AsyncResult);

// Create a unique file name
string fileName = string.Format("{0}_{1}.txt",
completeMessage.Label, Guid.NewGuid().ToString());

// Write to the file
fileStream = new System.IO.FileStream(
string.Concat(this.newMessageDirectory, fileName),
System.IO.FileMode.OpenOrCreate, System.IO.FileAccess.ReadWrite);
streamWriter = new System.IO.StreamWriter(fileStream);
streamWriter.Write(completeMessage.Body.ToString());

}
...
finally
{
...
// Even if message receive fails, keep this processing on the queue
this.messageQueue.BeginReceive();
}
}

The two most important features of this code are BeginReceive() and EndReceive(). BeginReceive() is called initially in the OnStart() method. When the service starts, any message currently in the queue, or any message the is delivered to the queue while the service is running, will be processed in the messageQueue_ReceiveCompleted() method. Once the message is handle (successfully or not), BeginReceive() is always called. If it is not, your message handler will not listen to the queue.

EndReceive() delivers the message that raised the event. Once you have that message, you’ll be responsible for making sure that information is not lost. For the sake of comparison, BizTalk always writes received messages to its database, and either archives or removes that message data once the process has completed successfully. What you do with the message depends on your architecture decision; you could write it to disk, database, or simply handle it and assume your process will succeed.

Now we’ll set up the file system watcher, which will move any new file to our processed file directory [see Figure 5]

Figure 5
...
private System.IO.FileSystemWatcher fileWatcher = null;
...
protected override void OnStart(string[] args)
{
...
// Create file system watcher
this.fileWatcher = new System.IO.FileSystemWatcher(this.newMessageDirectory, "*.txt");
// Assign event handler when file is changed
this.fileWatcher.Changed += new System.IO.FileSystemEventHandler(fileWatcher_Changed);
// Start watching
this.fileWatcher.EnableRaisingEvents = true;
...
}
...
private void fileWatcher_Changed(object sender, System.IO.FileSystemEventArgs e)
{
// Move file
System.IO.File.Move(e.FullPath,
string.Concat(this.processedMessageDirectory, e.Name));
}

We’ve assigned our event handler to watch when files are changed, not created or deleted. We want to ignore the create event, simply because when a new file is being written to, it is created first, supplied with data, then saved. We wouldn’t want to get this file while some other process is trying to write to it. Also, we limited our file system watcher to only look for *.txt files.

In networked environments, where your file system watcher is watching a networked resource, you will want to handle the Error event, to ensure your watcher does not fail if the directory momentarily disappears.

Finally, we’ll create the timer, and the event handler for it [see Figure 6].

Figure 6
...
private System.Timers.Timer timer = null;
...
protected override void OnStart(string[] args)
{
...
// Create timer to elapse every 60 seconds (60 s * 1000 ms)
this.timer = new System.Timers.Timer(60000);
// Set it to elapse every 60 seconds
this.timer.AutoReset = true;
// Assign event handler
this.timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed);
// Start timer
this.timer.Start();
...
}
...
private void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
// Delete every *.txt file
foreach (string file in System.IO.Directory.GetFiles(this.processedMessageDirectory, "*.txt"))
{
System.IO.File.Delete(file);
}
}

We’ve created a timer to run every 60 seconds (60000 milliseconds). By setting AutoReset to true, the timer will raise the Elapsed event every time the 60 second time period elapses.

Make sure you stop the message queue listener, file system watcher, and timer in the OnStop() method. Your Service1 class should look “Service1.cs”. Build the project to insure there are no errors.

Installing the Windows Service

If you are not yet familiar with Windows Services, the installation process is fairly straightforward. We need to add the service installers, by navigating to the design view of our service, right-clicking anywhere on the Service1 design surface, and selecting Add Installer [see Image 5].

By default, the service will be configured to run under a domain or local user. This is especially useful in networks where your service may have to impersonate a domain user to access various resources. This also helps your security administrators easily grant or restrict access to a small number of users. Running the service as LocalSystem is not as conducive to security or access control, but for this example, we’ll do so.

We’ll configure the service to run as LocalSystem by modifying the InitializeComponent() method of our newly created ProjectInstaller class [see Figure 7]. If needed, this would also be the method in which to specify dependent services. Your ProjectInstaller class should look like “ProjectInstaller.cs”. Build the project once more.

Figure 7
private void InitializeComponent()
{
...
this.serviceProcessInstaller1.Account =
System.ServiceProcess.ServiceAccount.LocalSystem;
...
}

Using the Visual Studio .NET Command Prompt, navigate to your built executable (typically [project]binDebug), and type the following at the command prompt [see Figure 8]:

Figure 8
installutil WindowsService1.exe

Follow the installation instructions, if you’ve specified an account type other than LocalSystem, and when it has completed, you will see Service1 in the Services management console [see Image 6].

Start the service

Start the service, and you’ll notice immediately that the directories have been created, and that very soon after, the messages have been saved to disk and the files then moved. After one minute, the files in the processed directory will be deleted. For a further test, you could run your ConsoleApplication1 again, generate more messages, and watch as they are processed.

Once you are done, you can stop and uninstall the service, again using the installutil utility [see Figure 9]. Be sure you close the Services management console before you uninstall the service.

Figure 9
installutil -u WindowsService1.exe

Final Thoughts

You’ve seen how to create a Windows Service that can mimic some of the functionality of BizTalk – receiving messages from a queue, watching the file system, and performing scheduled tasks. There was very little development time and overhead associated with this approach, and could easily be adapted for your own needs.

With this approach, I could envision a system designed around a configuration document, that would specify message queues to watch, directories to watch, and timed events, and assemblies and methods to invoke based upon those processes. This approach could be formulated using Reflection (to invoke the assemblies) and a custom configuration handler (to store a flexible XML design in your App.config). With just a little foresight, you’d have an enterprise system, without the cost of BizTalk!

Attachments:

Project Files: Living_without_Biztalk_Src.zip