Thursday, 22 November 2007

WCF Client Proxies and Project Pre-Build events

Over the last couple of days I've had some fun setting up a new web application that speaks to a WCF web service. Both the web app (client) and the service are under development and are continually changing, and it became evident early on that updating the WCF service proxy on the client side was going to be pretty tedious.

The service proxy was originally set up using the Add Service Reference function in Visual Studio (I'm running VS2005 by the way). This is all fine and dandy, espcially since updating the proxy just requires a right-click and Update Service Reference, except it doesn't allow you to use any of the flags that the underlying svcutil tool supports via the command line. As a result the VS service reference function generates not only a service proxy but proxies for the classes that are passed between the client and service as well.

I wanted to set up something that would easily update the service proxy, omit proxy code for classes consumed or returned by the service, and allow the use of Generics in collections returned by the service. Using svcutil on the command line this can be accomplished by using the /reference and /collectionType flags. As I've already mentioned, the Add Service Reference function in VS2005 doesn't allow these flags, but you can get around this by using project pre-build events. I thought this might be useful to others out there so I've put together some instructions that may be of help.

!!Note: The code that follows will only work if you have added svcutil to your system path.

The basic setup is quite straight forward. In your solution, right-click your client project and select Properties. From the properties page select the Build Events tab. On this tab there are two text boxes in which to enter pre-build and post-build events on the command line. In the pre-build event command line box at the top, enter the following:


mkdir $(ProjectDir)SVC
chdir $(ProjectDir)SVC
svcutil http://MyServiceURL/MyService.svc /language:C# /out:MyServiceProxy.cs
copy MyServiceProxy.cs $(ProjectDir)"Service References"
chdir $(ProjectDir)
rmdir /S /Q $(ProjectDir)SVC


As you can see, this makes use of good old DOS-style commands. The first line creates a subdirectory in your project called SVC, and the second line changes the working directory to that same directory. Notice the use of the $(ProjectDir) shortcut/macro. If you click into Edit pre-build > Macros you'll see a list of the shortcuts VS makes available.

Now, the third line is what's of real interest. This calls svcutil referencing a running instance of the service (via a URL) for which I want to generate the proxy. It also specifies the output language (you can set this to VB if you like) and the filename that the proxy is written to. Then, on the last three lines the proxy class is copied into the "Service References" folder (which VS creates when you set up the Service Reference initially), and the temporary SVC folder is removed.

That's how simple it is! Now, there are two more things to accomplish, namely omitting proxy code for service return and parameter classes, and allowing the client to use Generics.

In this example the client and the service both reference a common class library that hold the DataContract classes used between services. Since this library is directly accessible to the client, it is not necessary to generate a service proxy containing DataContract proxies. To omit DataContract proxies, I simply add the /reference flag to the svcutil command like this:

/reference:$(TargetDir)MyCommonLibrary.dll

Now we're almost there. The last thing to do is to inform svcutil about the CollectionType(s) of the service. My particular service implements a couple of OperationContracts that return a List. If you don't tell svcutil this, the proxy will default to returning T[] instead. The flag to use is /collectionType (/ct is the short version), and this is how you do it:

/collectionType:System.Collections.Generic.List`1

Notice the single reverse quote before the "1" in that line, and don't confuse it with an apostrophe. By the way, the "1" signifies that Generic list has one type specified (List).

Now - there's one more thing to throw into the mix: Namespaces. You want the generated proxy to reside in the same namespace (or a related one) as your client. If you don't add the /namespace flag on svcutil the proxy generated will just contain a class and no specific namespace and that could pose a problem. But it, too, is easily fixed by adding

/namespace:*,MyNamespace

Putting all of that together the whole pre-build event looks something like this:


mkdir $(ProjectDir)SVC
chdir $(ProjectDir)SVC
svcutil http://MyServiceURL/MyService.svc /language:C# /out:MyServiceProxy.cs /reference:$(TargetDir)MyCommonLibrary.dll /collectionType:System.Collections.Generic.List`1 /namespace:*,MyNamespace
copy MyServiceProxy.cs $(ProjectDir)"Service References"
chdir $(ProjectDir)
rmdir /S /Q $(ProjectDir)SVC


Happy days! Now every time you build your client project it will look up the service and regenerate the proxy for you. That's pretty neat, I think.

A final word on the URL used to point to the running service instance. If you are sharing the client project with other developers (most likely you are) then the URL you've specified in the pre-build event needs to be accessible to the other developers as well. I get around this by setting the URL in the pre-build event to the URL for the service running on our development server, and overriding that URL in my local hosts file so that, while I develop on the client, the URL points to whereever I need it to.

Doing it this way also means that anyone can check out only the client project and it will automagically update the proxy reference against the running instance on the development server and thus pick up any changes committed by other developers working on the service!

No comments: