 MSDN Magazine Launch 2008

MSDN Magazine Launch 2008

Mobile WCF: Write an IM App with the .NET Compact Framework

The .NET Compact Framework 3.5 provides a subset of Windows Communication Foundation (WCF) functionality that you can harness to communicate between Windows Mobile devices and desktop PCs. We'll show you how.

Andrew Arnott IIS 7.0: Build Web Server Solutions with End-To-End Extensibility

Mike Volodarsky demonstrates the IIS 7.0 extensibility model by extending the Response Modification into a configurable Web server module and a custom management page for IIS Manager.

Mike Volodarsky VSTO 3.0: Developing Office Business Apps with Visual Studio 2008

With custom form regions in Outlook you can pull in data from designated data sources and truly customize your users' Outlook 2007 experience.

Steve Fox Unit Testing: Apply Test-Driven Development to your Database Projects

Jamie Laflen extols the benefits of TDD when applied to database development—and supplies some useful techniques along the way.

Jamie Laflen F# Primer: Use Functional Programming Techniques in the .NET Framework

Here we introduce you to some of the concepts behind the new F# language, which combines elements of functional and object-oriented .NET languages. We then help you get started writing some simple programs.

Ted Neward Editor's Note: A Large Release Wave is Headed your Way

This month Howard Dierking discusses the coverage of Windows Server 2008, SQL Server 2008, and Visual Studio 2008.

Howard Dierking Toolbox: SQL Code Completion, Subversion Tools, Agile Development, and more

This month: help writing SQL code, modeling your database with SubSonic, and a review of Visual Studio Team System: Better Software Development for Agile Teams.

James Avery Cutting Edge: Modal Dialog Boxes with AJAX

This month Dino Esposito shows you how to get Windows-style modal dialog boxes for your Web applications thanks to the Ajax Control Toolkit and some clever coding.

Dino Esposito Test Run: Custom Test Automation with Team System

In this month's column Dr. James McCaffrey describes some of the ways you can use the Visual Studio 2005 Team System to manage custom software test automation.

Dr. James McCaffrey Wicked Code: Silverlight Tips, Tricks, and Best Practices

: Jeff Prosise presents great tips for Silverlight development, which while it's gaining wide adoption, still needs more documentation and best practices so developers can make the most of the dazzling new features.

Jeff Prosise Service Station: Web Service Software Factory Modeling Edition

Web Service Software Factory: Modeling Edition is a collection of resources for modeling and building Web services for Windows Communication Foundation and ASMX.

Gerardo de Geest and Gerben van Loon Foundations: Workflow Services

With the NET Framework 3.5, Windows Workflow Foundation can now truly be integrated with Windows Communication Foundation. This column describes how the integration works and how to make use of it effectively.

Matt Milner Windows with C++: Windows Services Enhancements

Windows Vista and Windows Server 2008 bring some significant changes that make it simpler to produce services that are more secure and responsive.

Kenny Kerr Team System: Check-in Notes and Policies

Learn how to add check-in notes and check-in policy support to a Word add-in, in this fifth and final installment of an ongoing discussion.

Brian A. Randell { End Bracket }: The Digital Declaration of Independence

Bill Hill discusses his Digital Declaration of Independence.

Bill Hill


Mobile WCF: Write an IM App with the .NET Compact Framework

Mobile WCF

Write an IM App with the .NET Compact Framework

Andrew Arnott
This article discusses:
  • Mail transports in the .NET Compact Framework
  • Writing simple messaging applications
  • Inside the WCF messaging plumbing
  • Consuming a WCF Web service
This article uses the following technologies:
.NET Compact Framework 3.5, Visual Studio 2008
Code download available at: WCFCompactFramework2008_Launch.exe (185KB)
Contents
Mail Transports
Writing a Windows Mobile App
WCF Messaging Plumbing
Creating and Sending the Message
Writing the Desktop Application
Running the Sample
Calling WCF Services
Creating a Proxy Class
Using the WCF Service

A
n addressability problem with mobile devices has traditionally made it difficult to write Windows Mobile® applications that would receive data pushed down from servers. Small devices often do not have static IP addresses or dynamic DNS entries that follow them around. A common workaround has been for the device to send an HTTP request to the server when the device comes online, and the server then lets that request wait until it has something to push down to the device. The server then responds to that long-standing request with the update, and the device processes the update and sends out another request to wait for the next update.

This workaround can cause scalability problems on the server since it has to hang onto many requests at once instead of responding to them right away and closing the connections. It can also reduce the battery life of the device, which must stay on to maintain the connection. If the device isn't on when an update from the server is ready, the server has no way to send the update and either must drop the update or maintain state on which devices have which updates. Finally, the application has no way to send or receive messages if the network is not available.

Visual Studio® 2008 gives developers of Windows Mobile applications the ability to access a subset of Windows® Communication Foundation (WCF) functionality via the Microsoft® .NET Compact Framework 3.5, which solves many of these problems with two new binding elements to WCF that facilitate sending and receiving messages using e-mail as the transport. Since many devices already have e-mail sync functionality, these transports take advantage of the inherent queuing nature of e-mail and the availability of e-mail servers already set up on the Internet to create an addressable message queue that enables peer-to-peer, device-to-server, and server-to-device message-level communication in a true message-push fashion. In this article, I will outline the subset of WCF that is supported by the .NET Compact Framework 3.5 and how you can utilize these transports and tools in your mobile applications.

Mail Transports
.NET Compact Framework Resources
  • .NET Compact Framework Team blog
  • .NET Compact Framework 3.5 Redistributable and Power Toys
  • Some individual blogs related to .NET Compact Framework development:
    • blogs.msdn.com/andrewarnottms
    • blogs.msdn.com/markprenticems
    • blogs.msdn.com/davidklinems
    • jmpinline.nerdbank.net

The .NET Compact Framework 3.5 includes WindowsMobileMailTransport for messaging on the Windows Mobile platform and ExchangeWebServiceMailTransport for messaging on the Windows desktop platform. The two transports interoperate so that communication among devices, among PCs, or between devices and PCs all works transparently. Since many applications may be using the mail transport at the same time for the same recipient, a mail channel name can be given to distinguish messages intended for your application from messages intended for other applications. This channel name is then included in the subject line of each message. As a result, the mail transport will only open messages with the subject name that corresponds to the channel name for which you are listening.

The mail transport for Windows Mobile devices that ships with the .NET Compact Framework 3.5 performs best if running on a Windows Mobile device that supports DirectPush, a feature that enables the associated Exchange server to push messages to Windows Mobile as soon as Exchange receives the message. To learn more about DirectPush (also known as Always-Up-To-Date) or to install it on your Windows Mobile device, visit msexchangeteam.com/archive/2004/04/26/120520.aspx.

If you are familiar with Microsoft Message Queuing (MSMQ), mail transport is very similar, except that it does not have the problem of device addressability. The mail stores on the device and on the Exchange server act as the message queues. The e-mail addresses of each device and server act as the queue's URI.

Note that while, theoretically, two devices using WindowsMobileMailTransport may be able to use any SMTP/POP3 servers to communicate, the only scenario that is currently supported in the .NET Compact Framework 3.5 uses Exchange Server 2007 as the mail server. Additionally, custom Windows CE devices are not supported.

To illustrate how the transports operate, I'm going to build a pair of small sample apps and use e-mail to send instant message-like messages between a Windows Mobile device and a PC. The mail transports only support one-way messaging (as opposed to request-reply), which is good for an instant message app.

The first two actions you should take are to install Visual Studio 2008 and to decide on your target device. This target device, whether physical or emulated, will need to be configured to access your Exchange Server 2007 mail account. Be sure to do this before you test your app. If you configure an emulated device, either leave it running or save its state before closing it—otherwise, it will revert to not having a mail account setup. If you forget, you'll probably find messages piling up in your device's outbox with nowhere to go.

Make sure you have Windows Mobile Device Center 6.1 or ActiveSync® 4.5 installed before you begin. You can download these from microsoft.com/activesync.

If you're running Windows Vista®, the easiest way to set up mail on your emulator is to open up the Device Emulator Manager and cradle the emulator. Windows Mobile Device Center (WMDC) should open when the device connects. If WMDC is installed but does not recognize a cradled emulator, use the Start menu to launch WMDC. Under Mobile Device Settings, check the Connection Settings and make sure that "Allow connections to one of the following:" is checked and that DMA is selected in the dropdown.

Click "Set up your device" and keep at least the E-mail option checked. Then, go ahead and provide the information to allow the device (or emulator) to connect directly to Exchange. This will allow your program to send and receive messages whenever there is a network available—even when it is not cradled. Finish setting up the partnership with the device or emulator.

If you decide to make use of the mail transport using your personal e-mail address, you may choose to set up an Exchange Rule to redirect all messages with a subject line starting with "SM": to another folder. The .NET Compact Framework 3.5 mail transports will only check two folders for WCF messages, Inbox and Service E-Mail, so be sure to keep your WCF messages in one of these two folders.

Writing a Windows Mobile App

You start writing a Windows Mobile application by creating a Visual C#® Smart Device project. Name the project DeviceMessagingApp and the solution MsdnMessagingSample, as in Figure 1. You will be taken to another dialog box, where you can choose your target platform and .NET Compact Framework version. Choose the Windows Mobile 6 Professional SDK platform and set the .NET Compact Framework version to 3.5. If you do not see an option for the Windows Mobile SDK, visit go.microsoft.com/fwlink/?LinkID=81684 and download the Windows Mobile SDK, then install it and work your way back to this step.

Figure 1 Setting Up Your Project
Figure 1 Setting Up Your Project(Click the image for a smaller view)
Figure 1 Setting Up Your Project
Figure 1 Setting Up Your Project (Click the image for a larger view)

You should find yourself looking at the Visual Studio 2008 form designer with the Windows Mobile device and your empty main form displayed. To write a simple chat program, drop a few TextBox and Label controls on the form. You might set TextBox.ReadOnly = True on your history textbox to prevent unintentional entry. And be sure to set TextBox.Multiline = True for the history box. Add a Send button to the menu so it can be pressed using a hardware button. And now for my favorite: set Form.MinimizeBox to False so that the X in the corner turns to "ok"—this way, your app will exit instead of just being hidden when you press it. In the end, you should have something that looks like Figure 2.

Figure 2 The Application UI
Figure 2 The Application UI

Since you'll be using the Compact Framework version of WCF, you'll need to add some assembly references. Right-click on the project's References folder within Solution Explorer and click Add Reference. From the .NET tab, select these assemblies:

  • System.ServiceModel.dll
  • System.Runtime.Serialization.dll
  • Microsoft.ServiceModel.Channels.Mail.dll
  • Microsoft.ServiceModel.Channels.Mail.WindowsMobile.dll

WCF relies on any implementation of the abstract class XmlObjectSerializer to fill the body of its messages. In the desktop version, WCF comes with DataContractSerializer, which derives from this class. Compact WCF does not include DataContractSerializer, so instead you'll use XmlSerializer. Since XmlSerializer does not derive from XmlObjectSerializer as required by WCF, you can write a small class that derives from it and simply calls on XmlSerializer to do the heavy lifting.

Add a new class called MessageSerializer, derived from XmlObjectSerializer, to your project. If you right-click on the base class name in your source file, Visual Studio will offer to stub out automatically all the functions you need to implement. All you need to do in this class is add a few lines of code, as shown in Figure 3. They define a constructor that initializes the XmlSerializer and implements the ReadObject and WriteObject methods. You'll need to write in the WriteObject method yourself.

Figure 3 XmlObjectSerializer (Close Figure 3)
using System;
using System.Runtime.Serialization;
using System.Xml.Serialization;

class MessageSerializer : XmlObjectSerializer {
    XmlSerializer serializer;

    public MessageSerializer(Type type) {
        serializer = new XmlSerializer(type);
    }

    public override void WriteObject(
        System.Xml.XmlDictionaryWriter writer, object graph) {
        serializer.Serialize(writer, graph);
    }

    public override object ReadObject(
        System.Xml.XmlDictionaryReader reader,
        bool verifyObjectName) {
        return serializer.Deserialize(reader);
    }

    // ...
}

Those methods Visual Studio added to your class that I don't list here can simply throw NotImplementedException, as WCF will not call them anyway.

WCF Messaging Plumbing

To send and receive messages, you must initialize the WCF mail channel. Since the .NET Compact Framework 3.5 does not implement many of the Service Model classes included in desktop WCF, you must use the messaging API, which requires a bit more code. Be sure to write this code in a platform-neutral way by only calling the WCF API exposed by the .NET Compact Framework so that you can share it between your device and desktop applications, thus making each individual application small and simple.

Add a new class file called Messaging.cs. This will contain the Messaging class, which will include all the plumbing necessary to send and receive messages. In a real application, this class may be much larger for better thread safety and scalability, but I'll keep my example short to focus on the task in this sample.

I chose to make the Messaging class a generic Messaging<T> class where T is the type of data being passed back and forth between apps (see Figure 4). In a more complex app, you could pass multiple types of objects around and distinguish the messages using the message Action string, but I will not do that here. Any T will work for this class as long as it can be serialized using XmlSerializer.

Figure 4 Simple Messaging Class (Close Figure 4)
using Microsoft.ServiceModel.Channels.Mail;

class Messaging<T> {
    public delegate void IncomingMessageCallback(T body);
    public const string DeviceSendChannel = "toDesktop";
    public const string DesktopSendChannel = "toDevice";

    public Messaging(MailBindingBase binding,
        string sendChannelName, string listenChannelName,
        IncomingMessageCallback incomingMessageCallback) {
        // ...
    }

    public void SendMessage(string recipient, T body) {
        // ...
    }

    public void Close() {
        // ...
    }
}

The Messaging<T> class will offer two methods for sending and receiving messages. Receiving messages will involve a background thread listening for messages and invoking a callback method whenever a message arrives. It will have to build the channel in the constructor and offer a Close method to tear down the channel when the application closes.

Since there are two mail binding classes (one for desktops and one for mobile devices), this class will take the binding to use as an argument to its constructor. So that you can use the same e-mail account for both desktop and device without the two apps reading each other's incoming messages, you'll need to use different channel names for the desktop and device—therefore, the constructor will take these as arguments, too.

So far, we've designed the skeletal structure for our class, as shown in Figure 4. With that skeletal code written, you can proceed with writing the rest of the device and desktop applications. The full implementation of Messaging.cs is included in the source code download for this article. Nearly all of it is ordinary WCF messaging layer code without any regard for a mail transport.

The next step is construction and teardown of the mail channel. Open the code behind the main form (Form1.cs). You'll need to initialize the mail channel in the constructor and tear it down when the form is closed. To start, add a field that will track the Messaging<T> instance. Since the app communicates using text messages, T will be a string:

Messaging<string> messaging;

You will initialize this field shortly. Since the Messaging<T> constructor requires a callback, first you define a compatible method:

void incomingMessage(string message) {
}

Now initialize the messaging field in the constructor. Be careful to order the two channel names so that the device app sends messages using the DeviceToDesktopChannel name and listens on the DesktopToDeviceChannel name. That way, the device will never receive messages it sends to the desktop, even if the desktop and device are sharing a single e-mail address:

messaging = new Messaging<string>(
    new WindowsMobileMailBinding(),
    Messaging<string>.DeviceToDesktopChannel,
    Messaging<string>.DesktopToDeviceChannel,
    (Messaging<string>.IncomingMessageCallback)incomingMessage);

Close the channel when the application exits in the Form.Closed event handler. The Form1_Closed method only has to call the Close method on the Messaging<T> object:

void Form1_Closed(object sender, EventArgs e) {
    messaging.Close();
}

Creating and Sending the Message

The device app is very nearly complete. All you need to do is send and respond to messages. You'll need to add a handler for the Send button. The event handler should call SendMessage on the Messaging<T> object and then clear the message textbox so the user knows the message was sent, like this:

void sendMenuItem_Click(object sender, EventArgs e) {
    messaging.SendMessage(toTextBox.Text, messageTextBox.Text);
    messageTextBox.Text = "";
}

That was easy. Now you just need to respond to incoming messages by implementing the incomingMessage method (see Figure 5). The catch here is that this callback is invoked from the background thread that listens for new messages, but you must add text to your conversation history textbox from the UI thread. Check the InvokeRequired property to see if you're on a background thread; if so, call Invoke to call the callback method on the UI thread. Once on the UI thread, simply append the incoming message to the conversation box and then make sure the history box is scrolled down far enough to read the newest message.

Figure 5 Responding to Incoming Messages (Close Figure 5)
void incomingMessage(string message) {
    // Invoke ourselves on the UI thread
    if (InvokeRequired) {
        Invoke((Messaging<string>.IncomingMessageCallback)
            incomingMessage, message);
    } else {
        // append incoming message to message history
        historyTextBox.Text += message + Environment.NewLine;
        // scroll to end of history window
        historyTextBox.Select(historyTextBox.Text.Length, 0);
        historyTextBox.ScrollToCaret();
    }
}

That completes the device app. If you had different e-mail accounts on two different devices and set them up with the right channel names, this app would be all you would need to have an IM chat between devices. But let's continue assuming one device with one desktop counterpart and just one e-mail address.

Writing the Desktop Application

Add a new WPF application project to your solution. If you don't see that as an option, check that the dropdown in the upper-right corner of the Add New Project dialog box is set to .NET Framework 3.5. A Windows Forms Application would work equally well, but, for these examples, we'll use the newer technology. Set the project name to DesktopMessagingApp.

On the form designer, drag out the same controls that you did for the device application. If you haven't used WPF before, don't be intimidated by the change in appearance of the designer. Also, you don't need to set TextBox.Multiline for the history box in WPF.

You should end up with something looking like Figure 6. If you'd prefer to just enter the XAML for this form, it's included in the source code download.

Figure 6 Basic UI for the Desktop App
Figure 6 Basic UI for the Desktop App(Click the image for a smaller view)
Figure 6 Basic UI for the Desktop App
Figure 6 Basic UI for the Desktop App (Click the image for a larger view)

Add several reference assemblies to the desktop project, similar to the way you did with your device project. The assemblies you need to reference are:

  • System.ServiceModel.dll
  • System.Runtime.Serialization.dll
  • Microsoft.ServiceModel.Channels.Mail.dll
  • Microsoft.ServiceModel.Channels.Mail.ExchangeWebService.dll

Next, link the MessageSerializer.cs and Messaging.cs files that you created in your device project into your desktop project. Right-click on DesktopMessagingApp in Solution Explorer, then click Add | Existing Item. Navigate to your device application directory. Select both MessageSerializer.cs and Messaging.cs, but don't click the Add button just yet. Instead, click the down arrow that is on the right edge of the button and select Add As Link. This allows you to share source files between the two projects.

The only catch here is that since you created the source files in the device project, the namespace around the shared classes matches the device project's namespace. If this bothers you too much, you can simply change the namespace for these classes to something more common. Additionally, you could put these two source files in a shared library project (being careful to not reference either the ExchangeWebService.dll or the WindowsMobile.dll assemblies).

Construction and teardown of the mail channel for the desktop app will be similar to the device application, except you will instantiate ExchangeWebServiceMailBinding instead of WindowsMobileMailBinding. Also worth noting is that while WindowsMobileMailBinding just used the mailbox on the device (or emulator), ExchangeWebServiceMailBinding needs network credentials. Assuming that the credentials are the same as your network login credentials, you can just pass null into the ExchangeWebServiceMailBinding constructor, and Exchange will authenticate using Windows Integrated Security. You can keep this pattern (which is more secure), or you can hardcode the user name and password, or you can prompt the user for credentials at run time before constructing the Messaging<T> instance.

One other difference between the two mail-binding classes: WindowsMobileMailBinding is notified immediately when ActiveSync brings down a new message, but ExchangeWebServiceMailBinding has to periodically call WebMethods to query for new messages, so you can either accept the default period of 30 seconds or set it yourself.

Start by adding the following two fields to the Window1.xaml.cs codebehind file. Change the Exchange server to your own Microsoft Outlook® Web Access URL:

Messaging<string> messaging;
readonly Uri exchangeUrl =  new Uri("https://mail.wingtiptoys.com");

Just like in the device app, create the message receiving method so you can pass it in as a callback to the Messaging<T> constructor:

void incomingMessage(string message) {
}

You can instantiate a Messaging<T> class in the Window1 constructor, passing it the Exchange mail binding (see Figure 7). This next step is very important: swap the order of the channel names from their order in the device app. By swapping the order, you allow the desktop to receive messages from the device and the device to receive messages from the desktop. If you fail to swap the channels, you have both sides speaking on the same channel and both listening on another channel—no messages would ever be received.

Figure 7 Starting the Mail Transport Binding (Close Figure 7)
public Window1() {
    InitializeComponent();
    ExchangeWebServiceMailBinding binding =
        new ExchangeWebServiceMailBinding(exchangeUrl, null);
    ((ExchangeWebServiceMailTransport)
        binding.Transport).ServerQueryInterval = 2000;

    messaging = new Messaging<string>(
        binding,
        Messaging<string>.DesktopToDeviceChannel,
        Messaging<string>.DeviceToDesktopChannel,
        incomingMessage);
}

And just so you do not have to wait half a minute to see any results, in this sample I force polling the Exchange server every two seconds. Be warned: setting the query interval to two seconds in a production app would probably introduce a scalability problem.

To close the channel, you will need to wire up a method to respond to the Window.Closed event. Open the XAML code in the Window1.xaml file and add the Closed="Window_Closed" attribute to your opening <Window> tag. It should look something like this:

<Window x:Class="DesktopMessagingApp.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Desktop Messaging App" Height="331" Width="440"
    Closed="Window_Closed">

Add a Window_Closed method to your Window1.xaml.cs codebehind file and then call the Close method on your Messaging<T> object, as shown here:

void Window_Closed(object sender, EventArgs e) {
    messaging.Close();
}

Once again, all you need to do is send and respond to messages. Add a handler for the Send menu button by double-clicking it in the designer. Call SendMessage and clear the textbox where the user typed her message so she can see that the message was sent:

void sendButton_Click(object sender, RoutedEventArgs e) {
    messaging.SendMessage(toBox.Text, messageBox.Text);
    messageBox.Clear();
}

To implement the incomingMessage method, once again keep in mind that it will be called from a background thread and you need to invoke it on the UI thread. The WPF way of doing this is slightly different than with Windows Forms. Instead of the InvokeRequired property, you call CheckAccess, and instead of calling Invoke on the control you call it on the control's Dispatcher object. Lastly, WPF has some handy methods on the TextBox control that do exactly what we want to do, so take advantage of them:

void incomingMessage(string message) {
    if (historyBox.CheckAccess()) {
        historyBox.AppendText(message + Environment.NewLine);
        historyBox.ScrollToEnd();
    } else {
        historyBox.Dispatcher.Invoke(DispatcherPriority.Normal,
            (Messaging<string>.IncomingMessageCallback)incomingMessage,
                message);
    }
}

And you're done! Let's run it!

Running the Sample

Build and deploy your device project. Be sure to deploy your device application to the same emulator or device that your mail account is configured on. Start each project individually. In Solution Explorer, right-click on the device project's item and select Debug | Start new instance. Repeat for the desktop project. Figure 8 shows what the device app should look like.

Figure 8 Sending a Message
Figure 8 Sending a Message

Go ahead and enter your Exchange e-mail address into the To: field of each application. Type a message and click Send. While you're waiting for the message to appear on the other app, let me explain what is happening and how you can watch it.

When either app sends a message, the text string you send is wrapped inside a SOAP envelope and dropped in your outbox. Exchange picks it up and sends it to the recipient's inbox (your own in this case). Each app is polling your inbox looking for messages with a matching channel name. Once it notices the waiting message, the application will download the message, delete it from the server (or move it to your Deleted Items folder), and process it by deserializing the string out of the envelope's body tag and appending that string to the conversation history textbox.

If you're interested in how the SOAP-crafted e-mail looks, you can check your outbox or inbox (if you check before the app finds and deletes it) or look through your Deleted Items folder (if the app has already processed the message).

A number of things can go wrong as you write or configure this application. If messages do not show up where you expect them, check your Inbox and Outbox folders in Outlook or Pocket Outlook for messages to see if they are being sent and/or received. If messages are just sitting in the Pocket Outlook Outbox or the Outlook Inbox but not syncing, check that your device is connected to your Exchange mail account and force ActiveSync on your device to send and receive messages. If messages seem to be delivered to the appropriate Inboxes but your application just doesn't pick them up, verify that you have the channel names for sending and receiving messages aligned correctly.

Since the method that receives messages runs on a background thread, any exception that terminates the thread will not be visible to your UI. It's possible that something went wrong and your app is no longer listening for messages. Check your debugger's Output window for an exception stack trace to see if anything has gone wrong. You might use your debugger to see if you have a thread still waiting for messages. In a business app you would want to add exception handeling and perhaps diagnostic logging to this background thread.

Calling WCF Services

The .NET Compact Framework 3.5 does not ship with support for the WCF Service Model, which would support hosting and calling services without all the plumbing code for sending and receiving messages. But with the proper helper classes, calling WebMethods on ASP.NET Web services, WCF services, and other WSDL-compliant services using Compact WCF can be just as easy as calling a method.

The .NET Compact Framework 3.5 PowerToys ship with a tool, NetCFSvcUtil.exe, that generates these service proxy classes for you. Since NetCFSvcUtil.exe does not ship with Visual Studio 2008, you need to download the Power Toys for .NET Compact Framework 3.5 at msdn2.microsoft.com/aa497280.

Because the .NET Compact Framework 3.5 ships with a subset of the bindings that you'll find with WCF on the desktop, a service must expose an endpoint that uses bindings supported by the .NET Compact Framework. You can also write your own bindings using Compact WCF, but that is beyond the scope of this article.

Now let's construct a .NET Compact Framework-compatible WCF service on the desktop and write an application that calls into that service using a proxy class generated by NetCFSvcUtil.exe. For the service application, create a new WCF Service Web site in Visual Studio 2008, verifying that .NET Framework 3.5 is selected in the upper-right-hand corner of the New Web Site dialog. For brevity's sake, go ahead and accept the WCF service exactly as the Visual Studio 2008 template gives it to you. That gives you automatically created GetData and GetDataUsingDataContract methods.

Since the .NET Compact Framework 3.5 does not support wsHttpBinding, you need to either change the default endpoint Visual Studio created to basicHttpBinding or add a new endpoint of that type. Since WCF makes it so easy to expose multiple endpoints, and since wsHttpBinding adds security for those apps that will support it, let's leave it there and add a new endpoint that uses basicHttpBinding.

Open your WCF service's web.config file. Find the <endpoint> tags and add a new one with an address of basic and with the basicHttppBinding binding:

<!-- Service Endpoints -->
<endpoint address="" binding="wsHttpBinding" contract="IService"/>
<endpoint address="mex" binding="mexHttpBinding"
    contract="IMetadataExchange"/>
<endpoint address="basic" binding="basicHttpBinding"
    contract="IService"/>

Check your service by right-clicking your Web project's Service.svc project item in Solution Explorer and clicking View in Browser.

Note that with BasicHttpBinding comes a few limitations on what you can do with your service. For instance, duplex service contracts, transactions, and callbacks are not supported. If you try to use a BasicHttpBinding endpoint to expose a service that uses any of these features (or a few others), you will get a runtime error from WCF.

One additional limitation remains in your service beyond just using BasicHttpBinding: custom headers are not supported by NetCFSvcUtil.exe. If your service requires custom headers to be included in your messages, you will need to either modify the code emitted by the tool or write your own proxy class to add support for these custom headers.

Now add a.NET Compact Framework application to your solution. Create a new project using the Smart Device Project template and name it NetCFClient. Add the Compact WCF assemblies to your project references: System.ServiceModel.dll and System.Runtime.Serialization.dll. Next, add a single button onto your Form1.cs design surface and set its caption to "Call Service". Double-click it to create an event handler. Now you just need a proxy class to invoke the service.

Creating a Proxy Class

Run the NetCFSvcUtil.exe tool against the service to generate the proxy class to include in the device project (see Figure 9). You'll want the source files generated to go into the device application's source directory, so run the tool from there. The tool itself (once installed) should be found in %PROGRAMFILES%\Microsoft .NET\SDK\CompactFramework\v3.5\bin, although if you're running on a 64-bit machine, check the Program Files (x86) directory.

Figure 9 Generating the Proxy Class (Close Figure 9)
C:\demos\MsdnCFServiceSample\NetCFClient>"\Program Files (x86)\Microsoft .NET\SDK\CompactFramework\v3.5\bin\NetCFSvcUtil.exe"
http://localhost:53222/Service/Service.svc?wsdl
Microsoft (R) .NET Compact Framework Service Model Metadata Tool
[Microsoft (R) Windows (R) Communication Foundation, Version 3.5.0.0]
Copyright (c) Microsoft Corporation.  All rights reserved.

Attempting to download metadata from 'http://localhost:53222/Service/Service.svc?wsdl' using WS-Metadata Exchange or DISCO.
Generating files...
C:\demos\MsdnCFServiceSample\NetCFClient\Service.cs
C:\demos\MsdnCFServiceSample\NetCFClient\CFClientBase.cs

C:\demos\MsdnCFServiceSample\NetCFClient>

To run the tool, you only need to pass in the URL to the service metadata (the service must be running). You can add /language:vb if you want the tool to emit the proxy class for a Visual Basic® project. Note that the URL you pass to the tool on the command line will be the URL that the device uses to contact your service, so http://localhost may work to generate your proxy, but your device app will try to talk to itself rather than contact your desktop PC.

Also note that this tool did not generate an output.config file the way the desktop svcutil.exe tool would. The .NET Compact Framework 3.5 does not support configuring WCF through configuration files, so all the endpoint information for a service needs to be in code. The ASP.NET development server will not respond to device requests unless the device or emulator is cradled. What I have done here is use localhost to generate the proxy, then modify the generated Service.cs file by finding the EndpointAddress field defined in the ServiceClient class and changing the URL to use my hostname (the service address) rather than localhost. The code should look something like this:

public static System.ServiceModel.EndpointAddress EndpointAddress =
  new System.ServiceModel.EndpointAddress(
  "http://mycomputername:53222/Service/Service.svc/
  basic");

Also, notice the /basic suffix to the URL. This is how the client indicates to the service which of the three offered endpoints it wants to use. When you have the generated proxy class source files, simply add them to your device project in Visual Studio.

Using the WCF Service

Earlier you created an event handler for the single Call Service button. Let's implement that now. The code should look just like calling a WCF service from a WCF desktop application:

void callServiceButton_Click(object sender, EventArgs e) {
    ServiceClient client = new ServiceClient();
    MessageBox.Show(client.GetData(5));
}

Deploy and run your device app. Click the Call Service button. After a moment you should see a message box pop up with "You entered: 5" (see Figure 10).

Figure 10 Setting Up Your Project
Figure 10 Setting Up Your Project

If you get an error that your host could not be reached, try cradling your device. If your device is an emulator, you can cradle the emulator via the Device Emulator Manager in Visual Studio.

The proxy class generated by the tool translates method calls into WCF messages and sends them using the .NET Compact Framework messaging layer. Response messages are then deserialized and passed back to your app via the method's return value.

The code generated by the NetCFSvcUtil tool will run equally well under the full version of WCF, in which case the proxy classes derive from a WCF class called ClientBase<T>. Since the .NET Compact Framework 3.5 does not ship with ClientBase<T>, the NetCFSvcUtil tool emits code for a CFClientBase<T> that must be included with your application. Although this class performs a similar function to the ClientBase<T> class found in WCF on the desktop, it is not exactly the same and may change in future versions.

Andrew Arnott has been programming using Microsoft technologies for 12 years, and his favorite pastime frameworks include WCF, WPF, and ASP.NET. He is currently working for Microsoft as a Software Development Engineer for the .NET Compact Framework. Andrew is married to his lovely wife, Cheryl, and has one son, who is less than a year old.

© 2007 Microsoft Corporation and CMP Media, LLC. All rights reserved; reproduction in part or in whole without permission is prohibited.


IIS 7.0: Build Web Server Solutions with End-To-End Extensibility

IIS 7.0

Build Web Server Solutions with End-To-End Extensibility

Mike Volodarsky
This article discusses:
  • IIS extension points
  • Building a Response Modification module
  • Adding IIS Manager configuration
  • Deploying the module
This article uses the following technologies:
IIS 7.0, .NET Framework
Contents
Extending the Web Server
A Response Modification Module
Extending Configuration
Response Modification Configuration
A Strongly Typed Configuration Class
Extending IIS Manager
Creating the Service
Creating the Module Page
Deploying the Extension
Tying It All Together
Build Your Own

T
he IIS 7.0 Web platform supports more application framework technologies for hosting rich applications than any previous version of IIS, and it comes chock-full of features that you can use to deploy these applications right out of the box. At the same time, however, what you see (in Windows® Setup) is not necessarily what you always get.

The IIS 7.0 architecture is designed to be extensible from top to bottom, allowing you to replace any part of the built-in feature set with a custom implementation that suits your needs. As a result, instead of providing a patchwork of plug-in points, IIS 7.0 makes the ultimate commitment to extensibility by implementing all of its own features on top of the public extensibility model. This design is found throughout the platform, from the modular Web server engine itself to the configuration system to the IIS Manager console.

In this article, I'll dive into the IIS 7.0 extensibility model by walking through the shared source Response Modification project, which enables responses from IIS applications to be modified on the fly using configurable response modification rules. I'll first build the Web server module by taking advantage of the server's integrated ASP.NET extensibility. Then, I'll shape the deployment and management features for the module by developing a custom configuration section and creating a custom management page for IIS Manager.

Extending the Web Server

The IIS 7.0 modular architecture offers the ability to completely customize the Web server for a required workload. This can often be done simply by installing only the features needed for your application, yielding a lean Web server that does exactly what is needed and nothing more.

However, this is only the first step. Often, the desired Web workload requires additional functionality that may not be part of the built-in IIS feature set. Or, in some cases, the application may require a customized set of functionality for which the built-in features are not flexible enough. Because all of the IIS 7.0 features are built on public extensibility APIs, you can replace any of them with a custom implementation that best fits your needs.

IIS 7.0 provides two options for developing Web server modules. First, you can use the new C++ module API on which most of the built-in features are based. The module API replaces the ISAPI extension and filter API offered in previous versions of IIS. This API is a significant improvement over ISAPI, as it is rich enough to support all of the IIS 7.0 features and it is significantly easier to program against. You can learn more about the improvements in this API at mvolo.com/blogs/serverside/archive/2006/10/07/10-reasons-why-server-development-is-better-with-IIS7.aspx.

Second, IIS 7.0 features ASP.NET integration, which allows IIS 7.0 modules to be developed by using the familiar ASP.NET module APIs. With ASP.NET Integrated mode, these modules become first-class citizens in the IIS request processing pipeline, as shown in Figure 1. This enables ASP.NET modules to access IIS intrinsic objects, such as the request and response, in all stages of request processing, and to process requests for all resource types—not just those handled by the ASP.NET framework.

Figure 1 ASP.NET Modules in IIS 7.0 Request Processing
Figure 1 ASP.NET Modules in IIS 7.0 Request Processing(Click the image for a smaller view)
Figure 1 ASP.NET Modules in IIS 7.0 Request Processing
Figure 1 ASP.NET Modules in IIS 7.0 Request Processing (Click the image for a larger view)

This allows powerful ASP.NET features like Forms-based authentication, Login controls, and the Membership service, to be used uniformly for the entire Web site. For an in-depth view of how ASP.NET Integrated mode can be used to add value to existing applications not written for the ASP.NET framework, be sure to read my article in the January 2008 issue of MSDN® Magazine at msdn.microsoft.com/msdnmag/issues/08/01/PHPandIIS7.

For developers, the real value of the ASP.NET Integrated mode is the ability to extend the IIS Web server using the Microsoft® .NET Framework. This brings significant benefits in rapid application development by providing the ability to build on the rich framework support of the .NET Framework, ASP.NET, and other frameworks like the Windows Workflow Foundation.

A Response Modification Module

The ASP.NET Integrated pipeline enables ASP.NET modules to perform a wide variety of tasks during the processing of each request. This wide variety ranges from performing authentication or authorization to modifying the outgoing response before it is sent to the client.

The purpose of the Response Modification module is to do the latter, to allow existing responses to be modified on the fly using a set of replacement rules. To do this, we can leverage the ASP.NET response filtering mechanism, which, thanks to the runtime integration, allows filtering of the outgoing responses for any content, including static files and ASP and PHP scripts.

If you have developed an ASP.NET module in the past, you'll appreciate the fact that you continue to use the same exact APIs to develop ASP.NET modules for IIS 7.0. In fact, your existing modules continue to work the same way as they did in previous versions of IIS (unless you take advantage of IIS 7.0 Integrated mode to run them for all requests). The module is a class that implements the System.Web.IHttpModule interface and registers event handlers for one or more request pipeline events. The Response Modification module is no different:

class ResponseModificationModule : IHttpModule
{
  public void Init(HttpApplication app)
  {
    // Wire up the filter in PreRequestHandlerExecute
    app.PreRequestHandlerExecute +=
      new EventHandler(OnPreRequestHandlerExecute);
  }

  public void OnPreRequestHandlerExecute(
    Object source, EventArgs e)
  {
    ...
  }
}

At its core, the module subscribes to the PreRequestHandlerExecute event, which occurs right before the request handler is executed. The OnPreRequestHandlerExecute method is used to read configuration information to determine whether the response has any applicable replacement rules (more on configuration later in the article), and if so, register a response filter stream that will later be used to filter the outgoing response, as shown in Figure 2.

Figure 2 Response Filter (Close Figure 2)
public void OnPreRequestHandlerExecute(Object source, EventArgs e)
{
  HttpApplication app = (HttpApplication)source;
  HttpContext context = app.Context;

  // Read configuration
  ResponseModificationConfigurationSection config;

  ...

  // Get the list of filters
  ResponseFilterList filters = GetApplicableFilters(context, config);

  // Create the filter and wire it up
  if (null != filters && filters.Count > 0)
  {
    context.Response.Filter =
      new ChainedBufferedStringResponseFilter(
        context, filters);
  }
}

The response filtering work is done inside the ChainedBufferedStringResponseFilter class, which is responsible for converting the response bytes into a string using the response character set, and invoking one or more response filters that apply to this request. The module associates the filter with the response by setting the HttpResponse.Filter property. This property accepts objects that inherit from the System.IO.Stream abstract class and later uses them to filter the outgoing response entity body.

The ASP.NET response filtering facility is just one of the mechanisms that you can take advantage of when developing ASP.NET modules for IIS 7.0 Integrated mode. You can also rewrite URLs, perform request authentication and authorization, modify or issue cookies and response headers, log the request, and so on. Most of the tasks that previously required native ISAPI development are now possible by simply writing an ASP.NET module. You can find a step-by-step walkthrough of building ASP.NET modules for IIS 7.0, the development environment options, and deployment steps at mvolo.com/blogs/serverside/archive/ 2007/08/15/Developing-IIS7-web-server-features-with-the-.NET-framework.aspx.

When developing an IIS 7.0 module, you can take advantage of a number of IIS features that aid in management and deployment. One of these features is the ability to specify custom module configuration in the IIS 7.0 configuration files, and either have that configuration deployed automatically with the application, or managed with a number of IIS 7.0 administration tools and APIs. Doing this is key to ensuring that the functionality provided by the module can be properly configured and managed by end users.

Extending Configuration

The new configuration system is the foundation for many of the key deployment and management scenarios possible with IIS 7.0. Instead of being a proprietary machine-central configuration store, the IIS 7.0 configuration system is based on structured XML configuration files located in the same configuration files used by the ASP.NET configuration system. Moreover, the syntax of the IIS configuration information is identical to that of the ASP.NET configuration information and can be placed side-by-side in distributed web.config files that ASP.NET developers are already very familiar with and love.

This design opens many doors. First, it allows applications to specify IIS configuration information alongside their application content, which makes them easy to deploy by simply publishing their content to the server. This ensures that the applications work correctly without needing to modify machine-centric configuration information on each server on which they are deployed and also without requiring Administrative privileges on each server. The structured XML configuration format, plus the ability to put ASP.NET configuration together with IIS configuration in a single file, makes it a lot easier for developers to author and manage all IIS-related configuration information. This can be done with an XML editor such as Notepad and Visual Studio® or can be automated by using the configuration APIs provided by the IIS 7.0 administration stack.

The best part about all of this is the fact that the IIS 7.0 configuration system is completely extensible. Allowing custom Web server features to publish configuration that can be set and managed side-by-side with the IIS 7.0 configuration plays a key role in building complete solutions for IIS 7.0.

Unlike the .NET configuration system, adding a new IIS 7.0 configuration section requires no code because all IIS 7.0 configuration sections are themselves defined using an XML-based schema. In fact, all built-in IIS 7.0 configuration sections are defined using this mechanism. You can find the definitions for all IIS configuration sections in the %windir%\system32\inetsrv\config\schema directory in the IIS_Schema.xml file. For example, the <defaultDocument> configuration section, which enables and configures default documents for your application, is defined as follows:

<sectionSchema name="system.webServer/defaultDocument">
  <attribute name="enabled" type="bool" defaultValue="true" />
  <element name="files">
    <collection addElement="add" clearElement="clear"
      removeElement="remove" mergeAppend="false">
      <attribute name="value" type="string" isUniqueKey="true"/>
    </collection>
  </element>
</sectionSchema>

This schema defines all of the elements and attributes that are part of the configuration section, their types, and additional information such as collection definitions, and attribute validation behavior. To learn more about the syntax of the schema definition, see the comments at the top of the IIS_Schema.xml configuration file.

Response Modification Configuration

The Response Modification module requires its own configuration section in order to configure a number of specific settings, including what filtering rules to enable for the application. To allow this configuration section to be used in the IIS configuration files, you first need to create the schema file describing the structure of the section (see Figure 3). This file defines the responseModification section and its structure, including the enabled attribute and the collection of filter rules that can be configured to invoke filter types with additional information about what to replace and with what to replace it.

Figure 3 Response Modification Configuration Schema (Close Figure 3)
<configSchema>
  <sectionSchema name="responseModification">
    <attribute name="enabled" type="bool" defaultValue="false" />
    <collection addElement="add" clearElement="clear"
      removeElement="remove">
      <attribute name="name" type="string" required="true"
        isUniqueKey="true" validationType="nonEmptyString" />
      <attribute name="conditionType" type="string" required="false" />
      <attribute name="condition" type="string" required="false" />
      <attribute name="replaceType" type="string" required="true"
        validationType="nonEmptyString" />
      <attribute name="replace" type="string" required="false" />
      <attribute name="replaceWith" type="string" required="false" />
      <attribute name="options" type="string" required="false" />
    </collection>
  </sectionSchema>
</configSchema>

To install this configuration section, you need to do two things. First, copy the responsemod_schema.xml file containing the section schema information to the %windir%\system32\inetsrv\config\schema directory. Then declare the configuration section in the server's main configuration file, applicationHost.config. The latter task requires writing some code if you'd like to perform this installation programmatically.

To simplify the process of installing IIS 7.0 configuration sections, I wrote the iisschema.exe tool, which performs both of these tasks automatically. You can get the tool from mvolo.com/blogs/serverside/archive/2007/08/04/IISSCHEMA.EXE-_2D00_-A-tool-to-register-IIS7-configuration-sections.aspx. With this tool, installing the configuration schema section becomes a single-step process:

IisSchema.exe /install responsemod_schema.xml

Now that the configuration section schema is installed and the section itself is declared in applicationHost.config, you can immediately begin using it to define configuration information for the module.

You can use any of the IIS configuration tools or APIs to manage the configuration for this section, just like you would normally do for any of the built-in IIS 7.0 configuration sections. For example, I can enable the Response Modification feature for my application by using AppCmd, the IIS 7.0 command-line tool:

%windir%\system32\inetsrv\AppCmd Set Config "Default Web Site/"
/section:responseModification  /enabled:true

I can also add a rule to the rule collection of the responseModification configuration section:

%windir%\system32\inetsrv\AppCmd Set Config "Default Web Site/"
"/+[name='StripWhitespace',replaceType=
'Mvolo.ResponseModification.Filters.HtmlDeflate']"

If you then open the web.config file in the root of the default Web site (which is typically %windir%\inetpub\wwwroot), you will find the following code:

<configuration>
  <responseModification enabled="true">
    <add name="StripWhitespace"
      replaceType="Mvolo.ResponseModification.Filters.HtmlDeflate" />
  </responseModification>
</configuration>

If you ever modify the web.config file for the application to change the module's configuration information and then upload it to the server, the application will be configured to use the new settings. Likewise, if you upload the application to another server that has the configuration section installed, you can be sure that the application will use the desired settings without needing to reconfigure it on that server.

In addition to AppCmd, you can now use any of the IIS 7.0 configuration APIs to manage the configuration information for this section, including the managed Microsoft.Web.Administration API, the IIS 7.0 Windows Management Instrumentation (WMI) provider, the IIS 7.0 configuration COM objects, and Windows PowerShell®. In addition to setting and reading this configuration information, you can also perform any management task that is supported by the IIS 7.0 configuration system, including controlling how this section is delegated to applications, protecting its content with configuration encryption, and so on. However, in order for this configuration information to be useful, you need to be able to read it from the Response Modification module.

The Microsoft.Web.Administration API is a new .NET Framework API provided in IIS 7.0 that enables programs to access the IIS 7.0 configuration information. In addition to allowing configuration and setup programs to manage configuration, it is also the API that can be used to read configuration information from within a managed module.

You can use the API to read the configuration section in the module immediately after registering the configuration section because the API exposes a loosely typed model for reading configuration sections. Before you do this, you will need to add a reference to Microsoft.Web.Administration.dll, which is located in the %windir%\system32\inetsrv directory. Note that this DLL is only included with IIS 7.0 on Windows Vista® or Windows Server® 2008 and is not included with the .NET Framework or Windows SDKs.

With the assembly reference added, I can read the module's configuration information:

ConfigurationSection section =
  WebConfigurationManager.GetSection(
    context,
    "responseModification");

bool enabled = (bool)section["enabled"];

The WebConfigurationManager class in the Microsoft.Web.Administration namespace is almost identical to WebConfigurationManager in the System.Web.Configuration namespace and is intended to replace the use of the latter for IIS 7.0 managed modules that read IIS 7.0 configuration. The GetSection method retrieves the configuration section object for the current HTTP request path.

A Strongly Typed Configuration Class

The ConfigurationSection object allows loosely typed access to any configuration section without any additional code. You can access the enabled attribute simply by retrieving it by name from the section object's indexer and casting it to the expected type.

However, you can take it one step further by generating a strongly typed wrapper for the configuration section. You can quickly generate the wrapper class by using the tool written by Kanwaljeet Singla, a fellow IIS team member, located at blogs.iis.net/ ksingla/archive/2006/12/04/tool-to-generate-strongly-typed-classes-for-configuration-sections.aspx. This will generate a wrapper class for the section that looks like Figure 4. This class simply wraps the underlying ConfigurationSection class, and provides strongly typed properties to access the underlying configuration data.

Figure 4 Strongly Typed Configuration Section Wrapper (Close Figure 4)
class ResponseModificationSection : ConfigurationSection
{
  public bool Enabled
  {
    get { return (bool)this["enabled"]; }
    set { this["enabled"] = value; }
  }

  public ResponseModificationRuleCollection FilterRules
  {
    get
    {
      return (ResponseModificationRuleCollection)
        GetCollection(typeof(ResponseModificationRuleCollection));
    }
  }
}

As you can see from the implementation of the Enabled property, wrapping the configuration attributes is straightforward. Wrapping child elements and child collections requires further implementation of wrapper classes that inherit from ConfigurationElement and ConfigurationCollection respectively (not shown), in a similar fashion to the section wrapper itself.

After the wrapper class has been generated, you can compile it into a separate assembly and reference it in your module project or simply add it as a source code file. This will enable the module to access the configuration section in a type-safe way:

ResponseModificationSection section =
  (ResponseModificationSection)WebConfigurationManager.GetSection(
    context,
    "responseModification",
    typeof(ResponseModificationSection)
  );

bool enabled = section.Enabled;

Likewise, you can also begin to access the response modification rule collection and the properties on each of the rule elements in a type-safe manner. This makes it a lot easier to program against configuration sections and minimizes the amount of type safety mistakes that are common with weakly typed data access. Also, because all of the code used to access the underlying data is generated automatically by the tool, you can set up Visual Studio to automatically build the wrapper classes from the configuration schema file each time you compile the project. This makes managing changes to the configuration section schema a breeze.

Extending IIS Manager

So far, I have shown you how to create the Response Modification module and enable it to read the custom configuration section that controls its behavior. This configuration can be stored in the same configuration files as the rest of IIS configuration information, can be deployed together with the application in web.config files, and can be managed with a variety of tools and APIs including AppCmd.exe and Microsoft.Web.Administration.

At this point, the Response Modification module can be deployed and properly configured in any IIS environment. However, you can take it one step further by providing a custom management experience for the IIS Manager console. Doing this brings a number of benefits. It allows system administrators to easily configure the module by using the IIS Manager console, without needing to edit configuration files directly or use any of the lower-level tools and APIs. It also allows the module configuration information to be managed using IIS Manager remote administration capabilities, which take advantage of the Web Management Service (WmSvc).

Unlike other tools that also support remote administration, the IIS Manager remote administration architecture offers several key advantages. First, it allows users who are not Administrators on the server to manage sites and applications over which they have control. Second, the IIS Manager remoting mechanism uses HTTPS, instead of DCOM, which is easier to expose through corporate firewalls. Together, these two capabilities make the IIS Manager attractive for delegated remote management of IIS Web sites, especially in shared Web-hosting environments.

In the true spirit of IIS 7.0, the IIS Manager provides an extensible architecture on which most of the built-in IIS Manager features are based. To facilitate the remote management scenario, each management feature consists of two parts: the client-side components that provide the UI experience inside IIS Manager, and the server-side component that provides the actual management services.

The server-side service is loaded inside IIS Manager for local management scenarios or inside the Web Management Service in remote management scenarios. In the latter case, IIS Manager handles the required communication between the components in IIS Manager on the client machine and the service running inside WmSvc on the target server machine.

Creating the Service

The client and server components are typically implemented in two separate .NET assemblies that do not reference each other. All communication between the client and server components is done via the IIS Manager ModuleServiceProxy abstraction that uses weakly typed property bags and basic .NET types to exchange the required information.

The server assembly usually contains an implementation of the Microsoft.Web.Management.Server.ModuleProvider class, which performs the necessary registration of services and client-side Modules, and an implementation of the Microsoft.Web.Management.Server.ModuleService class, which exposes the service methods to perform the required management tasks.

My Mvolo.ResponseModificationUI.Server.dll assembly contains the ResponseModificationModuleProvider class that is responsible for registering the ResponseModificationModuleService service class in addition to the client ResponseModificationModule module class, as shown in Figure 5.

Figure 5 Module Provider Class (Close Figure 5)
public sealed class ResponseModificationModuleProvider :
  ConfigurationModuleProvider
{
  public override Type ServiceType
  {
    get { return typeof(ResponseModificationModuleService); }
  }

  public override ModuleDefinition
    GetModuleDefinition(IManagementContext context)
  {
    return new ModuleDefinition(Name,
      "Mvolo.ResponseModification.UI.Client.ResponseModificationModule,"+
      "Mvolo.ResponseModificationUI.Client," +
      "Version=1.0.0.0, Culture=neutral," +
      "PublicKeyToken=309ac0e1b5482072");
  }

  protected override string ConfigurationSectionName
  {
    get { return "responseModification"; }
  }
}

The ResponseModificationModuleService class derives from the ConfigurationModuleProvider class, which provides built-in support for controlling IIS Manager feature delegation based on the delegation state of the underlying configuration section. All you have to do is supply the configuration section name, responseModification, and IIS Manager automatically toggles the delegation state of the feature based on whether the section is locked or unlocked for any particular configuration path. The Microsoft.Web.Management framework provides several other ModuleProvider base classes that support various other delegation scenarios for the IIS Manager tool.

The GetModuleDefinition method returns the type of the IIS Manager module that will be instantiated on the client in order to register the required IIS Manager UI components such as the management page itself.

Note that the type of the client-side ResponseModificationModule class is a string, not a Type object, because the server assembly may not reference the client assembly that will be loaded in IIS Manager on a remote client machine. This is one side effect of the fact that the client and server components may be running in different processes and cannot share custom types.

The ResponseModificationModuleService class is responsible for implementing the management functionality and exposing the service methods that will be invoked by the management page in IIS Manager (see Figure 6).

Figure 6 Module Service Class (Close Figure 6)
public sealed class ResponseModificationModuleService : ModuleService
{
  [ModuleServiceMethod]
  public ArrayList GetFilterRules()
  {
    ArrayList items = new ArrayList();

    ResponseModificationSection section =
      (ResponseModificationSection)ManagementUnit.Configuration
        .GetSection("responseModification",
          typeof(ResponseModificationSection));

    foreach (ResponseModificationRuleElement rule in section.Rules)
    {
      items.Add(GetPropertyBag(rule));
    }

  return items;
  }
  ...
}

The GetFilterRules method is an example of several methods exposed by the service, which perform management tasks using the configuration system and possibly other resources that are local to the server. The service is always instantiated on the server, so it always acts on local resources and does not need to worry about accessing resources differently in the remote administration scenario. Note that I'm again using the Microsoft.Web.Administration API to access the responseModification configuration section, and am using the strongly typed ResponseModificationConfigurationSection wrapper created for the module to access the configuration.

The ManagementUnit property is the central entry point to the local server, exposing a number of other objects that allow access to the configuration system and other server-centric management objects that have been created by the Microsoft.Web.Management framework to assist with server management tasks. As a best practice, service implementations should always use the management unit for local configuration management tasks instead of creating their own configuration systems.

Methods like GetFilterRules can return information to the client by using basic types such as ArrayList to relay information to the client components. Again, you cannot communicate using strongly typed classes defined in the server or client assemblies because they are intended to be separate and should not have references to each other. ResponseModificationModuleService also defines additional service methods similar to GetFilterRules, including EditFilterRule, AddFilterRule, and DeleteFilterRule, to support the required management operations that may be requested by the Response Modification module page.

Creating the Module Page

The client components define the actual GUI management functionality provided to the user by the IIS Manager tool. The client assembly typically contains the implementation of the following classes:

  • The Microsoft.Web.Management.Client.Module class, which is responsible for registering any required IIS Manager pages and other UI extensions.
  • The Microsoft.Web.Management.Client.ModulePage class, which describes the actual management page displayed by IIS Manager.
  • The Microsoft.Web.Management.Client.ModuleServiceProxy class, which provides the client components with a local proxy class for invoking the service methods exposed by the ModuleService.

The Mvolo.ResponseModificationUI.Server.dll assembly contains the ResponseModificationModule class shown in Figure 7 (not to be confused with the actual IHttpModule class that provides the module services in the IIS request processing pipeline), which provides the main entry point for registering all of the needed UI components in IIS Manager. This class registers the ResponseModificationModulePage class, which describes the actual management page.

Figure 7 Module Class (Close Figure 7)
public sealed class ResponseModificationModule : Module
{
  protected override void Initialize(IServiceProvider serviceProvider,
    ModuleInfo moduleInfo)
  {
    base.Initialize(serviceProvider, moduleInfo);

    IControlPanel controlPanel =
      (IControlPanel)GetService(typeof(IControlPanel));

    controlPanel.RegisterPage(
      new ModulePageInfo(this,
      typeof(ResponseModificationModulePage),
      Resources.PageTitle,
      Resources.PageDescription,
      null,
      null,
      Resources.PageText
      ));
  }
}

Each Module implementation is created once per the lifetime of a management connection made with IIS Manager, and it is responsible for registering all of the necessary management pages and UI elements for the connection. ResponseModificationModule registers the ResponseModificationModulePage class, which defines the contents of the management page.

To be honest, I am not a big fan of GUI programming, and I much prefer to write low-level server code that does not require interface development. Lucky for me, the Microsoft.Web.Management framework provides a number of base classes and supporting classes for implementing common-looking management pages, including the Microsoft.Web.Management.Client.Win32.ModuleListPage class that I used for ResponseModificationModulePage.

This class provides a built-in List view for viewing and managing lists of items, including sorting the list and interacting with list items. With a minimum amount of customization, I was able to create a management page containing a list of response modification rules (see the center panel of Figure 8).

Figure 8 Response Modification Page in IIS Manager
Figure 8 Response Modification Page in IIS Manager(Click the image for a smaller view)
Figure 8 Response Modification Page in IIS Manager
Figure 8 Response Modification Page in IIS Manager (Click the image for a larger view)

Next, I created the list of actions, displayed in the right panel, for allowing the response modification rules to be added, edited, and removed. I took advantage of the Microsoft.Web.Management.Client.TaskList class to create this task list (shown in the right panel in Figure 8).

With just a little more work, the Response Modification feature shown in Figure 8 was born. You can review the details of the UI in the source code for the Response Modification framework.

The initial page creation, and the add, edit, and delete operations triggered from the action panel are performed by invoking methods on the ResponseModificationModuleServiceProxy class, which provides a local proxy to the module methods exposed by the module service:

public sealed class ResponseModificationModuleServiceProxy :
  ModuleServiceProxy
{
  public ArrayList GetFilterRules()
  {
    return (ArrayList)Invoke("GetFilterRules");
  }

  public void AddFilterRule(PropertyBag bag)
  {
    Invoke("AddFilterRule", bag);
  }

  ...
}

IIS Manager automatically relays calls made to the module service proxy class to the associated module service over the communication channel, which may be a local shared memory channel or an https channel to a remote server.

Deploying the Extension

To deploy the IIS Manager extension, note that you have to register the module provider type in the IIS Manager configuration file on the server (%windir%\system32\inetsrv\config\administration.config). The configuration shown in Figure 9 registers the module provider and enables it to be used for all sites by default. Note that both the client and server assemblies that comprise the extension, Mvolo.ResponseModificationUI.Server.dll and Mvolo.ResponseModificationUI.Client.dll, must be registered in the Global Assembly Cache (GAC) and therefore must be strong-named and signed. This is a requirement for being able to install IIS Manager extensions.

Figure 9 IIS Manager Extension Configuration (Close Figure 9)
<moduleProviders>
  ...

  <!-- Mvolo ResponseModification -->
  <add name="ResponseModification"
    type="Mvolo.ResponseModification.UI.Server
        .ResponseModificationModuleProvider,
      Mvolo.ResponseModificationUI.Server,
      Version=1.0.0.0, Culture=neutral,
      PublicKeyToken=895e90205a14f2aa,
      processorArchitecture=MSIL" />
</moduleProviders>

<!-- For all Sites -->
<location path=".">
  <modules>
    ...
    <add name="ResponseModification" />
  </modules>
</location>

When IIS Manager is used to manage the server remotely, the tool will automatically prompt the user to download the client assembly whenever it is not present on the client already (this currently only works when managing IIS 7.0 on Windows Server 2008 from a Windows Vista SP1, Windows XP, or Windows Server 2003 client by using the remote IIS Manager available online from iis.net/ downloads/?tabid=34&g=6&i=1524). Be sure that you trust both the server you are connected to and the publisher of the IIS Manager extension before accepting this installation, as otherwise it may possibly allow untrusted code to execute on your machine with your user account privileges.

Tying It All Together

After deploying the module and installing the configuration section schema and the IIS Manager extension, the Response Modification feature is ready to use. You can now enable the feature for any of the applications on your server and configure it with any of the IIS 7.0 configuration tools and APIs.

In addition, you can use the IIS Manager tool to quickly manage the response modification rules for applications, both on the local server or on a remote server. Using the IIS Manager delegation support, you can even configure which users have the ability to remotely create and manage the response modification rules for specific sites on a server.

To demonstrate the Response Modification feature in action, Figure 10 shows how I configured rules for my Qdig image gallery application (featured in msdn.microsoft.com/msdnmag/issues/08/01/PHPandIIS7). Figure 11 shows how I use the Mvolo.ResponseModification.Filters.RegExReplace filter provided with the Response Modification framework to intelligently insert header and footer content into all pages in the Web site, without modifying the actual source code of the application.

Figure 10 Enabling Headers and Footers (Close Figure 10)
<responseModification enabled="true">
  <add name="Header"
    conditionType=
      "Mvolo.ResponseModification.Conditions.ContentTypeCondition"
    condition="text/html"
    replaceType="Mvolo.ResponseModification.Filters.RegExReplace"
    replace="&lt;body([^>]*)>"
    replaceWith="&lt;body$1>&lt;h1 align=&quot;center&quot;>
      Welcome to my gallery!&lt;/h1>"
    options="IgnoreCase" />

  <add name="Footer"
    conditionType=
      "Mvolo.ResponseModification.Conditions.ContentTypeCondition"
    condition="text/html"
    replaceType="Mvolo.ResponseModification.Filters.RegExReplace"
    replace="&lt;/body>"
    replaceWith="&lt;h3 align=&quot;center&quot;>
      Copyright mvolo 2007&lt;/h3>&lt;/body>"
    options="IgnoreCase"  />
 </responseModification>
Figure 11 Using Response Modification to Modify Responses
Figure 11 Using Response Modification to Modify Responses

The Response Modification module can also be used to easily implement any number of custom content insertions, hyperlink substitutions, and other response modification schemes by implementing replacement filters. These filters can then be deployed globally, or with an application, and configured via configuration rules in the responseModification configuration section.

Build Your Own

There are quite a few creative uses for the Response Modification feature whenever it is not possible to achieve the same results by modifying the source code of an application (which is always more efficient than modifying the response on the fly). For example, I also use it to inject a small script into HTML responses that changes image tags to use highly compressed versions of images and then replaces them with higher-quality versions on the fly to provide for a more responsive browsing experience for slow clients. This also uses a custom imaging handler that can provide JPEG images with any desired compression level for a static image using the Microsoft Windows Imaging Component APIs. Figure 11 shows the result in action, including the header, footer, and the low-quality preloaded image (right before it is automatically replaced with the higher resolution version).

Modifying responses on the fly can be an expensive mechanism, both in terms of CPU usage and memory requirements as multiple large buffers must be allocated to store transformed versions of the responses. This overhead may or may not be acceptable in production Web sites, depending on the types of response modification filters used, the number of them, and the performance requirements of the application.

In order to learn more about using the Response Modification framework, you should download it, along with its source code, from the Response Modification development site (www.mvolo.com/responsemod). After you understand how response filters work and can plan for using them given performance overhead considerations, look for further samples on the Response Modification development site. To learn more about extending IIS 7.0, be sure to visit iis.net and my blog (mvolo.com), which is dedicated to developing and using IIS 7.0.

Mike Volodarsky is a Technical Program Manager on the Web Platform and Tools Team at Microsoft. Over the past four years, he has driven the design and development of the core feature-set for ASP.NET 2.0 and IIS 7.0. He is now focusing on helping customers leverage the power of these technologies in Windows Server 2008.


VSTO 3.0: Developing Office Business Apps with Visual Studio 2008

VSTO 3.0

Developing Office Business Apps with Visual Studio 2008

Steve Fox
This article discusses:
  • New features in Visual Studio 2008
  • Creating custom form regions for Outlook
  • Connecting to a data source
  • Adding search capabilities
This article uses the following technologies:
Visual Studio 2008
Code download available at: CustomOutlookRegion2008_Launch.exe (4168KB)
Contents
Extend Outlook 2007 with Custom Form Regions
Building Your First Custom Form Region
Show Sales Data and Customer Search
Connecting the Data Source
The Customer Data Search Form
Adding the WPF Sales User Control
Adding the Final Piece
Building and Running the Custom Replacement Form Region
Further Reading

B
y now, I'm sure you've heard quite a few appealing things about Visual Studio® 2008. It has some awesome features, such as support for LINQ, improved Web development, and tighter integration with Windows Vista® and SharePoint®, to name just a few. One area where Visual Studio 2008 really stands out, though, is in its support for Microsoft® Office solutions development.

Visual Studio Team System 2005 included Visual Studio 2005 Tools for Office (VSTO)—you could also download VSTO Second Edition to begin solution development for both Office 2003 applications and the 2007 Microsoft Office system. However, in Visual Studio 2008, all the VSTO project templates are included.

You can use Visual Studio 2008, specifically VSTO 3.0, to develop Microsoft Outlook® customizations, which is what I'll cover in this article. I'll show you what you can do with a new type of form development technology called form regions, which can be special areas on existing Outlook forms. I'll also show you how to add Windows® Presentation Foundation (WPF) support to form regions to help improve their look, feel, and interactivity.

Extend Outlook 2007 with Custom Form Regions

VSTO 3.0 provides a Windows Forms-based design environment in Visual Studio 2008. This allows you to design and code the new Outlook form regions in one development environment and brings many advantages of Windows Forms to the hosting environment of Outlook. For example, you can create a VSTO form region that connects to a Web service to acquire customer relationship management (CRM) data and display it in a grid. You can even add a custom Ribbon to an Outlook 2007 inspector that will host the custom form region and add code to the Ribbon so that it interacts with the controls on the region.

Note that there are four types of Outlook 2007 form regions at your disposal:

Adjoining These appear at the bottom of the target Outlook form on the first Outlook form page and/or at the bottom of the Outlook reading pane.

Separate These add a new page to the form.

Replacement These cause the form region to replace all of the content on the first form page of the Outlook form (all existing form pages are left in place).

Replace All These cause the form region to replace all of the content on the first form page of the Outlook form, and they remove all other form pages from the form.

Each one of these form regions allows you to extend Outlook in interesting ways, but here I'll focus on the Replacement form region. The Replacement form region can be used to create a custom form for a custom message class. Unlike the Adjoining and Separate form regions that are created for existing Outlook message classes—for example, Mail Message (IPM.Note), Contact (IPM.Contact), and Appointment (IPM.Appointment)—when selecting the Replacement (or Replace All) form region, you create a custom message class that derives from one of the existing Outlook message classes. For example, in the sample, I'll walk through a custom message class called CustomersSalesData that derives from the Mail Message type; thus, what results is a custom message class called IPM.Note.RecentSales.

Building Your First Custom Form Region

To create this custom Replacement form region, I used Visual Studio 2008 to create an Outlook 2007 Add-in project (see Figure 1). I named the project CustomOFR and used the default project location. At this point, Visual Studio creates an Outlook 2007 Add-in project shell for you. This means that when you build and deploy your add-in (which will contain your custom Outlook form region), Visual Studio will automatically load your add-in when Outlook loads.

Figure 1 Selecting Outlook Add-In Project Template
Figure 1 Selecting Outlook Add-In Project Template(Click the image for a smaller view)
Figure 1 Selecting Outlook Add-In Project Template
Figure 1 Selecting Outlook Add-In Project Template (Click the image for a larger view)

With your project shell now created, let's go ahead and add a custom Outlook form region to it. Select Outook Form Region from the Add New Item Dialog and name the region SalesData. This invokes the New Outlook Form Region Wizard, which involves a number of steps to create a Replacement form region. In the first step you indicate whether you're designing a new form region or importing an existing one (.ofs file). In this sample, you want to create a new form region, so go ahead and click Next. Then you'll be asked to select the type of form region you want to create. Select the Replacement type and then click Next. At this point, you'll set some properties for your form region; specifically a name, title, description, and the modes in which you want to display the form region (see Figure 2). After you've entered this information, click Next.

Figure 2 Custom Form Region Properties
Figure 2 Custom Form Region Properties(Click the image for a smaller view)
Figure 2 Custom Form Region Properties
Figure 2 Custom Form Region Properties (Click the image for a larger view)

In the final step, you'll associate your form region with a specific message class. As I mentioned earlier, because this is the Replacement form region you must create a custom message class instead of using the stock message classes that ship with Outlook. You'll notice that in this case, this step has all of the stock message class options grayed out, and the only field available for this step is the custom message class textbox at the bottom of the dialog (see Figure 3). You'll see that in this field I've entered IPM.Note.CustomerSalesData, which means that I'm going to derive my custom message class from the Outlook IPM.Note message class type (or essentially create a custom message class type that inherits all of the same functionality in the stock Mail Message class).

Figure 3 Entering Custom Message Class Type
Figure 3 Entering Custom Message Class Type(Click the image for a smaller view)
Figure 3 Entering Custom Message Class Type
Figure 3 Entering Custom Message Class Type (Click the image for a larger view)

All form regions target one or more specific message classes in the Outlook environment. These message classes are directly related to the Outlook item type. There are eight standard message classes (see Figure 3) and, theoretically, an infinite number of custom message classes. You could similarly derive your custom class from other custom class types. After you've entered your custom message class derivation and name, click Finish. The name will be validated when you click the Finish button.

When you've completed the steps in the wizard, a form region will be added to the Outlook add-in project. In my case, the following three items were added to the project:

RecentSales.cs The primary class file for the form region.

RecentSales.Designer.cs The designer file that contains some default code for the form region.

RecentSales.rexs The XML file that is required to display the form region.

Now let's begin customization. After creating the form region, the default view in Visual Studio should be the designer mode of the form region. If not, right-click on RecentSales.cs and select View Designer. You can now begin to add controls to your form.

To keep things simple, I'm going to add three primary things to my custom Replacement form region: a search control that lists customer information, a WPF control that displays updated sales data, and a checkbox for controlling whether the user control is displayed or hidden. Figure 4 illustrates the controls that I've added to my project in the context of my Visual Studio IDE.

Figure 4 Form Region Designer
Figure 4 Form Region Designer(Click the image for a smaller view)
Figure 4 Form Region Designer
Figure 4 Form Region Designer (Click the image for a larger view)

Show Sales Data and Customer Search

The form region represents the design surface and supports the dragging and dropping of controls from the Toolbox to the design surface. So drag a checkbox to the design surface and set its Name property to showChart and its Text property to "Show Sales Data." Then double-click the checkbox control and add the following code to call the UpdateChart method—this method will update both the data and the visibility of the WPF sales chart:

private void checkBox1_CheckedChanged(object sender, EventArgs e)
  {
    UpdateChart();
  }

I'll return to this method a little later on in the article.

The Sales Search user control allows you to enter some search criteria and browse a small database of customer information that is dynamically updated within the control. To enable this search functionality, I've added a new project called SalesControlLibrary (a Windows Forms Control Library project) to the CustomOFR solution and then added the search control from the Toolbox onto the form region. There are two main parts to the SalesControlLibrary project in my solution: a connection to the data source and the user control that I'll use in the custom Replacement form region.

Connecting the Data Source

With the new project added to the solution, you can now add a data source by clicking Data and Add New Data Source. As you might expect, this sets off the Add New Data Source Wizard, which first prompts you to define and configure a simple connection to a data source (database). For this sample I'm using an Access database called Sales.mdb (which is included in the code download) with the following four schema elements (as you can see in Figure 5): CustomerID is the unique ID for a customer, SalesQuarter represents the fiscal quarter for sales, SalesYear represents the fiscal year in which sales were recorded, and SalesAmount is the dollar amount of a sale.

Figure 5 Customer and Sales Data
Figure 5 Customer and Sales Data(Click the image for a smaller view)
Figure 5 Customer and Sales Data
Figure 5 Customer and Sales Data (Click the image for a larger view)

If you were to integrate this add-in with a line-of-business (LOB) system, thus classifying this application as an Office Business Application (OBA), then you'd likely use a Windows Communication Foundation (WCF) service proxy to manage the connection and communication with your LOB system. Using services as proxies to LOB systems is a great way to circumnavigate difficult system interfaces or to pull specific business data directly into the context of your customer's common office applications.

When you think of specific value added when organizations build OBAs, certainly one of the major benefits is the ability to leverage the Office UI to integrate LOB data. This also offloads some of what would otherwise be custom development work to native Office features. And perhaps most importantly, it keeps end users in an environment that is comfortable and familiar (think native formatting and visualization of data in Excel® 2007). To see more information on the general area of OBAs, go online to msdn2.microsoft.com/aa905528.

The Customer Data Search Form

With the data source added to the SalesControlLibrary project, you can now design the component to search customer data. In the designer for my sales search form, I have added a number of controls to the project including some labels, fields, a data grid that is connected to the Sales database, and some fields that display the customer contact information that corresponds to the selected item in the data grid. Figure 6 shows the completed form with all of the controls added. Note that while most of these controls are straightforward to add, with the data-grid view, you'll need to first open the Data Sources pane, right-click the Sales data source and select Details, and then drag and drop the Sales records onto the data-grid view. Then right-click the control, select Edit Columns, and remove the SalesYear and SalesAmount, as you're only interested in showing the CustomerID and SalesQuarter in the data-grid view.

Figure 6 Building the Customer Data User Control
Figure 6 Building the Customer Data User Control(Click the image for a smaller view)
Figure 6 Building the Customer Data User Control
Figure 6 Building the Customer Data User Control (Click the image for a larger view)

At this point, you've got the new controls added to your form and are ready to add some code to the controls. So, let's take a look at some of the key code that was added to this form. The first things I'll look at are the declarations that I've added to the SalesSearch class. Specifically, I declared a table adapter for Company and Sales, and I created an event handler for any sales data changes:

namespace SalesControlLibrary
{
  public partial class SalesSearch : UserControl
  {
    private SalesDataSetTableAdapters.CompaniesTableAdapter
      companiesAdapter;
    private SalesDataSetTableAdapters.SalesTableAdapter
      salesAdapter;
    public event EventHandler<SalesSearchEventArgs>
      SalesDataChangeEvent;

    ...
  {
}

The primary event being triggered is the executeSearch_click event that fires when the Search button is clicked. It's found in the SalesSearch class, as you see in Figure 7.

Figure 7 Execute Search Triggered from Search Button (Close Figure 7)
private void executeSearch_Click(object sender, EventArgs e)
{
  try
  {
    companiesAdapter = new
      SalesDataSetTableAdapters.CompaniesTableAdapter();
    CompaniesBindingSource.DataSource =
      companiesAdapter.GetDataByCompanyAndIndustry(
        companyNameText.Text,
        industryText.Text);
  }
  catch (Exception ex)
  {
    MessageBox.Show(ex.Message, "Sales Add-in", MessageBoxButtons.OK);
  }
}

You can see that this creates a new instance of the companiesAdapter and calls the GetDataByCompanyAndIndustry method to retrieve data according to the parameters that the user entered in the search fields (the percent sign in Figure 8 indicates a wildcard search). This data will then be used to populate the Contact and Address fields to display customer information.

Figure 8 Handling BindingSource Change (Close Figure 8)
private void CompaniesBindingSource_CurrentChanged(object sender,
  EventArgs e)
{
  DataRowView company = (DataRowView)(CompaniesBindingSource.Current);
  try
  {
    string[] xData = new string[4];
    double[] yData = new double[4];
    if (company != null)
    {
      salesAdapter = new SalesDataSetTableAdapters.SalesTableAdapter();
      SalesDataSet.SalesDataTable sales =
        salesAdapter.GetDataByCustomerID((int)(company["ID"]));
      DataTableReader rdr = sales.CreateDataReader();
      int i = 0;
      while ((rdr.Read()) && (i < xData.Length) && (i < yData.Length))
      {
        xData[i] = String.Format("Q{0} {1}",
        rdr["SalesQuarter"].ToString(), rdr["SalesYear"].ToString());
        yData[i] = Convert.ToDouble(rdr["SalesAmount"]);
        i++;
      }
      OnSalesDataChanged(company["CompanyName"].ToString(),
        xData, yData);
    }
  }
  catch (Exception ex)
  {
  MessageBox.Show(ex.Message, "Sales Add-in", MessageBoxButtons.OK);
  }
}

In the code behind this form, there are also a couple of helper functions. For example, I've added an event on the CompaniesBindingSource object to handle the case where a user is browsing through the sales data from within the controls (see Figure 8).

Also, I've added an event handler to process any changes to sales data. For example, the following code shows the OnSalesDataChanged method that is invoked if there has been a change in the selection of the current record in the binding source:

protected virtual void OnSalesDataChanged(string companyName,
  string[] x, double[] y)
{
  if (SalesDataChangeEvent != null)
    SalesDataChangeEvent(this,
      new SalesSearchEventArgs(companyName, x, y));
}

With the selection change, the OnSalesDataChanged event has parameters for the company name, the year, and the sales for the current record.

Adding the WPF Sales User Control

Now let's look at how to create the WPF control and add it to the custom Outlook form region. Two steps are required to add a WPF user control to the custom Outlook Replacement form region. The first is to create the WPF user control, and the second is to add it to the Replacement form region (that is the RecentSales form region created earlier).

To create the form, add a new WPF User Control Library project to the solution. Visual Studio will create a default control and display the XAML editor. At this point, you can design a WPF chart control and add event handlers. Figure 9 shows the WPF control in the Visual Studio 2008 XAML designer.

Figure 9 WPF Chart Control
Figure 9 WPF Chart Control(Click the image for a smaller view)
Figure 9 WPF Chart Control
Figure 9 WPF Chart Control (Click the image for a larger view)

Since you've added a new project, you'll have some default XAML code; however, you won't have anything in the designer other than an empty container. Thus, you need to add some XAML code here to create the chart. To help you along, Figure 10 shows the XAML code I used to create the small WPF sales chart for the form region. The code is fairly straightforward in that it creates a small control with two columns and five rows and puts placeholder text on the chart. This text will be updated with data from the Sales database at run time. Note that the XAML is associated with the SalesChart class as declared in the opening line of XAML code.

Figure 10 XAML Code (Close Figure 10)
<UserControl x:Class="WpfChart.SalesChart"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Height="300" Width="300">
  <Grid Name="Grid1">
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="80"/>
      <ColumnDefinition Width="190"/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
      <RowDefinition Height="30"/>
      <RowDefinition Height="30"/>
      <RowDefinition Height="30"/>
      <RowDefinition Height="30"/>
      <RowDefinition Height="30"/>
    </Grid.RowDefinitions>
    <TextBlock Name="Category1" Text="X1" Grid.Column="0"
      Grid.Row="0" VerticalAlignment="Center"
      HorizontalAlignment="Center"/>
    <TextBlock Name="Category2" Text="X2" Grid.Column="0"
      Grid.Row="1" VerticalAlignment="Center"
      HorizontalAlignment="Center"/>
    <TextBlock Name="Category3" Text="X3" Grid.Column="0"
      Grid.Row="2" VerticalAlignment="Center"
      HorizontalAlignment="Center"/>
    <TextBlock Name="Category4" Text="X4" Grid.Column="0"
      Grid.Row="3" VerticalAlignment="Center"
      HorizontalAlignment="Center"/>
    <TextBlock Name="Title" FontSize="14" FontWeight="Bold"
       Text="Chart Title" Grid.Column="1" Grid.Row="4"
      VerticalAlignment="Center" HorizontalAlignment="Right"/>
    <Rectangle Name="Bar1"  Fill="LightSteelBlue" RadiusX="5"
      RadiusY="5" HorizontalAlignment="Left" Grid.Column="1"
      Grid.Row="0" Width="170" Height="18"/>
    <Rectangle Name="Bar2"  Fill=" LightSteelBlue" RadiusX="5"
      RadiusY="5" HorizontalAlignment="Left" Grid.Column="1"
      Grid.Row="1" Width="170" Height="18"/>
    <Rectangle Name="Bar3"  Fill=" LightSteelBlue" RadiusX="5"
      RadiusY="5" HorizontalAlignment="Left" Grid.Column="1"
      Grid.Row="2" Width="170" Height="18"/>
    <Rectangle Name="Bar4"  Fill=" LightSteelBlue" RadiusX="5"
      RadiusY="5" HorizontalAlignment="Left" Grid.Column="1"
      Grid.Row="3" Width="170" Height="18"/>
    <TextBlock Name="Label1" Text="$Y1" Grid.Column="1"
      Grid.Row="0" VerticalAlignment="Center"
      HorizontalAlignment="Right"/>
    <TextBlock Name="Label2" Text="$Y2" Grid.Column="1"
      Grid.Row="1" VerticalAlignment="Center"
      HorizontalAlignment="Right"/>
    <TextBlock Name="Label3" Text="$Y3" Grid.Column="1"
      Grid.Row="2" VerticalAlignment="Center"
      HorizontalAlignment="Right"/>
    <TextBlock Name="Label4" Text="$Y4" Grid.Column="1"
      Grid.Row="3" VerticalAlignment="Center"
      HorizontalAlignment="Right"/>
  </Grid>
</UserControl>

With the WPF chart created, the last step in the process is to ensure you create methods to update the chart in response to data changes. In the SalesChart class, the UpdateWPFChart method essentially takes a chart title (company name) and two string arrays representing sales data as parameters, and it scales the chart according to this data and updates the chart's text and bar indicators, as shown in Figure 11.

Figure 11 Code to Update Chart (Close Figure 11)
public void UpdateWPFChart(string chartTitle,
  string[] xVals, double[] yVals)
{
  double maxY = 0;
  foreach (double y in yVals)
  {
    if (y > maxY)
      maxY = y;
  }
  try
  {
    for (int i = 0; i < 4; i++)
    {
      if ((i >= xVals.Length) || (i >= yVals.Length))
        break;
      else
      {
        TextBlock xText = (TextBlock)(FindName(
          String.Format("Category{0}", i + 1)));
        if (xText != null)
          xText.Text = xVals[i];
        TextBlock yText = (TextBlock)(FindName(
          String.Format("Label{0}", i + 1)));
        if (yText != null)
          yText.Text = yVals[i].ToString();
        Rectangle bar = (Rectangle)(FindName(
          String.Format("Bar{0}", i + 1)));
        bar.Width = (yVals[i] / maxY) *
          Grid1.ColumnDefinitions[1].Width.Value;
      }
    }
    Title.Text = chartTitle;
  }
  catch (Exception ex)
  {
    MessageBox.Show(ex.Message, "SalesChart", MessageBoxButton.OK);
  }
}

At this point, all of the controls for the form region are finished, so you can now return to the Outlook Replacement form region designer, where we will add the controls just created and wire them up to our custom form region.

Adding the Final Piece

The last step is to add the controls to the form region and then connect the code in the custom Outlook Replacement form region to our SalesSearch control and WPF chart control. To add the SalesSearch control and the WPF control to the form region, build all of the components in your project—this action will add the controls to your toolbox so that you can drag them onto the designer surface. Drag both the SalesSearch control and the WPF chart control to where you want them on the designer surface of your form region. With the controls in place, I can now create the code that will connect these controls with the form region and with each other.

First, in the RecentSales class I've added some important variable declarations representing the sales (year and amount of sales) and company data:

partial class RecentSales
{
  string[] currentXData;
  double[] currentYData;
  string currentCompany;
  ...
}

Second, in the RecentSales_FormRegionShowing method (which essentially invokes whatever code you add to it when Outlook loads the custom form region) I've set the visibility of the WPF sales chart to hidden by default when Outlook loads the form region:

private void RecentSales_FormRegionShowing(object sender,
  System.EventArgs e)
{
  salesChart1.Visibility = System.Windows.Visibility.Hidden;
}

Next, the UpdateChart method (remember this method from above) uses the sales and company data to call the UpdateWPFChart method and update the data that's presented in the sales chart—the WPF-based bars that indicate sales amounts. Note that we've also got an event handler, which responds to the click events from the checkbox and calls the UpdateChart method. Figure 12 shows the code for both the UpdateChart method and the checkbox's CheckChanged event.

Figure 12 UpdateChart Method and CheckChanged Event (Close Figure 12)
private void UpdateChart()
{
  if (showChart.Checked)
  {
    if ((currentXData != null) && (currentYData != null))
    {
      salesChart1.UpdateWPFChart(
      currentCompany, currentXData, currentYData);
      salesChart1.Visibility = System.Windows.Visibility.Visible;
    }
    else
    {
      salesChart1.Visibility = System.Windows.Visibility.Hidden;
    }
  }
  else
  {
    salesChart1.Visibility = System.Windows.Visibility.Hidden;
  }
}

private void checkBox1_CheckedChanged(object sender, EventArgs e)
{
  UpdateChart();
}

Lastly, I have a method to handle data changed events on the SalesSearch control. My method updates the sales and company data and then it calls UpdateChart:

private void salesSearch1_SalesDataChangeEvent(object sender,
  SalesControlLibrary.SalesSearchEventArgs e)
{
  currentCompany = e.CompanyName;
  currentXData = e.XValues;
  currentYData = e.YValues;
  UpdateChart();
}

Building and Running the Custom Replacement Form Region

With the code now in place, let's build and run the project. To build and run Outlook add-ins, you'll need to close all running instances of Outlook. If you open the Designer view for the custom form region, you should see something similar to Figure 13. You can now build and run the project. Visual Studio will build the solution and, in doing so, launch Outlook and run the add-in assembly.

Figure 13 Final Custom Replacement Form Region
Figure 13 Final Custom Replacement Form Region(Click the image for a smaller view)
Figure 13 Final Custom Replacement Form Region
Figure 13 Final Custom Replacement Form Region (Click the image for a larger view)

To navigate to the custom Outlook Replacement form region, you click File, New, and Choose Forms. This will open the Choose Forms dialog that lists all of the custom Replacement forms that are installed on that client. In this case, you'll see that the Recent Sales form is the only one available in the custom Replacement form region library. To open the form region, select it and click Open.

At this point, Outlook will open the form, and what you should see is something similar to Figure 14. To test out your form region, click the Search button. It should load all of the data within your database (or in the case of the example above, the Sales.mdb database) into the data grid. You can then click the Show Sales Data checkbox to display the sales data for the selected company. Note that you can either select the company directly in the data grid or you can use the navigation strip along the bottom of the SalesSearch control to browse back and forth across the data. Each time you move across the data, the sales chart is automatically updated. Cool stuff!

Figure 14 Final Custom Outlook Replacement Form Region (with WPF Chart Visible)
Figure 14 Final Custom Outlook Replacement Form Region (with WPF Chart Visible)(Click the image for a smaller view)
Figure 14 Final Custom Outlook Replacement Form Region (with WPF Chart Visible)
Figure 14 Final Custom Outlook Replacement Form Region (with WPF Chart Visible) (Click the image for a larger view)

Further Reading

You have available to you all of the code that goes along with this article, so feel free to experiment as you see fit. Further, we have created an online hands-on lab that walks you through an application similar to the one described in this article if you want some prescriptive guidance on how to build Outlook form regions. You can find this hands-on lab on the VSTO Developer Portal at msdn2.microsoft.com/aa905533. I also encourage you to check out OBA Central (obacentral.com), a Web portal dedicated to showcasing and educating you on all things OBA. Further, you might also pick up my new book, Programming Microsoft Office Business Applications (Microsoft Press®, 2008), which provides in-depth information on how to develop and deploy OBAs. OBA is an exciting area in Office development that you will surely hear more about in the future.

As a final note, I'd like to thank Lori Turner (who helped with the coding and the online labs), Mike Morton, and Andrew Whitechapel for reviewing this article.

Steve Fox is a Program Manager with the Visual Studio Tools for Office (VSTO) team at Microsoft. He divides his time working externally with VSTO customers, providing VSTO training, delivering VSTO sessions at conferences, and working internally with the development team to help evolve the VSTO product.


Unit Testing: Apply Test-Driven Development to your Database Projects

Unit Testing

Apply Test-Driven Development to your Database Projects

Jamie Laflen
This article discusses:
  • The benefits of TDD
  • Bringing unit testing to your database development
  • Combining T-SQL and .NET–compliant languages
  • Connections, test conditions, and transactions
This article uses the following technologies:
Visual Studio 2008, SQL Server
Code download available at: DatabaseUnitTesting2008_Launch.exe (174KB)
Contents
Unit Testing in Software Development
Database Unit Testing
Defining Connections
Managing T-SQL Scripts
Modifying Generated Test Code
Transactions
System.Transactions
Data-Driven Testing
Custom Test Conditions
Defining the Test Condition
Modifying Test Execution
Looking Forward

L
n November 2006 Microsoft released Visual Studio® Team System Database Edition, also known as DBPro or Data Dude, which introduced a product lifecycle methodology to database development. DBPro also introduced a database unit-testing designer that makes it easy to either generate or write T-SQL-oriented unit tests to verify database objects prior to deployment. In this article, I will dig into database unit testing, explain how the feature was implemented, and show you how to perform unit testing of your own database projects.

Unit Testing in Software Development

In software development, unit testing plays a critical role in ensuring that the proper level of quality is maintained and that milestones are met. Although most developers agree that unit tests are important, most don't take the time to write the unit tests at the end of a product cycle. To address this issue, the test-driven development (TDD) methodology was conceived. Using this methodology, developers write unit tests for individual features before actually implementing the feature. This process allows the developer to see the API from the outside-in, thus providing the opportunity for the simplest design possible.

Today, many developers working in C# and Visual Basic® follow TDD by creating a unit test to exercise a method on a class that has not yet been written and then implement the bare minimum amount of code needed to compile the test. Once the test has compiled and failed, the developer writes only the code necessary to make the test pass in the next run. With each iteration, all unit tests are run to verify that there are no regression errors. The application and unit-test code are then checked into source code control. Each unit test is run at least twice for the method being developed.

To be useful, a unit test must set up its execution environment before it is run so that it can consistently verify the code it is meant to test. Once the test has been implemented and verified, it is checked into source code control and will be used for the rest of the product's lifetime to ensure that the method continues to behave as expected.

TDD is more challenging in database development because you need to provide a consistent test environment to verify code prior to checking in changes. For a database unit test to run successfully it must have the appropriate schema as well as the proper data. This means that in order to run tests, other developers must duplicate the environment you used when the unit tests were created. This is a tall order to fill without building a lot of costly infrastructure. Fortunately, DBPro provides features that allow database developers to follow the same unit-testing process that other developers follow, as in this scenario:

  1. Get the latest test and database projects from source code control and add a new stored procedure to the database project. Configure the procedure's input and output parameters.
  2. Right-click on the new stored procedure and select "create unit tests." Add the unit test to the existing test project. A new test class is added to the test project with T-SQL that will execute the new stored procedure with default input parameters.
  3. Modify the input parameters to contain expected values and add a test condition to verify that the stored procedure returns the expected results.
  4. Run the test. As part of test setup, the database project is deployed to the local SQL Server® instance and the data generation plan is executed to populate the newly deployed database with expected test data. The test executes against the local SQL Server instance and fails (as expected because the stored procedure's logic has not yet been implemented).
  5. Implement the stored procedure's logic and run the test again. The updated stored procedure is deployed, and the test passes.
  6. Run all tests to verify the database and then check the stored procedure and unit-test code into source code control.

The features provided by DBPro include: the generation of stub T-SQL unit tests from existing functions, stored procedures, or triggers; automatic deployment of database project updates to a local sandbox instance; generation of data as part of the test environment setup using a data generation plan; and execution of T-SQL tests against a target database. Although these features can be used together, that isn't required. For instance, a database unit test can be written from scratch and data generation does not have to be executed prior to every test run.

Database Unit Testing

The database unit-testing feature reflects two somewhat competing goals: the desire on the part of the database developer to use familiar T-SQL scripts to express the unit-test logic and the desire to take advantage of powerful Microsoft® .NET Framework-targeted languages such as C# or Visual Basic. These competing goals are reconciled by encapsulating the test in T-SQL code and then hosting the T-SQL in C# or Visual Basic .NET code. The unit-test designer generates both, enabling either (or both) to be modified to meet individual needs.

Database unit testing with Team System provides or supports the features you'd want if you were designing a database testing framework yourself. You need to be able to execute a test with the proper context. For example, you need to utilize the credentials your Web tier would use when connecting to the database or be able to use a different set of credentials to access or modify the underlying tables before and after a test has executed. Being able to run a script before and after each test in a "test group" to set up or reset the database state is helpful, as is the ability to run a script (for an individual test) before and after a test to set up or reset the database state.

Finally, you'd like to extend the set of test conditions available by adding conditions specific to your database, leverage transactions to control modifications to database state, and use data to drive multiple runs of your test with different input and output parameters. In some cases, to use these features you'll need additional code. Let's see what that entails.

Defining Connections

As I mentioned, it's important for a database test harness to allow tests to execute using a connection other than the one used to verify and set up the database. To support this goal, Microsoft created the concept of a connection context to contain all the information necessary to define the connection and how it should be used to execute the test. The base connection context contains an open connection, the provider factory (msdn2.microsoft.com/system.data.common.dbproviderfactory) to use when creating additional ADO.NET objects, as well as the timeout and DbTransaction to use when executing a test.

Using the context type, you define two context instances—execution context and privileged context—to support test setup, execution, and verification. As is clear from its name, execution context is used to execute the unit test. It should represent the configuration of the application that uses the database. Privileged context is used to verify unit tests as well as perform all other modification operations (including deployment and data generation). The definitions of these two contexts are stored in the accompanying sample test project's app.config file, in a special section defined for database unit testing.

Managing T-SQL Scripts

From a code perspective, database unit testing generates C# or Visual Basic .NET code that targets the unit-testing framework in Visual Studio. In the unit-testing framework, a class is used as a container to hold tests (each test is defined as a method). There are a number of opportunities to add scripts in the unit-testing designer to support the scripting features I've described. All scripts, other than the test script, are executed using the privileged context because they are meant to set up or verify the test environment. A script will be executed one or more times depending on where it is added, so let's look in more detail at the scripts and how they are executed (see Figure 1).

Figure 1 Test Scripts and Their Execution (Close Figure 1)

Script | Execution
Test Initialization | This T-SQL script is used to set up the test environment prior to each test's execution. This script is executed before every test in the class.
Pre-Test Setup | The setup prior to a specific test being executed. The pre-test script is available so that you can perform a set-up action for the test or verify that the database is in the appropriate state for the test to take place.
Test | The test itself.
Post-Test Cleanup | The cleanup after a specific test has executed. The post-test script is supplied so that you can restore or verify the state of the database.
Test Cleanup | This T-SQL script is used to restore the test environment after the test has been completed. This script is executed after every test.

Now let's see how to create, access, and modify scripts in the database unit-testing designer. First, create a database unit test by selecting New test from the Test menu, and then select the Database unit test template. Once the wizard has completed, you will see a dialog prompting you to configure the project—cancel this for now (I'll come back to it later). In the solution explorer a new file called DatabaseUnitTest1 will be created and the associated designer will be opened so you can create the first unit test.

In Figure 2 there are three interesting areas. First, the top-left dropdown allows you to select between common scripts and tests defined in the class. When a new test class is created, a default test called DatabaseTest1 is added to the class; you can rename this default test using the Rename button and add additional tests using the button with the green plus symbol.

Figure 2 New Unit Test
Figure 2 New Unit Test(Click the image for a smaller view)
Figure 2 New Unit Test
Figure 2 New Unit Test (Click the image for a larger view)

The dropdown to the left of that button allows you to switch between scripts. If Common scripts has been selected in the dropdown on the left, the test initialization/cleanup scripts that are run before and after every test are available to be edited. On the other hand, if a Test is selected, the pre/test/post scripts are available to be edited.

Third, the Test Conditions panel provides you access to the test conditions that operate on the result of the T-SQL script execution to verify that the results are what you expected. The dropdown is enabled once you click on the hyperlink "Click here to create." This dropdown provides access to the test conditions registered on your machine.

As you can see from the UI, there is a consistent pattern for all scripts. Everywhere you can define a script, you can also define one or more test conditions that can be used to verify that the script executed as you expected. Verification of tests is crucial when writing unit-test code. It is not enough to just execute a piece of code; you also need to verify that the execution did what you expected.

At this point, you are probably wondering how the test knows which database to connect to and what the privileged context I mentioned is all about. Well, to answer these questions you need to go back to that dialog I had asked you to cancel earlier. To get to the dialog, select the Test Project and go to Test | Database Test Configuration (see Figure 3).

Figure 3 Configuring the Unit Test
Figure 3 Configuring the Unit Test(Click the image for a smaller view)
Figure 3 Configuring the Unit Test
Figure 3 Configuring the Unit Test (Click the image for a larger view)

In the first dropdown option, you select the connection string that will be used to execute your test. This connection string will probably use Windows® authentication by default, but you can specify SQL Server authentication. If you do, the password will be stripped out, encrypted, and stored in the registry (the password will be retrieved from the registry during test execution).

In the second dropdown, specify a different set of credentials for modifying and verifying database state. For instance, if you want to execute your tests using the credentials the application will use (for example, the Web tier communicating with the database), you may want to use a different set of credentials with higher privileges to verify the database state. All other scripts, including deployment and data generation, use this connection string. If you do not specify a connection string, the execution connection string is used.

In the third dropdown, you select a database to deploy prior to executing any tests. If you select a database project (and select the default configuration), a differencing build will be performed and the change deployed to the database specified in the execution connection string. If you have not made any changes, this is a quick operation (thanks to changes in DBPro SP1) because MSBuild and the deployment script detect that no changes have been made. After the tests have been executed, you can see the results of the database deployment by clicking on the hyperlink at the top of the Test Results tool window.

In the very last dropdown, you select a data-generation plan that should be deployed prior to test execution. This plan will run every time your tests run, so you will want to configure a small amount of data to reduce the time it takes for your tests to actually begin execution.

After you click OK on the dialog, the information is stored in the test project's app.config file, as shown in Figure 4. Up to this point, I've shown you the major features exposed by the UI—now let's drill into the actual code.

Figure 4 App.config (Close Figure 4)
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="DatabaseUnitTesting"
      type="Microsoft.VisualStudio.TeamSystem.Data
        .UnitTesting.Configuration.DatabaseUnitTestingSection,
        Microsoft.VisualStudio.TeamSystem.Data.UnitTesting,
        Version=9.0.0.0, Culture=neutral,
        PublicKeyToken=b03f5f7f11d50a3a" />
  </configSections>
  <DatabaseUnitTesting>
    <DataGeneration ClearDatabase="true" />
    <ExecutionContext
      Provider="System.Data.SqlClient"
      ConnectionString="Data Source=(local)\sqlexpress
        ;Initial Catalog=Database1Sandbox;Integrated
        Security=True;Pooling=False"
      CommandTimeout="60"/>
    <PrivilegedContext
      Provider="System.Data.SqlClient"
      ConnectionString="Data Source=(local)\sqlexpress
      ;Initial Catalog=Database1Sandbox;Integrated
      Security=True;Pooling=False" />
  </DatabaseUnitTesting>
</configuration>

Modifying Generated Test Code

The unit-testing API was designed to meet your need to change or augment the way the test harness works out of the box. Microsoft expected that users would want to modify the code that's generated, write their own test conditions, and replace the default test service with their own implementation. Figure 5 lists the classes that make up the database unit-testing API. (Also see msdn2.microsoft.com/microsoft.visualstudio.teamsystem.data.unittesting for more information.)

Figure 5 Classes in the Database Unit-Testing API (Close Figure 5)

Class | Purpose
TestCondition | The base class for all test conditions used to verify test scripts. A subclass of Component, it supports a design-time and runtime execution. At design time, you configure the test condition with the values you expect during test execution, and at run time the test condition uses those design-time values to verify the actual test results.
DatabaseTestClass | The base class for all database unit tests in Visual Studio. The definition of this class integrates into the Visual Studio design environment and is responsible for serializing the code in InitializeComponent.
ConnectionContext | Encapsulates a connection to a database.
DatabaseTestActions | This class is a component, a design-time representation of a database unit test. Instances of this class host the DatabaseTestAction instances that comprise one test.
ExecutionResult | Represents the result of executing one batch during test execution.
DatabaseTestAction | This class is a component. Instances of this class contain a script and a collection of test conditions.
DatabaseTestService | The default implementation of the test harness. It implements database deployment, data generation, creation of execution and privileged connection contexts, and test execution.

The default instance of the test service is accessed through a static property on the DatabaseTestClass. If you would like to, you can replace the default implementation with your own and override any of the above implementations.

There are several reasons why you may want to modify the generated Visual Basic or C# code. The two most common scenarios are to execute your unit tests in a transaction or to take advantage of the unit-testing harness's data-driven testing feature.

Transactions

Unit tests that are executed in a shared environment must be careful not to dirty the execution environment; modifying the expected data or schema could cause subsequent tests to fail unexpectedly. Authors of database unit tests must be especially careful because it is very easy to make this kind of mistake.

That said, not surprising to any database developer, the way database unit-test authors avoid modifying the underlying database is through the use of transactions. Here I will show you three ways you can leverage transactions. Inside the executed T-SQL, use this transaction:

begin transaction

  -- Execute test

  -- We can clean up when the test passes successfully; otherwise, it
  -- is up to the engine to clean up the transaction when the
  -- connection is closed

rollback transaction

In an ADO.NET transaction, execute the code shown in Figure 6. To use System.Transactions, take a look at Figure 7.

Figure 7 Using System.Transactions (Close Figure 7)
[TestMethod()]
public void SystemTransactionsExample()
{
  using (TransactionScope transactionScope =
    new System.Transactions.TransactionScope(
    TransactionScopeOption.Required))
  {
    base.ExecutionContext.Connection.EnlistTransaction(
      Transaction.Current);
    base.PrivilegedContext.Connection.EnlistTransaction(
      Transaction.Current);

    DatabaseTestActions testActions = this.SystemTransactionsExampleData;

    // Execute the pre-test script
    Trace.WriteLineIf((testActions.PretestAction != null),
      "Executing pre-test script...");
    ExecutionResult[] pretestResults = TestService.Execute(
      this.PrivilegedContext,
      this.PrivilegedContext,
      testActions.PretestAction);

    // Execute the test script
    Trace.WriteLineIf((testActions.TestAction != null),
      "Executing test script...");
    ExecutionResult[] testResults = TestService.Execute(
      this.ExecutionContext,
      this.PrivilegedContext,
      testActions.TestAction);

    // Execute the post-test script
    Trace.WriteLineIf((testActions.PosttestAction != null),
      "Executing post-test script...");
    ExecutionResult[] posttestResults = TestService.Execute(
      this.PrivilegedContext,
      this.PrivilegedContext,
      testActions.PosttestAction);
  }
}
Figure 6 ADO.NET Transaction (Close Figure 6)
[TestMethod()]
public void ADONetTransactionSample()
{
  base.ExecutionContext.Transaction =
    base.ExecutionContext.Connection.BeginTransaction();
  try
  {
    DatabaseTestActions testActions = this.ADONetTransactionSampleData;

    // Execute the test script
    Trace.WriteLineIf(
      (testActions.TestAction != null),
      "Executing test script...");
    ExecutionResult[] testResults = TestService.Execute(
      this.ExecutionContext,
      this.ExecutionContext,
      testActions.TestAction);
  }
  finally
  {
    base.ExecutionContext.Transaction.Rollback();
  }
}

Of these three options, T-SQL and ADO.NET transactions work if you're not using the pre/post test scripts to set up the database or verify the results of the test; remember, the pre/post scripts are executed using a different connection so their modifications will not be rolled back. Using System.Transactions, the Privileged and Execution connections have already been created so they must be enlisted in the ambient transaction created when the TransactionScope is instantiated, but any changes will be rolled back.

System.Transactions

Because using ambient transactions (as in the System.Transactions example shown previously) is the best comprehensive solution, let's drill into the solution more. (You may also visit msdn2.microsoft.com/ms172152 for more background.) The ambient transaction is used across two different connections that may have different lifetimes, so the Distributed Transaction Coordinator (DTC) service must be used to manage the transaction's lifetime. This means that in order to use System.Transactions, the database's DTC service must be started for the test to pass. If the service is not started, your test will fail with an exception like the following:

Test method TestSamples.DatabaseUnitTest1.SystemTransactionsExample threw exception:
System.Data.SqlClient.SqlException: MSDTC on server '(LOCAL)\SQLEXPRESS' is unavailable.

Modifying each method to use an ambient transaction is tedious at best; most likely, you will want to group all the transactional tests together in one class and then modify the class so that a transaction is started and stopped for each test (see Figure 8).

Figure 8 Using Transactions for Each Test (Close Figure 8)
TransactionScope _ambientTransaction;

[TestInitialize()]
public void TestInitialize()
{
  // Create the transaction prior to the
  // connections being created.  When the
  // connections are created they will
  // automatically enlist in the transaction
  ambientTransaction = new TransactionScope(
    TransactionScopeOption.Required);

  base.InitializeTest();
}

[TestCleanup()]
public void TestCleanup()
{
  // Since Complete() was not called, disposing
  // of the ambient transaction will cause all
  // changes to be rolled back
  ambientTransaction.Dispose();

  base.CleanupTest();
}

Data-Driven Testing
Team System for Database Professionals: Resources

By reducing the risks involved in making database schema changes, you can better control the integrity of your database throughout the development process. Here are some resources to help you:

  • Rename Refactoring Support for Database Objects
  • Schema Compare to Keep Two Schema Versions Synchronized
  • Data Compare to Keep the Data in Two Databases Synchronized
  • Use Offline Database Projects for Change Isolation

Minor changes to the database can have a significant impact on other areas of the application without the impact being immediately visible. As such, it is valuable to determine the impact of schema changes early on. These Team System features will help:

  • Extensible Unit Test Functionality
  • Data Generator to Define Sets of Repeatable Test Data
  • New Designer to Create Unit Tests in T-SQL or Managed Code

One of the other benefits of DBPro is that, as a part of the Team System family, it is integrated with Team Foundation Server to give team members complete visibility into all development artifacts and activities on a project. This results in the reduction of issues related to poor communication and the improvement of team productivity and effectiveness.

  • Get a Unified View across Your Project with Team Explorer
  • Manage Change to Project Artifacts with Team Foundation Version Control
  • Communicate and Manage Work across the Team with Team Foundation Work Item Tracking
  • Collaborate Using the Team Portal
  • Define Your Development Process Using Team Foundation Process Templates
  • Integrate Your Team's Work Together Regularly Using Team Foundation Build
  • Make Real-Time Decisions Based on Performance and Quality Metrics with Reporting and Business Intelligence
  • Support Distributed and Outsourced Development Teams

The test harness that shipped with Visual Studio 2005 allows you to run a unit test multiple times, each time with a different set of input parameters. Documentation describing how to use this harness can be found in "Coding a Data-Driven Unit Test" at msdn2.microsoft.com/ms182527 and in "Walkthrough: Using a Configuration File to Define a Data Source" at msdn2.microsoft.com/ms243192.

The database unit-testing API supports data-driven testing by allowing you to supply zero or more DbParameter instances to the Execute method. To understand how the supplied DbParameter instances are used, think of the SQL test script as parameterized SQL that directly references the DbParameter instances as variables in the code. An example is shown in Figure 9.

Figure 9 Supplying DbParameter Instances (Close Figure 9)
[DataSource ("System.Data.SqlClient", "Data Source=(local)\\sqlexpress" +
  ";Initial Catalog=TestDB_DataDriven;Integrated Security=True",
  "DecisionAllocationTestData", DataAccessMethod.Sequential), TestMethod()]
public void DataDriveTestSample()
{
  // Setup and execute the test
  DatabaseTestAction test = new DatabaseTestAction();

  DbParameter customerID = CreateParameter(
    "@customerID", DbType.Int32,
    ParameterDirection.Input, "CustomerID");
  DbParameter offerID = CreateParameter(
    "@offerID", DbType.Int32,
    ParameterDirection.Input, "OfferID");
  DbParameter decision = CreateParameter(
    "@decision", DbType.StringFixedLength,
    ParameterDirection.Input, "Decision");
  DbParameter actualVault = CreateParameter(
    "@vault", DbType.Int32,
    ParameterDirection.Output);
  DbParameter actualBroker = CreateParameter(
    "@broker", DbType.Int32,
    ParameterDirection.Output);
  DbParameter actualMarket = CreateParameter(
    "@market", DbType.Int32,
    ParameterDirection.Output);

  test.SqlScript = @"
    declare @rc int
    execute @rc = AllocatePositionsByDecision
    @customerID,
    @offerID,
    @decision,
    @vault out,
    @broker out,
    @market out
  ";

  DatabaseTestClass.TestService.Execute(
    base.ExecutionContext, base.PrivilegedContext,
    test,customerID, offerID, decision, actualVault,
    actualBroker, actualMarket);

  // Verify the results
  int expectedVault = (int) base.TestContext.DataRow["ExpectedVault"];
  int expectedBroker = (int)base.TestContext.DataRow["ExpectedBroker"];
  int expectedMarket = (int)base.TestContext.DataRow["ExpectedMarket"];

  Assert.AreEqual(expectedVault, actualVault.Value, "Vault incorrect");
  Assert.AreEqual(expectedBroker, actualBroker.Value, "Broker incorrect");
  Assert.AreEqual(expectedMarket, actualMarket.Value, "Market incorrect");
}

private DbParameter CreateParameter(string paramName,
  DbType paramType, ParameterDirection paramDirection)
{
  return CreateParameter(paramName, paramType, paramDirection, null);
}

private DbParameter CreateParameter(string paramName,
  DbType paramType, ParameterDirection paramDirection,
  string dataColumnName)
{
  DbParameter newParameter =
    base.ExecutionContext.Provider.CreateParameter();
  newParameter.DbType = paramType;
  newParameter.Direction = paramDirection;
  newParameter.ParameterName = paramName;

  if (paramDirection == ParameterDirection.Input ||
    paramDirection == ParameterDirection.InputOutput)
  {
    newParameter.Value = base.TestContext.DataRow[dataColumnName];
  }

  return newParameter;
}

As you can see in Figure 9, the test method is decorated with an attribute that informs the test harness that it expects to be called once for each row of data retrieved from the DecisionAllocationTestData table in the TestDB_DataDriven database on my local SQL Server Express instance. The DatabaseTestAction class represents an action in a test composed of a test script to execute and test conditions to evaluate the results. You'll notice that I create five DbParameter instances representing the variables referenced in the test script; it is important to note that some of these parameters are output parameters. The CreateParameter method is just a small helper method that creates the instance and optionally populates it with data from the test context.

The test script you see next is defined to reference the DbParameter instances I'm providing as arguments to the Execute method. If you're familiar with ADO.NET code, you'll see this is simply parameterized SQL.

Next I call the Execute method on the default TestService instance, passing the DbParameter instances as arguments. After execution I verify the results. This is simply a matter of retrieving the expected values from the DataRow and comparing them with the output parameters.

The example in Figure 9 demonstrates that the database unit-testing API can be targeted directly rather than using the designer. There I am using data retrieved from a test table to drive execution of a stored procedure; the stored procedure being tested uses the first three input values to calculate the number of shares that will be retrieved from various locations to fulfill the customer's decision. This calculation is complex, depends only on the inputs, and has simple outputs. Therefore it makes sense to write this test as data-driven rather than duplicating the same test many times varying only the data.

At this point, I have covered several different ways you can modify or use the existing API to leverage transactions or create data-driven tests. The database unit-testing API can be extended or modified in several ways, though, by adding extra test conditions or by creating your own test service. So now let's look at ways you can extend the framework.

Custom Test Conditions

The database unit-testing feature comes with six test conditions that can be used to verify the execution of your unit test. Although these test conditions are sufficient for basic use, most users will quickly find that they need to verify a resultset rather than a single value, so here I'll build two additional test conditions. These two test conditions, plus the LoggingTestService, are additionally part of a new set of power tools for Visual Studio 2008 released by the Team System Data team. To download the new power tools, please see go.microsoft.com/fwlink/?Linkid=107300.

The first test condition is ChecksumCondition. At design time, it generates a hash of the expected results. At execution time, the actual results are hashed and the resulting value is compared with the expected result. The second, ExpectedSchemaCondition, collects and stores the expected schema at design time. At execution time, the actual schema is compared with the expected schema.

When utilizing test conditions, a user creates a database unit-test class and adds the test conditions from a list of available conditions (a reference to the assembly containing the new conditions is added automatically when the new test class is added to the project).

The user can access the test condition's configuration by pressing on the ellipses in the Configuration property. The resulting dialog, shown in Figure 10, allows the user to specify and execute the query that retrieves the expected data; in this figure, the data displayed is random (nonsensical) data generated by the DBPro Data Generator. When first displayed, the T-SQL from the test is provided in the textbox. The user can then modify the test code to reflect the query she actually wants and then click on the Retrieve button to execute the code against the server and display the results. Once the user is satisfied that the results are what was expected, she clicks OK, and the test condition configures itself accordingly.

Figure 10 Test Scripts and Their Execution
Figure 10 Test Scripts and Their Execution(Click the image for a smaller view)
Figure 10 Test Scripts and Their Execution
Figure 10 Test Scripts and Their Execution (Click the image for a larger view)

The user doesn't have to interact with the condition any further. Now that you have an idea what the end result will look like, you can create these test conditions. When developing the previous test conditions, the bulk of the interesting code is in the design-time behavior. I will walk through the high points in the following section; the complete code is available at msdn.microsoft.com/msdnmag/code08.aspx.

Defining the Test Condition

When defining a new test condition, I find that it is easiest to define the skeleton and registration of the condition first and verify that the skeleton works before developing the actual condition logic. Because the two sample test conditions are very similar, I defined a common base class, as you see in Figure 11. Once the project compiled, I then defined the registration XML file, called UnitTesting.Samples.extensions.xml, shown in Figure 12.

Figure 12 UnitTesting.Samples.extensions.xml (Close Figure 12)
<?xml version="1.0" encoding="utf-8"?>
<extensions
  assembly="UnitTesting.Samples, Version=1.0.0.0,
    Culture=neutral, PublicKeyToken=382814fd17d42dea" version="1"
  xmlns="urn:Microsoft.VisualStudio.TeamSystem.Data.Extensions"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="urn:Microsoft.VisualStudio.TeamSystem
    .Data.Extensions
    Microsoft.VisualStudio.TeamSystem.Data.Extensions.xsd">
  <extension
    type="UnitTesting.Samples.TestConditions.ExpectedSchemaCondition"
    enabled="true"/>
  <extension type=" UnitTesting.Samples.TestConditions.ChecksumCondition"
    enabled="true"/>
</extensions>
Figure 11 Common Base Class Defined (Close Figure 11)
namespace UnitTesting.Samples.TestConditions
{
  public abstract class DataSetTestCondition : TestCondition
  {
    //...
  }
}

namespace UnitTesting.Samples.TestConditions
{
  [DisplayName("Checksum")]
  public class ChecksumCondition : DataSetTestCondition
  {
    // ...
  }
}

namespace UnitTesting.Samples.TestConditions
{
  [DisplayName("Expected schema" )]
  public class ExpectedSchemaCondition : DataSetTestCondition
  {
    // ...
  }
}

I like to add the extensions file directly to the project and set it to automatically copy to the output directory so that it is easy to reference and use during development and deployment. The final step is setting up automatic deployment by copying the extensions file to the DBPro directory and installing the test conditions assembly into the Global Assembly Cache (GAC).This can be accomplished through a post-build step or by running a command prompt with elevated privileges. I used the following script in my project's post-build step:

set TargetNameVar=$(TargetName)
set DevEnvDirVar=$(DevEnvDir)
set WindowsSDKVar=$(ProgramFiles)\Microsoft SDKs\Windows\V6.0A

copy /Y %TargetNameVar%.extensions.xml "%DevEnvDirVar%..\..\DBPro\*"
copy /Y %TargetNameVar%.pdb "%DevEnvDirVar%PublicAssemblies\*"
"%WindowsSDKVar%\Bin\gacutil" /if %TargetNameVar%.dll

If you want to run the script as a separate .bat file, modify the first three lines to retrieve values from environment variables (instead of MSBuild variables).

After this initial setup has been completed, I can see the two new conditions in the test conditions dropdown. I added the DisplayNameAttribute (msdn2.microsoft.com/system.componentmodel.displaynameattribute) to the test conditions so they appear as Checksum and Expectedschema. Now that I have finished setup, I need to develop the test conditions' design-time experience so that the user can click on the ellipses in the property browser and configure the condition instance.

If you have designed components for Windows Forms in Visual Studio, then you are probably already familiar with the UITypeEditor in the .NET Framework. The UITypeEditor (msdn2.microsoft.com/system.drawing.design.uitypeeditor) and the EditorAttribute (msdn2.microsoft.com/system.componentmodel.editorattribute) enable the component developer to design a custom editing experience and assign that experience (through the EditorAttribute) to a component property that is designed in the IDE. In this case, I want the experience to be a modal dialog so I define the editor as in Figure 13. Since both test conditions are configured the same way, I define the property on the base class:

Figure 13 Defining Editor Style (Close Figure 13)
namespace UnitTesting.Samples.TestConditions.Design
{
  class DataSetEditor : UITypeEditor
  {
    public override UITypeEditorEditStyle
      GetEditStyle(ITypeDescriptorContext context)
    {
      return UITypeEditorEditStyle.Modal;
    }

    public override object EditValue(
      ITypeDescriptorContext context,
      IServiceProvider provider, object value)
    {
      MessageBox.Show("DataSetEditor::EditValue");
      return value;
    }
  }
}

[DesignOnly(true)]
[Editor(
  typeof(UnitTesting.Samples.TestConditions.Design.DataSetEditor),
  typeof(System.Drawing.Design.UITypeEditor))]
public string Configuration
{
    get { return "Press to configure"; }
    set { }
}

When I compile and deploy the test conditions, I see the Configuration property, and when I click on the ellipses, the message box displays. At this point, all the code needed to integrate with Visual Studio has been completed and I can concentrate on the logic of the test conditions themselves.

First I create a form—this will be displayed by the DataSetEditor I just created—that allows the user to configure the DataSet expected by the test condition. This form (named DataSetSelectorDialog) is seeded with the T-SQL from the test and allows the user to execute that T-SQL against a target server in order to preview the data prior to configuring the test condition. Once the user is happy with the DataSet, he can click OK on the dialog, signaling that the test condition should use the DataSet and return control to the editor.

Next, I modify the editor to display and process the form's content after the user clicks OK. Normally, a UI editor passes back the value that should be stored in the property being edited, but in this case I want the property to display the "Press here to configure" text and pass back the DataSet to the edited test condition. Since the conditions would process the DataSet differently, I define a virtual method on the base class and have the editor call this method to set the DataSet instance; the derived classes override the method to add their specific implementation. With these changes the EditValue method now looks like Figure 14. There, I retrieve the IUIService to display a modal dialog; this service integrates the display of the modal dialog with the rest of the Visual Studio windowing behavior.

Figure 14 EditValue Method (Close Figure 14)
public override object EditValue(
ITypeDescriptorContext context, IServiceProvider provider, object value)
{
  IUIService uiService = (IUIService)provider
    .GetService(typeof(IUIService));
  DataSetTestCondition cond = context.Instance as DataSetTestCondition;

  if (uiService != null && cond != null)
  {
    DataSetSelectorDialog dsDialog = new DataSetSelectorDialog();

    if (cond.Site != null)
    {
      dsDialog.Text = string.Format(CultureInfo.CurrentCulture,
        "Configuration for {0}", cond.Site.Name);
    }

    dsDialog.Query = cond.GetTestScript();
    dsDialog.StartPosition = FormStartPosition.CenterParent;

    if (uiService.ShowDialog(dsDialog) == DialogResult.OK)
    {
      value = dsDialog.Query;
      if (cond != null)
      {
        cond.ConfigureExpectedDataSet(dsDialog.Result);
      }
    }
  }

  return value;
}

Now that you've seen the design-time behavior of the test conditions, let's see what happens when the test script is executed against the target server and the results of that execution are collected inside a DataSet. After the test is executed, an instance of the ExecutionResult class is created and populated with information about the test's execution (including the DataSet). The test harness then loops through all test conditions that have been configured to verify the execution results and calls the Assert method on each test condition to verify those results. In the case of the test conditions in this example, they retrieve the DataSet from the first ExpectedResult in the results array, and then ChecksumCondition generates a hash of the actual DataSet and compares that hash with the hash generated during design time. ExpectedSchemaCondition deserializes the empty DataSet that represents the expected data tables (and their columns) and compares them with the DataSet generated by the test.

Modifying Test Execution

The default behavior for connection strings, database deployment, and test execution is accessed through the instance of the DatabaseTestService class that is registered on the static TestService property on the DatabaseTestClass. The methods on the DatabaseTestService class are marked as virtual so that they can be overridden individually or all together to change how tests are executed. You might want to modify the default behavior if, for example, you're using a custom connection string encryption/decryption mechanism, you're modifying the way database deployment or data generation is performed, or you're automatically logging execution results.

Each of these modifications (and more) can be achieved by writing your own test service class and then registering that test service with the harness so that it can be used during test execution. In this sample, I'm going to write a test service that will log the results of a test execution to a file using DataSet serialization. The service can be configured to log no results, all results, or only failed results. This simple service makes it easy to see the result of your tests' execution within the harness.

First, I define the class; I am simply modifying the way tests are executed, so the class looks like that in Figure 15. As you can see, the LoggingTestService subclasses the DatabaseTestService class and then overrides the Execute method. Next, I need to add results logging. Note that the default implementation of Execute executes the test script and then evaluates the test conditions; this implementation will not meet my needs, however, because I don't have access to the execution results in the event of verification failure. Therefore, to meet my goals, I'll instead evaluate the assigned test conditions after calling the base Execute method and then log the results accordingly. The complete Execute implementation looks like Figure 16.

Figure 16 Execute Implementation (Close Figure 16)
public override ExecutionResult[] Execute(
  ConnectionContext scriptExecutionContext,
  ConnectionContext privilegedExecutionContext,
  DatabaseTestAction action,
  params System.Data.Common.DbParameter[] sqlParameters)
{
  // We want to evaluate the conditions ourselves so that we
  // can log the results from every test's execution
  List<TestCondition> conditions = new List<TestCondition>();
  if (action != null)
  {
    conditions.AddRange(action.Conditions);
    action.Conditions.Clear();
  }

  // Execute the test
  ExecutionResult[] results = base.Execute(
    scriptExecutionContext,
    privilegedExecutionContext,
    action,
    sqlParameters);

  // Verify and log the results
  bool verificationFailed = false;
  try
  {
    foreach (TestCondition condition in conditions)
    {
      if (condition.Enabled)
      {
        condition.Assert(privilegedExecutionContext.Connection, results);
      }
    }
  }
  catch
  {
    verificationFailed = true;
    throw;
  }
  finally
  {
    if (results != null &&
      (Verbosity == LogVerbosity.AllResults ||
      (verificationFailed && Verbosity == LogVerbosity.OnlyFailure)))
    {
      // Serialize the dataset to an .xml file to be visualized
      string resultPrefix = Guid.NewGuid().ToString("N");

      for (int resultIndex = 0; resultIndex < results.Length;
        resultIndex++)
      {
        string dsFilePath = Path.GetFullPath(string.Format(
          CultureInfo.CurrentCulture,
          "{0}-{1}.xml", resultPrefix, resultIndex));

        ExecutionResult result = results[resultIndex];
        result.DataSet.WriteXml(dsFilePath,
          System.Data.XmlWriteMode.IgnoreSchema);

        Trace.TraceInformation("Log {0} {1}", resultIndex, dsFilePath);
      }
    }
  }

  return results;
}
Figure 15 LoggingTestService Class (Close Figure 15)
namespace UnitTesting.Samples
{
  public class LoggingTestService : DatabaseTestService
  {
    public override ExecutionResult[] Execute(
      ConnectionContext scriptExecutionContext,
      ConnectionContext privilegedExecutionContext,
      DatabaseTestAction action,
      params System.Data.Common.DbParameter[] sqlParameters)
    {

      // Execute the test
      ExecutionResult[] results = base.Execute(
        scriptExecutionContext,
        privilegedExecutionContext,
        action,
        sqlParameters);

      return results;
    }
  }
}

This implementation still uses the base Execute implementation, but, afterward, it verifies the results using the action's test conditions and writes the results of that execution to a file. I'm using a GUID to generate a unique name because this method will be called multiple times during a single test's execution. It is also important to check for null results because it is common for Execute to be called with no action.

Once the service has been compiled into an assembly, I still need to register it in order for it to be used during test execution. In my case, I want the service to be registered for all tests in my assembly but only to log if a test fails, so I modified the IntializeAssembly method to look like the following:

[AssemblyInitialize()]
public static void IntializeAssembly(TestContext ctx)
{
  LoggingTestService testService = new LoggingTestService();
  testService.Verbosity = LoggingTestService.LogVerbosity.OnlyFailure;
  DatabaseTestClass.TestService = testService;

  // Setup the test database based on setting in the
  // configuration file
  DatabaseTestClass.TestService.DeployDatabaseProject();
  DatabaseTestClass.TestService.GenerateData();
}

Now, when I run a unit test that fails, the path to the log file will be output to the test run directory and the path to the file will be in the test results window for that test.

To look at the results that caused the test failure, I created a very simple Windows Forms application (it is in the accompanying sample code download) that deserializes and displays the DataSet. Before I wrap up, I should mention that the Visual Studio test harness executes each test assembly in its own AppDomain. Because the service instance is registered via a static property, the service registration will only persist for the lifetime of the AppDomain. This does mean that you will need to reregister the test service during the initialization of each test assembly. Also, you should take a look at the sidebar, "Team System for Database Professionals: Resources", for more information on making database schema changes.

Looking Forward

I've touched on many features of DBPro in this article, but the best way to get a feel for what you can do is to get your hands dirty. I suggest you experiment with the product, including working with the data generation API and the new Visual Studio 2008 power tools. If you encounter any problems or just have questions, the forum is always open at: forums.microsoft.com/MSDN/ShowForum.aspx?ForumID=725&SiteID=1. In addition, check out Sachin Rekhi's whitepaper titled "Database Unit Testing with Team Edition for Database Professionals" at msdn2.microsoft.com/bb381703.

Jamie Laflen is a Tech Lead on the DBPro team; he is responsible for the unit testing, data generation, and build/deploy features. Prior to joining the development team, Jamie was part of Microsoft Services and helped customers implement .NET applications inside Visual Studio.


F# Primer: Use Functional Programming Techniques in the .NET Framework

F# Primer

Use Functional Programming Techniques in the .NET Framework

Ted Neward
This article discusses:
  • Installing F#
  • F# language basics
  • .NET interoperability
  • Asynchronous F#
This article uses the following technologies:
.NET Framework, F#
Contents
Why Use F#?
Installing F#
Hello, F#
The Let Expression
The For Keyword
The Pipeline
F# Can Do Objects, Too
Asynchronous F#
Fitting in with F#

A
recent entry into the Microsoft® .NET Framework family, F# provides type safety, performance, and the ability to work like a scripting language, all as part of the .NET environment. This functional language was created by Don Syme of Microsoft Research as a syntax-compatible OCaml variant for the CLR, but F# has been moving quickly from the lab into the workshop.

As concepts of functional programming creep into more mainstream languages like C# and Visual Basic® through technologies like .NET generics and LINQ, the visibility of F# has grown within the .NET community—so much so that in November 2007 Microsoft announced that it would move F# into the stable of supported .NET programming languages.

For years the area of functional languages (ML, Haskell, and so on) has been considered more appropriate for academic research than for professional development. It isn't that these languages aren't interesting. In fact, some important enhancements to .NET—generics, LINQ, PLINQ, and Futures, for example—come from the application of functional programming concepts to languages that had never seen them before. The lack of interest in these languages has been based more on the fact that they targeted platforms of little relevance to developers writing programs for Windows®, they didn't integrate well with the underlying platform, or they didn't support key functionality such as relational database access, XML parsing, and out-of-process communication mechanisms.

However, the CLR and its "many languages, one platform" approach made it inevitable that more of these languages would make their way into the world of Windows development. It was equally inevitable that they would start to make their presence felt to practicing programmers. F# is one such language. In this article, I will introduce you to some of the concepts behind and advantages of F#. Then, to help you get started with F#, I will walk you through installation and writing several simple programs.

Why Use F#?

It will be obvious to a small percentage of .NET programmers that learning a functional language for the .NET Framework is a positive step forward in writing powerful software. For the rest, the motivation to learn F# is a complete mystery. How do developers benefit from F#?

Writing safe concurrent programs has become a principal concern in the past three years, as multicore CPUs have become more widespread. Functional languages help developers support concurrency by encouraging immutable data structures that can be passed between threads and machines without having to worry about thread safety or atomic access. Functional languages also tend to make it easier to write better concurrency-friendly libraries such as the F# asynchronous workflows, which I will examine later in this article.

Although it won't seem as such to programmers deeply steeped in object-oriented development, functional programs are often simpler to write and maintain for certain kinds of applications. Consider, for example, writing a program to convert an XML document into a different form of data. While it would certainly be possible to write a C# program that parsed through the XML document and applied a variety of if statements to determine what actions to take at different points in the document, an arguably superior approach is to write the transformation as an eXtensible Stylesheet Language Transformation (XSLT) program. Not surprisingly, XSLT has a large streak of functionalism inside of it, as does SQL.

F# strongly discourages the use of null values and encourages the use of immutable data structures. These together help reduce the frequency of bugs in programming by reducing the amount of special-case code required.

Programs written in F# also tend to be more succinct. You actually type less, in both senses of the term: fewer keystrokes and fewer places where the compiler must be told what the type of the variable, arguments, or return type must be. That can mean a lot less code to maintain.

F# has a similar performance profile to C#. However, it has a much better performance profile than comparable succinct languages, in particular the dynamic and scripting languages. And, like many of the dynamic languages, F# includes the tools that allow you to explore data by writing program fragments and executing them interactively.

Installing F#

Available as a free download at research.microsoft.com/fsharp/fsharp.aspx, F# installs not only all the command-line tools but also a Visual Studio® extension package that provides color syntax highlighting, project and file templates (including a very detailed example of F# code as a starter guide), and IntelliSense® support. There's also an F# Interactive shell, which can run inside of Visual Studio, allowing developers to take expressions from source file windows, paste them into the interactive shell window, and see the immediate results from the code snippet—within something like an enhanced Immediates window.

As I'm writing this, F# runs as an external tool inside of Visual Studio, meaning that some of the seamlessness that developers get with C# or Visual Basic is missing. F# also lacks ASP.NET page designer support, among other things. (This is not to say that F# can't be used in ASP.NET—far from it. It is simply that the Visual Studio support for F# doesn't provide the same kind of out-of-the-box drag and drop development experience for F# that it offers for C# and Visual Basic.)

Having said all that, however, the current release of F# can be used anywhere that any other .NET-compliant language can be used. In the next few pages you'll see some examples.

Hello, F#

The inevitable introduction to any language comes via the ubiquitous "Hello, World" program. F# shall be no different:

printf "Hello, world!"

While a tad anticlimactic, this tiny little sample shows that F# belongs to that category of languages that doesn't require an explicit entry point (C#, Visual Basic, and C++/CLI all do); the language assumes that the first line of the program is the entry point and executes from there.

To run this, the budding F# developer has two choices: compiled or interpreted. Running this inside the F# interpreter (fsi.exe) is trivial. Simply fire up fsi.exe from the command line and enter the above line into the resulting prompt, as shown in Figure 1.

Figure 1 Running 'Hello, World' from inside the F# Interpreter
Figure 1 Running 'Hello, World' from inside the F# Interpreter(Click the image for a smaller view)
Figure 1 Running 'Hello, World' from inside the F# Interpreter
Figure 1 Running 'Hello, World' from inside the F# Interpreter (Click the image for a larger view)

Note that in the shell the statement has to be terminated with two semicolons. This is a quirk of the interactive mode and isn't required for compiled F# programs.

To run this sample as a standard.NET executable, fire up Visual Studio as usual and create a new F# project (which you'll find under Other Project Types). An F# project consists, at the beginning, of a single F# source file, called file1.fs. Opening this file reveals a large collection of sample F# code. Have a look at its contents, just to get an idea of what F# syntax looks like. When finished, replace the entirety of the file with the "Hello, world!" code shown earlier. Run the app and, not surprisingly, "Hello, world!" appears in a console application window.

If you prefer a command line, you can compile the code using the fsc.exe tool found in the \bin subdirectory of the F# installation directory. Note that fsc.exe acts like most command-line compilers, taking the source on the command line and producing an executable as a result. Most of the command-line switches are documented, though many should already be familiar if you have experience with the csc.exe or cl.exe compilers. Note, however, that one area where F# currently lags is with MSBuild; there is no direct support within the current install (1.9.3.7 as I write this) for MSBuild-driven compilation.

If you prefer your "Hello, world!" to be a bit more graphical, F# easily provides complete fidelity and interoperability with the underlying CLR platform, including the Windows Forms libraries. Try it out with this:

System.Windows.Forms.MessageBox.Show "Hello World"

Having the ability to make use of the .NET Framework Class Library as well as F# libraries makes the F# language attractive both to the math and scientific community that is already using functional languages like OCaml or Haskell and to the existing body of .NET developers worldwide.

The Let Expression

Let's take a look at some F# code that isn't as trivial as the traditional "Hello, world!". Consider the following:

let results = [ for i in 0 .. 100 -> (i, i*i) ]
printfn "results = %A" results

One curious element in this F# syntax is the let expression. This is the most important expression in the entirety of the language. Formally, let assigns a value to an identifier. The temptation for the Visual Basic and C# developer will be to translate that as "it defines a variable." This would be an untrue assumption. Rather, identifiers in F# embody two principles. First, an identifier, once defined, may never change. (This is how F# helps programmers create concurrency-safe programs because it discourages mutable state.) Second, the identifier can be not only a primitive or object type, as seen in C# and Visual Basic, but also a function type, similar to what you might see in LINQ.

Notice as well how identifiers are never explicitly defined as having a type. The results identifier, for example, is never defined; it is inferred from the right-hand side of the expression that follows it. This is known as type inference, and it represents the compiler's ability to analyze the code, determine the return value, and automatically plug that in. (This is similar to the new C# inferred type expressions via the var keyword.)

The let expression doesn't need to work solely with data. You can use it to define functions, which F# recognizes as first-class concepts. So, for example, the following defines an add function that takes two parameters, a and b:

let add a b =
    a + b

The implementation does pretty much what you would expect it to: add a and b and implicitly return the result to the caller. This means that technically every function in F# returns a value, even if that value is not a value, known as the special name unit. This will have some interesting implications in F# code, particularly where it intersects with the .NET Framework class library, but for now, C# and Visual Basic developers can think of unit as roughly equal to void.

There are times when a function should ignore a parameter passed to it. To do so in F#, simply use the underscore as a placeholder for the parameter itself:

let return10 _ =
    add 5 5

// 12 is effectively ignored, and ten is set to the resulting
// value of add 5 5
let ten = return10 12

printf "ten = %d\n" ten

Like many functional languages, F# permits currying, in which a function's application can be only partly defined, relying on its invocation to supply the remainder of the parameters:

let add5 a =
    add a 5

In some ways, this is similar to creating an overloaded method that takes a different parameter set and calls into another method:

public class Adders {
    public static int add(int a, int b) { return a + b; }
    public static int add5(int a) { return add(a, 5); }
}

But there is a subtle difference. Notice that, in the F# version, no types are explicitly defined. This means that the compiler will do its type-inferencing magic, determine whether the parameter to add5 is type-compatible with being added to the integer literal 5, and either compile as such or flag an error. In fact, much of the F# language is implicitly type-parameterized (that is, makes use of generics).

In Visual Studio, hovering the pointer over the previously shown definition of ten will reveal that its type is declared as:

val ten : ('a -> int)

In F# this means ten is a value, a function that takes one parameter of any type, and yields an int result. The tick syntax is the rough equivalent to the <T> syntax in C#, so the closest translation to a C# function would be to say that ten looks like a delegate instance to a type-parameterized method whose type you'd really like to ignore (but can't under the rules of C#):

delegate int Transformer<T>(T ignored);

public class App
{
  public static int return10(object ignored) { return 5 + 5; }

  static void Main()
  {
    Transformer<object> ten = return10;
    System.Console.WriteLine("ten = {0}", return10(0));
  }
}

The For Keyword

Now let's look at the for keyword in the first example:

#light

let results = [ for i in 0 .. 100 -> (i, i*i) ]
printfn "results = %A" results

Starting from the top of the code, notice the #light syntax. This is a concession to non-OCaml programmers coming to F#, relaxing some of the syntax requirements of the OCaml language, and using significant white space to define blocks of code. While it's not required, it does make the syntax easier to parse for the average developer coming from C# or Visual Basic and so it frequently appears in the F# samples and posted code snippets, making it the de facto standard for programming F#. (A future version of F# may actually make #light the default syntax, instead of the other way around.)

That seemingly innocent for loop is, in fact, anything but simple. Officially, this is a generated list, which is a fancy way of saying it is a block of code that will produce a result that is a list.

A list is a primitive construct found frequently in functional languages, and in that respect it is similar in many ways to an array. However, a list does not allow for position-based access (such as the traditional a[i] syntax in C#). Lists will show up in a variety of places in functional programming and, for the most part, can be thought of as the F# equivalent to the .NET Framework List<T> with some enhanced capabilities.

A list is always of some particular type, and in this case the identifier results is a list of tuples, specifically the tuple type identified by F# to be of type (int * int). This idea of a list of tuples is familiar if thought of as equivalent to a pair of columns returned by a SELECT statement in SQL. Thus, the example essentially creates a list of pairs of integers that is 100 items long.

Commonly, in functional languages, function definitions are used anywhere the code itself can appear. So, if you want to extend the previous example, you could write:

let compute2 x = (x, x*x)
let compute3 x = (x, x*x, x*x*x)
let results2 = [ for i in 0 .. 100 -> compute2 i ]
let results3 = [ for i in 0 .. 100 -> compute3 i ]

This idea of looping through a list (or array or some other iterable construct) is such a common task in functional languages that it has been generalized as a basic method call: List.iter. This simply calls a function on each element of the list. Other similar library functions provide useful capabilities. For example, List.map takes a function as an argument and applies the function to each element of the list, returning a new list in the process.

The Pipeline

Let's examine one more construct in F#—the pipeline operator—which takes the results of a function and, in a vein similar to pipes from command shells (like Windows PowerShell®), uses that as the input to a follow-up function. Consider the snippet of F# shown in Figure 2. This code uses the System.Net namespace to connect to an HTTP server, slurp down the corresponding HTML, and analyze the results.

Figure 2 Retrieving and Analyzing HTML (Close Figure 2)
/// Get the contents of the URL via a web request
let http(url: string) =
  let req = System.Net.WebRequest.Create(url)
  let resp = req.GetResponse()
  let stream = resp.GetResponseStream()
  let reader = new System.IO.StreamReader(stream)
  let html = reader.ReadToEnd()
  resp.Close()
  html
let getWords s = String.split [ ' '; '\n'; '\t'; '<'; '>'; '=' ] s
let getStats site =
  let url = "http://" + site
  let html = http url
  let words = html |> getWords
  let hrefs = html |> getWords |> List.filter (fun s -> s = "href")
  (site,html.Length, words.Length, hrefs.Length)

Note the words identifier in the definition of getStats. It takes the html value returned from the URL and applies the getWords function to it. I could have also written the definition to read:

let words = getWords html

The two are identical. But the hrefs identifier shows off the power of the pipeline operator, in that you can string an arbitrary number of applications together. Here I take the resulting list of words and pipe it through the List.filter function, which takes an anonymous function to look for the word href and return it if the expression holds true. And, just to top it all off, the results of the getStats call will be another tuple, this one a (string * int *int * int). To write that using C# will take up much more than 15 lines of code.

The example in Figure 2 also shows off more of the compatibility of F# with the .NET Framework, a theme which you can see repeated here:

open System.Collections.Generic
let capitals = Dictionary<string, string>()
capitals.["Great Britain"] <- "London"
capitals.["France"] <- "Paris"
capitals.ContainsKey("France")

This is really nothing more than exercising the Dictionary<K,V> type, but it demonstrates how generics are specified in F# (using the angle brackets, just like in C#), how to use indexers in F# (using the square-bracket syntax, again just like in C#), and how to execute .NET methods (using the "dot" and parentheses, just like in C#). In fact, the only new bit here is how to do assignment of mutable values, which uses the left-arrow operator. This is necessary because F#, like most functional languages, reserves the use of the equals operator for comparison, in keeping with the mathematical notion that if x = y, then x and y are of the same value, not that y is being assigned to x. (Real mathematicians have been known to turn up their nose—or giggle hysterically—at the idea that x = x + 1 could be true in any universe real or imagined.)

F# Can Do Objects, Too

Of course, not all .NET developers coming to F# will want to immediately embrace the functional concepts. In fact, most developers coming to F# from C# or Visual Basic will need to know that they can fall back to old habits without breaking the language. And to an extent, they can.

Consider, for example, the class definition for the two-dimensional vector show at the top of Figure 3. A couple of interesting ideas come out of this. First, notice that there is no explicit constructor body; the parameters on the first line indicate the parameters by which people will construct Vector2D instances, essentially serving as a constructor. The length identifier, along with the dx and dy identifiers, thus become private elements inside the Vector2D type, while the member keyword indicates members that should be available outside of Vector2D, via standard .NET property access. In essence, this F# code declares what you see at the bottom of Figure 3 (as reported by Reflector).

Figure 3 Vector Variations in F# and C# (Close Figure 3)
            VECTOR2D IN F#
type Vector2D(dx:float,dy:float) =
    let length = sqrt(dx*dx + dy*dy)
    member obj.Length = length
    member obj.DX = dx
    member obj.DY = dy
    member obj.Move(dx2,dy2) = Vector2D(dx+dx2,dy+dy2)

VECTOR2D IN C# (REFLECTOR>
[Serializable, CompilationMapping(SourceLevelConstruct.ObjectType)]
public class Vector2D
{
  // Fields
  internal double _dx@48;
  internal double _dy@48;
  internal double _length@49;

  // Methods
  public Vector2D(double dx, double dy)
  {
    Hello.Vector2D @this = this;
    @this._dx@48 = dx;
    @this._dy@48 = dy;
    double d = (@this._dx@48 * @this._dx@48) +
               (@this._dy@48 * @this._dy@48);
    @this._length@49 = Math.Sqrt(d);
  }

  public Hello.Vector2D Move(double dx2, double dy2)
  {
    return new Hello.Vector2D(this._dx@48 + dx2, this._dy@48 + dy2);
  }

  // Properties
  public double DX
  {
    get
    {
      return this._dx@48;
    }
  }

  public double DY
  {
    get
    {
      return this._dy@48;
    }
  }

  public double Length
  {
    get
    {
      return this._length@49;
    }
  }
}

Remember that F#, like most functional languages, encourages immutable values and state. This should be obvious when looking at the code in Figure 3, since all the properties are read-only, and the Move member doesn't modify the existing Vector2D but instead creates a new one from the current Vector2D and applies the modifying values to it before returning it.

Notice, too, that the F# version is not only entirely thread safe but entirely accessible from traditional C# or Visual Basic code. This provides an easy way to get started with F#: using it to define business objects or other types that want or need to be thread safe and immutable. And while it is certainly possible to create types in F# that provide the usual set of mutable operations (set properties and the like), it requires more work and the use of the mutable keyword to do so. In a world where concurrency concerns are becoming the order of the day, this is exactly the way many argue it should be—immutable by default, mutable when necessary or desired.

Creating types in F# is interesting, but it's also possible to use F# to do what traditional C# or Visual Basic code can do, such as create a simple Windows Forms application and gather input from the user, as shown in Figure 4.

Figure 4 Windows Forms with F# (Close Figure 4)
#light

open System
open System.IO
open System.Windows.Forms
open Printf

let form = new Form(Text="My First F# Form", Visible=true)

let menu = form.Menu <- new MainMenu()
let mnuFile = form.Menu.MenuItems.Add("&File")
let filter = "txt files (*.txt)|*.txt|All files (*.*)|*.*"

let mnuiOpen =
  new MenuItem("&Open...",
    new EventHandler(fun _ _ ->
      let dialog =
        new OpenFileDialog(InitialDirectory="c:\\",
          Filter=filter;
          FilterIndex=2,
          RestoreDirectory=true)
        if dialog.ShowDialog() = DialogResult.OK then
          match dialog.OpenFile() with
          | null -> printf "Could not read the file...\n"
          | s ->
            let r = new StreamReader(s)
            printf "First line is: %s!\n" (r.ReadLine());
            s.Close();
    ),
    Shortcut.CtrlO)

mnuFile.MenuItems.Add(mnuiOpen)

[<STAThread>]
do Application.Run(form)

Any developer familiar with Windows Forms will be able to recognize fairly quickly what's happening here: a simple Form is created, some properties populated, an event handler filled, and the application is told to run until the user clicks the Close button in the upper-right corner. Standard stuff as .NET applications go, allowing focus to remain entirely on the F# syntax.

The open statement operates in much the same way as C#'s using statement, essentially opening a .NET namespace for use without formal qualifiers. The Printf namespace is an F# original, technically a port of the OCaml module of the same name. F# ships not only with full fidelity to the .NET Framework class library but also with a mostly straightforward port of the OCaml libraries, making programmers who are familiar with that language feel more at home with the .NET Framework. (For those who are curious, Printf lives inside of the assembly FSharp.Core.dll.) Nothing prevents you from using System.Console.WriteLine—it's a simple matter of aesthetic preference.

The creation of the form identifier makes use of F# named parameters, which is equivalent to instantiating the object and then making a series of property set calls to populate those properties with values. I do the same with the dialog identifier creates a few lines below that.

The definition of the mnuiOpen identifier contains an interesting construct, one which won't be too unfamiliar to developers familiar with anonymous delegates from the .NET Framework 2.0 or lambda expressions from the .NET Framework 3.5. In the construction of the EventHandler associated with the Open MenuItem, you see an anonymous function, defined using the syntax:

fun _ _ -> ...

Like anonymous delegates, this creates a function that will be invoked when the menu item is selected, but the syntax is a bit tricky.

The definition of the EventHandler part of the MenuItem definition is an anonymous function that ignores the two parameters passed to it, which neatly correspond to the sender and event arguments in the standard EventHandler delegate type. The function stipulates that a new OpenFileDialog is to be displayed and, when OK is clicked, the results examined ... somehow:

if dialog.ShowDialog() = DialogResult.OK then
    match dialog.OpenFile() with
    | null -> printf "Could not read the file...\n"
    | s ->
        let r = new StreamReader(s) in
        printf "First line is: %s!\n" (r.ReadLine());
        s.Close();

The results are examined using pattern matching, a powerful feature of the functional language world. Ostensibly similar in some ways to a switch/case from C#, pattern matching essentially does as its name implies: it compares a value with a variety of different patterns—not all of which need to be constant values—and executes the block of code that matches. So, for example, in the match block shown here, the result of OpenFile is matched against two possible values: null, meaning no file could be opened, or s, which is assigned any non-null value and then used as the constructor to StreamReader to open and read the first line of the given text file.

Pattern matching is a big part of most functional languages and deserves a bit of explanation. One of its most common uses is in conjunction with a discriminated union type, vaguely reminiscent of an enumerated type from C# or Visual Basic:

// Declaration of the 'Expr' type
type Expr =
  | Binary   of string * Expr * Expr
  | Variable of string
  | Constant of int

// Create a value 'v' representing 'x + 10'
let v = Binary("+", Variable "x", Constant 10)

This is commonly used in functional languages to create core representations of domain-specific languages that developers can use to write more complicated and powerful constructs. For example, it's not hard to imagine extending this syntax to create a full computation language that can easily be extended further simply by adding new elements to the Expr type. One thing to be careful of here; the syntax using the * character does not indicate that we use multiplication but is a standard way among functional languages to indicate a type that consists of multiple parts.

In fact, functional languages are commonly used to write language-oriented programming tools such as interpreters and compilers, with the Expr type eventually becoming the full set of language expression types. F# makes this even simpler by including two tools, fslex and fsyacc, specifically designed to take traditional language inputs—lex and yacc files—and compile them into F# code for easier manipulation. Download the F# installer to explore this if you're interested; in particular, the Parsing example in the standard F# distribution will provide a nice basic structure from which to get started.

The discriminated union is only one of the benefits of pattern matching; the second is the execution of the expressions, and you see that in Figure 5. The rec in the definition of eval is necessary to tell the F# compiler that eval will be invoked recursively within the body of the definition. Without it, F# will be expecting a local nested function named eval to be present. I use the function getVarValue to return some predefined values for the variables—in a real-world application, getVarValue would likely examine a Dictionary for the values to hand back, as established at the time the variable was created.

Figure 5 Expression Execution (Close Figure 5)
let getVarValue v =
    match v with
    | "x" -> 25
    | "y" -> 12
    | _ -> 0

let rec eval x =
    match x with
    | Binary(op, l, r) ->
        let (lv, rv) = (eval l, eval r) in
        if   (op = "+") then lv + rv
        elif (op = "-") then lv - rv
        else failwith "E_UNSUPPORTED"
    | Variable(var) ->
        getVarValue var
    | Constant(n) ->
        n

do printf "Results = %d\n" (eval v)

When eval is called, it takes the value v and discovers that it is a Binary value. This matches the first subexpression, which in turn binds the value (lv, rv) to the evaluated results of the left and right parts of the Binary value just examined. The unnamed value (lv, rv) is a tuple, essentially a single value of multiple parts, not unlike a relational set or C struct.

When eval l is first called, l from the Binary instance happens to be a Variable type, so the recursive call to eval matches against that branch of the pattern-matching block. This in turn calls getVarValue, which returns the hardcoded 25, which is ultimately bound against the value lv. The same sequence is executed for r, which is a Constant containing the value 10, so this is bound against rv. The remainder of the block is executed, an if/else-if/else block, which reads pretty easily for a developer familiar with C#, Visual Basic, or C++.

The key thing to recognize here is that, once again, every expression returns a value, even inside the pattern-matching block. In this case, the return value is an integer value, either the value of the operation, the value retrieved from the variable, or the constant itself. This single point, more than any other, seems to trip up developers more accustomed to object-oriented or imperative programming since, in C#, Visual Basic, or C++, having return values is optional and can be ignored even when specified. In functional languages such as F#, to ignore a return value requires an explicit coding idiom. In such cases, programmers can feed the results to a function called ignore, which does exactly as its name implies.

Asynchronous F#

Thus far, my dive into the F# syntax has taken one of two forms: either working with fairly simple functional constructs or making it look like a stranger—and more terse—variation on the traditional object-oriented, .NET-compliant languages (C#, Visual Basic, or C++/CLI). These make hardly a compelling argument for the adoption of F# within the enterprise.

But take a look at Figure 6. This most certainly does not fit either of the two previously mentioned categories. Aside from the ! characters that show up in a couple of places and the use of the async modifier, this appears to be relatively straightforward code: loading a source graphic image, extracting its data, passing that data to a standalone function for manipulation (rotation, skew, or whatever), and writing that data back into an output file.

Figure 6 Manipulating an Image (Close Figure 6)
let TransformImage pixels i =
  // Some kind of graphic manipulation of images

let ProcessImage(i) =
  async { use  inStream  = File.OpenRead(sprintf "source%d.jpg" i)
          let! pixels    = inStream.ReadAsync(1024*1024)
          let  pixels'   = TransformImage(pixels,i)
          use  outStream = File.OpenWrite(sprintf "result%d.jpg" i)
          do!  outStream.WriteAsync(pixels')
          do   Console.WriteLine "done!"  }

let ProcessImages() =
  Async.Run (Async.Parallel
               [ for i in 1 .. numImages -> ProcessImage(i) ])

What's not obvious is that the use of the async modifier makes this code into what F# calls an asynchronous workflow (no relation to Windows Workflow Foundation), meaning that each of these load/process/save steps is taking place on parallel threads from a .NET thread pool.

Putting it more simply, consider the code in Figure 7. This particular sequence shows off async workflows but in a fairly simple and easy-to-swallow way. Without going into too much detail, evals is an array of functions waiting to be executed, each of which is queued for execution on the thread pool by the Async.Parallel call. When executed, it becomes apparent that the functions inside of evals are, in fact, on a separate thread from the function in awr (though due to the nature of the .NET system thread pool, some or all of the evals functions may be executed on the same thread).

Figure 7 Executing Functions Asynchronously (Close Figure 7)
#light
open System.Threading

let printWithThread str =
    printfn "[ThreadId = %d] %s" Thread.CurrentThread.ManagedThreadId str

let evals =
    let z = 4.0
    [ async { do printWithThread "Computing z*z\n"
              return z * z };
      async { do printWithThread "Computing sin(z)\n"
              return (sin z) };
      async { do printWithThread "Computing log(z)\n"
              return (log z) } ]

let awr =
    async { let! vs = Async.Parallel evals
            do printWithThread "Computing v1+v2+v3\n"
            return (Array.fold_left (fun a b -> a + b) 0.0 vs) }

let R = Async.Run awr
printf "Result = %f\n" R

The fact that these are executed out of the .NET thread pool highlights once again just how well the F# language supports interoperability with the underlying runtime. Its reliance on the .NET Framework class library even for areas traditionally reserved for specialized implementation (such as threading) in functional languages means that the C# programmer can make use of F# libraries or modules, just as F# developers make use of C# libraries. In fact, in the future, F# features such as async tasks will be able to take advantage of new .NET Framework libraries such as the Task Processing Library in the Parallel Extensions library.

Fitting in with F#

If it's not apparent already, there is much more to be said about the F# language than can be covered in a single article; in fact, between the new syntax and the entirely new way of thinking (functional vs. imperative), it's fair to say that for the average object-oriented developer accustomed to C# or Visual Basic, mastering F# will take some time. Fortunately, F# remains entirely interoperable with the rest of the .NET ecosystem, meaning you can take advantage of much existing knowledge and many existing tools to help bring F# into your coding arsenal.

All the foundation libraries are fully accessible to the F# developer, and, since F# supports some aspects of imperative and object development, it's fully feasible to consider using the F# interactive mode as a way of learning both the F# syntax and the details of Windows Presentation Foundation, Windows Communication Foundation, or Windows Workflow Foundation without having to pause for compile cycles.

As already mentioned, developers can write business objects in F# for use by other parts of their application code. Because the F# type construct produces classes that are, for the most part, identical to their C# or Visual Basic equivalents, persistence libraries like NHibernate will persist the F# types without trouble, making for a seamless injection of F# into working business applications.

Merely learning F# will help you comprehend many of the features of future versions of C# and Visual Basic, since many of those ideas and concepts—including generics, iterators (the yield keyword in C#), and LINQ—were born of functional roots and the research conducted by the F# team. Regardless of how you look at it, functional programming is here, and it's here to stay.

Ted Neward is an independent consultant specializing in high-scale enterprise systems. He is the author and coauthor of several books, a Microsoft MVP Architect, a BEA Technical Director, an INETA speaker, and a PluralSight instructor. Reach Ted at ted@tedneward.com or visit his blog at blogs.tedneward.com.


Editor's Note: A Large Release Wave is Headed your Way

Editor's Note
A Large Release Wave is Headed your Way
Howard Dierking

This issue of MSDN Magazine comes during the largest release wave in the history of Microsoft, a wave consisting of Windows Server 2008, SQL Server 2008, and Visual Studio 2008. In my December Editor's Note, I talked about all of the new features that were planned for the Visual Studio 2008 release timeframe. Hard on the heels of that release came talk of entirely new platforms with names like "Oslo" and "Volta." Likewise, there were formal announcements of new programming languages, such as F#. And if that wasn't enough, we now have two additional (and enormous) releases: Windows Server 2008 and SQL Server 2008. So what do these releases mean to you? Aren't they technologies for system administrators?

When I worked on the certification team here at Microsoft, we grouped job roles into one of three major categories: developer, IT professional, and information worker. Developers are individuals like you—people who write software using tools such as Visual Studio. IT pros are folks who design, implement, and maintain IT infrastructure components such as Windows domains, SQL Server clusters, and Web server farms. Lastly, information workers describe people who represent customers for many of you. They are individuals who use products such as Microsoft Office and custom business apps to perform potentially non-technology-related tasks.

Why am I spending time describing these categories? Because, as I believe this latest launch wave illustrates, the lines that separate these categories are simply not as clear as they once were. For example, when you consider a technology like SQL Server, where is the line separating developer from IT pro? Naturally, there are a few obvious tasks that can cleanly fall into one category or another (backups come to mind), but should a developer care about indexes and query plans? Should an IT professional understand something about SQL CLR to properly manage a SQL Server instance? And if the delineation still seems too clear with SQL Server, consider SharePoint—it is a technology for developers, IT pros, and information workers (and a few other categories that we don't even have names for).

The point here is that in order to be successful as developers these days, we need to look beyond our compilers (and interpreters if you are of that persuasion) and develop a broader knowledge base. We need to understand, if even at a basic level, how networks are put together and how directory systems such as Active Directory are used to manage them. We need to develop an appreciation for how business works so that we can know how to best leverage technologies such as SQL Server Analysis Services, SQL Server Reporting Services, and SharePoint to provide better information to decision makers.

And it is this idea of building a broader knowledge base that makes this launch wave so exciting. In this issue, we talk about how new platform technologies can enable you to build better apps, both for the information workers who will use them and the system administrators who will manage them. From leveraging new capabilities in platform services such as IIS, to building custom functionality into existing information worker applications such as Microsoft Office, to bringing together the developer and DBA in the product lifecycle, this issue has something for everyone.

Finally, I want to call out one last point of interest. This issue has an official issue date of "Launch." As such, the order of your 2008 issues will go as follows: January, February, Launch, March, etc ... While initially confusing, we took advantage of the launch to do something revolutionary—that is, to bring the issue date closer to the actual date. Practically, this means that now, while your launch issue will arrive sometime in February, your March issue will arrive ... amazingly enough ... in March!

Thanks to the following Microsoft technical experts for their help with this issue: David Aiken, David Anson, Matt Gibbs, Ed Hintz, Luke Hoban, Dominic Hopton, David Kline, Thomas Marquardt, Keith Pijanowski, Dave Reed, Don Smith, Bill Staples, and Don Syme.


Toolbox: SQL Code Completion, Subversion Tools, Agile Development, and more

Toolbox
SQL Code Completion, Subversion Tools, Agile Development, and more
James Avery

Contents
Get Help Writing SQL
Integrating Visual Studio and Subversion
Rapid App Development
The Bookshelf

Get Help Writing SQL

As a developer, it can be tough to switch languages. For instance, if you are using Visual Basic® .NET and need to write some C#, it can take time to remember the equivalent C# syntax. Happily, it's rare that as developers using the Microsoft®.NET Framework we need to write both Visual Basic .NET and C# on a routine basis. There is another language we write in quite a bit—SQL. But unlike Visual Basic .NET and C#, both of which provide IntelliSense® to help us remember language keywords, Visual Studio® 2005 and SQL Server® Management Studio don't provide any clues at all.

This is where SqlAssist comes to the rescue. There are two editions of SqlAssist that provide IntelliSense-like code completion: one for Visual Studio 2005 and another for SQL Server Management Studio.

The code completion in SqlAssist starts with basic SQL commands. For instance, instead of typing FROM you can simply type "f" and then press the Tab key. It also includes support for database tables and columns (even when they are aliased). In addition, SqlAssist includes support for local variables and displays parameter information when calling stored procedures or functions.

But perhaps the most interesting code-completion feature occurs when you are writing a SQL statement and you start to write a join clause: you will see a list of the foreign keys for the table you are querying. Select a foreign key, and the join clause will be automatically added.

SqlAssist also provides some valuable text editor support. When you type a table or column name, it will automatically ensure that the name is properly cased. For instance, vendoraddress will automatically be changed to VendorAddress. Furthermore, when opening SQL files, it is not uncommon for them to be formatted in strange ways (strange being any way other than how you like it); SqlAssist provides a SQL formatting functionality that will automatically reformat the file based on configurable options.

SqlAssist also provides a mechanism to create and store SQL code snippets, called SQL templates. You can easily create templates for commonly used SQL or for headers, and then you can drag them into your query when needed.

The Visual Studio add-in also provides excellent support for working with SQL files in Visual Studio. Simply open a SQL file and you will be able to connect to your database, get the support of SqlAssist's code completion, and execute your script files. Since installing SqlAssist, I haven't found myself opening SQL Server Management Studio except for true administration tasks like attaching or detaching databases.

Price: $49 per edition, $75 for both.

roundpolygons.com

SqlAssist Provides Code Completion
SqlAssist Provides Code Completion (Click the image for a smaller view)
SqlAssist Provides Code Completion
SqlAssist Provides Code Completion (Click the image for a larger view)

Integrating Visual Studio and Subversion

Subversion is an open-source source-control system that has grown in popularity over the past couple of years. It takes a simple and user-friendly approach to handling many of the common source-control features like branching, tagging, and merging. Subversion's popularity is due to a number of factors: it is free and open source, it is easy to install and use, and it has excellent tools like TortoiseSVN. A Windows® Explorer extension, TortoiseSVN gives you the ability to perform all of your source-control functions right in the standard Explorer window instead of relying on a separate tool.

SourceForge has started providing Subversion hosting, and CodePlex even provides Subversion emulation so TortoiseSVN can be used with the site. With some .NET shops moving to Subversion, one of the features most often missed is the close integration with Visual Studio available through other popular source-control systems.

Enter VisualSVN, a Visual Studio add-in that provides excellent integration between Visual Studio and Subversion. When you use VisualSVN, you will see easy-to-understand markers next to each of your files in the Solution Explorer provided by Visual Studio (note that your solution must be stored in a Subversion repository). The markers show when a file is modified, unmodified, or conflicted. You can right-click on the file and show changes, revert changes, and update or commit.

Using the VisualSVN menu, you can manage changes for the entire project. The VisualSVN menu also contains shortcuts to common TortoiseSVN functions like the repo-browser, creating and applying patches, showing a subversion log, as well as branching, merging, and switching repositories.

One of the most important agile concepts is checking in early and often. By integrating as quickly as possible, you discover any potential merge issues, and if you implement continuous integration, all of your code will be built and your tests will be run. VisualSVN makes it easier to check in early and often because of the close integration with Visual Studio—you will constantly be reminded that you have modified changes that you haven't yet checked in. There's no reason to procrastinate since you can check those changes in directly from within Visual Studio.

Price: $49.

visualsvn.com

VisualSVN Provides Function Shortcuts
VisualSVN Provides Function Shortcuts (Click the image for a smaller view)
VisualSVN Provides Function Shortcuts
VisualSVN Provides Function Shortcuts (Click the image for a larger view)

Rapid App Development

This past fall, Microsoft announced the ASP.NET MVC Framework, which enables developers to write Model View Controller-based Web applications. Many ASP.NET developers have been clamoring for this as an alternative to the standard ASP.NET page model.

Don't worry—the ASP.NET page model isn't going away. This just means that now we have another option. The ASP.NET MVC Framework will make it easier to develop Web applications with a clear separation of concerns. The page logic will be stored in your controller, the HTML will be in the view, and the application logic will be in your model.

The ASP.NET MVC Framework is the first piece of the puzzle for Microsoft. A month or so after the MVC Framework was announced, the second piece of the puzzle fell into place: Microsoft hired Rob Conery to work full-time on his project, SubSonic.

SubSonic is an application toolset that is centered on its ability to completely generate your data access layer. Unlike some other Object Relation Mapping (ORM) frameworks, SubSonic takes the approach of generating and compiling your data access layer as opposed to performing a reflection-based mapping at run time (and for this reason, some would call it a code generation tool rather than an ORM).

SubSonic is different from other ORMs in how it maps your tables to your domain model. NHibernate, another ORM for .NET, takes the approach of specifying your model (or business entities) and then mapping those entities to your database through either XML or .NET attributes. SubSonic switches that around and creates a model based on your database schema. Both approaches have their advantages and disadvantages, but usually I find it comes down to personal preference.

To get a complete model of your database, install SubSonic and configure it in your app by way of a few settings in your web.config file. The model consists of simple classes with the same names as your tables, which contain methods for all of the common database actions. For instance, a Product table would translate to a Product class and have methods like Save, Destroy, and Where, the latter being used to query the database. A separate Query object is available for complicated queries.

Unlike most ORM tools, SubSonic provides a graceful way to handle stored procedures. Stored procedures can be wrapped in a single class and are called the same way you would call a method. Accessing views is also supported.

While the ORM portion of SubSonic is its largest feature, SubSonic also includes numerous other helper functions and controls. One of the more useful controls, Scaffold, makes it easy to get a quick application up and running—and while you might not use it for your production code, it's perfect for building a prototype. Some of the more useful helper methods center on validations with a host of different validation methods available for just about every scenario (numeric, alpha-only, ZIP codes, every major credit card, and so on)

SubSonic on its own is a very powerful application toolset; when paired with ASP.NET MVC, it becomes a valuable application framework that will be a compelling competitor to other rapid development application frameworks available in .NET and other languages.

Price: Free, source code available.

subsonicproject.com

The Bookshelf

When Agile methodologies first started getting popular, they were largely disconnected from the modeling and project management software at the time; unified modeling language-heavy design phases and gigantic Gannt charts were counter to everything Agile was championing. As Agile methodologies have become more mainstream, tools have been created to cater to Agile projects. In fact, the premier application lifecycle management tool from Microsoft, Visual Studio Team System (VSTS), was built to work well with Agile (although you don't have to use Agile to use VSTS).

Visual Studio Team System: Better Software Development for Agile Teams by Will Stott and James W. Newkirk (Addison-Wesley Professional, 2007) is an excellent introduction to both the Team System tool and to working on an Agile project. The book introduces the pieces of Visual Studio Team System and explains how they fit together.

Following this is a brief lesson on Extreme Programming. You're better off reading a separate book dedicated to Extreme Programming, however, if you want to use it on your project. The book then goes into more detail on the Microsoft Solutions Framework for Agile processes and how to go about changing your current process.

Then the book starts digging into the details. It walks through source control, continuous integration, and test-driven development, highlighting how Team System provides functionality that lines up well with these core Agile practices. Each section not only includes good practical advice but includes step-by-step Team System instructions and screenshots.

The book goes on to cover an Agile-friendly method of modeling with Class Responsibility Collaborator (CRC)card-inspired, lightweight modeling. I was pleasantly surprised to see a section dedicated to the Framework for Integrated Test (FIT). FIT is an open-source testing tool that can enable your users or business analysts to drive the testing of your app.

The book is based on Visual Studio Team System 2005, but Visual Studio Team System 2008 will probably have shipped by the time this column is published. The general guidance will still be relevant, but the version-specific instructions and screenshots may become out of date.

There are better books on Agile, and there are others that take a more methodology-independent approach to learning Team System. However, if you are in that perfect cross-section of starting an Agile project using Team System, this book is the ideal place to start.

Price: $49.99.

aw-bc.com

(Click the image for a smaller view)

Send your questions and comments to toolsmm@microsoft.com.

James Avery runs his own .NET consulting practice, Infozerk Inc. His most recent book is Windows Developer Power Tools (O'Reilly, 2006). You can e-mail him at javery@infozerk.com and read his weblog at www.dotavery.com/blog.


Cutting Edge: Modal Dialog Boxes with AJAX

Cutting Edge
Modal Dialog Boxes with AJAX
Dino Esposito

Code download available at: CuttingEdge2008_Launch.exe (419KB)
Contents
The ModalPopupExtender Control
Closing the Popup Using the Esc Key
Adding Animation on Display
Returning Data to the Server
It's Your Turn

D
ialog boxes have been around in Windows® for a long time, and they do have their advantages. But if you want your Web application to have dialog boxes, you're basically stuck with popups, and, as you know, most users disable them with popup blockers. So what are you to do if you need a popup dialog?

With Microsoft® ASP.NET AJAX, dialog boxes are especially important for displaying context-sensitive information without a page reload or new page. It is therefore important to devise a different implementation of dialog boxes that are as effective as modal popups but hassle-free for users.

Neither ASP.NET nor AJAX extensions have built-in support for popups or Web dialog boxes; however, the AJAX Control Toolkit does. The AJAX Control Toolkit is a shared-source library of ASP.NET extender controls. One of its most useful controls is the ModalPopupExtender control that I briefly introduced in the January 2008 installment of Cutting Edge (msdn.microsoft.com/msdnmag/issues/08/01/CuttingEdge). The ModalPopup extender takes the markup generated by a server-side ASP.NET panel and shows or hides it as the user clicks on an associated HTML element. Initially styled as hidden, the dialog box is downloaded onto the client when the page is loaded and then shown or hidden on demand. How is modality guaranteed? Take a look at the code snippet in Figure 1.

Figure 1 Details of the ModalPopupExtender Control (Close Figure 1)
// Code excerpted from modalpopupbehavior.js.
// Method initialize()
//
// Download the source code of the AJAX Control Toolkit from
// http://www.codeplex.com/atlascontroltoolkit.
//

// The panel defined as the popup is saved as the foreground element.
this._foregroundElement = this._popupElement;

// A new DIV tag is created as the background element for the panel.
// The panel is invisible and placed at 0,0 coordinates (fixed position).
// In addition, the background element is given a high z-index so that it
// sits above everything else.
this._backgroundElement = document.createElement('div');
this._backgroundElement.id = this.get_id() + '_backgroundElement';
this._backgroundElement.style.display = 'none';
this._backgroundElement.style.position = 'fixed';
this._backgroundElement.style.left = '0px';
this._backgroundElement.style.top = '0px';
this._backgroundElement.style.zIndex = 10000;

// The background element is styled as specified.
if (this._BackgroundCssClass)
{
     this._backgroundElement.className = this._BackgroundCssClass;
}

// The background element is appended to the parent of the foreground
// element.
this._foregroundElement.parentNode.appendChild(this._backgroundElement);

// The foreground element is programmatically hidden from view. In
// addition, it is given absolute positioning and an higher z-index than
// the background element.
this._foregroundElement.style.display = 'none';
this._foregroundElement.style.position = 'fixed';
this._foregroundElement.style.zIndex = $common.getCurrentStyle(
this._backgroundElement, 'zIndex', this._backgroundElement.style.zIndex) + 1;

// A click handler is added to the target element of the extender. In
// this case, It is the DOM element whose ID is passed on as the
// TargetControlID property.
this._showHandler = Function.createDelegate(this, this._onShow);
$addHandler(this.get_element(), 'click', this._showHandler);

The code shows an excerpt from the script used to initialize the ModalPopup extender on the client. Note that an ASP.NET AJAX extender usually comprises a server control—the extender—and a client behavior class written in JavaScript. The code in Figure 1 comes from the ModalPopup extender's client behavior class. If you download the source code for the AJAX Control Toolkit from codeplex.com/atlascontroltoolkit, you'll find the aforementioned script code in the modalpopupbehavior.js file.

As you can see in Figure 1, the root element of the dialog's markup tree is designated as the foreground element and programmatically hidden from view. At the same time, a DIV tag is dynamically created and designated as the background element. The tag is given a fixed position at coordinates 0,0 and styled appropriately. The DIV tag is also given an extremely high (but arbitrary) z-index that virtually ensures it will sit on top of all other elements in the document, since the z-index property sets the stack order of an HTML element, and the element with the greatest stack order is always displayed on top of elements with lower stack order.

The DIV is then added to the Document Object Model (DOM) as a child of the foreground element's parent. Finally, the foreground element—the dialog box content—is given a z-index just higher than that of the background.

The net effect of this intermediate DIV is that a new transparent element is layered over all page elements except the popup panel. A click handler is also dynamically applied to the extender controls target in order to ensure that any user input performed outside the popup panel (on the foreground element) is lost and never reaches the intended target. This is the behavior that ensures modality.

The content of the server panel you define in the source ASPX page is popped up as a modal window, just like a classic Windows message box. At the same time, it delivers an unprecedented level of UI flexibility—after all, it's an ASP.NET panel, so you can fill it with any combination of controls you like, and you can style it however you like.

The ModalPopupExtender Control

Setting up a modal popup using the AJAX Control Toolkit library is a relatively simple task. You start by defining a panel to provide the UI, and then add a button control to trigger the display of the dialog:

<asp:Button runat="server" ID="btnEditCustomer"
    Text="Edit text" />
<asp:Panel runat="server" ID="pnlEditCustomer">
  ...
</asp:Panel>

Next, you set up the extender and specify the target control ID and the popup control ID:

<act:ModalPopupExtender ID="ModalPopupExtender1"
    runat="server"
    TargetControlID="btnEditCustomer"
    PopupControlID="pnlEditCustomer"
    BackgroundCssClass="modalBackground"
    OkControlID="editBox_OK"
    OnOkScript="yes()" />

The target control ID of a ModalPopup extender is the ID of the server control that, when clicked, causes the dialog box to pop up. The popup control ID is the ID of the server control that provides the content for the dialog box.

The AJAX Control Toolkit framework does not provide any default style for the dialog box. The user has to employ the visual elements in the panel to dismiss the dialog box. Figure 2 shows a sample dialog box in action. It allows confirmation and provides some information. But if you want context-sensitive dialogs where some further user interaction is required, you need to do some more work.

Figure 2 Sample Web Modal Dialog Box in Action
Figure 2 Sample Web Modal Dialog Box in Action(Click the image for a smaller view)
Figure 2 Sample Web Modal Dialog Box in Action
Figure 2 Sample Web Modal Dialog Box in Action (Click the image for a larger view)

To make the ModalPopup extender more like that of a Windows dialog box, a number of features can be added. For instance, the ability to dismiss the dialog box by simply pressing the Esc key—a common functionality in Windows but not supported yet in the AJAX Control Toolkit—is a good idea.

Before I implement any of these features, let's briefly review the methods and properties of the ModalPopupExtender control. Figure 3 lists its properties but does not include all the properties automatically inherited from base classes.

Figure 3 Properties of the ModalPopupExtender Control (Close Figure 3)

Property | Description
BackgroundCssClass | The CSS class linked to the host application to apply to any content underneath the modal popup when the popup is displayed.
DropShadow | Boolean property that indicates whether the extender will automatically add a drop shadow to the modal popup. True by default.
OkCancelID | ID of the DOM element in the displayed UI that dismisses the modal popup with a Cancel action.
OkControlID | ID of the DOM element in the displayed UI that dismisses the modal popup with an OK action.
OkCancelScript | The name of a JavaScript function linked to the page to run when the modal popup is dismissed with the Cancel action.
OnOkScript | The name of a JavaScript function linked to the page to run when the modal popup is dismissed with the OK action.
PopupControlID | ID of the root element in the DOM tree to display as the content of the modal popup.
PopupDragHandleControlID | ID of the embedded element that contains the header/title that will be used as a drag handle for the whole popup.
RepositionMode | Indicates whether the popup needs to be repositioned when the browser window is resized or scrolled. Feasible values belong to the ModalPopupRepositionMode enumerated type.
TargetControlID | ID of the DOM element that, when clicked, activates the modal popup.
X | X coordinate of the top-left corner of the modal popup.
Y | Y coordinate of the top-left corner of the modal popup.

The signature of the ModalPopupExtender control class is shown here:

    public class ModalPopupExtender :
        DynamicPopulateExtenderControlBase

The base class here is a library-defined extender that provides DynamicPopulate support to multiple extenders. DynamicPopulate is another extender in the AJAX Control Toolkit that replaces the markup of a DOM element with text returned by a Web service call. The ModalPopup extender has dependencies on other AJAX Control Toolkit extenders including DropShadow and DragPanel.

The content of the dialog box is fully determined by a DOM sub-tree rooted in an element specified through the PopupControlID property. Quite often, this element will be an ASP.NET server control such as a Panel. The position of the popup is determined by the X and Y properties, but the popup will be centered horizontally if no coordinates are specified.

Just as in any classic Windows dialog box, the Web modal popup can be dragged around. To enable this, you specify in the PopupDragHandleControlID property the ID of the element to be used as the drag handle, and the embedded script does the rest. Note that you can drag the modal popup around, but only within the page area covered by the DOM. In other words, if your page area is defined by a DIV with a height of 100 pixels, and you display the page in a browser window of 1600 by 1024 pixels, you can only drag the popup vertically for 100 pixels, regardless of the physical height of the browser window.

By setting the RepositionMode property to any value but "None" (the default), you enable the behavior's script to update the position of the popup when the user scrolls or resizes the Web browser window (see Figure 4).

Figure 4 Repositioning the Modal Dialog
Figure 4 Repositioning the Modal Dialog(Click the image for a smaller view)
Figure 4 Repositioning the Modal Dialog
Figure 4 Repositioning the Modal Dialog (Click the image for a larger view)

Closing the Popup Using the Esc Key

In Windows, the user can dismiss a message or dialog box by pressing the Esc key. In the AJAX Control Toolkit, this ability is not present natively in the modal popup behavior. As you'll see in a moment, though, detecting and handling the Esc key is not a big issue. Consider the following JavaScript code, which is attached to the KeyDown event of the page's DOM:

   function OnKeyPress(args)
   {
     if(args.keyCode == Sys.UI.Key.esc)
     {
       $find("ModalPopupExtender1").hide();
     }
   }

The code is triggered by keystrokes that affect the entire document. The handler receives a Sys.UI.DomEvent object through the args parameter. The object describes the mouse and keyboard status when a DOM-level event is caught. In particular, the keyCode property indicates the ASCII code of the key pressed. Additional information about the Ctrl, Shift, and Alt keys is available, as well as information about the mouse position and button status. The Sys.UI.Key enumeration lists some predefined constants for the code of the most commonly used keys, including Esc, Enter, and Del buttons. Using these tools, detecting if the Esc key is pressed is a breeze. Now let's see how you can programmatically hide the popup.

An AJAX Control Toolkit extender has a client and server programming interface. The server interface is an ASP.NET server control; the client interface is a JavaScript behavior class. This class exposes a JavaScript programming model that mirrors the server-side programming model, so each of the properties in Figure 3 has its JavaScript counterpart. The container of these properties is an object with the ModalPopup extender control ID. This control is not part of the DOM, as it is created by the infrastructure of the Microsoft AJAX Library. Hence, you can't use the $get function to get an instance of a behavior class; you must use the $find function instead. Both the server and client API of the modal popup have show and hide methods to control visibility.

Note that the OnKeyPress event handler will not work unless you register it with the Microsoft AJAX Library. The best place to run this registration code is the pageLoad function, like so:

function pageLoad(sender, args)
{
  $addHandler(document, "keydown", OnKeyPress);
}

The Microsoft AJAX Library invokes the pageLoad function when all of its startup tasks have completed and everything in the library and the page has been initialized. The library also automatically wires its loading stage to any JavaScript function with that name that can be found in the page. Now, any modal popup managed through the AJAX Control Toolkit's ModalPopup extender can be dismissed with the Esc key.

Adding Animation on Display

With this completed, we can progress to other things. Wouldn't you love to achieve the same kind of fade-in effect Windows Vista® uses for your Web applications? The effect is built-in for regular Windows-managed windows, such as the window that pops up when the window.alert method is invoked. On custom modal popups, like those I'm building here, you have to provide for it yourself.

Animation may become a native feature of the ModalPopup extender in the near future, but, for the time being, getting it to work is relatively easy thanks to the animation API already available in the AJAX Control Toolkit library. The following code refers to the Animation extender, which supports animation for a number of common predefined DOM events such as OnLoad, OnClick, OnMouseOver, and OnMouseOut. The TargetControlID points to the DOM element whose events will trigger the animation:

<act:AnimationExtender ID="popUpAnimation" runat="server"
  TargetControlID="btnViewMore">
  <Animations>
    <OnClick>
      <Parallel AnimationTarget="pnlViewCustomer"
        Duration=".3" Fps="25">
        <FadeIn />
      </Parallel>
    </OnClick>
  </Animations>
</act:AnimationExtender>

Animations can be combined in a sequence, and a few can even be played in parallel. The preceding code just fades in the panel associated with a modal popup for the specified duration and number of frames after the button is clicked. The code below shows a more sophisticated animation that executes multiple effects:

<act:AnimationExtender ID="popUpAnimation" runat="server"
  TargetControlID="btnViewMore">
  <Animations>
    <OnClick>
      <Parallel AnimationTarget="pnlViewCustomer"
        Duration=".3" Fps="25">
        <Move Horizontal="100" Vertical="100" />
        <Resize Width="280" Height="180" />
        <Color PropertyKey="backgroundColor"
          StartValue="#FFFFFF"
          EndValue="#FFFF00" />
      </Parallel>
    </OnClick>
  </Animations>
</act:AnimationExtender>

Here, the modal popup is first moved to a new relative position and then resized and colored. The gallery of examples at asp.net/AJAX/AjaxControlToolkit/Samples/Animation/Animation.aspx shows some demos; however, most of them can't be applied to a modal popup without modifying the source code. The main issue lies in the fact that the modal popup is displayed before the animation starts. While that might work for a simple fade-in effect, if you want to create explosions or wipe-out effects, you need to get clever.

The ModalPopup extender fires a few helpful events on the client, including the showing event. Here's how you can subscribe to it:

function pageLoad(sender, args)
{
    $find("ModalPopupExtender1").add_showing(onModalShowing);
}

Any code you associate with the showing event runs just before the popup is displayed. You can use this event to perform any client-side initialization tasks. Other client events to which you can bind include hiding, hidden, and shown.

Initializing the Elements of the Popup

On the client, the ModalPopup extender toggles the visibility of the DOM tree, as identified by the PopupControlID attribute. In the source code for the client behavior, you'll see additional code to intercept markup that was downloaded from the server as a constituent part of the host page, and you can style it for display.

For this reason you can't make changes to the template or content of the popup once the page has been served. Consider the following scenario. Your user chooses a customer from, say, a dropdown list and views some details. Next, he may want to edit some of the customer information. In a traditional Web application, you just redirect the user to a new page or, perhaps, make a postback to load a different view. In ASP.NET 2.0 and newer versions, this is what the DetailsView control does. In a desktop application, you would use a modal dialog box. For AJAX-enabled applications, a Web dialog box is now an option.

The dialog box used to edit a customer, however, has a fixed layout that is populated with new content every time it is going to pop up. Suppose that you originate a full postback when a customer is selected. In the selection-changed event, you update the customer view and optionally the controls in the modal popup. In this way, when the button is clicked to pop up the dialog box the latest information is already loaded.

But an AJAX application doesn't normally perform full postbacks. Figure 5 shows a fragment of an ASP.NET AJAX page that uses partial rendering to update a customer view instead. The updatable region is linked to the SelectedIndexChanged event on the child dropdown list control. The dropdown list, in turn, has the AutoPostBack property set to true. The net effect is that whenever the user changes the selection in the dropdown list, the table in Figure 5 is updated without a full page reload. So far, so good.

Figure 5 Using Partial Rendering to Update the Customer View (Close Figure 5)
<table>
  <tr>
    <td valign="top">
      <b>Customers</b><br />
      <asp:DropDownList id="ddlCustomers" runat="server"
        DataSourceID="odsCustomers"
        DataTextField="CompanyName"
        DataValueField="ID"
        AutoPostBack="true"
        OnSelectedIndexChanged="ddlCustomers_SelectedIndexChanged"
        ondatabound="ddlCustomers_DataBound" />
      <asp:ObjectDataSource ID="odsCustomers" runat="server"
        TypeName="IntroAjax.CustomerManager"
        SelectMethod="LoadAll">
      </asp:ObjectDataSource>
    </td>
    <td valign="top">
      <asp:UpdatePanel ID="UpdatePanel1" runat="server">
        <ContentTemplate>
          <table>
            <tr>
              <td><b>Customer ID:</b></td>
              <td>
                <asp:Label runat="server" id="lblCustomerID" />
              </td>
            </tr>
            <tr>
              <td><b>Company Name:</b></td>
              <td>
                <asp:Label runat="server" id="lblCompanyName" />
              </td>
            </tr>
            <tr>
              <td><b>Contact Name:</b></td>
              <td>
                <asp:Label runat="server" id="lblContactName" />
              </td>
            </tr>
            <tr>
              <td><b>Country:</b></td>
              <td><asp:Label runat="server" id="lblCountry" /></td>
            </tr>
          </table>
        </ContentTemplate>
        <Triggers>
          <asp:AsyncPostBackTrigger ControlID="ddlCustomers"
            EventName="SelectedIndexChanged" />
        </Triggers>
      </asp:UpdatePanel>
    </td>
  </tr>
</table>

The next step entails updating the panel in Figure 6, which represents the content of the modal popup. The panel must be in sync with the customer's view.

Figure 6 The Content of a Modal Popup Supports Partial Rendering (Close Figure 6)
<asp:Panel ID="pnlEditCustomer" runat="server"
  CssClass="modalPopup" style="display:none">
  <div style="margin:10px">
    <asp:UpdatePanel runat="server" ID="ModalPanel1"
      RenderMode="Inline" UpdateMode="Conditional">
      <ContentTemplate>
        <table>
          <tr>
            <td><b>Customer ID:</b></td>
            <td>
              <asp:Label runat="server" id="editCustomerID" />
            </td>
          </tr>
          <tr>
            <td><b>Company Name:</b></td>
            <td>
              <asp:TextBox runat="server" id="editTxtCompanyName" />
            </td>
          </tr>
          <tr>
            <td><b>Contact Name:</b></td>
            <td>
              <asp:TextBox runat="server" id="editTxtContactName" />
            </td>
          </tr>
          <tr>
            <td><b>Country:</b></td>
            <td>
              <asp:TextBox runat="server" id="editTxtCountry" />
            </td>
          </tr>
        </table>
        <hr />
        <asp:Button ID="btnApply" runat="server"
          Text="Apply" OnClick="btnApply_Click" />
      </ContentTemplate>
    </asp:UpdatePanel>

    <asp:Button ID="editBox_OK" runat="server"
       Text="OK" OnClick="editBox_OK_Click" />
    <asp:Button ID="editBox_Cancel" runat="server" Text="Cancel" />
  </div>
</asp:Panel>

You can update the panel being displayed in the popup when a new customer is displayed or just before the popup is displayed:

<act:ModalPopupExtender ID="ModalPopupExtender1" runat="server"
  TargetControlID="hiddenTargetControlForModalPopup"
  PopupControlID="pnlEditCustomer"
  BackgroundCssClass="modalBackground"
  DropShadow="false"
  OkControlID="editBox_OK"
  OnOkScript="ok()"
  OnCancelScript="cancel()"
  CancelControlID="editBox_Cancel" />

If you update the popup content just before display (the approach I recommend), then your dialog initialization code needs to run on the server, but you won't have any postback event from any control that brings up the modal dialog. And even if you did, it would be too late for you to edit the controls in the dialog box. The ModalPopup extender, in fact, adds a client-side onclick event handler to the target control and prevents the default action—in this case, a postback—from taking place.

The idea, then, is to use a fake target control for the ModalPopup extender so that the extender will never be automatically kicked off out of your control. How would you then trigger the modal popup? Here's an example. When the button below is clicked, it executes its server-side OnClick handler over a partial rendering operation:

<asp:UpdatePanel runat="server" ID="DialogBoxUpdatePanel"
  UpdateMode="Conditional">
  <ContentTemplate>
    <asp:Button runat="server" ID="btnEditText"
      Text="Edit text"
      OnClick="btnEditText_Click" />
  </ContentTemplate>
</asp:UpdatePanel>

This has two benefits. First, no full refresh affects the page. Second, the OnClick server code can be used to properly initialize the popup panel and then order the extender to show the popup:

protected void btnEditText_Click(object sender, EventArgs e)
{
    InitDialog();
    ModalPanel1.Update();
    ModalPopupExtender1.Show();
}

The InitDialog method contains the internal code required to initialize all controls in the panel in Figure 6. This code is sufficient to change the state of involved controls but doesn't modify their markup. This is because you're executing the code over a partial rendering postback. So in the next step, you explicitly refresh the updatable panel. Finally, call the Show method on the ModalPopup extender. This last call ensures that the script to show the dialog as the page loads is properly downloaded to the browser. Figure 7 shows a sample page in action designed along these guidelines.

Figure 7 Modal Dialog Box with Data from Current Context
Figure 7 Modal Dialog Box with Data from Current Context(Click the image for a smaller view)
Figure 7 Modal Dialog Box with Data from Current Context
Figure 7 Modal Dialog Box with Data from Current Context (Click the image for a larger view)

Do you really need all those partial rendering regions in the page? Well, if your only purpose is find out how to initialize the dialog box with server code, then you could perhaps save some updatable regions. However, if your dialog requires server-side initialization work, then it likely requires some work to update the underlying page with collected data. In that case you would need to return data to the server.

Returning Data to the Server

The ModalPopup extender allows you to identify controls that serve as the OK and Cancel buttons by using the properties OkControlID and CancelControlID for this purpose. When any of these buttons are clicked, the popup is dismissed and some JavaScript code optionally executes. You may define a JavaScript function to run when the OK and Cancel buttons are clicked by using the OnOkScript and OnCancelScript properties. The popup doesn't post back when any of the predefined OK and Cancel buttons are clicked. The following code excerpt from the modalpopupbehavior.js source file explains. This code belongs to the built-in handlers of the click event for both the OK and Cancel buttons:

var element = $get(this._OkControlID);
if (element && !element.disabled) {
   if (this.hide() && this._OnOkScript) {
      window.setTimeout(this._OnOkScript, 0);
   }
   e.preventDefault();
   return false;
}

Here's a sample handler for the OK button:

function onOK(sender, e)
{
  // refresh the UI

  // if you need to run server code, you
  // can invoke a Web service method
}

It's Your Turn

To refresh, I didn't add code to the existing control or client behavior to complete this sample. I simply used the existing set of members (both client and server) to improve the initialization of the dialog and the data exchange with the host page. To avoid full page refreshes, I employed partial rendering whenever it made sense.

To replicate the tricks in the sample code discussed here in your own application, install the latest version of the AJAX Control Toolkit and then wrap the modal panel in an UpdatePanel region, paying attention to not include the OK and Cancel buttons in the partial region. Next, bind the ModalPopup extender to an invisible control and display and hide the popup programmatically. Finally, if you need additional buttons in the popup to post without leaving the dialog, all that you have to do is add these buttons to the UpdatePanel that wraps the popup. For more details, have a look at the source code; a line of code is worth a thousand words.

Send your questions and comments for Dino to cutting@microsoft.com.

Dino Esposito is the author of Programming ASP.NET 3.5 Core Reference (Microsoft Press, 2008). Based in Italy, Dino is a frequent speaker at industry events worldwide. You can join his blog at weblogs.asp.net/despos.


Test Run: Custom Test Automation with Team System

Test Run
Custom Test Automation with Team System
Dr. James McCaffrey

Code download available at: TestRun2008_Launch.exe (205KB)
Contents
Wrapping a Simple Test Automation Script
Extended Test Results
Publishing Test Results to Team Foundation Server
Wrapping Up

T
here is no single best way to test software. In addition to manual testing, depending on your particular development environment, you may be using commercial test automation frameworks, open source and in-house test automation frameworks, and custom test automation scripts. All these approaches have pros and cons.

C
ustom test automation scripts have the advantages of being quick to write and providing maximum flexibility. However, a downside to using custom test automation is manageability. Your testing effort can become overwhelmed by the sheer volume of test scripts, test case data, and test results. Luckily, Visual Studio® 2005 Team System provides you with the ability to manage custom test automation. Let me show you what I mean using a couple of screen shots. First, consider the test automation script shown executing in Figure 1.

Figure 1 Typical Custom Test Automation Script
Figure 1 Typical Custom Test Automation Script(Click the image for a smaller view)
Figure 1 Typical Custom Test Automation Script
Figure 1 Typical Custom Test Automation Script (Click the image for a larger view)

The test automation is a very short JavaScript script that is performing module testing on a method named TriMax that simply returns the largest of three integers. The TriMax method is housed inside a classic COM DLL file. Although simple and effective, test automation using this type of approach has several weaknesses. Where are the test results being stored? Against which build of the DLL is this test run being executed? How can these test results be shared with other members of the development team? Are these results related to any open bugs? Visual Studio Team System is designed to handle such issues, as Figure 2 shows.

Figure 2 Managing Custom Automation with Team System
Figure 2 Managing Custom Automation with Team System(Click the image for a smaller view)
Figure 2 Managing Custom Automation with Team System
Figure 2 Managing Custom Automation with Team System (Click the image for a larger view)

Visual Studio 2005 Team System consists of Team Foundation Server (TFS) plus one of the client-side editions of Visual Studio. You can think of TFS as an intelligent back-end data warehouse that stores and manages all the data associated with a software development project, including source code, bugs, test results, specification documents, and much more.

The custom test automation functionality I describe in this column is provided in Visual Studio Team Edition for Testers. You can download the 180-day evaluation version of Visual Studio 2005 Team Suite, which includes the functionality of all editions, plus Team Foundation Server from microsoft.com/downloads.

I'll present three examples in this column. The first shows how to use VSTE for Testers to take a very simple piece of custom test automation and wrap it into a test that can be managed by the Team System. The second example expands on the first by showing you how to modify custom test automation so you can create test case results that have more detailed information than a simple pass or fail result. My third example shows how to use Team Foundation Server to store and manage test run result data.

Before diving in, let me briefly describe the module under test so you'll understand the simple custom test automation, and so you can create the module if you want to. (If you are familiar with creating COM objects using ATL, you may want to skip ahead to the next section.)

My module is a Win32® C++ method named TriMax. Using Visual Studio, I created a new Project using a C++ ATL template and named it MyCOMLib. I accepted the ATL wizard's default settings, which instruct Visual Studio to create a DLL. In the project's Solution Explorer, I right-clicked on the project name and selected Add | Class from the context menu. Next, from the ATL category, I selected the ATL Simple Object Template. Then, in the ATL Simple Object Template wizard, I typed "MyMethods" into the C++ Short Name field, which automatically populated the remaining seven name fields (.h file as MyMethods.h, Class as CMyMethods, .cpp file as MyMethods.cpp, Coclass as MyMethods, Type as MyMethodsClass, Interface as IMyMethods, and ProgID as MyCOMLib.MyMethods).

After finishing the wizard, I switched to the Class View window, right-clicked on the IMyMethods interface and selected Add | Add Method from the context menu. In the Add Method wizard, I named my method TriMax. Next I added three input parameters, named x, y, and z, all of type LONG, and then added a LONG* retval parameter to hold my method's return value. After clicking the finish button in the Add Method wizard, I switched back to the Solution Explorer view in Visual Studio and double-clicked on MyMethods.cpp in the Source Files folder. Here is the simple code for my method under test:

STDMETHODIMP CMyMethods::TriMax(LONG x, LONG y, LONG z, LONG* pRes)
{
  if (x > y && x > z)
    *pRes = x;
  else if (y > z)
    *pRes = y;
  else
    *pRes = z;

  return S_OK;
}

I pressed Ctrl+Shift+B to perform a local build of my COM object. And, finally, I used Windows Explorer to verify that the resulting MyCOMLib.dll file was created successfully in the \MyCOMLib\Debug subdirectory. As you can see, my TriMax method is unrealistically simple; I just want something to perform test automation against. Let me emphasize that even though the examples that follow use JavaScript test automation code to test a Win32 COM object, the principles of using Visual Studio Team System to manage custom test automation apply to any type of custom test automation (including C#, Visual Basic® .NET, Windows PowerShell®, and the like) targeting any type of system under test (including .NET managed libraries, Windows® form-based applications, ASP.NET Web applications, ASP.NET Web services, and so on).

Wrapping a Simple Test Automation Script

Now let's look at the simple test automation script in Figure 3. Later I'll show you how to integrate the script into VSTE for Testers and describe the advantages of doing so. My script begins by displaying a couple of messages using the Echo method of the Windows Script Host. Next I instantiate the COM object that contains my method under test:

Figure 3 Simple Test Automation Script (Close Figure 3)
// testSimple.js

WScript.Echo("\nBegin test run\n");
WScript.Echo("Testing TriMax() in MyCOMLib.dll\n");
var o = new ActiveXObject("MyCOMLib.MyMethods");

var cases = new Array( "001:3*7*5:7",
                       "002:0*0*0:1",
                       "003:2*4*6:6" );
var allPassed = true;

for (var i = 0; i < cases.length; ++i)
{
  var tokens = cases[i].split(":");
  var id = tokens[0];
  var inputs = tokens[1];
  var expected = tokens[2];

  var temp = inputs.split("*");
  var arg1 = temp[0];
  var arg2 = temp[1];
  var arg3 = temp[2];

  WScript.Echo("==============");
  WScript.Echo("Case ID = " + id);
  WScript.Echo("Inputs = " + arg1 + " " + arg2 + " " + arg3);
  var actual = o.TriMax(arg1,arg2,arg3);
  WScript.Echo("Expected = " + expected);
  WScript.Echo("Actual = " + actual);
  if (actual == expected) {
    WScript.Echo("Pass");
  }
  else {
    WScript.Echo("**FAIL**");
    allPassed = false;
  }
}

WScript.Echo("==============");
if (allPassed == true) {
  WScript.Echo("\nAll test cases passed");
  WScript.Quit(0);
}
else {
  WScript.Echo("\nOne or more cases failed");
  WScript.Quit(1);
}

var o = new ActiveXObject("MyCOMLib.MyMethods");

Because I am testing a classic COM object, I can reference the object using the object's ProgID. Additionally, because I compiled the COM object on my test host machine, the object information was stored into my system registry. Note that if I had copied the MyCOMLib.dll file to my test host machine from a different machine, I would have needed to register the DLL by calling regsvr32.exe using the Run method of the WScript.Shell object. My example script stores test its case data internally:

var cases = new Array( "001:3*7*5:7",
                       "002:0*0*0:1",
                       "003:2*4*6:6" );
var allPassed = true;

As a general rule, it is better to store test case data externally to the test automation code. External storage allows test cases to be shared and modified more easily than internally stored data. However, internal data is simpler and sometimes a good design choice.

In this simple example, I have three test cases. The first test of the TriMax(x,y,z) method has an ID of 001, inputs of 3, 5, and 7, and an expected result of 7. The second test case has a deliberate failure for demonstration purposes. Notice that I initialize a variable named allPassed to true. This variable tracks whether or not all the individual test cases pass. My test automation iterates through each test case, parsing out the test case ID, the inputs, and the expected value. With those values in hand, my script invokes the method under test and compares the actual result with the expected result to determine a pass/fail result for the individual test case:

var actual = o.TriMax(arg1,arg2,arg3);
if (actual == expected) {
  WScript.Echo("Pass");
}
else {
  WScript.Echo("**FAIL**");
  allPassed = false;
}

If I find any failing test case, I set the value of the allPassed variable to false. Finally, my example test script finishes by using the WScript.Quit method to set the process exit code to 0 or 1:

if (allPassed == true) {
  WScript.Echo("\nAll test cases passed");
  WScript.Quit(0);
}
else {
  WScript.Echo("\nOne or more cases failed");
  WScript.Quit(1);
}

If you intend to manage simple custom test automation with VSTE for Testers, your automation must set an exit code of 0 to indicate that the overall test result is pass or an exit code of 1 to indicate failure. This means that if you are going to integrate existing test automation with VSTS, you will likely have to edit your automation because test automation rarely sets the exit code value. Exactly how you set the exit code varies slightly from language to language. For example, in a .NET-compliant language program, you can use the System.Environment.ExitCode property, and in a Windows PowerShell script, you can use the exit statement.

At this point you can execute the test automation script locally, as shown in Figure 1. But you can also run custom automation from within VSTE for Testers. Select File | New | Project. The New Project dialog has a Test Projects type with a Test Documents subtype. Select Test Documents and you will see a Test Project template. Next, give the test project a name such as MyTestProject and specify a location for the project root directory.

A Test Project can hold one or more tests. Each test can hold one or more individual test cases. By default, you will get a Test Project that contains a general help text document named AuthoringTests.txt and the skeletons for two tests: a manual test and a unit test. You can delete these three files in the Solution Explorer window.

Now right-click on your Test Project name and select Add | New Test from the context menu. In the Add New Test dialog box, select the Generic Test template, name it Simple.GenericTest, and then click OK. VSTE for Testers has built-in templates for load tests, unit tests, and Web tests. The Generic Test type is essentially a wrapper for custom test automation and has all the plumbing necessary to enable custom automation to interoperate with VSTE for Testers. The screenshot in Figure 4 shows you what the process of wrapping custom test automation as a Generic Test looks like.

Figure 4 Creating a Simple Generic Test
Figure 4 Creating a Simple Generic Test(Click the image for a smaller view)
Figure 4 Creating a Simple Generic Test
Figure 4 Creating a Simple Generic Test (Click the image for a larger view)

The first field specifies an existing test program to run. This value must be the path to an .exe file. (While I used an absolute path in this example, you can additionally specify the script path as an environment variable, a relative path, or a deployment path.) In the case of script-based test automation, this is the cscript.exe program, usually located at C:\Windows\System32\. If your test automation is an .exe itself, such as a C# program, you would specify the full path to your automation. The second field specifies command-line arguments that are to be passed to the .exe file specified in the first field. In this example, the value would be the full path to the script, such as C:\VSTTIntegration\Scripts\testSimple.js. These are the only two fields you need to specify in order to run simple script-based tests with internal test case storage.

There are several ways to run tests from within VSTE for Testers. One simple way is to display all tests in your project by clicking on Test | Windows | Test View. Then select the test you wish to run, right-click on the test name, and click Run Selection. The screenshot in Figure 5 shows an example.

Figure 5 Executing a Generic Test
Figure 5 Executing a Generic Test(Click the image for a smaller view)
Figure 5 Executing a Generic Test
Figure 5 Executing a Generic Test (Click the image for a larger view)

The Test Results window displays the overall pass/fail result. If you double-click on the result, you get detailed results including the test run duration, start time, end time, and a copy of the console output. A test result is stored by default on the host machine as an XML file with a .trx (test results XML) file extension in a TestResults subdirectory. By default, these .trx file names are stamped with user name / machine name / date / time, making them easy to organize. The actual structure of .trx files is somewhat complex but easy to interpret. For example, a small part of the .trx file generated by this example Generic Test is:

   <errorInfo>
     <message type="System.String">
       The test failed with exit code 1.
     </message>
     <stackTrace type="System.String" />
   </errorInfo>

The example I've just described wraps custom test automation that has test case data embedded into the automation code. If your automation reads test case data from an external source, you may need to edit one of the Generic Test fields. Suppose your test automation resembles this:

   var fsi = new ActiveXObject(
     "Scripting.FileSystemObject");
   var fi = fsi.OpenTextFile(".\\testCases.txt");

   while (!fi.AtEndOfStream)
   {
     line = fi.ReadLine();
     // parse test case data
     // call method under test
     // determine pass/fail
   }

Here the script uses a relative file path to read test case data from a file located in the same directory as the script. When a Generic Test executes, it copies the executables into a working directory, which means the test case data would not be found. One solution to this is to specify a hardcoded path in the Working Directory field when you create a Generic Test. A slightly more flexible approach is to set the values of one or more environment variables such as %TestDir%, %TestLocation%, and %TestOutputDirectory%, and then use these environment variables to control your test execution. However, the most flexible option is to declare the script as a deployment item in Visual Studio. By doing this, your test case data will be copied into the output directory so that it can be referenced via a local, relative path.

Extended Test Results

Now you've seen how to use VSTE for Testers to manage simple custom test automation. You can also create test automation that saves additional information and more detailed test results. The basic idea is to code your test automation so that it saves intermediate detailed test results as a simple XML file that conforms to a special SummaryResult.xsd schema. VSTE for Testers automatically uses the emitted simple XML results file to create the more complex .trx results file that is managed by Visual Studio Team System. This two-step process is much easier than trying to write test automation that directly produces a .trx test results file. It is easiest to understand this process by working backward. The listing in Figure 6 shows an XML test results file that conforms to SummaryResult.xsd and so can be automatically converted to a .trx results file.

Figure 6 Intermediate Extended Test Results File (Close Figure 6)
<SummaryResult>
  <TestName>MyCOMLib Test Run</TestName>
  <TestResult>Failed</TestResult>
  <DetailedResultsFile>
    C:\AdditionalResults\Details.txt
  </DetailedResultsFile>
  <InnerTests>
    <InnerTest>
      <TestName>001</TestName>
      <TestResult>Passed</TestResult>
    </InnerTest>
    <InnerTest>
      <TestName>002</TestName>
      <TestResult>Failed</TestResult>
    </InnerTest>
  </InnerTests>
</SummaryResult>

The file in Figure 6 has required overall TestName and TestResult values and an optional overall DetailedResultsFile value. Other optional values defined by the SummaryResult.xsd schema include InnerTest to store the name and results of test cases that make up the overall Generic Test, and ErrorMessage values.

The SummaryResult.xsd schema file is installed with VSTE for Testers and is typically located at C:\Program Files\Microsoft Visual Studio 8\Xml\Schemas\. The schema defines test result types other than simply Passed and Failed. Additional defined result types are Aborted, Error, Inconclusive, NotRunnable, NotExecuted, Discontinued, Warning, InProgress, Pending, PassedButRunAborted, and Completed.

Exactly how you write your custom test automation to emit an intermediate extended XML test case results file depends on the programming language you are using and the structure of your automation logic. If you want to use the InnerTest tag, the SummaryResult.xsd schema dictates that you must specify the overall test result before you specify the result of each test case. This means you cannot build up your intermediate XML results file in a strictly sequential way while processing your test case data.

There are many approaches you can take. The listing in Figure 7 shows one way to modify the test automation script presented in Figure 3 to emit an extended results file. The overall idea is to build up the XML result file as one long string by capturing the results of each test case as each case is processed and then constructing the final string after the overall test result is known.

Figure 7 Test Script that Emits an Extended Results File (Close Figure 7)
// testExtended.js

function main()
{
  WScript.Echo("\nBegin test run\n");
  var o = new ActiveXObject("MyCOMLib.MyMethods");

  var cases = new Array( "001:3*7*5:7",
                         "002:0*0*0:1",
                         "003:2*4*6:6" );
  var allPassed = true;
  var innerTestText = new Array(cases.length);

  for (var i = 0; i < cases.length; ++i)
  {
    var tokens = cases[i].split(":");
    var id = tokens[0];
    var inputs = tokens[1];
    var expected = tokens[2];

    var temp = inputs.split("*");
    var arg1 = temp[0];
    var arg2 = temp[1];
    var arg3 = temp[2];

    WScript.Echo("==============");
    WScript.Echo("Case ID = " + id);
    WScript.Echo("Inputs = " + arg1 + " " + arg2 + " " + arg3);
    var actual = o.TriMax(arg1,arg2,arg3);
    WScript.Echo("Expected = " + expected);
    WScript.Echo("Actual = " + actual);

    innerTestText[i] = "<TestName>" + id + "</TestName>";
    innerTestText[i] += "<TestResult>";  // inner test result

    if (actual == expected) {
      WScript.Echo("Pass");
      innerTestText[i] += "Passed";
    }
    else {
      WScript.Echo("FAIL");
      innerTestText[i] += "Failed";
      allPassed = false;
    }

    innerTestText[i] += "</TestResult>";
  } // main loop

  WScript.Echo("==============");
  var extendedResults = "<SummaryResult>";
  extendedResults += "<TestName>MyCOMLib Test Run</TestName>";
  extendedResults += "<TestResult>";  // overall meta-result
  if (allPassed == true) {
    extendedResults += "Passed</TestResult>";
  }
  else {
    extendedResults += "Failed</TestResult>";
  }

  extendedResults += "<InnerTests>";

  for (var j = 0; j < innerTestText.length; ++j) {
    extendedResults += "<InnerTest>";
    extendedResults += innerTestText[j];
    extendedResults += "</InnerTest>";
  }

  extendedResults += "</InnerTests>";
  extendedResults += "</SummaryResult>";

  var fso = new ActiveXObject("Scripting.FileSystemObject");
  var f = fso.CreateTextFile("C:\\VSTTIntegration\\Scripts\\results.xml");
  f.WriteLine(extendedResults);
  f.Close();
} // main()

main()

I create an array of strings, one string for each test case, to hold the InnerTest values:

var innerTestText = new Array(cases.length);

Inside the main processing loop I write out the TestName value and the start of the TestResult value:

innerTestText[i] = "<TestName>" + id + "</TestName>";
innerTestText[i] += "<TestResult>"; // inner test result

After I process an individual test case, I can supply a test result value and add the closing TestResult tag:

if (actual == expected) {
  WScript.Echo("Pass");
  innerTestText[i] += "Passed";
}
else {
  WScript.Echo("FAIL");
  innerTestText[i] += "Failed";
  allPassed = false;
}
innerTestText[i] += "</TestResult>";

After the main processing loop has finished and all my InnerTest data has been constructed, I can create the first part of my extended results file:

var extendedResults = "<SummaryResult>";
extendedResults += "<TestName>MyCOMLib Test Run</TestName>";
extendedResults += "<TestResult>";  // overall meta-result
if (allPassed == true) {
  extendedResults += "Passed</TestResult>";
}
else {
  extendedResults += "Failed</TestResult>";
}

I finish by adding the InnerTest data I saved earlier and writing the entire string as an XML file:

extendedResults += "<InnerTests>";
for (var j = 0; j < innerTestText.length; ++j) {
  extendedResults += "<InnerTest>";
  extendedResults += innerTestText[j];
  extendedResults += "</InnerTest>";
}

extendedResults += "</InnerTests>";
extendedResults += "</SummaryResult>";

var fso = new ActiveXObject("Scripting.FileSystemObject");
var f = fso.CreateTextFile("C:\\VSTTIntegration\\Scripts\\results.xml");
f.WriteLine(extendedResults);

There are many other approaches you can take to emit XML results. My column "Five Ways to Emit Test Results as XML" in the June 2006 issue of MSDN® Magazine (msdn.microsoft.com/msdnmag/issues/06/06/TestRun) describes some of these techniques.

At this point, I have a custom test automation script that writes detailed test results to a results.xml file that conforms to the SummaryResult.xsd schema. To use this automation from within VSTE for Testers, I create a new Generic Test around the automation using the technique I described earlier, but with one change. After Add | New Test and naming the test (say, Extended.GenricTest) in the definition pane, I check the Summary Results File checkbox and enter the location of the results file in the associated textbox (say, C:\VSTTIntegration\Scripts\Results\results.xml). Now when I run my Generic Test, VSTE for Testers will run my automation, find the results.xml intermediate file, and automatically use the data there to create the final, detailed name-date-time stamped .trx result file. Very neat!

Publishing Test Results to Team Foundation Server

Wrapping custom test automation as a Generic Test using VSTE for Testers gives you a great way to manage tests and test results. If you are using Team Foundation Server in your development environment, you can publish test results to the TFS data warehouse to give you even better management of test results.

The TFS back-end data warehouse is extremely complex, so you do not want to try and save .trx test results directly to the underlying SQL tables. However, you can save test result data to TFS using VSTE for Testers. The process is very simple. After running a Generic Test, connect to your TFS project by selecting Tools | Connect to Team Foundation Server from the main menu. In the Test Results window menu, click the Publish icon. You will see a Publish Test Results dialog similar to the one shown in Figure 8.

Figure 8 Publishing Test Results to Team Foundation Server
Figure 8 Publishing Test Results to Team Foundation Server(Click the image for a smaller view)
Figure 8 Publishing Test Results to Team Foundation Server
Figure 8 Publishing Test Results to Team Foundation Server (Click the image for a larger view)

You can select one or more loaded test results and then choose the appropriate build of the system under test from a dropdown control. Note that your test results file must be located in your test output directory in order for it to be available for publishing to the data warehouse. After clicking OK, your test results will be published to the TFS data warehouse. This approach allows anyone on your development team to easily view your test results.

One way to view results from within VSTE for Testers is to connect to your TFS project and expand the Builds folder in the Team Explorer window. If you double-click on the appropriate build type, you will see a list of all specific builds of that type. If you double-click on a particular build entry, you will see the data associated with that build, including published test results. Even better, because TFS bug report data can be associated with published test results, you can view built-in reports such as Tests Failing without Active Bugs, or you can create custom reports that use this data.

Notice that in order to publish test results to Team Foundation Server, you must associate those test results to a particular build of the system under test. This means that you must have a TFS build system in place. But what if you are performing regression testing on a legacy component that is no longer being actively built, or even a component for which the source code no longer exists? One simple solution is to create a dummy project, perform a single build of that project, and then use this build number as a placeholder to target your test results against. For more on this, take a look at the Team System column written by Brian Randell, "Team Foundation Server Version Control," in the January 2007 issue of MSDN Magazine (msdn.microsoft.com/msdnmag/issues/07/01/TeamSystem).

Wrapping Up

The Visual Studio 2005 Team Edition for Testers has the ability to create a wide range of built-in test types, such as unit tests and load tests. These well-known test types are tightly integrated into the development process and are generally associated with test-driven development. However, there are many possible development situations where you have legacy test automation or wish to create custom test automation. For example, you may be outsourcing your test effort and for security reasons do not want the testing team to have direct access to your system's source code. VSTE for Testers allows you to wrap any kind of custom test automation into a Generic Test type. In addition to providing a centralized management structure for your custom test automation, you automatically gain extra results data such as the start time, end time, and duration of each test run, as well as a log of any console output.

Generic Test results are stored as .trx files on the local host machine, and they can easily interoperate with other test systems if necessary because they are stored in a standard XML format. Although you can configure custom test automation to record a simple pass or fail result, VSTE for Testers also gives you the ability to record extended results, including result types such as Inconclusive and Aborted. You do this by modifying your custom test automation to produce an intermediate XML results file that conforms to a SummaryResult.xsd schema. VSTE for Testers automatically uses the intermediate XML file to produce a detailed .trx file.

In development scenarios where you are using Team Foundation Server to manage source code, specification documents, bug data, and the build process, you can publish test results to the TFS data warehouse. Publishing test results to TFS allows these results to be viewed by all other members of the development team and easily determines the relationships between bugs, builds, and test results.

Visual Studio 2008 has just been released, and, from what I've seen so far, you will have all the existing ability to manage custom test automation, plus additional test management functionality that will help you create better software systems.

Thanks to Howard Dierking for suggesting the topic of this column, and to Team System expert Brian Randell for reviewing it.

Send your questions and comments for James to testrun@microsoft.com.

Dr. James McCaffrey works for Volt Information Sciences, Inc., where he manages technical training for software engineers working at the Microsoft campus in Redmond. He has worked on several Microsoft products including Internet Explorer and MSN Search. James is the author of .NET Test Automation Recipes (Apress, 2006) and can be reached at jmccaffrey@volt.com or v-jammc@microsoft.com.


Wicked Code: Silverlight Tips, Tricks, and Best Practices

Wicked Code
Silverlight Tips, Tricks, and Best Practices
Jeff Prosise

Code download available at: WickedCode2008_Launch.exe (33860KB)
Contents
Take Performance Tips to Heart
Offer a Compelling Installation Experience
Don't Make the User Wait
Use CreateFromXaml to Reduce XAML Size
For Accessibility, Support Interactive Zooms
Use Storyboards for Manual Animations
Use the Tag Property to Store Per-Object Data
Use ASP.NET AJAX for Server Callbacks

U
nless you've been stuck on a desert island for the past several months, you've undoubtedly heard about Silverlight™, the new cross-browser, cross-platform solution from Microsoft that helps you build rich Internet applications and rich, immersive media experiences in the browser. Version 1.0, which combines a XAML rendering engine with a JavaScript API, shipped in September 2007. Version 2.0, which will enter beta soon, will pair an enhanced XAML engine with a version of the CLR that runs in the browser, a Silverlight version of the Microsoft® .NET Framework Base Class Library (BCL), and a managed API. (That's right: C# in the browser!)

Silverlight 1.0 applications are proliferating on the Web, thanks in part to some of the high-profile companies that adopted it early and helped spread the love. The programming model is simple enough for an experienced developer to begin building Silverlight applications in a matter of hours. What's missing for this still very young platform, however, is that wealth of knowledge and resources that accompanies more mature platforms.

I've been developing Silverlight apps for more than a year now and have put together a list of best practices that I and other developers on my team use to build better applications. This column contains some of the most helpful ones.

Take Performance Tips to Heart

Not long before Silverlight 1.0 shipped, Microsoft published a document titled "Performance Tips for Silverlight-Based Applications" (msdn2.microsoft.com/bb693295). This guide is a compilation of tips for optimizing performance by avoiding some common mistakes. The best practices you'll find include:

  • Use the Visibility property rather than Opacity to hide objects.
  • Don't use the Width and Height properties of MediaElement and Path objects.
  • Detach programmatically registered event handlers after use.
  • Use transparent control backgrounds sparingly.

Most of these make sense when you read them. It's not hard to imagine why it's better to encode videos using the width and height at which you intend to display them (rather than encode them at one scale and display them at another). It's because the Silverlight rendering engine can avoid resizing each frame on the fly. Still, it's good to see them written down and to know that Silverlight doesn't do something fancy to resize video without incurring a performance penalty.

One best practice that I would add to that document is to avoid redundant calls to FindName. I often see event handlers structured this way:

function onClick(sender, args)
{
    var rect = sender.findName('Rect');
    rect.fill = 'red';
}

The problem is that FindName has to walk the tree of XAML objects to find its target. If you're going to be referencing the XAML object named "Rect" many times over the lifetime of the app, you should initialize it once—for instance, in an event handler for the root canvas's Loaded event—and store the reference in a global variable. Then, instead of calling FindName every time onClick is called, you can do this:

function onClick(sender, args)
{
    _rect.fill = 'red';
}

The resulting code will be faster than the original. But if a developer does this, he should be sure to release that reference when finished with the plugin. In normal pages this is never an issue, but in an AJAX app where whole pieces may be coming and going, its crucial to release those references to avoid a memory leak. For that reason, the Sys.UI.Silverlight.Control ajax type that is used when using the ASP.NET Silverlight Control (currently available in the Extensions 3.5 Preview Community Technology Preview) provides a pluginDispose method you can override to release references and event handlers.

Offer a Compelling Installation Experience

Too often the Silverlight-based application installation experience is not friendly enough to users who don't have Silverlight installed, as exemplified by the barren landscape displayed in Figure 1. Silverlight.createObjectEx—the function that's typically called to instantiate a Silverlight control—displays a "Get Microsoft Silverlight" button when Silverlight isn't present. Clicking the button transports the user to the Silverlight Web site to download and install it. You can improve the experience somewhat by setting Silverlight.createObjectEx's inplaceInstallPrompt parameter to true, enabling the user to download and install Silverlight without leaving the Web page. But even that leaves something to be desired, especially when Silverlight comprises most or all of the content on the page.

Figure 1 Default Silverlight Installation Experience
Figure 1 Default Silverlight Installation Experience(Click the image for a smaller view)
Figure 1 Default Silverlight Installation Experience
Figure 1 Default Silverlight Installation Experience (Click the image for a larger view)

Microsoft recently published a document that should be required reading for all developers using Silverlight—"Silverlight Installation Experience Guide." It's available for downloading along with sample code at go.microsoft.com/fwlink/?LinkId=106023. The document outlines a general technique for displaying HTML content (including a "Get Microsoft Silverlight" button and browser-specific instructions) in a Silverlight DIV when Silverlight isn't installed, and XAML content when it is. The idea is to paint a picture of what the user would see if Silverlight were installed and hopefully to entice him to click the button.

This column is accompanied by a sample application named RevolvingAuto that demonstrates some of the best practices presented here, including how to build a better installation experience. I'll show you key portions of its source code shortly.

RevolvingAuto is notable for its installation experience. Figure 2 shows what the home page looks like to a visitor who doesn't have Silverlight installed. Behind the "Get Microsoft Silverlight" button is a shaded version of the same user experience the user would see if Silverlight were installed. Because the call to Silverlight.createObjectEx includes an inplaceInstallPrompt="true" parameter, the user can install Silverlight without leaving the Web page. And to top it off, the home page includes logic to create the Silverlight control after installation is complete, eliminating the need for a manual refresh. (This feature works great in Internet Explorer® because it doesn't have to be restarted after Silverlight is installed. It's powerless in browsers that require a restart, unfortunately.)

Figure 2 RevolvingAuto before Silverlight Is Installed
Figure 2 RevolvingAuto before Silverlight Is Installed(Click the image for a smaller view)
Figure 2 RevolvingAuto before Silverlight Is Installed
Figure 2 RevolvingAuto before Silverlight Is Installed (Click the image for a larger view)

The JavaScript that drives the install experience lives within Default.html (see Figure 3). To create a Silverlight control, most Silverlight applications structure the code and markup like this:

Figure 3 RevolvingAuto Application—Default.html (Close Figure 3)
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Revolving Auto</title>
    <script type="text/javascript" src="Silverlight.js"></script>
    <script type="text/javascript" src="Default.html.js"></script>
    <style>
        .AgInstalled {
            margin-left: auto; margin-right: auto;
            width: 612px; height: 700px; text-align: left }
        .AgNotInstalled {
            margin-left: auto; margin-right: auto;
            width: 612px; height: 700px; text-align: left;
            background-image: url(Images/Install.jpg);
            background-repeat: no-repeat; padding-top: 100px }
    </style>
</head>

<body style="background-color: black; text-align: center">
    <div style="height: 40px"></div>
    <div id="Container" class="AgInstalled">
        <div id="SilverlightPlugInHost" style="padding-left: 200px">
        </div>
    </div>
    <script type="text/javascript">
        var _id;

        if (!Silverlight.isInstalled('1.0'))
        {
            document.getElementById('Container').className =
                'AgNotInstalled';
            _id = window.setTimeout('checkInstall()', 3000);
        }
        else
            document.getElementById ('SilverlightPlugInHost')
                .removeAttribute('style');

        createSilverlight();

        function checkInstall()
        {
            if (Silverlight.isInstalled('1.0'))
            {
                window.clearInterval(_id);
                document.getElementById('Container')
                    .className = 'AgInstalled';
                document.getElementById ('SilverlightPlugInHost')
                    .removeAttribute('style');
                createSilverlight();
            }
        }
    </script>
</body>
</html>

<div id="SilverlightPlugInHost">
  <script type="text/javascript">
    createSilverlight();
  </script>
</div>

RevolvingAuto structures it this way instead:

<div id="Container">
  <div id="SilverlightPlugInHost">
  </div>
</div>
<script type="text/javascript">
   // Code to create the control
</script>

The code inside the <script> element calls Silverlight.isInstalled (which is implemented in Silverlight.js along with Silverlight.createObjectEx) to determine whether Silverlight is installed. If the answer is yes, the script calls createSilverlight to create the control. If the answer is no, the script dynamically styles the DIV containing the Silverlight DIV to show a background image, and then calls createSilverlight to display the "Get Microsoft Silverlight" button. The background image is the one behind the button in Figure 2.

If it determines that Silverlight is not installed, the creation script also uses window.setTimeout to set up calls to a local function named checkInstall every three seconds. Once installation is complete, checkInstall removes the background image, clears the timer so the function won't be called again, and calls createSilverlight again to create the Silverlight control.

Don't Make the User Wait

The hallmark of Silverlight 1.0 is a rich media experience for images, audio, and video. But files such as these can be large (very large!), and if you declare Image or MediaElement objects in XAML and declare media to go along with them, the download time can be tremendous. The problem is that Silverlight doesn't display one speck of XAML until all of the XAML and the assets that it references have been downloaded. The user, meanwhile, is left wondering what's going on—or if anything is going on at all. It's not a big deal if the user waits a couple of seconds, but if seconds become minutes, the user is likely to give up and move on to better-built sites.

That's why well-designed Silverlight apps don't statically link to media assets; instead, they use the built-in Silverlight downloader object, which piggybacks on the browser's XmlHttpRequest stack to download those assets asynchronously. Instead of doing this

<MediaElement Source="FunnyVideo.wmv" />

you declare the MediaElement without a Source attribute, which prevents the Silverlight control from waiting for a video to download before rendering any XAML. Then you create a downloader object and asynchronously download the video, calling the MediaElement's SetSource method to pass the video to the MediaElement once the download has finished. If you like, you can update a progress indicator here as well. The downloader fires downloadProgressChanged and Completed events to aid in the process.

Silverlight also makes it easy to download multiple assets rather than just one. Just package all your assets—audio, video, images, even XAML or XML—in a ZIP file and use the downloader to download it. Then, when you call SetSource to assign content to an object, specify the name of the file inside the ZIP file in the second parameter. It couldn't be much simpler.

Here's an example:

downloader.open('GET', 'Assets.zip');
downloader.send();
  ...
// In the Completed event handler
_media.setSource(sender, 'FunnyVideo.wmv');

The RevolvingAuto application uses a downloader object to asynchronously download a ZIP file containing 105 image files. Together, these files comprise a 360-degree view of an automobile at 3.5-degree increments. At run time, the application swaps images in and out to revolve the automobile.

The DownloadProgressChanged event handler updates a progress bar, which is nothing more than a XAML rectangle whose width increases as the download progresses. The Completed event handler assigns the downloaded images to dynamically created Image objects. It also hides the progress bar and unregisters the two downloader event handlers in accordance with the performance document referenced in the first section of this article.

Speaking of not making the user wait, most browsers drive the UI and execute JavaScript on the same thread. Consequently, if you engage in a long-running JavaScript task (say, some number crunching that takes 5 or 10 seconds to execute), the UI will be unresponsive for the duration. The aforementioned performance-tips document recommends breaking long-running JavaScript tasks into smaller, more granular tasks for this reason. In Silverlight 2.0, you'll be able to delegate long-running tasks to background threads. In version 1.0, however, that's not an option.

Use CreateFromXaml to Reduce XAML Size

Another takeaway from the sample application is how it uses CreateFromXaml to create XAML Image objects dynamically rather than cluttering the XAML file with scores of nearly identical Image declarations. The XAML file you see in Figure 4, Scene.xaml, is nearly empty. It declares a couple of Canvases and Rectangles, a Storyboard, and a ScaleTransform, but it doesn't declare a single Image object. Instead, a for loop in Default.html.js creates 105 Image objects—one per image in the animation—and initializes them with image bits from the downloader (see Figure 5).

Figure 5 RevolvingAuto Application —Default.html.js (Close Figure 5)
function createSilverlight()
{
  Silverlight.createObjectEx({
    source: 'Scene.xaml',
    parentElement: document.getElementById('SilverlightPlugInHost'),
    id: 'SilverlightPlugIn',
    properties: {
      width: '900',
      height: '700',
      background:'black',
      isWindowless: false,
      inplaceInstallPrompt: true,
      version: '1.0'
    },
    events: {
      onError: null,
      onLoad: null
    },
    context: null
  });
}

var _zoom = 1;                  // Zoom factor
var _index = 0;                 // Image index
var _progressBar;               // Progress bar
var _progressBarContainer;      // Progress bar container
var _progressBarCanvas;         // Progress bar canvas
var _progressBarWidth;          // Full width of progress bar
var _control                    // Silverlight control
var _transform;                 // Zoom transform
var _timer;                     // Timer storyboard
var _token1, _token2;           // Event handler tokens
var _images = new Array(36);    // References to XAML images
var _photos = 105;              // Number of photos

function onLoaded(sender)
{
  // Initialize XAML references and other variables
  transform = sender.findName('ZoomTransform');
  timer = sender.findName('TimerStoryboard');
  progressBar = sender.findName('ProgressBar');
  progressBarContainer = sender.findName('ProgressBarContainer');
  progressBarCanvas = sender.findName('ProgressBarCanvas');
  progressBarWidth = _progressBarContainer.width - 4;
  control = sender.getHost();

  var downloader = _control.createObject('downloader');
  token1 = downloader.addEventListener('downloadProgressChanged',
    downloadProgressChanged);
  token2 = downloader.addEventListener('completed', downloadCompleted);
  downloader.open('GET', 'Assets/AutoPhotos.zip');
  downloader.send();
}

function downloadProgressChanged(sender, args)
{
  // Update the progress bar
 _progressBar.width = _progressBarWidth * sender.downloadProgress;
}

function downloadCompleted(sender, args)
{
  // Hide the progress bar
  progressBarCanvas.visibility = 'Collapsed';

  // Deregister downloader event handlers
  sender.removeEventListener('downloadProgressChanged', _token1);
  sender.removeEventListener('completed', _token2);

  // Create XAML images and assign downloaded bits to them
  var canvas = sender.findName('AutoCanvas');

  for (i=0; i<_photos; i++)
  {
    var xaml =
      '<Image Canvas.Left="225" Width="450" Visibility="Collapsed" />';
    var image = _control.content.createFromXaml(xaml);
    image.setSource(sender, (i + 1).toString() + '.JPG');
    canvas.children.add(image);
    images[i] = image;
  }

  // Register mousewheel event handler
  if (window.addEventListener)
    window.addEventListener('DOMMouseScroll', onMouseWheelTurned, false);
  else
    window.onmousewheel = document.onmousewheel = onMouseWheelTurned;

  // Make the first image visible
  images[0].visibility = 'Visible';

  // Start rotating
  timer.begin();
}

function onTick(sender, args)
{
  // Hide the current image
  images[_index].visibility = 'Collapsed';

  // Update the image index
  if (++_index == _photos)
    index = 0; // Wrap around

  // Show the new image
    images[_index].visibility = 'Visible';

  // Restart the timer
  timer.begin();
}

function onMouseWheelTurned(event)
{
  var delta = 0;
  if (!event) // Internet Explorer
    event = window.event;

  if (event.wheelDelta) // Internet Explorer
  {
    delta = event.wheelDelta;
    if (window.opera)
      delta = -delta;
  }
  else if (event.detail) // Mozilla
    delta = -event.detail;

  if (delta != 0)
  {
    if (delta > 0)
    {
      // Zoom in
      zoom = Math.min(2, _zoom + 0.05);
    }
    else
    {
      // Zoom out
      zoom = Math.max(1, _zoom - 0.05);
    }
    transform.scaleX = _transform.scaleY = _zoom;
  }

  if (event.preventDefault)
    event.preventDefault();

  event.returnValue = false;
}
Figure 4 RevolvingAuto Application—Scene.xaml (Close Figure 4)
<Canvas
  xmlns="http://schemas.microsoft.com/client/2007"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Loaded="onLoaded">
  <Canvas x:Name="ProgressBarCanvas"
    Canvas.Left="260" Canvas.Top="100">
    <Rectangle x:Name="ProgressBarContainer" Canvas.Left="0"
      Canvas.Top="0" Width="380" Height="12" Stroke="#606060" />
    <Rectangle x:Name="ProgressBar" Canvas.Left="2" Canvas.Top="2"
      Width="0" Height="8" Fill="#303030" />
  </Canvas>
  <Canvas x:Name="AutoCanvas">
    <Canvas.Resources>
      <Storyboard x:Name="TimerStoryboard" Duration="0:0:0.2"
        Completed="onTick" />
    </Canvas.Resources>
    <Canvas.RenderTransform>
      <ScaleTransform x:Name="ZoomTransform" CenterX="450" />
    </Canvas.RenderTransform>
  </Canvas>
</Canvas>

It's always advisable to keep XAML files as small as possible to reduce the application's load time. And it's no slower. (Parsing XML is, after all, not an inexpensive proposition.) Anytime you find yourself declaring XAML objects repetitively, consider using CreateFromXaml instead.

For Accessibility, Support Interactive Zooms

"Section 508" is a buzzword in usability circles these days and for good reason: it spells out U.S. Federal requirements for making software accessible to people with disabilities. If you haven't been asked about 508 compliance yet, just wait—if you live in the U.S., you'll need to know about it and probably sooner than you think. (For details regarding Section 508, see section508.gov.)

Section 508 covers a lot of ground, from requiring that information conveyed by color coding also be accessible by means other than color coding (for example, through labels or popup tooltips) to imposing upper and lower limits on the frequency of page flicker. Strangely enough, it doesn't specify a minimum font size to aid vision-impaired users. Text size strikes close to home for me, because I've reached an age where I have to use reading glasses to read small print. That's why I often go ahead and build a zoom feature into the Silverlight apps that I author. Fortunately, Silverlight makes my job very easy.

Silverlight ScaleTransforms can be used to scale any XAML object, including Canvas objects containing other objects. Implementing interactive zooms is as simple as declaring a ScaleTransform and modifying its ScaleX and ScaleY properties in response to mouse-wheel actions or other events. To scale the entire Silverlight display, simply apply the ScaleTransform to the root Canvas.

When RevolvingAuto's home page first appears, the rotating automobile images are relatively small. However, you can enlarge the display up to two times by rolling the mouse wheel (see Figure 6). The key code is found in the onMouseWheelTurned function in Figure 6 (Default.html.js). It does some work to account for differences in the way browsers report mouse-wheel events and then increments or decrements the ScaleX and ScaleY properties of a ZoomTransform.

Figure 6 RevolvingAuto before and after Zooming
Figure 6 RevolvingAuto before and after Zooming(Click the image for a smaller view)
Figure 6 RevolvingAuto before and after Zooming
Figure 6 RevolvingAuto before and after Zooming (Click the image for a larger view)

Observe that you don't lose resolution when you zoom in on the rotating car. That's because the downloaded images are twice the size of the Image objects that they're assigned to. As you zoom, the ScaleTransform increases the dimensions of the currently displayed Image object, allowing the Image object more real estate to display the information available to it.

Use Storyboards for Manual Animations

One of the coolest features of Silverlight is its rich support for animations. You can make objects fade in and out, zoom across the page, and pop in and out of view with a few lines of XAML. But you can't animate just anything, at least not declaratively. You can easily animate numeric properties, Color properties, and Point properties. But if you want to, say, animate an image by varying its Source property over time (swapping one image for another on each tick), you'll have to write some code, and the way you structure that code will affect the quality of the animation.

Silverlight 1.0 lacks an explicit timer object, and window.setTimeout is less than ideal for animations. That's why savvy Silverlight developers use Storyboard objects when they need programmable timers. All you have to do is declare an empty Storyboard object in XAML and designate a handler for its completed event, like so:

<Storyboard x:Name="Timer"
  Duration="0:0:0.05"
  Completed="onTick">
</Storyboard>

When you're ready to start the animation, you call the Storyboard's begin method:

_timer.begin();

Finally, in the Completed event handler, do whatever you need to do (for example, modify an Image object's Source property) and call begin to start the timer running again:

function onTick(sender, args)
{
    // TODO: Do work
    _timer.begin();
}

In this example, _timer is a variable that refers to the XAML Storyboard object named "Timer."

This is precisely the technique that RevolvingAuto uses to render revolving animations. The onTick function in Figure 5, which is called in response to Storyboard.Completed events, advances the animation one frame by turning the previous image's Visibility property off (Collapsed) and the next image's Visibility property on (Visible).

Use the Tag Property to Store Per-Object Data

Every XAML object that a Silverlight application creates has a read/write property named Tag that can be used to store user-defined strings. The Tag property provides a powerful and easy-to-use means for associating arbitrary data with XAML objects.

Members of my team and I have used Tag in a variety of ways. In one project whose requirements included giving the user the ability to search through content rendered by Silverlight, we embedded keywords in the page's XAML objects and built a search feature that scanned the XAML from top to bottom inspecting each object's Tag property. In another instance, we used Tag to include metadata in our XAML elements. At run time we used the metadata, which was stored in the form of name/value pairs, to build more complex XAML around the XAML elements in the XAML document. A designer building content for our application could, for example, declare an Image object this way:

<Image Source="ProductDemo.jpg"
  Tag="Mode:Lightbox" />

Our application would see "Mode:Lightbox" and wrap a lightbox around the image, enabling users to zoom in and out, pan around the image, and more.

One very pragmatic use for the Tag property is as a means for attaching descriptions to images. Imagine a canvas containing hundreds of images, accompanied by a requirement to pop up an image description when the cursor hovers over one. You could use the Tag property to attach descriptions to each image and read the Tag property of the image under the cursor prior to popping up a description.

Use ASP.NET AJAX for Server Callbacks

Developers sometimes bemoan the fact that Silverlight 1.0 has such a limited networking stack. Other than the downloader object, version 1.0 offers little in terms of networking. This will change in Silverlight 2.0, which will have a rich, multiprotocol networking stack. Until then, however, you can use ASP.NET AJAX to fire off calls from a browser hosting a Silverlight application to the server.

The Silverlight viewer in the MyComix comic-cataloging application I recently published offers a concrete example. To see for yourself, type in the URL mycomix.wintellect.com/Spotlight.aspx?Item=1166 and wait for a comic book cover to appear. Then move the cursor into the upper part of the window; an info panel will slide into view to display information about the comic, including its title, issue number, grade, and year of publication. The data in the panel comes from a database stored on the server, and it's retrieved by calling an ASP.NET AJAX Web service.

The Web service, MyComix.asmx, exposes a method named GetComicInfo that accepts a comic ID as an input parameter and returns a ComicInfo object containing all the information available regarding the comic. (An ASP.NET AJAX Web service is basically a conventional ASMX with the Web service class attributed ScriptService rather than WebService.) The viewer page declares a reference to the Web service like this:

<asp:ScriptManager ID="ScriptManager1" runat="server">
    <Services>
        <asp:ServiceReference Path="~/MyComix.asmx" />
    </Services>
</asp:ScriptManager>

Then, it fires an asynchronous call to the Web service's GetComicInfo method, as shown here:

MyComixScriptService.GetComicInfo(_id, OnGetComicInfoCompleted);

The call is extremely efficient on the wire because it uses JavaScript Object Notation (JSON) encoding and because no view state or other unnecessary data is transmitted. And because the call is asynchronous, the browser doesn't freeze up waiting for it to complete. If the user displays the info panel before the call returns, the panel simply shows up empty.

You can download the MyComix source code, including the ASP.NET AJAX Web service, from wintellect.com/Downloads/MyComix.zip. The Silverlight viewer can be found in the files that are named Spotlight.aspx and Spotlight.xaml.

As you're playing around with Silverlight you may find you have best practices of your own and would like to add them to those presented here. If so, shoot me some e-mail at wicked@microsoft.com. Meanwhile, happy Silverlight developing!

Send your questions and comments for Jeff to wicked@microsoft.com.

Jeff Prosise is a contributing editor to MSDN Magazine and the author of several books, including Programming Microsoft .NET (Microsoft Press, 2002). He's also cofounder of Wintellect (www.wintellect.com), a software consulting and education firm that specializes in Microsoft .NET. Have a comment on this column? Contact Jeff at wicked@microsoft.com.


Service Station: Web Service Software Factory Modeling Edition

Service Station
Web Service Software Factory Modeling Edition
Gerardo de Geest and Gerben van Loon

Code download available at: ServiceStation2008_Launch.exe (199KB)
Contents
Data Contract Model
Service Contract Model
ASMX or WCF?
Host Model
Generating Code
Customizing the Service Factory
Enterprise Library Exception Shielding
Building the Extension
Using the Extension

W
eb Service Software Factory: Modeling Edition, also known as the Service Factory, is a collection of resources to help you model and build Web services for Windows® Communication Foundation (WCF) and ASMX in an easy and efficient way. The main difference from previous versions of the Service Factory is that this latest edition uses models whereas the previous release was based on wizards by way of the Guidance Automation Toolkit (GAT). The Service Factory now allows you to build a Web service by creating three different models: the data contract model, the service contract model, and the host model. We will first discuss these three models, and then we will show you how to customize the Service Factory. You can find additional information at the Web sites listed in the "Online Service Factory Resources" sidebar.

The main disadvantage of using wizards by way of GAT for doing code generation is that it complicates matters when you have to change something in the generated code. Suppose you want to rename an operation of a generated service—you have to either save your custom code and redo the entire wizard where you use the new name or rename the operation in the generated code. Redoing the wizard is time-consuming; since the wizard has no memory, you have to specify everything again. Changing the code generated by the wizard is not ideal either since you are back to working in the details while the goal of the wizard is to avoid this. Models provide a good solution to the problem because, in contrast to wizards, models are able to remember your input.

Building modeling languages that integrate into Visual Studio® was difficult in the past, but the Domain-Specific Language (DSL) Tools that ship with the Visual Studio SDK make it easier. The Service Factory now ships with three types of models built using the DSL Tools. Note that there are no models for data access and service security. The Data Access Guidance Package from the previous Service Factory release is now available as a separate factory called Repository Factory, and the Security Guidance Package is available as a separate package on the Service Factory community site.

The new version of the Service Factory also serves as a good example for building your own Software Factories since it is using and combining all the tools that are available to build one:

  • GAT for the wizards used to generate projects and provide context menus
  • DSL Tools for the three different model types
  • Designer Integration Service (DIS) to support cross-model references

The Service Factory source also contains some interesting libraries that have features the current factories tools do not have. It could be interesting to reuse these if you are building your own factory:

  • A code-generation library that allows you to generate code in separate files
  • A cross-model validation library to assist you in verifying cross-model references
  • A Visual Studio mock objects library to help you easily unit test your factory

As we mentioned, the Service Factory consists of three types of models. In the data contract model, the data contracts of the Web service are defined. In the service contract model, the service and its contracts, operations, and messages are defined. Finally, the host model is used to model endpoints of the services and client proxies.

To introduce the factory, we will create a Web service for ordering pizzas from Peedy's Pizza Palace. The Web service will take orders for pizzas, allowing you to choose among different sizes and different toppings. When the order is placed, the price of the order is returned to the customer as a confirmation.

Data Contract Model

In the data contract model, different kinds of data can be modeled together. In addition to defining basic types as contract elements, you can also define collections, enumerations, and fault contracts. Fault contracts can be returned when some error occurs in the service.

In the data contract model for Peedy's Pizza Palace, we have defined a PizzaOrder as a collection of pizzas that the customer would like to order. Every pizza has a size and a topping. This is modeled using enumerations. Furthermore, we have defined a fault contract, OutOfOrder, that is thrown in case a pizza is out of order, meaning that Peedy's Pizza Palace cannot deliver the pizza because it is missing some part of the topping. It is also possible to return values even when there is a fault contract. For example, in this case we show the price of the order to the user anyway. The complete data contract model is shown in Figure 1.

Figure 1 Data Contract Model for Peedy's Pizza Palace
Figure 1 Data Contract Model for Peedy's Pizza Palace(Click the image for a smaller view)
Figure 1 Data Contract Model for Peedy's Pizza Palace
Figure 1 Data Contract Model for Peedy's Pizza Palace (Click the image for a larger view)

Service Contract Model

Data contracts are used in the service contract model. In this sample, the service contracts and operations of the Peedy's Pizza Palace Web service are modeled. The model (see Figure 2) is actually quite simple since we only need an operation to order pizzas.

Figure 2 Service Contract Model for Peedy's Pizza Palace
Figure 2 Service Contract Model for Peedy's Pizza Palace(Click the image for a smaller view)
Figure 2 Service Contract Model for Peedy's Pizza Palace
Figure 2 Service Contract Model for Peedy's Pizza Palace (Click the image for a larger view)

Each operation in a service contract model can have a maximum of two message contracts associated with it: one message contract for the request message and one message contract for the response message. You can add parts to a message contract by right-clicking on it. A part can be either a primitive type, such as System.Decimal, or a data contract as defined in the data contract model. The DSL Model Element Selector, shown in Figure 3, shows all the data contracts that are available in the project.

Figure 3 Selecting the Data Contract
Figure 3 Selecting the Data Contract

ASMX or WCF?

Before we move on to the host model, we must first create our solution structure. This is a set of projects in which the code for the Web services will be generated. It is at this point that we have to choose our implementation technology. It is important to note that in the Service Factory, two technologies are already available—ASMX and WCF—and it is easy to extend the Service Factory with your own service technology. For our demonstration, we will use WCF. In order to make this selection, we simply right-click on the solution, and the options shown in Figure 4 are displayed. We select WCF Implementation Projects at the bottom of the menu, and the Service Factory creates a couple of projects where the code generated from the models will be stored.

Figure 4 Create the WCF Solution Structure
Figure 4 Create the WCF Solution Structure

Figure 5 shows the generated solution structure. Note that a variety of projects have been generated—this is to make the code easier to manage. We will get back to this solution structure in a bit, when we discuss the project mapping table.

Figure 5 Solution Structure for Peedy's Pizza Palace
Figure 5 Solution Structure for Peedy's Pizza Palace

We also need to specify whether we want to use ASMX or WCF on the data contract model and on the service contract model. Each of these models has a property called Implementation Technology that can be set to ASMX, WCF, or your own custom implementation technology. When this property is set, many of the entities on the models get new properties that are specific to the chosen technology (see Figure 6a). As you can see in Figure 6b, after we've selected WCF as the implementation technology, a WCF settings category is added to the properties window. This lets us choose what kind of collection type the PizzaOrder collection should be.

Figure 6a Before Selecting a Technology
Figure 6a Before Selecting a Technology
Figure 6b After Selecting a Technology
Figure 6b After Selecting a Technology

Host Model

The host model is used to model the endpoints of the Web service and the client proxies. This model looks completely different from the other two models we've discussed previously. At this point, you can start modeling in the host explorer by right-clicking on elements, as opposed to simply dragging and dropping elements onto the design surface. By right-clicking on the host model element, you can add a new host and a new client, as shown in Figure 7.

Figure 7 Adding Clients and Hosts to the Host Model
Figure 7 Adding Clients and Hosts to the Host Model

After adding a PizzaHost and a PizzaClient, the implementation technology for both can be selected. We have chosen to set both to WCF. It is also necessary to select an implementation project for both. Fortunately, the Service Factory has already generated those for us. As you can see, the Tests folder in the solution structure shown in Figure 5 contains the projects PizzaService.Host and PizzaService.Client. These can be used as implementation projects.

We create a new ServiceReference by right-clicking on the PizzaHost. For this new ServiceReference, we need to select a service implementation—this is a reference to our service model. We can select the implementation in the same way we selected data contracts in Figure 2.

For each ServiceReference you create, you can define different endpoints. We have created one endpoint, called PizzaEndpoint, and have configured it to use the wsHttpBinding.

Now that we have an endpoint, we can create a proxy for our client. To add a new proxy, we right-click on the PizzaClient in the Host Explorer. With a combobox in the property window of the proxy, we can select the endpoint. The completed host model is shown in Figure 8.

Figure 8 Completed Host Model for the Pizza Service
Figure 8 Completed Host Model for the Pizza Service

Generating Code
Online Service Factory Resources
  • Domain-Specific Language Tools
  • Designer Integration Powertoy for Domain-Specific Language Tools
  • Web Service Software Factory
  • The Repository Factory
  • Enterprise Library

All the models we need for our pizza ordering service are now defined, and we are ready to generate the service. To do this, we first have to fill in the project mapping table field for each of our models—this can be defined in the property window of each model. When selecting this property, a selection window pops up. In our case, there is only one option: PizzaService. This is because all the roles were already defined when we created the solution structure. The roles can be found in the file projectmapping.xml, which is also part of our solution structure. In this file, a role can be associated with a project.

The next step is to actually generate the code. Right-clicking on the data contract model gives us the Generate Code option. Selecting this option generates the data contracts in the PizzaService.DataContracts project. The same can be done for the service contract model, which generates code in the PizzaService.MessageContracts, PizzaService.ServiceContracts, and PizzaService.ServiceImplementation projects.

Generating code for the host model is done in two steps: we generate code once for the host and once for the client. To generate code for the host, we click on the PizzaService ServiceReference and the Visual Studio designer presents a button called Validate Model. When we click the button, the model is validated correctly and we then are presented with the Generate Service button. Clicking this, as you've surely guessed by now, generates code in the PizzaService.Host project.

Now to generate the client, we first have to run the host, so we compile the PizzaService.Host project and run it in a Web browser. Next we click on the PizzaProxy in our host model and click generate code. This opens a wizard, shown in Figure 9, that allows us to configure the client. While the service address is filled in automatically by the Service Factory, we go ahead and define the security settings for the host. For instance, if an X.509 certificate is needed in order to connect to this service, we can add it to the client by using this wizard.

Figure 9 Wizard Used to Generate the Client
Figure 9 Wizard Used to Generate the Client(Click the image for a smaller view)
Figure 9 Wizard Used to Generate the Client
Figure 9 Wizard Used to Generate the Client (Click the image for a larger view)

At this point, the service is ready to use and can be published in IIS. Business logic and entities for the service can be added to the PizzaService.BusinessEntities and PizzaService.BusinessLogic projects. The only other thing we need to do is create a new partial class, called PizzaServicePeedy, in the PizzaService.ServiceImplementation project. This is used to add business logic to the generated service implementation. Unfortunately, if we edit the generated code directly, our logic will be overwritten the next time we generate code. But with a partial class, this is not the case since the generator does not touch a file we have created ourselves.

To show this, we have defined a class in the PizzaService.BusinessLogic project called PriceCalculator with a method called CalculatePrice. (It's important to note that this is not a recommended practice—it's much better if the business logic doesn't "know" anything about the service, which is why there is a translation layer. To support this process, Service Factory has a translator built in that helps you translate your data contract into business entities. But while you may not want to do this in production, we're using this technique here as a simple way to illustrate a fundamental point.) This method takes a PizzaOrder data contract as a parameter and returns the price of the order. The only thing we need to do now is encapsulate this price in a MessageContract and then return the MessageContract. Therefore, to do this we create a partial class in the PizzaService.ServiceImplementation project called PizzaServicePeedy that overrides the method OrderPizza generated by the Service Factory. This partial class is shown in Figure 10.

Figure 10 PizzaServicePeedy Partial Class (Close Figure 10)
namespace PizzaService.ServiceImplementation
{
  public partial class PizzaServicePeedy
  {
    public override PizzaService.MessageContracts.OrderPizzaResp
      OrderPizza(PizzaService.MessageContracts.OrderPizzaReq request)
    {
      PizzaService.MessageContracts.OrderPizzaResp result = new
        PizzaService.MessageContracts.OrderPizzaResp();
      result.price = PizzaService.BusinessLogic.PriceCalculator
        .CalculatePrice(request.order);
      return result;
    }
  }
}

To test our service, we start the PizzaService.Host project. In the PizzaService.Client project, we can then define the following method to call the service:

static void Main()
{
  PizzaOrder order = new PizzaOrder();
  Pizza pizza = new Pizza();
  pizza.PizzaSize= PizzaSize.Large;
  pizza.Topping = Topping.Hawaii;
  order.Add(pizza);
  PizzaContractClient proxy = new PizzaContractClient();
  Console.WriteLine(proxy.OrderPizza(order));
  proxy.Close();
}

When the program runs, the number 7 is printed to the output window of Visual Studio, as this is the price for every type of large pizza available at Peedy's.

Customizing the Service Factory

The Service Factory automates many things for you when building services. But what if you need to do things a little bit differently or you need to integrate these services with your existing application framework? Well, the full source code of the Service Factory is available on the CodePlex site (www.codeplex.com/servicefactory), which means you can customize the factory itself.

When you look at the Service Factory source, you'll find that it consists of the following parts:

Domain-Specific Languages Specifically, these include the data contract, host designer, and service contract. These projects specify how the modeling languages are constructed and visualized.

Extenders There are six extender projects. These projects specify the extra properties that are needed by the DSLs to support specific technologies (ASMX and WCF).

Service Factory Guidance Package This contains the solutions template and the custom context menus.

Libraries These contain the code-generation framework, validation framework, code-generation strategies, and the validation rules.

Model Project This custom project contains the models.

For this discussion, we will divide the customizations into two categories: those that require you to rebuild the source and those that don't require you to rebuild the source (extensions). These are outlined in Figure 11.

Figure 11 The Two Customization Categories (Close Figure 11)

Must rebuild the factory
Changes | Description
Modifying existing GAX (Guidance Animation eXtensions) recipes | You must make changes to the Service Factory Guidance Package, and this, in turn, means you must rebuild your own factory.
Adding new project roles | You must make changes to the Service Factory Guidance Package, and, therefore, you must rebuild your own factory.
Modifying solutions templates | You must make changes to the Service Factory Guidance Package, and this, in turn, means you must rebuild your own factory.
Adding a new model | You must recompile the factory since you need to make changes to the model project to support your new model.
Changing the modeling languages | This requires changes to the existing DSL projects, and, therefore, you must rebuild your factory.
Do not need to rebuild the factory
Changes | Description
Adding new solution templates | You can simply define your solution templates in your own separate GAT guidance package.
Adding new GAX recipes | You can define new GAX recipes, such as wizards and context menus, in your own GAT guidance package.
Adding new generation strategies | You can add extra strategies in your own assembly using the ICodeGenerationStrategy interface from the libraries. (Technology extenders are able to use the assembly.)
Modifying text templates | You can change the T4 text templates used to generate code if you want to change how code is generated. These are located in the service factory installation folder in the "Guidance Package\TextTemplates" subfolder.
Adding new model validation rules | You can add extra validation rules to the ruleset.config in the Guidance Package folder of the service factory installation folder.
Adding new technology extensions or properties | By default, the Service Factory ships with technology extenders for ASMX and WCF. You can add extra properties to these extenders or add your own extender in your assembly by deriving from ExtensionProviderBase. New or changed extenders can be dropped in the service factory installation folder in the "Guidance Package\Lib" subfolder.

In general, you should try to avoid customizations that require you to rebuild the factory and create a new installer. Aside from being more complex and potentially error-prone, these types of customizations also increase the probability of encountering problems when migrating to the next version of the factory or integrating with future Microsoft products. That said, the Service Factory source does include a setup project to help with the rebuild process. This project includes code-generation templates that generate WIX (Windows Installer XML), making the task easier and faster.

In the next section, we will show you how to add a property to the existing WCF extender and change the templates to generate extra code for the new property. It is beyond the scope of this column to examine the other extension points, so check out the Service Factory Extensions Hands-on Lab, available at codeplex.com/servicefactory.

Note that we are modifying the source of the factory, but we are only changing a single assembly, which is loaded dynamically. Thus, we are not rebuilding the factory.

Enterprise Library Exception Shielding

In services, it is common to prevent sending unknown exceptions to clients. The Exception Handling Application Block in Enterprise Library (EntLib) 3.1 can help by applying configured exception shielding policies to WCF services. This requires the ExceptionShielding attribute on a service with the name of the exception shielding policy from the Exception Handling block as a parameter.

In the Service Factory, the whole service class is generated so we can set the attribute on the generated code manually, but that will be overwritten whenever we have to regenerate code. A solution for this is to extend the service model with a setting for the EntLib Exception shielding attribute. Note that EntLib exception shielding only works with WCF services. Also, adding new properties to extenders doesn't require us to rebuild the whole factory.

Building the Extension

We are going to modify the source of the factory, but we're only making changes to a single assembly: the WCF service contract extender. The extender assembly is loaded dynamically, so we only need to build the changed extender. For building this extension, we assume that you have the Service Factory installed using the binary installers. You also need to install the source since we are going to make changes to a part of it. You can partially follow "Installation Instructions for Source Code Installer" from the Service Factory installation instructions. You have to skip the steps where you rebuild the entire solution or register the Guidance Package, since that will make changes to the original binary installed version.

We are going to add our new property to the WCF service contract extender, found in the ServiceFactory.Extenders.ServiceContract.Wcf project of the Service Factory Source. Just open this single project instead of the whole solution, since we only need to make changes to this project. The exception shielding setting is a property of a WCF service, so we can add a new property to the WCFService class (as shown in Figure 12).

Figure 12 Code for Extra Property (Close Figure 12)
namespace
  Microsoft.Practices.ServiceFactory.Extenders.ServiceContract.Wcf
{
  [Serializable]
  [CLSCompliant(false)]
  [ObjectExtender(typeof(Service))]
  public class WCFService : ObjectExtender<Service>
  {
    ...

    private string entLibWCFExceptionPolicy;

    [Category("Enterprise Library Integration"),
      Description("EntLib WCF Exception Shielding Policy"),
      DisplayName("EntLib Exception Shielding Policy"),
      ReadOnly(false),
      Browsable(true)]
    [XmlElement("EntLibWCFExceptionPolicy")]
    public string EntLibWCFExceptionPolicy
    {
      get { return entLibWCFExceptionPolicy; }
      set { entLibWCFExceptionPolicy = value; }
    }
  }
}

We also need extra references to the EntLib DLLs when we're using the ExceptionShielding attribute. We can add extra assembly references to the ServiceLink class, and, in this way, the generated projects will automatically get these references:

[AssemblyReference("Microsoft.Practices.EnterpriseLibrary" +
  ".ExceptionHandling, Version=3.1.0.0, Culture=neutral," +
  "PublicKeyToken=b03f5f7f11d50a3a")]

[AssemblyReference("Microsoft.Practices.EnterpriseLibrary" +
  ".ExceptionHandling.WCF, Version=3.1.0.0, Culture=neutral," +
  "PublicKeyToken=b03f5f7f11d50a3a")]

Now we have to change the code-generation templates to generate the extra code for the new property. The templates for generating WCF code are in the Guidance Package\TextTemplates\WCF\CS subfolder of the Service Factory installation folder.

We're going to add extra code to the ServiceImplementation.tt file. Save a copy of the original file first in case you ever want to switch back to the original template. At the bottom of the ServiceImplementation.tt file, we add the following method, which returns the EntLib exception policy only when a value is filled in:

private string
  GetEntLibExceptionShieldingPolicy(
    Service service)
{
  WCFService wfcService =
    GetObjectExtender<WCFService>(service);

  if(string.IsNullOrEmpty(
    wfcService.EntLibWCFExceptionPolicy))
  {
    return string.Empty;
  }
  else
  {
    return "[ExceptionShielding(\"" +
      wfcService.EntLibWCFExceptionPolicy +
      "\")]";
  }
}

To complete our extension, we also need to insert a using statement for the EntLib namespace and a call to the previously created method right above the base class definition, as shown here:

using System;
using WCF = global::System.ServiceModel;
using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.WCF;

namespace <#=CurrentExtender.ArtifactLink.Namespace#>
{
  /// <summary>
  /// Service Class - <#= CurrentElement.Name #>
  /// </summary>
  [WCF::ServiceBehavior(Name = "<#= CurrentElement.Name #>", ...
  <#= GetEntLibExceptionShieldingPolicy(CurrentElement) #>
  public abstract class <#= CurrentElement.Name #>Base...
...
}

Using the Extension

To use the extension, we only need to rebuild the changed ServiceFactory.Extenders.ServiceContract.Wcf project and make sure the installed service factory dynamically loads our changed WCF extender assembly. But we must warn you: do not rebuild the whole source code of the Service Factory at this point; this will make changes to the binary installed version. Only rebuild the extender project.

Again, it is a good practice to save a copy of the original extender. (The original WCF extender Microsoft.Practices.ServiceFactory.Extenders.ServiceContract.Wcf.dll can be found in the Guidance Package\Lib subfolder of the Service Factory installation folder.) Now we shut down all instances of Visual Studio and overwrite the extender DLL with our own version. Then when we restart Visual Studio and create a new service that uses WCF Extension, our new property pops up when the service is selected (see Figure 13).

Figure 13 New EntLib Property on the Service
Figure 13 New EntLib Property on the Service(Click the image for a smaller view)
Figure 13 New EntLib Property on the Service
Figure 13 New EntLib Property on the Service (Click the image for a larger view)

When we fill in an exception policy and generate the code, the right EntLib assemblies are added to the generated project. In addition, the using statement and the EntLib attribute are added to the service implementation class. Of course, now our added attribute does not get lost when regenerating since we changed the code-generation template instead of the generated code. The new service code looks like this:

using System;
using WCF = global::System.ServiceModel;
using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.WCF;

namespace WCFService1.ServiceImplementation
{
  /// <summary>
  /// Service Class - Service1
  /// </summary>
  [WCF::ServiceBehavior(Name = "Service1", ...
  [ExceptionShielding("MyExceptionPolicy")]
  public abstract class Service1Base :
    WCFService1.ServiceContracts.IServiceContract1
  {
    ...
  }
}

In order to really get EntLib exception shielding working, we also have to configure an Exception Handling Application Block with an exception shielding policy. You can refer to the EntLib documentation for more details on creating exception handling policies.

For this extension, we added a property to the existing WCF extender, told the extender that we needed extra assembly references, changed the templates to generate the right code for the new property, and made the Service Factory use our changed WCF extender. The upcoming policy properties extension point will make these sorts of extensions easier by allowing you to specify new properties in separate assemblies. In this way, it won't be necessary to make changes to existing extenders.

Send your questions and comments to sstation@microsoft.com.

Gerardo de Geest is a Solution Developer at Avanade, specializing in software factories and domain-specific languages. He has been doing research into the evolution of domain-specific languages, especially within Microsoft Web Service Software Factory Modeling Edition. Gerardo can be contacted at gerardod@avanade.com.

Gerben van Loon is a Solution Developer at Avanade, specializing in software factories and domain-specific languages. He started using Microsoft DSL-Tools back with the early CTPs. Gerben can be contacted at gerbenva@avanade.com.


Foundations: Workflow Services

Foundations
Workflow Services
Matt Milner

Code download available at: Foundations2008_Launch.exe (174KB)
Contents
What's Required for Integration
Modeling Communications with Send and Receive Activities
Hosting Workflows as Services
Hosting Using IIS or Windows Activation Services
Using Context to Manage Long-Running Services

W
hen a new version of Visual Studio® is released, the first thing developers typically want to know is what new features they will find in their favorite programming tool. Although Windows® Workflow Foundation (WF) and Windows Communication Foundation (WCF) were released in November 2006 as part of the Microsoft® .NET Framework version 3.0, it is only with the release of Visual Studio 2008 and the .NET Framework 3.5 that people are becoming aware of the great features these technologies provide.

Unfortunately, with the .NET Framework 3.0, there was not a very good model for combining the declarative programming model of Windows WF with the simplified yet rich communications model exposed by WCF. What's compelling about Windows WF in the .NET Framework 3.5 is that it provides true integration with WCF. This integration means that workflows can now more easily consume and coordinate services and be exposed through service endpoints.

What's Required for Integration

When considering the integration of Windows WF and WCF, there are several key areas of focus. The primary components of any workflow are the activities that allow modeling of application logic. In the .NET Framework 3.5 two new activities, Send and Receive, provide a generic model both for consuming services and for implementing service endpoints within a workflow. I will talk more about how to use these activities shortly.

In addition to being able to model communication, integration requires a mechanism for hosting the runtimes of both WCF and Windows WF. Because each technology has its own runtime, configuration, and extensibility model, we need a way to host both technologies in a single process. The tools provided in the new framework are based on extensibility points in WCF that allow the workflow runtime to be hosted by the WCF runtime.

The final item required for integration is really the crux of the matter: how to marshal WCF messages into workflow instances and map those operations onto activities in the workflow. Again, this feature relies on the WCF extensibility model and is responsible for issues around security, workflow instance management, and marshaling the message bodies as data to activities.

A new assembly, System.WorkflowServices.dll, holds the new classes that provide the integration between Windows WF and WCF. This new assembly is specific for .NET 3.5, while the existing 3.0 assemblies remain unchanged except for bug fixes. By using the existing extensibility models of WCF and Windows WF, it was possible to provide integration without having to modify existing components. I will cover the core classes used when integrating these technologies and explain how they fit together.

In addition to the new assembly, Visual Studio 2008 comes with a few new project and item templates specifically for building workflows to be published as services. In the new project dialog, you will find both the Sequential Workflow Service Library and State Machine Workflow Service Library projects under the WCF category. You will also find new item templates for Windows WF Sequential Service and Windows WF State Machine Service items and their XAML counterparts. Each of these new templates adds the workflow, the interface defining the service contract, and an app.config file that configures the endpoints for the service.

Modeling Communications with Send and Receive Activities

The new receive activity is used to model points in the workflow where your process will receive data as part of a WCF service contract. This means that each receive activity in your workflow will need to be configured with the appropriate information related to the service contract and be tied to an operation on that contract. In order to accomplish this binding between the activity and the operation, the activity has a ServiceOperationInfo property.

The contract can be declared in one of two ways: using WCF service contract attributes or directly within the workflow. The latter is often referred to as "workflow first" design, in contrast to "contract first" design. When defining the contract directly in the workflow, the contract information is embedded within the workflow definition rather than exposed as a .NET interface, as it is when defining a contract with WCF.

When you edit the ServiceOperationInfo property, you are presented with a dialog that allows you to choose the operation, and this is also where you must decide if you are going to use an existing service contract or define a new one. The dialog is shown in Figure 1 and has two buttons in the upper-right corner that allow you to import a service contract or add a new contract. If you choose to import a contract, all operations marked with an OperationContract attribute are imported into the dialog where you can choose which operation your activity implements. In the bottom of the dialog, there are three tabs showing parameters, properties and permissions for the operation. When you import the contract, the parameters and properties are drawn directly from the service contract and may not be changed.

Figure 1 ServiceOperationInfo Configuration Dialog
Figure 1 ServiceOperationInfo Configuration Dialog(Click the image for a smaller view)
Figure 1 ServiceOperationInfo Configuration Dialog
Figure 1 ServiceOperationInfo Configuration Dialog (Click the image for a larger view)

During the creation of a service contract, the dialog lets you configure the properties on each operation to properly shape the service contract. You specify the parameters and return types of the operation and determine the message exchange pattern by setting the IsOneWay property. Additionally, you can specify the protection level to be applied to your messages so that they will be signed and/or encrypted.

After the contract is imported or defined and the operation selected for the receive activity, there are still a few more items to configure on the activity for basic receiving. First is the CanCreateInstance property, which tells the runtime whether the workflow can be started with this operation. Each workflow that will be exposed as a service must have at least one receive activity with the CanCreateInstance property set to true; otherwise there's no way to start the workflow through a service invocation.

The receive activity is implemented as a composite activity, meaning it can have child activities. Essentially, the receive activity works like a sequential activity in that it executes all of its child activities in order until completion. Figure 2 shows a receive activity implementing an operation using a child activity. These child activities make up your implementation of the service operation. At the end of the execution, the receive activity will either return the value bound to its return value property, or it will raise the fault bound to its fault property.

Figure 2 Receive Activity Implementing an Operation
Figure 2 Receive Activity Implementing an Operation

There are two key points to remember about the work you do within the receive activity: you should set the return value or fault property during execution, and any work you do must complete before the service call can return. Setting the output or fault is important, but missing this step presents itself pretty early in the testing cycle. What is sometimes less obvious is the duration of your work, which can impact performance of your service. Because you are in the workflow designer, it can be easy to forget that you are coding a service operation and that a client application is waiting for a response. If your operation is a one-way operation, obviously this is less of a concern and there's also no need to set the output or fault properties, as the client will not receive either.

The send activity consumes services from within a workflow, allowing you to use workflows to coordinate services or to take advantage of logic and data residing in a service. In WCF, clients create channels configured with endpoint information to invoke a service operation. The send activity uses the same ServiceOperationInfo dialog to allow you to import a service contract and choose the operation you want to invoke. The send activity designer then dynamically creates properties for the parameters and return value of the operation you have chosen so that you can bind or set those values in your workflow before calling the service.

To configure the client endpoint, you use two properties: ChannelToken and CustomAddress. The ChannelToken has an EndpointName property that should be configured with the name of a WCF client endpoint found in the configuration file of the process hosting the workflow. This endpoint will contain the address and binding information the runtime needs to create a client channel and invoke the operation at runtime. In addition, the CustomAddress property allows you to change the address, probably the most dynamic part of the endpoint, at runtime or get that value from data that is present in the workflow context.

Other key features of the send activity include events for pre- and post-processing and caching of WCF channels at a scoped level. The events provide hooks for your code to run and either prepare the send activity and its parameters for processing or process the result of the operation invocation before control proceeds to the next activity in the workflow. The caching provides a performance improvement if you are going to make multiple calls to the same service within a workflow. The creation of the client channel and negotiation of security and other protocols can be expensive, and the ability to cache the channel can reduce the time spent on these steps. You configure the scope of the caching by using the ChannelToken property on the send activity and choosing a parent composite activity for the OwnerActivityName property on the token. To reuse this same channel on another send activity, you set the ChannelToken property to the one used in the other activity.

Hosting Workflows as Services

For workflows that receive data through WCF services, the workflow must be hosted just like any other WCF service. The hosting of the service creates the WCF runtime, which is responsible for receiving messages over the network, processing those messages, and then delivering them to a service. If the service is implemented as a workflow, the message payload must be sent to the workflow instance where the receive activity is waiting for data. For more information on how data is communicated to the workflow, see my previous column on workflow communications (msdn.microsoft.com/msdnmag/issues/07/09/Foundations).

In order to host a workflow service you use the new class, WorkflowServiceHost, which derives from the ServiceHostBase class found in System.ServiceModel. Note that the ServiceHostBase class provides the basic logic to host a WCF service and is responsible for building up the WCF runtime including the channels, behaviors, and so forth. When a developer wants to customize the WCF runtime, creating a custom service host is one option for doing so. The hosting model used with workflow services depends heavily on extending the WCF runtime model. For more information on extending WCF, see the Service Station column in the December 2007 issue of MSDN® Magazine (msdn.microsoft.com/msdnmag/issues/07/12/ServiceStation).

The code that follows shows hosting a simple workflow type using the WorkflowServiceHost class in a .NET application:

WorkflowServiceHost host = new
  WorkflowServiceHost(typeof(Workflows.SimpleWorkflow));
host.Open();

In this example, I have passed the type information for the workflow to the constructor of the WorkflowServiceHost. This is exactly how you construct a ServiceHost for a standard WCF service.

When defining workflows, you can either define the structure in a new class or define your workflow using Extensible Application Markup Language (XAML). The WorkflowServiceHost provides several overloaded constructors (see Figure 3). Not only can you provide the path to your XAML file, but you can also provide a stream. This allows you more flexibility in where you store workflow definitions. In addition, these overloads take paths or streams for the XML files containing the rules for your workflow. In these overloaded constructors you can begin to see the overlap of the two technologies as the CreateWorkflow method of the WorkflowRuntime and the constructor of the ServiceHost class get combined into the WorkflowServiceHost.

Figure 3 WorkflowServiceHost Constructors (Close Figure 3)
public WorkflowServiceHost(Stream workflowDefinition,
  params Uri[] baseAddress);
public WorkflowServiceHost(string workflowDefinitionPath,
  params Uri[] baseAddress);
public WorkflowServiceHost(Type workflowType,
  params Uri[] baseAddress);
public WorkflowServiceHost(Stream workflowDefinition,
  Stream ruleDefinition, params
  Uri[] baseAddress);
public WorkflowServiceHost(string workflowDefinitionPath,
  string ruleDefinitionPath,
  params Uri[] baseAddress);
public WorkflowServiceHost(Stream workflowDefinition,
  Stream ruleDefinition,
  ITypeProvider typeProvider,
  params Uri[] baseAddress);
public WorkflowServiceHost(string workflowDefinitionPath,
  string ruleDefinitionPath,
  ITypeProvider typeProvider,
  params Uri[] baseAddress);

Once the WorkflowServiceHost has been created, you can configure the WCF runtime just as you would with any other service using either code or configuration. This allows you to add endpoints or behaviors to the runtime or modify the service description. When editing workflow definitions using pure XAML, the name used to identify the service in the configuration file depends on the markup you use. For example, if you use the x:Name attribute, the value becomes the name of your service. However, if you omit the name attribute, then the name of the first activity will be used, which is generally not a very unique name across workflows.

Because the WCF hosting model is used for the service, you access the WorkflowRuntime through the WCF host. New to the .NET Framework 3.5 is the WorkflowRuntimeBehavior that gets added automatically by the WorkflowServiceHost if you do not explicitly add it to the WCF description. Through this behavior you can configure the WorkflowRuntime object in code or in the configuration file. In the configuration file, you define the services you want to add to the runtime using a service behavior configuration that includes this new element. Then you configure the services as you would normally when configuring the Windows WF runtime. Figure 4 shows an example of a configuration file where the runtime is being configured.

Figure 4 Configuring the Workflow Runtime via Behaviors (Close Figure 4)
<behaviors>
  <serviceBehaviors>
    <behavior name="wfService">
      <serviceMetadata httpGetEnabled ="true"/>
      <workflowRuntime cachedInstanceExpiration="00:10:00"
        validateOnCreate="false">
        <services>
          <add
type="System.Workflow.Runtime.Hosting.SqlWorkflowPersistenceService ..." ConnectionString="server=.;database=wfpersist;integrated security=SSPI" UnloadOnIdle="false"/>
        </services>
      </workflowRuntime>
    </behavior>
  </serviceBehaviors>
</behaviors>

One configuration item not available on the standard workflow configuration section is the cachedInstanceExpiration attribute, which lets you configure how long the runtime keeps a workflow instance in memory after it goes idle. This provides a bit more control than the base functionality of the SQL persistence service workflows as soon as they go idle.

When you want to access the workflow runtime in the host code, you first must access the behavior, and then get the runtime from the properties on the behavior. To access the behavior, you navigate to the service description on the WorkflowServiceHost and then find the WorkflowRuntimeBehavior by type. The behavior has a strongly typed property for accessing the runtime, which you can then interact with to add services, register event handlers, or write any other host-specific, workflow runtime-related code. Figure 5 provides an example of accessing the runtime to add services and register handlers for hosting related events.

Figure 5 Accessing the Workflow Runtime in the Host Code (Close Figure 5)
WorkflowServiceHost host = new
  WorkflowServiceHost(typeof(Workflows.SimpleWorkflow));

WorkflowRuntimeBehavior wfbehavior =
  host.Description.Behaviors.Find<WorkflowRuntimeBehavior>();

WorkflowRuntime wfruntime = wfbehavior.WorkflowRuntime;

wfruntime.WorkflowTerminated +=
  delegate(object sender, WorkflowTerminatedEventArgs wte)
  {
    Console.WriteLine(wte.Exception.Message);
  };

host.Open();

After you have modified the objects in the WCF and Windows WF runtimes in code, simply call Open on the WorkflowServiceHost, and it builds and configures both runtimes and registers all service endpoints to listen for data. Using WorkflowServiceHost directly is known as self-hosting, in contrast to using a provided host process such as IIS or Windows Process Activation Service (WAS)—each of these is discussed in the next section.

Because the WorkflowRuntime is managed by a WCF behavior, there will be a single WorkflowRuntime for each WorkflowServiceHost and thus for each service in your host. (This is in contrast to the common practice in Windows WF of having a single WorkflowRuntime that hosts many different types of workflows.) You will also have multiple instances of the runtime services you have chosen, and each will be running independently. As an example, if you have three services hosted in the same application, and each is configured to use the SQL persistence service, then you will have three instances of the persistence service running, each one polling the database periodically for expired timers. If you configure these services with the same connection string, they will act as three different host processes so ownership duration and locking may become an issue. Finally, note that this behavior exists even if you configure all services with the same behavior configuration. Because it is only configuration, each WorkflowServiceHost will create a new instance of the behavior, which will in turn create a new WorkflowRuntime.

Hosting Using IIS or Windows Activation Services

Hosting services in IIS or WAS is similar in many ways to previous Web service technologies such as the ASMX Web services in the .NET Framework 2.0, but with more extensibility options. A typical WCF service endpoint hosted in IIS uses a file with the .svc extension to act as the addressable resource that will be invoked by clients. This file simply indicates the service type used to implement the service, and the .svc extension is mapped to the WCF handler so it can properly route messages to that service.

One key extensibility point in this model is that you can take control of the ServiceHost creation process by creating a custom factory. In general, the default IIS hosting works out of the box for a large number of scenarios and creates a ServiceHost instance for the service named in the .svc file. However, by allowing you to customize the service host, you can take advantage of the hosting features in IIS without having to give up control of the host completely.

To host a workflow service, you create the .svc file and specify either the workflow type or the path to a XAML file containing the workflow definition, and you specify a new type, WorkflowServiceHostFactory for the Factory attribute. Here's a basic workflow service defined in an .svc file:

<%@ ServiceHost Language="C#" Service=" SimpleWorkflow.xoml"
  Factory="System.ServiceModel.Activation.WorkflowServiceHostFactory" %>

Notice that the service is defined as the path to a XAML file. The WorkflowServiceHostFactory is responsible for loading the XAML file and using it, along with a rules file if there is one present, to create a service host for the workflow and return it when the WCF runtime requests it. The rules file must have the same name as the XAML file but with a .rules extension. This provides a very powerful model where you can drop a few text files—the XAML, .rules, and .svc files—into a virtual directory and have only to add configuration for endpoints to the configuration file.

Using Context to Manage Long-Running Services

Using workflows to implement your service becomes especially compelling when the service is long-running and has multiple interactions with a client. For example, suppose you have a service to manage an expense report approval process. This service, implemented as a workflow, could have at least two interaction points, including the initial submission by the employee and the approval or rejection by the manager. In such scenarios, you need a way to manage the workflow over time and to route messages to the correct workflow instance. In other words, you need a way to manage the context of your messaging.

In .NET Framework 3.5, the support for context is manifested in a new binding element called ContextBindingElement. This element allows developers to create bindings that are context-aware and can help manage the routing of messages to the correct workflow instance. The framework also includes three pre-built bindings for the most common scenarios: WSHttpContextBinding, BasicHttpContextBinding, and NetTcpContextBinding. Each of these bindings matches its non-contextual counterpart in features and configuration but adds the context channel into the runtime stack. This context channel manages the unique conversation identifiers for the interactions as they flow through the channel stack.

Figure 6 shows how this context management works with the included context channel. When a client channel is used to interact with a service, the context information is sent back to the client on the first message exchange. This information minimally contains the instance ID of the workflow that was created to handle the request. The next time the client channel is used to call the service, the context information is sent back to the service where it is used to route the message to the already running workflow instance.

Figure 6 Context Management
Figure 6 Context Management(Click the image for a smaller view)
Figure 6 Context Management
Figure 6 Context Management (Click the image for a larger view)

In addition to routing messages to the correct instance, some interactions between services get more complex. For example, a workflow might initiate communication with three different instances of a service using the send activity and then wait to receive responses from all three instances. In this case, the workflow will have three instances of the receive activity waiting for the same message type to arrive. As a result, there needs to be a mechanism for routing the incoming message not only to the correct workflow but also to the correct receive activity within the workflow. The context mechanism supports not only the passing of the instance ID but also includes a conversation identifier for those scenarios where the workflow is waiting for multiple messages on the same operation. Figure 7 provides an example of this type of scenario.

Figure 7 Conversation Management
Figure 7 Conversation Management(Click the image for a smaller view)
Figure 7 Conversation Management
Figure 7 Conversation Management (Click the image for a larger view)

The context is managed as a collection of name-values pairs and can be passed via SOAP headers or an HTTP cookie. The context channel handles this for you in most cases at the lower layers, but as a workflow author, you are responsible for managing the context within your workflow. In the previous example where I called out to three instances of a service, each instance of that service would need to have code that set the context before it could send a response to my workflow service. This means that, in some way, the calling workflow must send the context information without using the built-in channel features, and the called service must use that context information when later calling back into the workflow. The send activity conveniently contains a context property that can be set before the service is called, which then causes the activity to set the context on the channel before invoking the service.

If all communication is initiated from the calling workflow, the need to pass the context to the called workflow is irrelevant as the called workflow can simply send responses to the requests it receives. While this implementation mainly targets intranet-style applications and has some limitations, it does provide a framework for extensibility, which I'll cover in a later issue.

Send your questions and comments to mmnet30@microsoft.com.

Matt Milner is an independent software consultant specializing in Microsoft technologies including .NET, Web services, Windows Workflow Foundation, Windows Communication Foundation, and BizTalk Server. At Pluralsight, Matt teaches courses on Workflow, BizTalk Server, and Windows Communication Foundation. Contact Matt via his blog at pluralsight.com/blogs/matt.


Windows with C++: Windows Services Enhancements

Windows with C++
Windows Services Enhancements
Kenny Kerr

Contents
Delaying Auto-Start Services
Improving Shutdown Predictability
Failure Actions and Controlled Stop
Reducing Privileges
Protecting Service Data
Protecting Others with Restricted Tokens
Receiving Service Notifications
What's Next

T
he state of Windows® service development was largely unchanged since the dawn of services in Windows NT®, but Windows Vista® and Windows Server® 2008 bring some significant changes to services. Many of these features are focused on making it simpler to produce services that are more secure, but among the non-security-related service features are a few aimed at improving the overall responsiveness and reliability of Windows.

Delaying Auto-Start Services

Services can be configured to start only when they are specifically requested to do so via the StartService function. Such a service is called a demand-start service. Alternatively, they can be configured to start automatically during the operating system's startup process. These are known as auto-start services, and this mode makes sense for a lot of services whose functionality should be available at all times or that may be required by other services. Some auto-start services, however, don't absolutely need to be running at the time the user logs on to the computer because they may not be used immediately.

Windows Vista takes another step toward reducing the Windows startup time by providing a facility for auto-start services to be started only after Windows has finished its own startup process. Specifically, a delayed auto-start service is activated roughly two minutes after regular auto-start services. The Service Control Manager (SCM) also creates the main service thread with a lower priority, ensuring that any logged on user isn't notably impacted by delayed auto-start services. The priority is set back to normal after the service updates its state indicating that it is running. You can force a delayed auto-start service to start sooner by using the StartService function if it turns out that it hasn't started by the time your application needs to make use of it.

The following function shows how you can control this option (it should be called by your service's setup program):

bool ChangeDelayedAutoStart(
    SC_HANDLE service, bool delayed)
{
    ASSERT(0 != service);

    SERVICE_DELAYED_AUTO_START_INFO info = { delayed };

    return 0 != ::ChangeServiceConfig2(
        service,
        SERVICE_CONFIG_DELAYED_AUTO_START_INFO,
        &info);
}

The service handle identifies the service to configure. This handle can be obtained by calling the OpenService or CreateService functions. The ChangeServiceConfig2 function lets you change a number of more advanced service configuration options. In this case, I'm using the SERVICE_CONFIG_DELAYED_AUTO_START_INFO flag to tell the function to expect a SERVICE_DELAYED_AUTO_START_INFO pointer as its third parameter. ChangeServiceConfig2 returns zero if it fails, and you can get extended error information by calling the GetLastError function.

Improving Shutdown Predictability

Prior to Windows Vista, it wasn't always possible to ensure that services were stopped gracefully when the computer was shutting down. When the system notified the SCM that it was shutting down, it had only about 20 seconds to instruct all the running services to stop. If it took too long, the system would eventually simply terminate the SCM process. This could cause a number of problems including, ironically, increasing startup time since services that didn't shut down gracefully would often need to deal with inconsistent state when they were started up once again.

Windows Vista introduced a new pre-shutdown notification that services can ask to receive. If you are developing a service that must shutdown gracefully and cannot necessarily do so quickly, then you can ask the SCM for a pre-shutdown notification. The SCM will wait (potentially indefinitely) for all of the pre-shutdown services to stop before proceeding with its traditional services shutdown process. Although this is good for services, it's not necessarily that great for users who may want their computers to shut down promptly. For this reason, you should limit the use of this feature to very critical services and, preferably, only in server scenarios.

A service indicates its desire to receive a pre-shutdown notification by including the SERVICE_ACCEPT_PRESHUTDOWN flag in its service status. The service's handler function will then be notified via the SERVICE_CONTROL_PRESHUTDOWN control code. Although Windows is willing to wait indefinitely for a pre-shutdown service to stop, the service is required to remain responsive to queries from the SCM and update the service's state by incrementing checkpoint values. If the SCM decides that your service is unresponsive, it will eventually give up and the system will continue to shut down. Keep in mind that if your service handles the SERVICE_CONTROL_PRESHUTDOWN control code, it will not receive the traditional SERVICE_CONTROL_SHUTDOWN control code. In most cases, it is best to use a single shutdown procedure for all stop-related control codes.

A significant behavioral characteristic of the SCM is that it only ever communicates with one service at a time. This has certain implications that will be obvious in a moment if they aren't already: if two applications (or threads) call service control functions, like StartService or ControlService, then the SCM will queue them and service the requests one at a time. Another more problematic scenario is when Window is shutting down. This communication is performed via a named pipe established between the SCM and the service when the SCM calls the StartServiceCtrlDispatcher function. When the SCM sends a service a control code, it communicates over the named pipe to the StartServiceCtrlDispatcher function within the service. It forwards the request to the handler for the particular service (for shared process services), and the handler is given the opportunity to process the request before it returns. The problem is that there is nothing to prevent a handler function from blocking for a long time. The request sent by the SCM will eventually time out if the service does not respond within 30 seconds or so, but in the meantime no other services can be controlled.

As a service developer, you need to keep this in mind and never block your service's handler function. A well-behaving service should acknowledge the request, set a flag or kick off some worker thread to handle the request, and return immediately. This allows the SCM to continue communicating with other services while your service has time to handle the request. Figure 1 shows how you might implement the stop and shutdown control codes.

Figure 1 Handling Stop and Shutdown (Close Figure 1)
DWORD WINAPI StopThread(PVOID)
{
  // perform any length shutdown operation

  m_status.dwCurrentState = SERVICE_STOPPED;

  VERIFY(::SetServiceStatus(m_handle,
                            &m_status));
  return 0;
}

...

switch (control)
{
  case SERVICE_CONTROL_STOP:
  case SERVICE_CONTROL_SHUTDOWN:
  case SERVICE_CONTROL_PRESHUTDOWN:
  {
    m_status.dwCurrentState = SERVICE_STOP_PENDING;

    VERIFY(::SetServiceStatus(m_handle,
                              &m_status));

    CHandle thread(::CreateThread(0, // default security
                                  0, // default stack size
                                  StopThread,
                                  this, // context
                                  0, // no flags
                                  0)); // ignore thread id

    break;
  }

  ...
}

The control handler, which handles the SERVICE_CONTROL_STOP, SERVICE_CONTROL_SHUTDOWN, and SERVICE_CONTROL_PRESHUTDOWN control codes, sets the service state to SERVICE_STOP_PENDING, creates a worker thread to do the work, and returns without further ado. The StopThread function can block as long as necessary and then set the service's state to SERVICE_STOPPED, informing the SCM, which can subsequently let the StartServiceCtrlDispatcher function return and terminate the process (provided it was the last service for shared process services). Just remember that if your service is in a pending state, it needs to regularly update the service's state with an incrementing checkpoint value to assure the SCM that it is not hanging.

Failure Actions and Controlled Stop

Failure actions have long been available as a means for services that terminate abruptly to recover in some way. Prior to Windows Vista, failure actions were only executed if the service process terminated without first setting the service's state to SERVICE_STOPPED. Windows Vista allows the service to request that its failure actions should also be executed if the service's state is set to SERVICE_STOPPED, but the service's exit code, provided by the SERVICE_STATUS::dwWin32ExitCode member, is set to something other than ERROR_SUCCESS. This allows you to employ failure actions when your service fails while still letting you stop your service gracefully.

The following function shows how you can control this option to stop a service (and again, it should be called by your service's setup program):

bool ChangeFailureActionsOnNonCrashFailures(
  SC_HANDLE service,
  bool failureActionsOnNonCrashFailures)
{
  ASSERT(0 != service);

  SERVICE_FAILURE_ACTIONS_FLAG flag = {
   failureActionsOnNonCrashFailures };

  return 0 != ::ChangeServiceConfig2(service,
    SERVICE_CONFIG_FAILURE_ACTIONS_FLAG,
    &flag);
}

For backward compatibility, the default for this option is false, thus preserving the behavior of older versions of Windows.

Reducing Privileges

Most administrators prefer services that run under built-in Windows service accounts. This avoids the headaches inherent in password management for service accounts. Of course, there are only so many built-in service accounts, and the privileges and permissions they afford might be more than your service needs. Although Windows Vista has not added any more service accounts—that would simply not be practical—it has introduced some powerful new configuration options that allow you to precisely lock down a service using restricted tokens.

The first such configuration option allows you to specify exactly which privileges your service requires. The SCM will ensure that the service's process token includes only the privileges you need. Figure 2 shows how you can control this option. It should be called by your service's setup program.

Figure 2 Setting Service Privileges (Close Figure 2)
bool ChangeRequiredPrivileges(
    SC_HANDLE service,
    const CAtlArray<PCWSTR>& requiredPrivileges)
{
  ASSERT(0 != service);

  size_t bufferSize = 1;

  for (size_t index = 0; index < requiredPrivileges.GetCount(); ++index)
  {
    bufferSize += wcslen(requiredPrivileges[index]) + 1;
  }

  CAtlArray<WCHAR> buffer;

  if (!buffer.SetCount(bufferSize))
  {
    ::SetLastError(ERROR_OUTOFMEMORY);
    return false;
  }

  PWSTR position = &buffer[0];

  for (size_t index = 0; index < requiredPrivileges.GetCount(); ++index)
  {
    PCWSTR name = requiredPrivileges[index];
    const size_t nameSize = wcslen(name);

    wcscpy_s(position,
             nameSize + 1,
             name);

    position += nameSize + 1;
  }

  SERVICE_REQUIRED_PRIVILEGES_INFO info = { &buffer[0] };

  return 0 != ::ChangeServiceConfig2(
    service,
    SERVICE_CONFIG_REQUIRED_PRIVILEGES_INFO,
    &info);
}

This function may look a bit complicated, but that's only because the ChangeServiceConfig2 function requires that the list of privilege names be provided as a sequence of null-terminated strings, terminated by an empty string. Using the ChangeRequiredPrivileges function is painless, however:

CAtlArray<PCWSTR> requiredPrivileges;
requiredPrivileges.Add(SE_CHANGE_NOTIFY_NAME);
requiredPrivileges.Add(SE_IMPERSONATE_NAME);

if (!ChangeRequiredPrivileges(service, requiredPrivileges))
{
    // call GetLastError for more info
}

It's important to keep in mind that the privileges you specify actually need to be granted to the account your service is configured to use. If, for example, you configure your service to require the SE_TCB_NAME privilege, but your service is configured to run under the LocalService account, which does not have this privilege, the SCM will fail to start the service because it assumes that your service will not function correctly without the required privilege.

The other thing to keep in mind is related to shared-process services. Since all services that share a process also share the same security context, the SCM will include the union of the privileges required by all the services that can potentially share the process. This may impact which services you decide are candidates for sharing an address space and a security context.

Protecting Service Data

Although reducing the available privileges is useful in limiting what an exploited service can do (to a degree), it does not limit what data the service can access. For all their administrative benefits, the built-in service accounts have never been ideal since all services that use the same service account gain access to the same secured resources. Prior to Windows Vista, there was no way to say that Service A running as LocalService should have certain permissions on some resource while Service B should not. This made it harder to secure service-specific data from attack by other services.

Windows Vista fixes this by introducing service security identifiers (SIDs) that can be used in access control lists (ACLs) to set the permissions not only for the particular service account but also for the particular service. Every service on Windows Vista and Windows Server 2008 has a SID that identifies it, and that SID can be used in access control editors and indeed almost anywhere a security descriptor is specified. Figure 3 provides an example of a folder with an access control entry specifically granting service permissions to the folder.

Figure 3 Permissions Granted to Service A
Figure 3 Permissions Granted to Service A

You can specify a service name in access control editors using the NT Service domain. In the example shown in Figure 3, the account name is NT Service\ServiceA. You can also use the LookupAccountName function if you need to use the service SID programmatically.

By default, the process token created for a service does not include the service SID. Changing this default option is a job for the ChangeServiceConfig2 function. The following function shows how you can control this option (yes, it should be called by your service's setup program):

bool ChangeSidType(SC_HANDLE service,
  DWORD sidType)
{
  ASSERT(0 != service);

  SERVICE_SID_INFO info = { sidType };

  return 0 != ::ChangeServiceConfig2(service,
    SERVICE_CONFIG_SERVICE_SID_INFO,
    &info);
}

The default value for the SID type is SERVICE_SID_TYPE_NONE. This is for application compatibility and produces a similar security context as you might get for a service on an older version of Windows. Specifying SERVICE_SID_TYPE_UNRESTRICTED as the SID type instructs the SCM to add the service SID to the service's process token, thus allowing the service to gain access to resources that you may have configured to allow access only to your specific service.

Protecting Others with Restricted Tokens

While protecting a service's resources with a service SID is good for keeping other users and services from accessing or at least modifying its data or settings, it does nothing to prevent the service from accessing other resources that the service does not need access to. Why does this matter? Well, if your service is compromised, it could be used as an attack vector.

Windows provides a third option for the SERVICE_CONFIG_SERVICE_SID_INFO flag using the SID type SERVICE_SID_TYPE_RESTRICTED. This SID type results in the service SID being included in the service's process token, but it also instructs the SCM to add a list of restricting SIDs to the token to further constrain the resources to which the service has access.

Restricting SIDs have traditionally been hard to apply generally as it can be very hard to come up with a constrained list of SIDs that will effectively reduce the reach of a token without also constraining it so much that it becomes useless. To solve this problem, Windows Vista introduces a new type of token known as a write-restricted token. It is basically a relaxation of the rules governing restricting SIDs. Instead of being used to simply constrain access beyond that which is allowed by the discretionary access control list (DACL), the write-restricted token is used only when evaluating write-access checks. This increases the applicability of restricting SIDs considerably.

With the SID type of SERVICE_SID_TYPE_RESTRICTED, the SCM will create a restricted token for the service process with the following restricting SIDs: the Everyone SID, the Logon SID, the service SID, and the write-restricted SID. This allows you sufficient freedom to grant write access to resources based on a specific service SID or any write-restricted token. More importantly, it severely constrains where the service can write to, thereby limiting the impact it might have in the event that it is compromised.

Receiving Service Notifications

Controlling and querying service status has traditionally been a challenging task. You can, for example, call the StartService function to request that a particular service is started. You are, however, not immediately notified that the service has started successfully since it may take some time to complete its startup process. Instead, you're left to wait for the service to start and can call the QueryServiceStatus function periodically to check on its progress. Needless to say, this is not an ideal solution. Among other reasons, polling leads to poor performance, as the calling thread is needlessly scheduled in order to check the status of the service. Fortunately, Windows Vista finally addresses this problem with the introduction of service status notifications.

The new NotifyServiceStatusChange function enables callers to receive notifications in the form of asynchronous procedure calls (APCs). Each call to NotifyServiceStatusChange will result in, at most, one APC being queued, so if you need to be informed of multiple changes, you will need to call NotifyServiceStatusChange again. If you are no longer interested in receiving a notification, all you have to do is simply close the service handle, and the system will dequeue any pending APC.

NotifyServiceStatusChange has the ability to provide notifications for either a particular service or for the SCM itself. Figure 4 shows how you can receive a notification when the Windows Search service has started.

Figure 4 Receiving APC Notifications (Close Figure 4)
void CALLBACK ServiceNotifyCallback(void* param)
{
  SERVICE_NOTIFY* serviceNotify = static_cast<SERVICE_NOTIFY*>(param);

  // Check the notification results
}

int main()
{
  SC_HANDLE scm = ::OpenSCManager(
    0,                              // local SCM
    SERVICES_ACTIVE_DATABASE,
    SC_MANAGER_ENUMERATE_SERVICE);

  ASSERT(0 != scm); // Call GetLastError for more info.

  SC_HANDLE service = ::OpenService(
    scm,
    L"wsearch",
    SERVICE_QUERY_STATUS);

  ASSERT(0 != service); // Call GetLastError for more info.

  SERVICE_NOTIFY serviceNotify =
    { SERVICE_NOTIFY_STATUS_CHANGE,
      ServiceNotifyCallback };

  const DWORD result = ::NotifyServiceStatusChange(
    service,
    SERVICE_NOTIFY_RUNNING,
    &serviceNotify);

  ASSERT(ERROR_SUCCESS == result);

  ::SleepEx(INFINITE, TRUE); // Wait for the notification

  ::CloseServiceHandle(service);
  ::CloseServiceHandle(scm);
}

If you would like to receive SCM notifications, simply use the handle for the SCM instead of the service handle. Note that different notification flags are used. Only the SERVICE_NOTIFY_CREATED and SERVICE_NOTIFY_DELETED flags apply to the SCM. The rest, including SERVICE_NOTIFY_RUNNING, SERVICE_NOTIFY_STOPPED, and others all require a service handle. For a complete list of flags, take a look at the NotifyServiceStatusChange documentation.

The address of the same SERVICE_NOTIFY structure is passed to the notification callback function. You can then determine the specifics of the notification.

Finally, the use of APCs for notifications might be a bit intimidating if you're not familiar with the process, but it's fairly straightforward once you get used to it. For a detailed look at APCs, be sure to check out my Parallel Programming with C++ series of articles at weblogs.asp.net/kennykerr.

What's Next

That about covers the Windows services enhancements introduced with Windows Vista. I should mention that the new Windows Firewall API has also been enhanced with support for restricting a service's access to the network. The Windows Firewall API is, however, beyond what I can cover in this issue, but I hope to dedicate some space in a future column to it.

Send your questions and comments for Kenny to mmwincpp@microsoft.com.

Kenny Kerr is a software craftsman specializing in software development for Windows. He has a passion for writing and teaching developers about programming and software design. Reach Kenny at weblogs.asp.net/kennykerr.


Team System: Check-in Notes and Policies

Team System
Check-in Notes and Policies
Brian A. Randell

Code download available at: TeamSystem2008_Launch.exe (150KB)
Contents
Check-In Notes
Check-In Policies
Check-In Conflicts

B
ack in the January 2007 installment of this column, I started a discussion on using the Team Foundation Server (TFS) version control APIs from a Microsoft® Word 2003 add-in. Here I am, more than a year later, with the fifth and final part dedicated to the version control APIs and the Word add-in. (I really didn't think it would take this many installments.)

You may recall from earlier columns that the add-in currently supports check-in, check-out, undo pending changes, and the ability to associate work items with a check-in. In my most recent column, I examined check-in notes and check-in policies. This month, I will show you how to add check-in notes and check-in policy support to the Word add-in. In addition, you'll learn about how to deal with check-in conflicts. And if you haven't read the rest of this series (listed in the "First Four Parts of This Series" sidebar), be sure to check them out.

Check-In Notes

Check-in notes are free-form text fields that you can associate with a check-in. You can mark check-in notes as mandatory and control the order in which they appear in the Visual Studio® check-in dialog. In order to mimic this behavior in the Word add-in, you should get the list of check-in notes for the active team project, sort the list, and display a label and textbox for each check-in note.

The VersionControlServer object exposes the GetCheckinNoteDefinitions method, which accepts an array of team project names and returns an array of CheckinNoteFieldDefinition objects. Each CheckinNoteFieldDefinition instance exposes properties for check-in note name, display order, whether it's required, and a server item string. GetCheckinNoteDefinitions returns the list of items in what appears to be a seemingly random order. In my tests, I found that notes are returned sorted by check-in note name and grouped by team project name no matter what order the team project references are passed into the method. Thus, assuming there are two team projects defined, one called MSF-Agile and one called MSF-CMMI with the default set of notes, the results would appear as shown in Figure 1. And if you created a new check-in note called Build Master and added it to the MSF-CMMI team project, it would appear first in the list.

Figure 1 Results from Calling GetCheckinNoteDefinitions (Close Figure 1)

Check-In Note Name | Team Project
Code Reviewer | MSF-Agile
Code Reviewer | MSF-CMMI
Performance Reviewer | MSF-Agile
Performance Reviewer | MSF-CMMI
Security Reviewer | MSF-Agile
Security Reviewer | MSF-CMMI

The tfsVCUtil class, created for the Word add-in, has two new shared methods that you can use to retrieve check-in notes: GetAllCheckInNotesByTeamProjectName and GetAllCheckInNotesByItemLocalPath. Using either of these methods, you can get the unmodified list of CheckinNoteFieldDefinitions, if any, for the team project.

Supporting check-in notes on the check-in dialog requires that you add an extra toggle button under the existing work items button, an extra panel to act as a container, and a label and textbox for each check-in note. (The code download provides a simple implementation.) However, adding support for check-in notes to the check-in dialog is just one piece of the puzzle. You also need to add code to enforce required notes. Check-in policies and conflict detection require that you make additional changes to how the check-in dialog and supporting code in the tfsVCUtil class work.

Check-In Policies
First Four Parts of This Series
  • Team Foundation Server Version Control (January 2007)
  • Work Item Tracking (April 2007)
  • Work Items and Undo Support (September 2007)
  • Custom Check-in Policies (November 2007)

Just as with check-in notes, the first order of business for supporting check-in policies is figuring out what policies are enabled for the active team project. The second order of business is deciding how to process check-in policies. Because Microsoft designed check-in policies primarily for developers checking in code from Visual Studio, two of the three built-in policies that ship with TFS 2005 don't work correctly when used to a validate a check-in for a single Word document. Thus it's necessary that the add-in only process errors from check-in policies that make sense in the context of a Word document.

It turns out there is only one way to solve this problem. You have the add-in evaluate all enabled policies and ignore the errors generated by those policies that don't make sense. On initial inspection of how check-in policies work, it looks as if a possible alternative is to have the add-in run the evaluation process manually against those policies that should work. But it turns out this is not possible.

By default, check-in policies and check-in notes are not evaluated as part of the check-in process when using custom code. In order to mimic the behavior of Visual Studio, it's up to your application code to ensure that required check-in notes are provided and that there are no policy violations—or, if there are violations, that policy override information is provided.

To make life a bit easier, the Workspace class exposes an EvaluateCheckin method. You use this to check for conditions that might cause a check-in to fail. Here's the method prototype from the MSDN® documentation:

Public Function EvaluateCheckin ( _
    options As CheckinEvaluationOptions, _
    allChanges As PendingChange(), _
    changes As PendingChange(), _
    comment As String, _
    checkinNote As CheckinNote, _
    workItemChanges As WorkItemCheckinInfo() _
) As CheckinEvaluationResult

The method first wants to know what you would like to evaluate. You provide this information by passing the correct enumeration value via the options parameters. You can have the method look for check-in conflicts, required check-in notes, policies violations, or all three.

The method then requires that you pass in an array of PendingChange objects for the second and third parameters. Why two sets? In Visual Studio, for example, you could have ten modified files in the current workspace, but the user can choose to check only three files for check-in. Thus, Visual Studio passes all ten files via the allChanges parameters and the three checked files via the changes parameter. The method provides this information for the enabled policy assemblies. In the case of the Word add-in, you pass the same array for both parameters.

For the fourth parameter, you pass in the comment text, if any, provided by the user doing the check-in. To pass check-in notes, you create an instance of the type CheckInNote, which accepts an array of CheckinNoteFieldValue instances, and pass this as the fifth parameter. Finally, you pass an array of WorkItemCheckinInfo instances for the last parameter. You can pass nothing for any of the last three parameters.

Once you've called EvaluateCheckin, you receive a CheckinEvaluationResult instance. This object exposes the results via three array properties: Conflicts, NoteFailures, and PolicyFailures. If there are no values for a particular result set, EvaluateCheckin returns a zero-length array. It is up to you to enumerate each array and process the results. Note that even if EvaluateCheckin returns failures for check-in notes or policies, this does not block a check-in. You must implement logic in your code to prevent a check-in when these rules aren't satisfied. However, if conflicts exist, TFS does block the check-in. To take advantage of this method for the Word add-in, expose EvaluateCheckin via the tfsVCUtil class using a shared method called PreValidateCheckIn:

Public Shared Function PreValidateCheckIn( _
  ByVal docPath As String, _
  ByVal options As CheckinEvaluationOptions, _
  ByVal comments As String, ByVal notes As CheckinNote, _
  ByVal workitems() As WorkItemCheckinInfo) _
  As CheckinEvaluationResult

  Dim pc As PendingChange() = _
      m_userWorkspace.GetPendingChanges(docPath)

  Return m_userWorkspace.EvaluateCheckin(options, _
      pc, pc, comments, notes, workitems)
End Function

As you can see from the code listing, PreValidateCheckIn mimics the signature of EvaluateCheckin with the additional parameter for the document to evaluate. Once you call this method from the check-in dialog, you enumerate the arrays for the items you requested to be validated, looking for issues and, if necessary, providing a visual indication to the user of what's wrong.

With regard to policy violations, you have to decide what policies make sense for your application. Naturally, the built-in policies offered by Microsoft, as well as many of those available in the wild, are biased to the check-in experience in Visual Studio. In the case of the Word add-in, only the work-item-required policy makes sense when examining the three built-in policies in the 2005 edition of Visual Studio Team System. Aside from the built-in policies, the Word add-in solution needs to worry only about policy errors raised by relevant assemblies. Using some out-of-band mechanism, such as name/value pairs stored in the add-in's configuration file, the code knows which policies should be respected. Then, at run time, the code examines the policy errors collection and ignores all errors not on the list. One way you can do this is to cast the IPolicyEvaluation reference returned from the PolicyFailure instance's Policy property to an instance of IPolicyDefinition. Then, IPolicyDefinition exposes a Type property that can be used to identify the policy source. Here's a simple example:

Dim cer As CheckinEvaluationResult = {Call to server elided}
Dim i As IPolicyDefinition = Nothing
For Each pf As PolicyFailure In cer.PolicyFailures
  i = DirectCast(pf.Policy, IPolicyDefinition)
  If i.Type = "Work Items" Then
    ' process this policy failure
  End If
Next

There is a downside to this approach. As part of the Visual Studio 2005 Team Foundation Server Power Tools, Microsoft added four additional check-in policies (see msdn2.microsoft.com/vstudio/aa718351). One policy in particular, the custom path policy, uses a bit of indirection via delegation to run another policy, such as the work item policy, when the pending changes being provided match a particular regular expression. Teams use this policy to enforce stricter check-in rules on only part of their code repository while another section can be less restrictive. Thus, if this policy is used, the previously listed code sees the type of the custom path policy, not the actual policy executing—for example, the work items policy.

You might try to do what Visual Studio does, but that doesn't work. While you can discover the list of enabled policies by calling GetCheckinPolicies off of a valid TeamProject instance, there's no (reasonable) way to acquire a valid IPendingCheckin reference that is required as a parameter to IPolicyEvaluation's Initialize method. Thus, to deal with the custom path policy, you need to examine the Description property returned from IPolicyDefinition. The authors of the policy chose to store the Type value from the delegated policy here. Thus, the refactored code looks like this:

Dim cer As CheckinEvaluationResult = '{Call to server elided}
Dim i As IPolicyDefinition = Nothing
Dim processPolicy As Boolean = False
For Each pf As PolicyFailure In cer.PolicyFailures
  i = DirectCast(pf.Policy, IPolicyDefinition)

  processPolicy = (i.Type = "Work Items")
  If Not processPolicy AndAlso _
    i.Type = "Custom Path Policy" Then
    processPolicy = (i.Description = "Work Items")
  End If
  If processPolicy Then
    ' process this policy failure
  End If

This version does a basic job of handling both the standard Work Items policy and a delegated version using the Custom Path Policy. Your own implementation may be more complex and may require a more elaborate solution, but now you are equipped with the tools to solve the problem.

I've shown you how to get the list of check-in notes so you can present a UI. If you look at EvaluateCheckin and the wrapper method PreValidateCheckIn, you'll see that in order to validate check-in notes, you need to create and then pass an instance of CheckinNote. After you execute EvaluateCheckin, either directly or via the wrapper, you need to examine the NoteFailures collection. Each NoteFailure instance exposes a Definition property that represents the CheckinNoteFieldDefinitions for the check-in note. From there, you can easily access the Name property to know which check-in notes to highlight in your UI. The Message property is a generic message stating "A value must be specified for the check-in note." As mentioned earlier, neither policy violations nor missing check-in notes will block a check-in executed from code. It's up to you to ensure that things are, in fact, satisfactory before you call your check-in code.

Check-In Conflicts

Check-in conflicts, on the other hand, do block your check-in. You control the default check-out behavior for artifacts from the version control repository using the File Types dialog (shown in Figure 2), which is accessible via Team | Team Foundation Server Settings | Source Control File Types. Using this dialog, you control which files support file merging and thus simultaneous check out. For example, you can see in Figure 2 that Microsoft Office Files have file merging disabled. As a result, when you perform a check out using the Source Control explorer or the APIs, you get an exclusive check out. To adjust the multiple check-out and file merging settings for a set of file extensions, simply click the Edit button to open the Edit File Types dialog.

Figure 2 Configured File Types for Version Control
Figure 2 Configured File Types for Version Control(Click the image for a smaller view)
Figure 2 Configured File Types for Version Control
Figure 2 Configured File Types for Version Control (Click the image for a larger view)

If file merging is enabled for a particular file extension and a conflict occurs at check-in, Visual Studio, by default, displays the Team Explorer Merge Tool. Microsoft made this a configurable option on a per developer basis. Within Visual Studio, you can go to Tools | Options, and then under the Source Control node, select the Visual Studio Team Foundation Server node. On the options page, you click Configure User Tools and then Add. You now have full control over how a particular file extension or set of extensions are handled (see Figure 3).

Figure 3 Configuring a New Merge Tool for a File Extension
Figure 3 Configuring a New Merge Tool for a File Extension(Click the image for a smaller view)
Figure 3 Configuring a New Merge Tool for a File Extension
Figure 3 Configuring a New Merge Tool for a File Extension (Click the image for a larger view)

Thus, the default behavior is for an exclusive check out when you use the Word add-in to check out a Word document. In theory, this eliminates the need to deal with conflicts in the add-in code. However, there's an issue with this default behavior that catches many new TFS users off guard. By default, if you request a checkout using TFS 2005 via Visual Studio or the APIs, the check out occurs against the current local file in your workspace—even if it's not the most current version of the file stored in TFS. The underlying code does not perform a get latest. So it is possible for you to check out a file via the Word add-in, make changes, and then have the check-in fail when you are done.

You can solve this problem in a number of ways. In fact, Microsoft has listened to customer feedback, and in TFS 2008 you can enable Get Latest on Check out on a per Team Project basis; you can also control this feature at your local installation. Microsoft also updated the PendEdit API to support this feature. However, for the add-in working against TFS 2005, you need to modify the existing check out code to manually perform a get latest before check out. To do this, you need to add a line of code that calls Workspace.Get with the correct parameters before the call to PendEdit. The code in Figure 4 does just that.

Figure 4 Code to Get Latest before Check Out (Close Figure 4)
Public Shared Function CheckOutDocument( _
  ByVal docPath As String) As Boolean

  If serverValidated Then
    If m_userWorkspace Is Nothing Then
      ' We need the Workstation
      Dim userWorkstation As Workstation = Workstation.Current
      ' We need the Workspace
      Dim docWSInfo As WorkspaceInfo = _
        userWorkstation.GetLocalWorkspaceInfo(docPath)

      m_userWorkspace = m_tfsVCS.GetWorkspace(docWSInfo)
    End If

    Dim status As GetStatus = _
    m_userWorkspace.Get(New String() {docPath}, _
      VersionSpec.Latest, RecursionType.None, _
      GetOptions.GetAll Or GetOptions.Overwrite)

    Dim statusCount As Integer = status.NumConflicts + _
      status.NumFailures + status.NumOperations + _
      status.NumWarnings

    If statusCount = 1 Then
      If m_userWorkspace.PendEdit(docPath) = 1 Then
        Return True
      Else
        Return False
      End If
    Else
      Throw New tfsUtilException( _
        String.Format(MSG_UNEXPECTED_GETLATEST_CONFLICT, _
          "Failures count: " & status.NumFailures))
    End If
  Else
    Throw New tfsUtilException(MSG_SERVER_NOT_VALIDATED)
  End If
End Function

The Get command returns an instance of a GetStatus object, which exposes a set of properties that provide statistics related to the number of conflicts, failures, operations, and warnings. You can use this data to determine if the Get command worked as expected.

With the add-in using this version of CheckOutDocument, you shouldn't run into any conflicts. But if you're still concerned about conflicts, just check the Conflicts array returned by EvaluateCheckin. In case there are conflicts, you need to provide an experience that lets the user pick the so-called "winner." If you want the winner to be the person attempting to perform the current check-in, you need to use the QueryConflicts method of the Workspace object. This returns an array of Conflict objects. You then set the Resolution property of the Conflict instance to Resolution.AcceptYours and pass the object to the Workspace.ResolveConflict method. Providing a richer experience than this will require serious thought and additional investment in both time and code.

Now, provided you have all five parts of this series on hand, you should have all the tools and information necessary to code up your own rich custom check-in experiences for your applications. Going forward, you'll need to review the updated APIs provided with Team Foundation Server 2008 and get ready for even more enhancements in the "Rosario" release coming sometime in the future.

A special thanks to Martin Woodward of Teamprise and all the folks on the Visual Studio Team System team who have helped with this and the previous columns in this series.

Send your questions and comments to mmvsts@microsoft.com.

Brian A. Randell is a Senior Consultant with MCW Technologies LLC. He spends his time speaking, teaching, and writing about Microsoft technologies. Brian is the author of Pluralsight's Applied Team System course and is a Microsoft MVP. You can contact Brian via his blog at mcwtech.com/cs/blogs/brianr.


{ End Bracket }: The Digital Declaration of Independence

{ End Bracket }
The Digital Declaration of Independence
Bill Hill

Digital technology is remaking our world. Five hundred years from now, historians will look back at the things all of us who read MSDN® Magazine are doing today, and I believe they'll call our time The Digital Renaissance.

All of the science, technology, medicine, and economic prosperity we enjoy today—and the political shape of the planet—can be traced back to the printing technology developed by Johannes Gutenberg. It's hard to imagine how any of those developments could have happened without the widespread adult literacy and ability to record and share knowledge, which his technology enabled. The Bible that Gutenberg produced in 1455 wasn't the first book ever printed. But it was the first ever printed using movable metal type in a mechanized system. This innovation was like an undersea earthquake. Not even noticeable at first, it became a tidal wave that changed the world forever; its ripples are still being felt after more than half a millennium.

The ability to easily store and share information broke the stranglehold of church and state. It enabled widespread adult literacy and it led to the explosion of thought we call the Renaissance. It also led to the new political and economic thinking historians call the Age of Enlightenment. People knew their world was changing. Thomas Paine, one of the thinkers behind the American Revolution, captured it best. On St. Valentine's Day, 1776, he said, "We have it in our power to begin the world over again. A situation, similar to the present, hath not happened since the days of Noah until now. The birthday of a new world is at hand."

Once it was clear the shape of the future was up for grabs, the group of like-minded thinkers to which Paine belonged began to ask: "What kind of world do we want to build?" Their thinking led to the Declaration of Independence, which defined a set of human rights toward which we still strive today. We haven't yet achieved all of the lofty ideals it contains. But it has been like a beacon on a high hill, against which we can check our progress.

An even greater change is happening now. A new undersea quake occurred about 40 years ago, when the first crude "personal computer" appeared. It's taken since then to build the huge tidal wave that is once again sweeping across the planet. My son, who is 17, takes it for granted that he can carry his entire gigantic music collection with him wherever he goes. He can have online friends in Massachusetts or Mongolia. He can produce his own music, video, or film. He can get news from anywhere in the world, buy airline tickets or any other kinds of goods online, and so on and on.

Those of us who create the digital technology to make possible these things—and things we haven't yet thought of—have it in our power to "begin the world over again," as Paine said. So: what kind of world do we want to create? There are huge issues and questions that need to be addressed.

Microsoft, with more than a billion users, has hit many of them first. There's never been a company whose products had the potential to change the lives of billions of people across the globe. And there's no playbook to follow. So we'll stumble sometimes, but we'll continue to grow if we get more things right than wrong. Are our first billion customers the "early adopters," who paid a premium price to be in the first wave? What about the next billion?

A couple of these big questions led Microsoft to create the Unlimited Potential program. This program, aimed at the five billion people living in underserved communities worldwide, attempts to assist those individuals and communities in achieving their dreams by supplying current and affordable technologies.

I don't know even a fraction of the questions we'll all have to ask—far less the answers. There's no detailed map of this journey. But we do need another beacon to give us a direction. I couldn't find a better one than the Declaration of Independence, updated for the Digital Age. So here's my Digital Declaration of Independence:

We hold this truth to be self-evident, that every human has an equal and unalienable right to the means to create, distribute, and consume information to realize his potential for Life, Liberty, and the Pursuit of Happiness—regardless of the country he lives in, his gender, beliefs, racial origin, language, or any impairments he may have.

It's not about forcing democracy on the whole world. And it's not about trying to change everything at once. Remember, it took more than 200 years from the first Declaration to bring about equal rights just in the United States, even though slavery had been abolished for more than 100 years—and are we there even yet?

The Digital Declaration is not a map; it's a compass bearing. Join me on the journey?

Bill Hill joined Microsoft in 1995 because he believed the company would lead the transition from reading on paper to reading on the screen. He is one of the inventors of the ClearType technology, which dramatically improved the screen readability of text.