Customising WCF Proxy Generation in Visual Studio 2008

I was asked today how easy it is to hook into the WCF client proxy generation process in Visual Studio 2008. The answer is “It is very easy”.

Visual Studio has this great extensibility point that allows third-parties to create “Custom Tools” for specific files. One of the properties that is accessible to you for files within a Visual Studio project is called “Custom Tool”. The value for this property implicitly maps to a class in an assembly registered with Visual Studio that performs custom code generation for that file. For instance if you have an XSD file, you can create a custom tool which allows you to generate .NET code for that XSD.

 

Client proxy generation also uses the same technique. When you add a new service reference to your project a hidden XML file (usually named Reference.svcmap and is known as ‘map’) is created under the service reference. In order to be able to see this file you need to select “Show All Files” in the Solution Explorer:

 

The content of this map file is very interesting. In fact, this is the input file which is passed to the code generation tool. Here is an example of this file:

<ReferenceGroup xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="https://www.w3.org/2001/XMLSchema" ID="7d7f6cec-6831-4791-829b-b9f07d87e960" xmlns="urn:schemas-microsoft-com:xml-wcfservicemap">

  <ClientOptions>

    <GenerateAsynchronousMethods>false</GenerateAsynchronousMethods>

    <EnableDataBinding>true</EnableDataBinding>

    <ExcludedTypes />

    <ImportXmlTypes>false</ImportXmlTypes>

    <GenerateInternalTypes>false</GenerateInternalTypes>

    <GenerateMessageContracts>false</GenerateMessageContracts>

    <NamespaceMappings />

    <CollectionMappings />

    <GenerateSerializableTypes>true</GenerateSerializableTypes>

    <Serializer>Auto</Serializer>

    <ReferenceAllAssemblies>true</ReferenceAllAssemblies>

    <ReferencedAssemblies />

    <ReferencedDataContractTypes />

    <ServiceContractMappings />

  </ClientOptions>

  <MetadataSources>

    <MetadataSource Address="https://localhost:8080/Service1" Protocol="http" SourceId="1" />

  </MetadataSources>

  <Metadata>

    <MetadataFile FileName="Service1.disco" MetadataType="Disco" ID="580e8667-08a2-44a8-b937-71198a28fb8c" SourceId="1" SourceUrl="https://localhost:8080/Service1?disco" />

    <MetadataFile FileName="Service1.xsd" MetadataType="Schema" ID="5f113f5f-cb7e-4af4-915b-a00473f4b877" SourceId="1" SourceUrl="https://localhost:8080/Service1?xsd=xsd1" />

    <MetadataFile FileName="Service11.xsd" MetadataType="Schema" ID="d1d8268b-c6ac-4aac-b3ca-ddeb752fe3d9" SourceId="1" SourceUrl="https://localhost:8080/Service1?xsd=xsd0" />

    <MetadataFile FileName="Service1.wsdl" MetadataType="Wsdl" ID="51286a6a-043f-4cd0-a30d-15567f483f93" SourceId="1" SourceUrl="https://localhost:8080/Service1?wsdl" />

  </Metadata>

  <Extensions>

    <ExtensionFile FileName="configuration.svcinfo" Name="configuration.svcinfo" />

  </Extensions>

</ReferenceGroup>

This file has some very interesting properties such as “GenerateAsynchronousMethods” which dictate to the code generator whether or not to generate asynchronous proxy methods on top of the usual synchronous methods. By changing this property you get the APM (asynchronous Programming Model) enabled for the generated proxy. This option is very similar to /async switch of the svcutil tool.

A custom tool is nothing more than a .NET class which implements the following 2 interfaces:

Microsoft.VisualStudio.Shell.Interop.IVsSingleFileGenerator
Microsoft.VisualStudio.OLE.Interop.IObjectWithSite

The IVsSingleFileGenerator interface has 2 methods which are very significant to the generation process:

1- DefaultExtensions(...): This method is called by Visual Studio and returns the extension of the file which is generated by the tool.

2- Generate(...): This method is given the content of the original file and is expected to generate the content of the output file. In the case of the “WCF Proxy Generator” the input file is the map and the output is the proxy code.

If you want to understand more about writing your own custom code generators then see this article https://blogs.conchango.com/pauloreichert/archive/2005/05/21/1459.aspx.

The custom tool for WCF proxy generation is called “WCF Proxy Generator”. The actual class that generates the proxy code is a.NET class with the public access modifier. This class is defined in Microsoft.VisualStudio.Editors.dll that can be found in the GAC. Let’s have a closer look at this class:

namespace Microsoft.VisualStudio.Editors.WCF

{

  [Guid("69cf4e9e-c755-408a-b407-117cc3acabeb")]

  public class WCFProxyGenerator :
IVsSingleFileGenerator,
IObjectWithSite,
System.IServiceProvider

  {
protected virtual void
CallCodeGeneratorExtensions(CodeCompileUnit compileUnit)
{ ... }

As you can see this class implements both IVsSingleFileGenerator and IObjectWithSite interfaces. Inside its Generate method it creates a code tree which represents the code for the proxy file. The code tree is represented using CodeDom. Just before converting the CodeDom representation of the code tree into .NET code and returning that to the IDE it calls the virtual CallCodeGeneratorExtensions method passing a copy of the tree (represented by an instance of the CodeCompileUnit). Within that method the generator has the opportunity to modify the CodeDom used for generating the final proxy.

As mentioned, this is a protected virtual method therefore it is possible to inherit from this class and override this method. The overridden method is then called instead of the original method specified in WCFProxyGenerator. You can now modify the generated code using the argument which was passed to the method:

[GuidAttribute("69cf4e9e-c755-408a-b407-117cc3acabec")]

public class CustomWCFProxyGenerator : WCFProxyGenerator

{

  static readonly CodeStatement s_assignmentStatement = ...;

  protected override void CallCodeGeneratorExtensions(CodeCompileUnit compileUnit)

  {

    base.CallCodeGeneratorExtensions(compileUnit);

    // find all classes that inherit from ClientBase (all proxies)

    var proxies = FindAllProxyClasses(compileUnit);

  // add impersonation code to their constructors

    foreach (CodeTypeDeclaration proxy in proxies)

    {

      AddImpersonationCodeToConstructors(proxy);

    }

  }

  protected virtual CodeTypeDeclarationCollection

    FindAllProxyClasses(CodeCompileUnit compileUnit)

  {

    CodeTypeDeclarationCollection result = new CodeTypeDeclarationCollection();

    // search for all the proxy class (the ones that inherits from ClientBase)

    ...

    return result;

  }

  protected virtual void

    AddImpersonationCodeToConstructors(CodeTypeDeclaration type)

  {

    foreach (CodeTypeMember member in type.Members)

    {

      CodeConstructor ctor = member as CodeConstructor;

      if (ctor != null)

      {

        // we got a constructor

       

        // add some comment to this constructor

        ctor.Statements.Add(

          new CodeCommentStatement(

            "Added by custom WCF proxy generator"));

        // add the impersonation code here

     ctor.Statements.Add(s_assignmentStatement);

      }

    }

  }

}

The CustomWCFProxyGenerator class inherits from the WCFProxyGenerator and overrides the CallCodeGeneratorExtensions. Within this method it finds all classes inside the CodeDom representation of the proxy that inherit from ClientBase<> (All proxies inherit from ClientBase<>). It then identifies their constructors and adds a comment statement and an assignment statement to those constructors. The following code listing highlights the added sections to the constructors of the generated proxy:

[System.Diagnostics.DebuggerStepThroughAttribute()]

[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]

public partial class Service1Client :

  ClientBase<Client.ServiceReference.IService1>,

  IService1

{

   

  public Service1Client() {

  // Added by custom WCF proxy generator

  this.ChannelFactory.Credentials.Windows.AllowedImpersonationLevel =

  System.Security.Principal.TokenImpersonationLevel.Delegation;

  }

  

  public Service1Client(string endpointConfigurationName) :

  base(endpointConfigurationName) {

  // Added by custom WCF proxy generator

  this.ChannelFactory.Credentials.Windows.AllowedImpersonationLevel =

  System.Security.Principal.TokenImpersonationLevel.Delegation;

  }

So far I have shown you how to hook into the proxy generation process. In order to be able to use this custom generator you need to sign the custom code generator’s assembly, install it into GAC and register it with Visual Studio. The registration process is simple and is done using System Registry. You have probably noticed that the CustomWCFProxyGenerator class has a Guid attribute. This attribute has to be unique and is used for registration process with Visual Studio:

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\9.0\CLSID\ {69cf4e9e-c755-408a-b407-117cc3acabec} ]
@="CustomWcfProxyGenerator.CustomWCFProxyGenerator"
"InprocServer32"="C:\\WINDOWS\\system32\\mscoree.dll"
"Class"="CustomWcfProxyGenerator.CustomWCFProxyGenerator"
"Assembly"="CustomWcfProxyGenerator, Version=1.0.0.0, Culture=neutral, PublicKeyToken=a47c4f0a05463cf9"
"ThreadingModel"="Both"

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\9.0\Generators\{164B10B9-B200-11D0-8C61-00A0C91E29D5}\CustomWcfProxyGenerator]
@="Custom WCF Proxy Generator"
"CLSID"=" {69cf4e9e-c755-408a-b407-117cc3acabec} "
"GeneratesDesignTimeSource"=dword:00000001

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\9.0\Generators\{E6FDF8B0-F3D1-11D4-8576-0002A516ECE8}\CustomWcfProxyGenerator]
@="Custom WCF Proxy Generator"
"CLSID"=" {69cf4e9e-c755-408a-b407-117cc3acabec} "
"GeneratesDesignTimeSource"=dword:00000001

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\9.0\Generators\{FAE04EC1-301F-11d3-BF4B-00C04F79EFBC}\CustomWcfProxyGenerator]
@="Custom WCF Proxy Generator"
"CLSID"=" {69cf4e9e-c755-408a-b407-117cc3acabec} "
"GeneratesDesignTimeSource"=dword:00000001

You can download the code for the custom proxy generator from here (This is written for Visual Studio 2008 Beta 2 – Orcas Beta 2).

How to use the sample:

- Compile the CustomWcfProxyGenerator project (Found under the Generator folder)

- Install the output into GAC

- Run the registry.reg file in order to register the custom control with Visual Studio 2008 Beta 2

- Close Visual Studio

- Open the WCFProxyTest project

- Select “Show All Files”

- Find References.svcmap file, right click and select “Run Custom Tool”

- Optionaly you can change the Custom Tool from “CustomWcfProxyGenerator” back to the original version “WCF Proxy Generator” and run the custom tool again. You will notice that a slightly different proxy file (“reference.cs”) is created.

Download sample code

Sample.zip