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.
Comments
Anonymous
August 10, 2007
PingBack from http://msdnrss.thecoderblogs.com/2007/08/10/customising-wcf-proxy-generation-in-visual-studio-2008/Anonymous
August 10, 2007
PingBack from http://technology.jibberjabber.us/customizing-wcf-proxy-generation-in-visual-studio-2008/Anonymous
March 06, 2008
I was looking for info about customizing the default Visual Studio 2008 WCF proxy generator. Turns outAnonymous
March 06, 2008
The comment has been removedAnonymous
June 25, 2008
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?Anonymous
June 26, 2008
The path for the installation of registry keys under Vista 64bit is: HKEY_LOCAL_MACHINESOFTWAREWow6432NodeMicrosoftVisualStudiox.x** Where x.x is your VS version number You can find it by looking for the key "WCF Proxy Generator" in the registry editorAnonymous
August 07, 2008
You've been kicked (a good thing) - Trackback from DotNetKicks.comAnonymous
August 11, 2008
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 moreAnonymous
September 26, 2008
CustomWcfProxyGeneration solution ask for password...May i know what it will be??Anonymous
September 26, 2008
BlackCat: There is no password on this zip file. PedramAnonymous
October 25, 2008
Interesting bits from searching the web: MSDN Code Gallery has a page on extending Visual Studio . Anonymous
January 19, 2010
How do you get a ref to the original types so that you can inspect them?Anonymous
March 30, 2010
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 keyAnonymous
June 27, 2010
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?Anonymous
March 07, 2011
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.Anonymous
March 20, 2012
Long time, no updates... Looks like there is still no goog flexible solution to adapt wfc using tools on the fly.