Foundations

What's New for WCF in Visual Studio 2008

Juval Lowy

Code download available at:Foundations2008_02.exe(329 KB)

Contents

.NET Framework Cross-Targeting
WCF-Provided Host
WCF-Provided Test Client
WCF Service Libraries
Adding Service References

Visual Studio® 2008 and the underlying Microsoft® .NET Framework 3.5 offer new tools and support for Windows® Communication Foundation (WCF). They don't change the basic capabilities of WCF 1.0 (released with the .NET Framework 3.0); rather, they extend and complete it.

Visual Studio 2008 automates manual WCF tasks, which includes updating proxy references, and eliminates repetitive tasks such as creating simple host projects. Visual Studio also addresses some tough problems such as cross-targeting and data contract type sharing. In this column I'll walk through the new features, point out their advantages, and explain any pitfalls and workarounds. Although I'll use C# project settings here, everything applies to Visual Basic® as well unless I state otherwise.

.NET Framework Cross-Targeting

Previous Visual Studio releases always targeted the version of the .NET Framework they shipped with. For example, Visual Studio 2005 can only generate assemblies that target the .NET Framework 2.0. This practice did not reflect the reality most developers face. Typically, developers need to maintain old versions of their applications written for previous versions of .NET while at the same time using the new version of Visual Studio on the application's next version.

Moreover, this practice meant that developers who were maintaining applications written for previous .NET Framework versions could not benefit from productivity enhancements, such as the code refactoring support introduced in Visual Studio 2005.

The problem was that there was no cross-targeting of .NET Framework versions. You either had to have multiple versions of Visual Studio installed or compensate with separate testing and deployment builds. Visual Studio 2008 tries to address this by providing adequate (albeit imperfect) support for targeting multiple versions of the .NET Framework. Since the .NET Framework 3.0 and the .NET Framework 3.5 actually use the same CLR version as the .NET Framework 2.0, and the only differences are in new referenced assemblies, Visual Studio can still target the same runtime yet provide cross-targeting for the .NET Framework versions 2.0, 3.0, and 3.5 (where the .NET Framework version number corresponds to release numbers, not runtime versions—that is still the CLR 2.0).

In Visual Studio 2008, the Application pane of the project Properties contains a new combobox called Target Framework that allows you to target the .NET Framework versions 2.0, 3.0, and 3.5 (see Figure 1).

Figure 1 Target Framework Property in Visual Studio 2008

Figure 1** Target Framework Property in Visual Studio 2008 **(Click the image for a larger view)

The Target Framework value is only in effect at development time; it has no effect at run time (your assembly still targets the .NET 2.0 CLR). The value you choose represents the oldest version of the .NET Framework your assembly can be built against. New projects are configured by default to target .NET Framework 3.5. It gets a bit more complicated when adding references; if you downgrade the Target Framework version while referencing higher-version assemblies, Visual Studio 2008 will prompt you, fault the reference, and fail the build. Visual Studio 2008 will not allow you to add a reference to a .NET Framework assembly that requires a higher version of the Framework than your project. If you add a reference to another project in the same solution that is of a higher version, Visual Studio 2008 will alert you as to the possible conflict. If you add a reference by browsing to an assembly, Visual Studio 2008 will not intervene to stop you.

With respect to languages and cross-targeting, note that in C# (but not Visual Basic), you can restrict use of features such as anonymous types and extension methods in a .NET Framework 2.0 or 3.0 project by restricting the compiler version. You can do this by going to the Build pane, clicking the Advanced button, and selecting ISO-2 (C# 2.0) for the language version (instead of the default, which has not been standardized as of yet).

When opening a Visual Studio 2005 WCF project in Visual Studio 2008, the upgrade process will keep the framework version at 2.0. While this will actually work (remember, the underlying runtime version is unchanged), I recommend manually setting it to version 3.0 or 3.5, as required.

The Target Framework version matters the most when taking advantage of new project templates. WCF workflow and syndication projects must be built targeting the .NET Framework 3.5; the Service Library project requires targeting the .NET Framework 3.0 or 3.5. The Add Service Reference feature (described later in this column) is only available when selecting Framework version 3.0 or 3.5 for the project.

WCF-Provided Host

Visual Studio 2008 ships with a ready-made, general-purpose service host called WcfSvcHost.exe. It's found under C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE. For ease of use, I recommend adding that location to the system's Path variable. WcfSvcHost is a simple command-line utility, accepting two parameters: the file path to a .NET assembly containing the service class or classes, and a file path to the host .config file. For example:

WcfSvcHost.exe /service:MyService.dll /config:App.config

The specified service assembly can be a class library assembly (DLL) or an application assembly (EXE). WcfSvcHost will launch a new process that will automatically host all the service classes listed in the services section of the specified .config file. Note that these service classes, along with their service contracts and data contracts, need not be public types—they can be internal. In addition, auto-hosted services need not provide any metadata, but they can publish metadata if they choose to.

WcfSvcHost is a Windows Forms application that resides as a desktop tray icon. To close the host, simply select Exit from the tray icon context menu. Terminating the host this way is an ungraceful exit—WcfSvcHost will abort all calls currently in progress, and clients are likely to receive an exception. If you click on the WcfSvcHost tray icon, it will bring up a dialog listing all the hosted services (see Figure 2).

Figure 2 WcfSvcHost Services List

Figure 2** WcfSvcHost Services List  **(Click the image for a larger view)

The dialog also shows the status of the service and its metadata address, which you can copy to the clipboard, perhaps for use later when adding a reference to the service. Closing the WcfSvcHost UI merely collapses it back to the tray.

WcfSvcHost is designed to eliminate the need during development for a separate host assembly to accompany your service library. Developing such host projects is a repetitive task; these hosts typically contain the same lines of code over and over again, and they tend to bloat the solution when you have multiple service libraries. For development and testing purposes, you can integrate WcfSvcHost directly into your Visual Studio 2008 service library projects. In the Debug pane of the project properties, specify WcfSvcHost.exe as the external program to start, and then specify your class library name and its .config file (the one auto-generated and auto-copied to the bin folder) as arguments.

With that done, when you run the class library (something you could not do before), it will be automatically hosted by WcfSvcHost with the debugger attached to that process. When you stop debugging, Visual Studio 2008 will abort the host ungracefully.

You can even use WcfSvcHost in your .NET Framework 3.0 applications and with Visual Studio 2005 projects, since all WcfSvcHost requires is the .NET Framework 3.0. Simply copy WcfSvcHost from a machine where Visual Studio 2008 is installed. To make things easier, I recommend adding WcfSvcHost to the Global Assembly Cache (GAC) on your .NET Framework 3.0 machine.

The last feature of WcfSvcHost is its ability to automatically launch a client application and even provide the client with optional parameters specific for that application:

WcfSvcHost.exe /service:MyService.dll /config:App.config /client:MyClient.exe /clientArgs:123,ABC

This is useful in automated testing and even simple deployment scenarios to launch both the host and the client.

The biggest drawback of WcfSvcHost is that it is only suitable for simple scenarios where you do not require programmatic access to the host instance before opening it or programmatic access to its event model once opened. Unlike hosting with IIS or the Windows Activation Service (WAS), there is no equivalent service host factory support. Consequently, there is no ability to dynamically add base addresses, configure endpoints, throttle calls, configure custom behaviors at the host level, and so on. My experience with WCF is that in all but the simplest cases, eventually you will need programmatic access to the host instance, so I do not view WcfSvcHost as a full-fledged production-worthy host, as I do the WAS or a dedicated self host.

WCF-Provided Test Client

In addition to the service host, Visual Studio 2008 ships with a simple, general-purpose test client for rudimentary testing that you can use to invoke operations on most services. The test client, WcfTestClient.exe, is found after normal installation at C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE. You must provide WcfTestClient with a single command-line argument containing the metadata address of the service to test:

WcfTestClient.exe https://localhost:9000/

You can specify any metadata address, such as HTTP-GET, or metadata endpoint over HTTP, TCP, or IPC (named pipes). You can also specify multiple metadata addresses:

WcfTestClient.exe https://localhost:8000/ net.tcp://localhost:9000/MEX

WcfTestClient is a Windows Forms 3.5 application (see Figure 3). In this figure, the tree control on the left contains the tested services and their endpoints. You can drill into an endpoint's contract and select an operation. The information specific to that invocation will be displayed in a tab on the right pane. An example is the simple contract and implementation in Figure 4.

Figure 4 A Sample Service

[ServiceContract] interface IMyContract {
    [OperationContract] string MyMethod(int someNumber, string someText);
}
class MyService: IMyContract {
    public string MyMethod(int someNumber, string someText) {
        return "Hello";
    }
}

Figure 3 Using WcfTestClient

Figure 3** Using WcfTestClient **(Click the image for a larger view)

The method tab will let you provide an integer and a string as operation parameters in the Request section, as in Figure 3. When you click the Invoke button, it will dispatch the call to the service and display the returned value or out-parameters in the Response. If the operation is one-way, WcfTestClient will notify you in a message box upon the operation's successful dispatch. In case of an exception, WcfTestClient will display the exception information in a message box and let you issue additional calls.

WcfTestClient does not maintain a transport-level session (or any other session) with the tested service. All calls are made on new proxy instances. In addition, all calls are made asynchronously so that the UI is kept responsive. However, while the calls are asynchronous, WcfTestClient will only let you dispatch one call at a time.

WcfTestClient functions by silently creating an assembly from a proxy file, complete with a .config file, and then loading it from a temporary location. If you click on the Config File item in the tree, you can actually grab that .config file (the same one generated when adding a service reference), and you can display it in its own tab.

Unlike the old Visual Studio ASMX Web services test page, WcfTestClient allows you to invoke operations with enumerations, composite parameters such as classes or structures (each being a composite of other classes or structures), and even collections and arrays of parameters. Simply expand the items in the Request section, set their values from dropdown lists (such as enum values), and invoke the call. If the operation accepts a collection or an array, you will also need to set the length. For example, for the following operation, Figure 5 shows the resulting request and response:

Figure 5 Specifying an Array Length and Values

Figure 5** Specifying an Array Length and Values **(Click the image for a larger view)

[OperationContract] bool ProcessNumbers(int[] numbers]

Much the same way, the Response pane will contain any returned composite value or out parameters. This brings us to a drawback of WcfTestClient, which is that in order to specify different services to test, you have to shut it down, change the command-line arguments, and relaunch it. It would have been better had it been possible to provide the service addresses in the GUI as well.

You can integrate WcfTestClient directly into your Visual Studio 2008 solution. First, add a class library project to the solution and delete all references, folders, and source files, as you have no need for them. Next, set WcfTestClient.exe as the external start program and provide the metadata address (or addresses) of the tested service (or services), such as the .svc address of an IIS or WAS hosted project, or, for that matter, any other metadata address of a host project, inside or outside your solution.

Note that you cannot use WcfTestClient on machines with only .NET Framework 3.0, since it makes use of an internal .NET Framework 3.5 tree grid control (the one used to represent composite parameters).

Of course, you can combine both WcfTestClient and WcfSvcHost in a single step to automatically host a service in a service library and test it:

WcfSvcHost.exe /service:MyService.dll /config:App.config /client:WcfTestClient.exe /clientArgs:https://localhost:9000/

However, with WcfSvcHost, specifying the metadata arguments is optional. By default WcfSvcHost will pipe in to the specified client application the metadata addresses it found in the service .config file. You should specify the metadata address explicitly only if the service or services do not provide their own metadata or if you would like the test client to use different addresses. Also, if the service .config file contains multiple metadata endpoints for a given service, they will be provided in this order of precedence: HTTP, TCP, IPC, HTTP-GET. You can incorporate these steps in Visual Studio 2008 for a seamless hosting and testing experience. Specify WcfSvcHost.exe as the start-up program along with the .config file and WcfTestClient.exe as the client.

WCF Service Libraries

As a function of the Target Framework, Visual Studio 2008 offers several new WCF project templates. The New Project dialog offers a combobox that lets you specify the Target Framework version (2.0, 3.0, or 3.5), as shown in Figure 6.

Figure 6 WCF Project Templates

Figure 6** WCF Project Templates **(Click the image for a larger view)

If you select Framework 2.0, then no new templates are available. With Framework 3.0 there is a new project template called the WCF Service Library. This project type is merely the pre-built usage of WcfSvcHost and WcfTestClient, and it is very similar to the technique that I mentioned previously (combining the two). Note that with the WCF Service Library template there is no need to specify WcfSvcHost.exe as the start-up program or the .config file because the project file contains a new ProjectTypeGuids element for a WCF service library.

An unfortunate side effect of the template is that stopping the debugger does not terminate the test client; over time, your desktop becomes littered with orphaned clients. To fix this, simply revert to the manual explicit steps described earlier.

The WCF Service Library also provides a simple template for a service contract, its implementation, and the matching .config file.

The Syndication Service Library allows you to implement an RSS feed over a WCF endpoint, and it starts you off with a simple service contract that returns a feed, its implementation, and a matching .config file. You can host and expose your feed like any other service. The syndicated endpoints make use of the new WebHttpBinding binding. This new binding is designed to receive Web requests and cannot be used for normal service invocations.

The Sequential Workflow Service Library template allows you to implement an endpoint's contract operations as workflow activities or, for that matter, to expose a workflow as a service. The project will contain a single sequential activity that implements a simple contract and a matching .config file. The client still interacts with what looks like a traditional endpoint, and yet the implementation is purely workflow-driven.

The State Machine Workflow Service Library template uses a state machine instead of a sequential workflow to implement its operations (trigger state transitions). The workflow project templates make use of WcfSvcHost and WcfTestClient, just as the plain WCF Service Library does. The workflow templates also make use of the new context bindings to manage passing the workflow instance ID to support durable workflows. I will address these binding in detail in a future column.

Adding Service References

The Visual Studio 2005 extensions for .NET Framework 3.0 provided a rudimentary ability to add a reference to a WCF service, without many of the advanced features of SvcUtil. Visual Studio 2008 features a new service reference dialog, as shown in Figure 7.

Figure 7 Add Service Reference Dialog

Figure 7** Add Service Reference Dialog **(Click the image for a larger view)

You can bring up the new dialog in any project by right-clicking anywhere inside the project in the Solution Explorer and selecting Add Service Reference from the context menu. Note that the project must be configured to target .NET Framework 3.0 and above to enable this option.

In the Add Service Reference dialog, you first need to specify the service metadata address (not the service URL, as the dialog states) and click Go to view the available service endpoints (not Services, as labeled). You must specify a namespace (such as MyService) to contain the generated proxy and click OK to generate the proxy and update the .config file. Note that, in most cases, Visual Studio 2008 is not smart enough to infer the cleanest binding values, and it will therefore butcher the .config file by declaring all the default values for the bindings. This issue will be addressed in a future release of Visual Studio. If you care about maintaining the .config file, before adding the reference, open the .config file, add the reference, then perform a single Undo (Ctrl+Z), and manually add the .config file entries in the client section.

The Discover button lets you discover WCF services in your own solution as long as they are either hosted in a Web site project or in one of the new WCF service libraries. In the case of a Web site project, Visual Studio 2008 will either retrieve the metadata from IIS or will launch the ASP.NET file-system-based development server. In the case of a WCF service library, WCF will auto-launch its host (WcfSvcHost) to get the metadata.

The Advanced button brings up the settings dialog that lets you tweak the proxy generation almost as if you were using SvcUtil (see Figure 8). The more intuitive options let you configure the visibility of the generated proxy and contracts (public or internal); you can generate message contracts for your data types for advanced interoperability scenarios in which you have to comply with an existing, typically custom, message format, and you can click the Add Web Reference button to convert the reference to an old ASMX Web service reference.

Figure 8 Service Reference Advanced Options

Figure 8** Service Reference Advanced Options **(Click the image for a larger view)

The Generate asynchronous operations checkbox adds for each operation in the imported contract a matching pair of Begin<operation> and End<operation> elements that allows the client to issue the call asynchronously on a worker thread and later sync up with the completion of the operation by either providing a completion callback method or by blocking for completion. For example, given this contract definition

[ServiceContract] interface ICalculator { 
  [OperationContract] int Add(int number1,int number2); 
}

the imported contract will look like Figure 9.

Figure 9 Imported Async Contract

[ServiceContract] interface ICalculator {
    [OperationContract] int Add(int number1, int number2);
    [OperationContract(AsyncPattern = true)]
    IAsyncResult BeginAdd(int number1, int number2, 
      AsyncCallback callback, object asyncState);
    int EndAdd(IAsyncResult result);
    //Rest of the methods 
}

The matching client code for asynchronous invocation will look like the following:

CalculatorClient proxy = new CalculatorClient();
int sum;
AsyncCallback completion = (result) = > {
    sum = proxy.EndAdd(result);
    Debug.Assert(sum == 5);
    proxy.Close();
};
proxy.BeginAdd(2, 3, completion, null);

While you could use these methods as-is, the completion callback provided to Begin<operation> is called on a thread from the thread pool. This presents a serious problem if the callback is used to access some resources that have affinity to a particular thread or threads. The classic example is a Windows Forms (or WPF) application that dispatches a lengthy service call asynchronously (to avoid blocking the UI) and then wishes to update the UI with the result of the invocation. Using the raw Begin<operation> is disallowed since only the UI thread is allowed to update it. To better handle this situation, the ClientBase<T> base class has been extended with a protected InvokeAsync method that picks up the synchronization context of the client and will use it to invoke the completion callback, as shown in Figure 10.

Figure 10 Async Callback Management in ClientBase<T>

public abstract class ClientBase < T > : ... {
    protected delegate IAsyncResult BeginOperationDelegate(object[] inValues, 
      AsyncCallback asyncCallback, object state);
    protected delegate object[] EndOperationDelegate(IAsyncResult result);
    //Picks up sync context used for completion callback protected 
    void InvokeAsync(BeginOperationDelegate beginOpDelegate, object[] inValues, 
      EndOperationDelegate endOpDelegate, SendOrPostCallback opCompletedCallback, 
      object userState) {}
    //More members 
}

ClientBase<T> also provides an event arguments helper class and two dedicated delegates used to begin and end the asynchronous call. The generated proxy class that derives from ClientBase<T> makes use of the base functionality. The proxy will have a public event called <operation>Completed that uses a strongly typed event argument class specific to the results of the asynchronous method, and two methods called <operation>Async used to dispatch the call asynchronously:

partial class AddCompletedEventArgs: AsyncCompletedEventArgs {
    public int Result {
        get;
    }
}
class CalculatorClient: ClientBase < ICalculator > ,
ICalculator {
    public event EventHandler < AddCompletedEventArgs > AddCompleted;
    public void AddAsync(int number1, int number2, object userState);
    public void AddAsync(int number1, int number2);
    //Rest of the proxy 
}

The client can also subscribe an event handler to the <operation>Completed event, to have that handler called upon completion. The big difference using <operation>Async as opposed to Begin<operation> is that the <operation>Async methods will pick up the synchronization context of the client and will fire the <operation>Completed event on that synchronization context, as shown in Figure 11.

Figure 11 UI-Friendly Asynchronous Call Invocation

partial class CalculatorForm: Form {
    CalculatorClient m_Proxy;
    public MyClient() {
        InitializeComponent();
        m_Proxy = new CalculatorClient();
        m_Proxy.AddCompleted += OnAddCompleted;
    }
    void CallAsync(object sender, EventArgs args) {
        m_Proxy.AddAsync(2, 3);
        //Sync context picked up here 
    }
    //Called on the UI thread 
    void OnAddCompleted(object sender, AddCompletedEventArgs args) {
        Text = "Sum = " + args.Result;
    }
}

The Collection type combobox lets you specify how to represent to the client certain kinds of collections and arrays found in the service metadata. For example, if the service operation returns one of the IEnumerable<T>, IList<T>, or ICollection<T> collections, then by default the proxy will present it as an array. For example, the following service-side operation

[OperationContract] IEnumerable<int> GetNumbers();

will be expressed on the proxy as:

[OperationContract] int[] GetNumbers();

However, you can request that Visual Studio 2008 use another collection such as BindingList for data binding, a List<T>, Collection, LinkedList<T>, and so on. If a conversion is possible, the proxy will use the requested collection type instead of an array, like so:

[OperationContract] List<int> GetNumbers();

A similar feature is available for dictionaries. Normally, if the service operation returns a serializable dictionary such as this

[Serializable] class MyDictionary < K, T > : IDictionary < K,
T > {
...
}[OperationContract] MyDictionary < int,
string > GetDictionary();

then the proxy class will express that dictionary as a Dictionary<T,K>, which is the default value of the following Dictionary collection type combobox:

[OperationContract] Dictionary<int,string> GetDictionary();

However, you can request other dictionary types, such as the SortedDictionary<T,K>, HashTable, or the ListDictionary, and the proxy will use that dictionary instead if possible:

[OperationContract] SortedDictionary<int,string> GetDictionary();

By far, the most important feature of the new service reference feature is its ability to share data contract types across assemblies. With Visual Studio 2005, if a client added a service reference to two independent services that supported the same data contract, the client would get two distinct yet identical types representing the same data contract. With Visual Studio 2008, by default, if any of the assemblies referenced by the client already has a data contract type matching a data contract type exposed in the metadata of the referenced service, Visual Studio 2008 will not import that type again. It is worth emphasizing again that the existing data contract reference must be in another referenced assembly, not in the client project itself. This limitation may be addressed in a future release of Visual Studio. For now, the workaround and best practice is obvious: factor all of your shared data contracts to a designated class library and have all clients reference that assembly.

The advanced settings dialog of the service reference lets you configure data contract sharing. The "Reuse types in the referenced assemblies" checkbox is checked by default, but you can turn this feature off. Despite its name, it will only share data contracts, not service contracts. Using the radio buttons underneath it (see Figure 8), you can also instruct Visual Studio 2008 to reuse data contracts across all referenced assemblies, or restrict the sharing to specific assemblies by placing a check mark next to them in the list.

Once a reference is added, your project will have a new folder called Service References, inside of which is a service reference item for each referenced service (see Figure 12).

Figure 12 Service References Folder

Figure 12** Service References Folder **

You can always right-click on the reference and select Update Service Reference to regenerate the proxy and update the client's .config file. This is possible because the service reference item also contains a file that records the original metadata address used.

You can also select Configure Service Reference to bring up a dialog similar to the advanced settings dialog used when adding the reference. The configure service reference dialog lets you change the service metadata address as well as the rest of the advanced proxy settings.

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

Juval Lowy is a software architect with IDesign providing WCF training and architecture consulting. He is also the Microsoft Regional Director for the Silicon Valley. Juval's recent book is Programming WCF Services (O'Reilly, 2007), and he can be contacted at www.idesign.net.