Click here to Skip to main content
15,881,173 members
Articles / Programming Languages / C#

Generate Proxy Class at Build Time

Rate me:
Please Sign up or sign in to vote.
4.74/5 (13 votes)
15 Nov 2011Ms-PL5 min read 36.6K   214   25  
An introduction to Genuilder Extensibility
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<!--------------------------------------------------------------------------->
<!--                           INTRODUCTION                                

 The Code Project article submission template (HTML version)

Using this template will help us post your article sooner. To use, just 
follow the 3 easy steps below:
 
     1. Fill in the article description details
     2. Add links to your images and downloads
     3. Include the main article text

That's all there is to it! All formatting will be done by our submission
scripts and style sheets. 

-->
<!--------------------------------------------------------------------------->
<!--                        IGNORE THIS SECTION                            -->
<html>
<head>
	<title>The Code Project</title>
	<style>
		BODY, P, TD
		{
			font-family: Verdana, Arial, Helvetica, sans-serif;
			font-size: 10pt;
		}
		H2, H3, H4, H5
		{
			color: #ff9900;
			font-weight: bold;
		}
		H2
		{
			font-size: 13pt;
		}
		H3
		{
			font-size: 12pt;
		}
		H4
		{
			font-size: 10pt;
			color: black;
		}
		PRE
		{
			background-color: #FBEDBB;
			font-family: "Courier New" , Courier, mono;
			white-space: pre;
		}
		CODE
		{
			color: #990000;
			font-family: "Courier New" , Courier, mono;
		}
	</style>
	<link rel="stylesheet" type="text/css" href="http://www.codeproject.com/App_Themes/NetCommunity/CodeProject.css">
</head>
<body bgcolor="#FFFFFF" color="#000000">
	<!--------------------------------------------------------------------------->
	<!-------------------------------     STEP 1      --------------------------->
	<!--  Fill in the details (CodeProject will reformat this section for you) -->
	<!-------------------------------     STEP 2      --------------------------->
	<!--  Include download and sample image information.                       -->
	<ul class="download">
		<li><a href="http://genuilder.codeplex.com/">Go to Genuilder Codeplex</a></li>
	</ul>
	<img src="ProxyGeneration/Genuilder.png" />
	<h3 id="content">
		A solution for proxy class generation</h3>
	<ul>
		<li><a href="#Problem">Problem</a></li>
		<li><a href="#Solution">Solution : proxy pattern</a></li>
		<li><a href="#Problem2">Problem 2 : eliminating grunt code</a></li>
		<li><a href="#Solution2">Solution 2 : generate your base proxy with Genuilder</a></li>
		<li><a href="#Debug">Debugging an extension</a></li>
		<li><a href="#UnderTheHood">Under the hood of Genuilder</a></li>
		<li><a href="#Conclusion">Conclusion</a></li>
	</ul>
	<h3>
		<a name="Problem">The problem</a>
	</h3>
	<p>
		Given an object, you want to intercept and control every external access to members
		of this object which belongs to a given interface. What can you do ?</p>
	<p>
		For example, you have an object of type <code>MembershipProvider</code>, his goal
		is to list and manage users of your ecommerce site. You want that only administrators
		can add or delete members.</p>
	<p>
		For whatever reason you can't, or don't want to modify <code>MembershipProvider</code>
		class. <code>MembershipProvider</code> is an implementation of <code>IMembershipProvider</code>
		interface.</p>
	<img src="ProxyGeneration/Problem.png" />
	<h3>
		<a name="Solution">The solution : proxy pattern</a>
	</h3>
	<p>
		Create a class that implement IMembershipProvider, checks that the current user
		is administrator for methods AddMember and DeleteMember, then delegate every method
		calls to your real object.</p>
	<img src="ProxyGeneration/SecurityProxyMembershipProvider.png" />
	<p>
		Here what the implementation looks like :</p>
	<pre lang="cs">public bool ValidateUser(string login, string password)
{
	return RealObject.ValidateUser(login, password);
}

public IEnumerable&lt;string&gt; ListUsers()
{
	return RealObject.ListUsers();
}

public void AddUser(string login, string password)
{
	if(!ApplicationContext.User.IsAdmin)
		throw new SecurityException("Not authorized, admin only");
	RealObject.AddUser(login, password);
}

public void DeleteUser(string login)
{
	if(!ApplicationContext.User.IsAdmin)
		throw new SecurityException("Not authorized, admin only");
	RealObject.DeleteUser(login);
}</pre>
	<p>
		Everything looks fine, but now you want to create a proxy to log every call to the
		method <code>ListUsers</code>.
	</p>
	<p>
		To remove duplication you decide to create a base proxy class, and override only
		required methods. Here what looks like your refactoring.</p>
	<img src="ProxyGeneration/ProxyMembershipProvider.png" />
	<p>
		<code>SecurityProxyMembershipProvider</code> is now much more simple, with 2 methods
		to override instead of 4, <code>LogProxyMembershipProvider</code> with 1 method
		instead of 4.</p>
	<pre lang="cs">public override IEnumerable&lt;string&gt; ListUsers()
{
	_Logger.WriteLine("Listing..." + DateTime.Now.ToString());
	var result = base.ListUsers();
	_Logger.WriteLine("Done..." + DateTime.Now.ToString());
	return result;
}</pre>
	<p>
		Less code, less bugs, less coffee, more life.</p>
	<h3>
		<a name="Problem2">Problem 2 : eliminating grunt code</h3>
	</a>
	<p>
		All is well : your proxy base class, <code>ProxyMembershipProvider</code>, was not
		that exciting to code, but it works take a look.</p>
	<pre lang="cs">public class ProxyMembershipProvider : IMembershipProvider
{
	public ProxyMembershipProvider(IMembershipProvider realObject)
	{
		_RealObject = realObject;
	}
	private readonly IMembershipProvider _RealObject;
	public IMembershipProvider RealObject
	{
		get
		{
			return _RealObject;
		}
	}

	#region IMembershipProvider Members

	public virtual bool ValidateUser(string login, string password)
	{
		return RealObject.ValidateUser(login, password);
	}

	public virtual IEnumerable&lt;string&gt; ListUsers()
	{
		return RealObject.ListUsers();
	}

	public virtual void AddUser(string login, string password)
	{
		RealObject.AddUser(login, password);
	}

	public virtual void DeleteUser(string login)
	{
		RealObject.DeleteUser(login);
	}

	#endregion
}</pre>
	<p>
		Until, in a terrible and rainy day, the October 14th, terrible news run up to you.
		Your own teamate comes innocently smiling at you with... that.</p>
	<img src="ProxyGeneration/IRoleProvider.png" />
	<p>
		You smile back, but not for the same reason... now before stabbing him, twice, and
		commiting something you will regret all your life, I want you to take a look at
		my solution. Because I know that you don't like grunty coding. Me neither.</p>
	<h3>
		<a name="Solution2">Solution 2 : generate your base proxy with Genuilder</h3>
	</a>
	<p>
		Well I have created a project, 2 years ago, that I refactored a little bit these
		last days and it should help in your crisis.</p>
	<p>
		Disclaimer : If you write this extension yourself, it will takes more time than
		the writing the proxy directly. (but more fun)</p>
	<p>
		That's why I will centralize all extensions inside a single library so you don't
		have to create one for most common tasks. This article is here just to show you what Genuilder can do for you.</p>
	<p>
		It will permit you to automatically generate a proxy class for any interface.</p>
	<p>
		For that you will parse the C# code, and generate during compilation the proxy base
		class. Let's start.</p>
	<p>
		So you go to <a href="http://genuilder.codeplex.com">genuilder.codeplex.com</a>
		and download the Genuilder template.</p>
	<img src="ProxyGeneration/GenuilderSite.png" />
	<p>
		By curiosity you look at the cool screencast I've done, and you copy the template
		in documents :</p>
	<img src="ProxyGeneration/GenuilderInstall.png" />
	<p>
		You add a Genuilder project to your solution :</p>
	<img src="ProxyGeneration/ProjectTemplate.png" />
	<p>
		Just to be sure everything works, you create a simple genuilder extension in this
		new project, which creates during compilation a class called Test.</p>
	<pre lang="cs">public class ProxyGeneratorExtension : IExtension
{
	#region IExtension Members

	public void Execute(ExtensionContext extensionContext)
	{
		extensionContext.CreateChildItem("Generated.cs").WriteAllText("class Test{}");
	}

	#endregion
}</pre>
	<p>
		And since you have seen the cool screencast, you know that you need to install this
		extension in your project.</p>
	<p>
		To do that you need to modify the <code>Program.cs</code> of your genuilder project
		this way.</p>
	<pre lang="cs">static void Main(string[] args)
{
	foreach(var project in Projects.InSubDirectories("../../..").ExceptForThisAssembly())
	{
		var ex = new ExtensibilityFeature();
		ex.AddExtension(new ProxyGeneratorExtension());
		project.InstallFeature(ex);
		project.Save();
	}
}</pre>
	<p>
		You run you Genuilder project, and reload your Membership project.</p>
	<p>
		Now compile your Membership project, and as magic, your class Test exists !</p>
	<img src="ProxyGeneration/GeneratedTest.png" />
	<p>
		Since you will need to do some parsing, you reference Genuilder.Extensibility.NRefactory
		and ICSharpCode*.dll in your Genuilder project. These are shipped with bins directory
		of your Genuilder Project.</p>
	<img src="ProxyGeneration/References.png" />
	<p>
		Now you want to parse your IRoleProvider.cs file, and create a new .cs file with
		your base proxy.</p>
	<p>
		To do that you use the CompilationUnitExtension and visit the code AST -abstract
		syntax tree- of IRoleProvider.cs.
	</p>
	<p>
		Using a visitor is more readable than doing imbricated foreach on all nodes of your
		AST. The <code>CodeWriter</code> class will generate readable classes.</p>
	<p>
		It's not because these classes are generated that no one will read them !
	</p>
	<pre lang="cs">public void Execute(ExtensionContext extensionContext)
{
	var item = extensionContext.GenItems.FirstOrDefault(o => o.Name == "IRoleProvider.cs");
	if(item != null)
	{
		var asyncItem = item.CreateChildItem("Proxy" + item.Name);
		CodeWriter writer = new CodeWriter(asyncItem.Open());
		var itemAst = item.GetExtension&lt;CompilationUnitExtension&gt;();
		itemAst.ParseMethodBodies = false;
		itemAst.CompilationUnit.AcceptVisitor(new ProxiedInterfaceVisitor(writer), null);
		writer.Flush();
	}
}</pre>
	<p>
		The visitor is really simple, you just inherit from AbstractVisitor, then overrides
		method you want to. For example, here I copy in the generated file the usings of
		the input file.
	</p>
	<pre lang="cs">public override object VisitUsing(ICSharpCode.NRefactory.Ast.Using @using, object data)
{
	writer.WriteUsing(@using.Name);
	return base.VisitUsing(@using, data);
}</pre>
	<p>
		The writer is of type <code>CodeWriter</code>, it makes more easy code generation.</p>
	<p>
		Then I visit the namespace, so the proxy class will be in the same namespace as
		the proxied interface.
	</p>
	<pre lang="cs">public override object VisitNamespaceDeclaration(ICSharpCode.NRefactory.Ast.NamespaceDeclaration namespaceDeclaration, object data)
{
	using(writer.WriteNamespace(namespaceDeclaration.Name))
	{
		return base.VisitNamespaceDeclaration(namespaceDeclaration, data);
	}
}</pre>
	<p>
		The writer will automatically write the namespace, open the bracket, indent, and
		close the bracket at the end of the using.</p>
	<p>
		Then you need to create your RealObject property, and the constructor of your proxy
		class.</p>
	<pre lang="cs">public override object VisitTypeDeclaration(ICSharpCode.NRefactory.Ast.TypeDeclaration typeDeclaration, object data)
{
	if(typeDeclaration.Type == ICSharpCode.NRefactory.Ast.ClassType.Interface)
	{
		var typeName = "Proxy" + typeDeclaration.Name;
		writer.Write("public class " + typeName + " : " + typeDeclaration.Name);
		writer.NewLine();
		using(writer.WriteBrackets())
		{
			writer.Write("public " + typeDeclaration.Name + " RealObject { get; private set; }");
			writer.NewLine();
			writer.Write("public " + typeName + "(" + typeDeclaration.Name + " realObject)");
			writer.NewLine();
			using(writer.WriteBrackets())
			{
				writer.Write("this.RealObject = realObject;");
			}
			return base.VisitTypeDeclaration(typeDeclaration, data);
		}

	}
	return null;
}</pre>
	<p>
		Then you generate the proxy's properties.
	</p>
	<pre lang="cs">public override object VisitPropertyDeclaration(ICSharpCode.NRefactory.Ast.PropertyDeclaration propertyDeclaration, object data)
{
	writer.Write("public virtual " + propertyDeclaration.TypeReference + " " + propertyDeclaration.Name);
	writer.NewLine();
	using(writer.WriteBrackets())
	{
		if(propertyDeclaration.HasGetRegion)
		{
			writer.Write("get");
			writer.NewLine();
			using(writer.WriteBrackets())
			{
				writer.Write("return RealObject." + propertyDeclaration.Name + ";");
			}
		}
		if(propertyDeclaration.HasSetRegion)
		{
			writer.Write("set");
			writer.NewLine();
			using(writer.WriteBrackets())
			{
				writer.Write("RealObject." + propertyDeclaration.Name + " = value;");
			}
		}
	}
	return null;
}</pre>
	<p>
		And finally your methods...</p>
	<pre lang="cs">public override object VisitMethodDeclaration(ICSharpCode.NRefactory.Ast.MethodDeclaration methodDeclaration, object data)
{
	writer.Write("public virtual " + ToName(methodDeclaration.TypeReference) + " " + methodDeclaration.Name + "(" + String.Join(", ", methodDeclaration.Parameters.Select(p => p.TypeReference + " " + p.ParameterName)) + ")");
	writer.NewLine();
	using(writer.WriteBrackets())
	{
		bool isVoid = methodDeclaration.TypeReference.Type == "System.Void";
		writer.Write((isVoid ? "" : "return ") + "RealObject." + methodDeclaration.Name + "(" + String.Join(",", methodDeclaration.Parameters.Select(p => p.ParameterName).ToArray()) + ");");
	}
	return null;
}</pre>
	<p>
		I don't really comment the code because I will be more confusing than the code itself...
	</p>
	<p>
		So now compile your membership project and ...</p>
	<img src="ProxyGeneration/ProxyIRoleProviderIntelli.png" />
	<p>
		The result is a proxy of generated ProxyIRoleProvider automatically at compile time
		:</p>
	<pre lang="cs">public class ProxyIRoleProvider : IRoleProvider
{
	public IRoleProvider RealObject { get; private set; }
	public ProxyIRoleProvider(IRoleProvider realObject)
	{
		this.RealObject = realObject;
	}
	public virtual System.String Name
	{
		get
		{
			return RealObject.Name;
		}
		set
		{
			RealObject.Name = value;
		}
			
	}
	public virtual void AddRole(System.String role)
	{
		RealObject.AddRole(role);
	}
	public virtual void DeleteRole(System.String role)
	{
		RealObject.DeleteRole(role);
	}
	public virtual IEnumerable&lt;System.String&gt; ListRoles()
	{
		return RealObject.ListRoles();
	}
	public virtual IAsyncResult BeginListRoles(AsyncCallback ac, System.Object state)
	{
		return RealObject.BeginListRoles(ac,state);
	}
	public virtual IEnumerable&lt;System.String&gt; EndListRoles(IAsyncResult ar)
	{
		return RealObject.EndListRoles(ar);
	}
	public virtual IAsyncResult BeginAddRole(System.String role, AsyncCallback ac, System.Object state)
	{
		return RealObject.BeginAddRole(role,ac,state);
	}
	public virtual void EndAddRole(IAsyncResult ar)
	{
		RealObject.EndAddRole(ar);
	}
	public virtual IAsyncResult BeginDeleteRole(System.String role, AsyncCallback ac, System.Object state)
	{
		return RealObject.BeginDeleteRole(role,ac,state);
	}
	public virtual void EndDeleteRole(IAsyncResult ar)
	{
		RealObject.EndDeleteRole(ar);
	}
	public virtual void Initialize(System.String conf)
	{
		RealObject.Initialize(conf);
	}
	public virtual void BeginInitialize(System.String conf, AsyncCallback ac, System.Object state)
	{
		RealObject.BeginInitialize(conf,ac,state);
	}
	public virtual void EndInitialize(IAsyncResult ar)
	{
		RealObject.EndInitialize(ar);
	}
	public virtual IEnumerable&lt;System.String&gt; ListUserInRole(System.String role)
	{
		return RealObject.ListUserInRole(role);
	}
	public virtual IAsyncResult BeginListUserInRole(System.String role, AsyncCallback ac, System.Object state)
	{
		return RealObject.BeginListUserInRole(role,ac,state);
	}
	public virtual IEnumerable&lt;System.String&gt; EndListUserInRole(IAsyncResult ar)
	{
		return RealObject.EndListUserInRole(ar);
	}
		
}</pre>
	<p>
		Not efficient with large solution you said ? right, but you are not obliged to generate
		this class everytime you compile... you can generate it everytime IRoleProvider.cs
		change.</p>
	<p>
		In your extension, you just have to change this line :</p>
	<pre lang="cs">public void Execute(ExtensionContext extensionContext)
{
	var item = extensionContext.GenItems.FirstOrDefault(o => o.Name == "IRoleProvider.cs");
	if(item != null)
	{</pre>
	<p>
		To</p>
	<pre lang="cs">public void Execute(ExtensionContext extensionContext)
{
	var item = extensionContext.GenItems.FirstOrDefault(o => o.Name == "IRoleProvider.cs");
	if(item != null && item.Modified)
	{</pre>
	<p>
		Genuilder keeps track of modified files for you, so you don't waste time during
		compilation.</p>
	<p>
		I don't check the modified property when I'm developping an extension, because,
		to test it, I want it to run even though I have not modified any file.</p>
	<h3>
		<a name="Debug">Debugging an extension</h3>
	</a>
	<p>
		Genuilder has been done keeping in mind that developping extension should be easy.
		So debugging is also easy.</p>
	<p>
		You can debug step by step your extension by installing it, calling <code>Project.Build</code>
		and running your Genuilder project.</p>
	<pre lang="cs">static void Main(string[] args)
{
	foreach(var project in Projects.InSubDirectories("../../..").ExceptForThisAssembly())
	{
		var ex = new ExtensibilityFeature();
		ex.AddExtension(new ProxyGeneratorExtension());
		project.InstallFeature(ex);
		project.Build();
	}
}</pre>
	<p>
		Breakpoints in your extension will be hit.</p>
	<p>
		Also, you don't have to run the Genuilder project everytime you change your extension.
		An extension is unloaded by Visual studio after every build, so you can have
		a quick edit/build/debug workflow.</p>
	<h3>
		<a name="UnderTheHood">Under the hood of Genuilder</h3>
	</a>
	<p>
		Under the hood, when you run the Genuilder project it hooks the end of you project
		file this way :</p>
	<pre lang="xml"> &lt;Import Project="StartGenuilder" Condition="'Genuilder internal dont touch  or apocalypse will come' == 'true'" /&gt;
  &lt;ItemGroup&gt;
    &lt;GenuilderExtension Include="Membership.Genuilder.ProxyGeneratorExtension|..\Membership.Genuilder\bin\Debug\Membership.Genuilder.exe|QBdQcm94eUdlbmVyYXRvckV4dGVuc2lvbgg8aHR0cDovL3NjaGVtYXMuZGF0YWNvbnRyYWN0Lm9yZy8yMDA0LzA3L01lbWJlcnNoaXAuR2VudWlsZGVyCQFpKWh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlAQ=="&gt;
      &lt;Visible&gt;false&lt;/Visible&gt;
    &lt;/GenuilderExtension&gt;
  &lt;/ItemGroup&gt;
  &lt;Import Project="..\Membership.Genuilder\bin\Debug\Genuilder.Extensibility.targets" /&gt;
  &lt;PropertyGroup&gt;
    &lt;UseHostCompilerIfAvailable&gt;false&lt;/UseHostCompilerIfAvailable&gt;
  &lt;/PropertyGroup&gt;
  &lt;Import Project="EndGenuilder" Condition="'Genuilder internal dont touch or apocalypse will come' == 'true'" /&gt;</pre>
	<p>
		Every extension is serialized in your project file (See the GenuilderExtension MSBuild
		Item). Genuilder use <code>DataContractSerializer</code> to do that, so only <code>[DataMember]</code>
		properties of your Extension will be saved when installed.</p>
	<h3>
		<a name="Conclusion">Conclusion</a></h3>
	<p>
		Genuilder will keep evolving, and my next project with Genuilder is to make an extension
		that will check {Binding} in XAML file, and output MSBuild error if paths are wrong.</p>
	<p>
		Because I forgot to tell you, but you can easily output message to the Error and
		warning window of visual studio.</p>
	<pre lang="cs">public void Execute(ExtensionContext extensionContext)
{
	var item = extensionContext.GenItems.FirstOrDefault(o => o.Name == "IRoleProvider.cs");
	item.Logger.Error("See you soon :)", 1, 5);</pre>
	<img src="ProxyGeneration/SeeYouSoon.png" />
</body>
</html>

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)


Written By
Software Developer Freelance
France France
I am currently the CTO of Metaco, we are leveraging the Bitcoin Blockchain for delivering financial services.

I also developed a tool to make IaaS on Azure more easy to use IaaS Management Studio.

If you want to contact me, go this way Smile | :)

Comments and Discussions