Customising WCF Proxy Generation in Visual Studio 2008

Customising WCF Proxy Generation in Visual Studio 2008

  • Comments 16

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="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://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="http://localhost:8080/Service1" Protocol="http" SourceId="1" />

  </MetadataSources>

  <Metadata>

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

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

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

    <MetadataFile FileName="Service1.wsdl" MetadataType="Wsdl" ID="51286a6a-043f-4cd0-a30d-15567f483f93" SourceId="1" SourceUrl="http://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 http://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

Attachment: Sample.zip
Leave a Comment
  • Please add 8 and 5 and type the answer here:
  • Post
  • PingBack from http://msdnrss.thecoderblogs.com/2007/08/10/customising-wcf-proxy-generation-in-visual-studio-2008/

  • PingBack from http://technology.jibberjabber.us/customizing-wcf-proxy-generation-in-visual-studio-2008/

  • I was looking for info about customizing the default Visual Studio 2008 WCF proxy generator. Turns out

  • Would this be a reasonable way to decorate the generated proxy classes with COM interop attributes so that these classes could be used directly from a COM client?  E.g. build the bare proxy classes into an assembly.  

    We've tried the naïve way of doing this by turning on COM visibility in the app's Assembly Information and checking Register for COM Interop in the project build props, but the generated types remain opaque in COM, e.g. no property accessor functions show up.

    Any suggestions welcome.

    Cheers,

    Richard (raw@raw.com)

  • Just wanted to point out that the installation of the custom generator won't work on Vista (seems that the registry keys are not at the right place), do anyone have a solution?

  • The path for the installation of registry keys under Vista 64bit is:

    HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\VisualStudio\x.x\**

    Where x.x is your VS version number

    You can find it by looking for the key "WCF Proxy Generator" in the registry editor

  • You've been kicked (a good thing) - Trackback from DotNetKicks.com

  • My latest in a series of the weekly, or more often, summary of interesting links I come across related to Visual Studio. Via DotNetKicks : Customizing WCF Proxy Generation in Visual Studio 2008 . Deparallelizer 1.0 is a utility to to make build logs more

  • CustomWcfProxyGeneration solution ask for password...May i know what it will be??

  • BlackCat: There is no password on this zip file.

    Pedram

  • Interesting bits from searching the web: MSDN Code Gallery has a page on extending Visual Studio .&#160;

  • How do you get a ref to the original types so that you can inspect them?

  • Good one , even for me it asked for password for the key file used for strong name signing. so i had to recreate a new key file and update the .reg file to use my public key

  • Does this also work for Visual Studie 10?

    I have tried this. I had to add a reference to Microsoft.VisualStudio.Shell.interop.8.0

    After that, when clicking 'Run Custom Tool' I get the Message: "The custon tool '.....' failed. The method or operation is not implemented.

    Any idea?

  • I am having the same problem as Christof with VS 2010:

    I am trying to run this same code in VS 2010 and the CallCodeGeneratorExtensions or FindAllProxies method(s) never get called from the WCF service I am updating.

    Have you gotten this working with VS 2010 using the 4.0 framework?

    Do you have any examples?

    Thanks in advance for your help.

Page 1 of 2 (16 items) 12