Friday, 30 July 2010

Pre-build events, shared data types, and WCF

Generating WCF proxies has always been a bit of pain, especially when one has services that share data types. The Service Reference wizard in Visual Studio is wont to mangle configurations (among other things) and so is best avoided. The recommended practice is to use SvcUtil – ideally as part of a pre-build event. There a few gotchas associated with this which have taken me a while to resolve so I thought I’d present the step-by-step guide to managing proxies.

Before starting you will need the value of PATH from the Visual Studio command prompt (the easiest way to do it is PATH > path.txt followed by a copy-and-paste). This can’t be guaranteed to be the same for each machine, but if you’re part of a team then it helps if everyone has the same build.

To set up the build event:

  1. Right-click the project in which you want the proxy file to be built and select the property pages (this won’t work for websites but it will for web applications)
  2. On the left-hand side select ‘Build events’
  3. In the pre-build event command line paste in the contents of the Visual Studio command prompt PATH that you got earlier (alternatively you can set it in the system environment variables if you have permissions)
  4. On the next line under this enter the SvcUtil bit:
    Svcutil http://localhost/MyFirstService.svc http://localhost/MySecondService.svc /language:c# /t:code /namespace:*,"MyNamespace" /noconfig /out:"$(ProjectDir)ServiceProxy.cs" /reference:"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\System.dll" /collectionType:System.Collections.ObjectModel.ObservableCollection`1 /async /tcv:version35

    Replacing the addresses of the services with a space-delimited list of service addresses. Note the reference to System.dll which is necessary to return list types as an ObservableCollection.

  5. Add a blank file called “ServiceProxy.cs” to the project (this will be overwritten by the build event but has to be in place for the project to include it).

Now if you build the project the proxy class(es) will be generated. The only problem is that if they share types then you’ll get a stack of errors along the lines of:

EXEC : warning : There was a validation error on a schema generated during export: Source: Line: 1 Column: 1619 EXEC : Validation warning : The simpleType 'http://schemas.microsoft.com/2003/10/Serialization/:duration' has already been declared.

which will preclude a valid build. To get round this I found the following article, the gist of which is to add a section manually to the project file. To do this find the .csproj file in Windows Explorer (right-click the project in VS 2010 and select ‘Open folder in Windows Explorer) and then open it with notepad. Then paste this bit in immediately before the last </Project> tag

<Target Name="PreBuildEvent" Condition="'$(PreBuildEvent)'!=''" DependsOnTargets="$(PreBuildEventDependsOn)"> <Exec WorkingDirectory="$(OutDir)" Command="$(PreBuildEvent)" ContinueOnError="true" /> </Target>

this will turn all those errors into warnings (which won’t fail the build). Voila – a single file proxy with shared types on each build!

One point I forgot to mention with this was around namespaces. If all the services use the same WSDL namespace, then SvcUtil will mess up the return types and asynchronous delegate names. To deal with this I recommend that all services have a unique WSDL namespace (see this post for more information on how to set all namespaces) and all proxy classes have a unique CLR namespace. To do this, for each service add a:

/namespace:http://mynamespace/myservice/,MyServiceNamespace

to the end of the SvcUtil call (where the http bit is the WSDL namespace and the MyServiceNamespace bit is the CLR namespace you want the proxy class to have). This should cause the proxy code for each service to have it's own namespace while putting all the shared types in the default namespace indicated by /namespace:*,MyTypeNamespace. For example:

Svcutil http://localhost/MyFirstService.svc http://localhost/MySecondService.svc /language:c# /t:code /namespace:*,"MyNamespace.SharedTypes" /namespace:http://myurl/mysecondservice/2010/07,"MyNamespace.MySecondService" /namespace:http://myurl/myfirstservice/2010/07,"MyNamespace.MyFirstService" /noconfig /out:"$(ProjectDir)ServiceProxy.cs" /reference:"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\System.dll" /collectionType:System.Collections.ObjectModel.ObservableCollection`1 /async /tcv:version35

will output all shared types to MyNamespace.SharedTypes but put the proxy for each service in its own namespace.

UPDATE: a better way of getting the path that is both less verbose and not machine-specific, is to use the Visual Studio environment variables.  So:

PATH=$(FrameworkSDKDir)Bin;$(MSBuildBinPath)

Will set the path to include not only SvcUtil, but also the reference assemblies.

1 comment: