Click here to Skip to main content
15,867,453 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.5K   214   25   7
An introduction to Genuilder Extensibility
Image 1

ProxyGeneratorExtension has now been integrated in Genuilder 2.1.

A Solution for Proxy Class Generation

The Problem

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?

For example, you have an object of type MembershipProvider, its goal is to list and manage users of your ecommerce site. You want that only administrators can add or delete members.

For whatever reason, you can't or don't want to modify the MembershipProvider class. MembershipProvider is an implementation of IMembershipProvider interface.

Image 2

The Solution: Proxy Pattern

Create a class that implements IMembershipProvider, checks that the current user is administrator for methods AddMember and DeleteMember, then delegates every method call to your real object.

Image 3

Here is what the implementation looks like:

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

public IEnumerable<string> 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);
}

Everything looks fine, but now you want to create a proxy to log every call to the method ListUsers.

To remove duplication, you decide to create a base proxy class, and override only required methods. Here's what your refactoring looks like:

Image 4

SecurityProxyMembershipProvider is now much more simple, with 2 methods to override instead of 4, LogProxyMembershipProvider with 1 method instead of 4.

C#
public override IEnumerable<string> ListUsers()
{
    _Logger.WriteLine("Listing..." + DateTime.Now.ToString());
    var result = base.ListUsers();
    _Logger.WriteLine("Done..." + DateTime.Now.ToString());
    return result;
}

Less code, less bugs, less coffee, more life.

Problem 2: Eliminating Grunt Code

All is well: your proxy base class, ProxyMembershipProvider, was not that exciting to code, but it works. Take a look.

C#
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<string> 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
}

Until, in a terrible and rainy day, the October 14th, terrible news runs up to you. Your own teammate comes innocently smiling at you with... that.

Image 5

You smile back, but not for the same reason... now before stabbing him, twice, and committing 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.

Solution 2: Generate Your Base Proxy with Genuilder

Well I created a project, 2 years ago, that I refactored a little bit these last days and it should help in your crisis.

Disclaimer: If you write this extension yourself, it will take more time than writing the proxy directly (but more fun).

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.

It will permit you to automatically generate a proxy class for any interface.

For that, you will parse the C# code, and generate during compilation the proxy base class. Let's start.

So you go to genuilder.codeplex.com and download the Genuilder template.

Click to enlarge image

By curiosity, you look at the cool screencast I've done, and you copy the template in documents:

Image 7

You add a Genuilder project to your solution:

Image 8

Just to be sure everything works, you create a simple genuilder extension in this new project, which creates during compilation a class called Test.

C#
public class ProxyGeneratorExtension : IExtension
{
    #region IExtension Members

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

    #endregion
}

And since you have seen the cool screencast, you know that you need to install this extension in your project.

To do that, you need to modify the Program.cs of your genuilder project this way.

C#
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();
    }
}

You run you Genuilder project, and reload your Membership project.

Now compile your Membership project, and as magic, your class Test exists !

Image 9

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.

Image 10

Now you want to parse your IRoleProvider.cs file, and create a new .cs file with your base proxy.

To do that, you use the CompilationUnitExtension and visit the code AST - abstract syntax tree- of IRoleProvider.cs.

Using a visitor is more readable than doing imbricated foreach on all nodes of your AST. The CodeWriter class will generate readable classes.

It's not because these classes are generated that no one will read them!

C#
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<CompilationUnitExtension>();
        itemAst.ParseMethodBodies = false;
        itemAst.CompilationUnit.AcceptVisitor(new ProxiedInterfaceVisitor(writer), null);
        writer.Flush();
    }
}

The visitor is really simple, you just inherit from AbstractVisitor, then override method you want to. For example, here I copy in the generated file the usings of the input file.

C#
public override object VisitUsing(ICSharpCode.NRefactory.Ast.Using @using, object data)
{
    writer.WriteUsing(@using.Name);
    return base.VisitUsing(@using, data);
}

The writer is of type CodeWriter, it makes code generation more easy.

Then I visit the namespace, so the proxy class will be in the same namespace as the proxied interface.

C#
public override object VisitNamespaceDeclaration
  (ICSharpCode.NRefactory.Ast.NamespaceDeclaration namespaceDeclaration, object data)
{
    using(writer.WriteNamespace(namespaceDeclaration.Name))
    {
        return base.VisitNamespaceDeclaration(namespaceDeclaration, data);
    }
}

The writer will automatically write the namespace, open the bracket, indent, and close the bracket at the end of the using.

Then you need to create your RealObject property, and the constructor of your proxy class.

C#
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;
}

Then you generate the proxy's properties.

C#
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;
}

And finally your methods...

C#
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;
}

I don't really comment the code because it will be more confusing than the code itself...

So now compile your membership project and ...

Image 11

The result is a proxy of generated ProxyIRoleProvider automatically at compile time:

C#
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<System.String> ListRoles()
    {
        return RealObject.ListRoles();
    }
    public virtual IAsyncResult BeginListRoles(AsyncCallback ac, System.Object state)
    {
        return RealObject.BeginListRoles(ac,state);
    }
    public virtual IEnumerable<System.String> 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<System.String> 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<System.String> EndListUserInRole(IAsyncResult ar)
    {
        return RealObject.EndListUserInRole(ar);
    }        
}

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 changes.

In your extension, you just have to change this line:

C#
public void Execute(ExtensionContext extensionContext)
{
    var item = extensionContext.GenItems.FirstOrDefault
            (o => o.Name == "IRoleProvider.cs");
    if(item != null)
    {

to:

C#
public void Execute(ExtensionContext extensionContext)
{
    var item = extensionContext.GenItems.FirstOrDefault
            (o => o.Name == "IRoleProvider.cs");
    if(item != null && item.Modified)
    {

Genuilder keeps track of modified files for you, so you don't waste time during compilation.

I don't check the modified property when I'm developing an extension, because, to test it, I want it to run even though I have not modified any file.

Debugging an Extension

Genuilder has been done keeping in mind that developing extension should be easy. So debugging is also easy.

You can debug step by step your extension by installing it, calling Project.Build and running your Genuilder project.

C#
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();
    }
}

Breakpoints in your extension will be hit.

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.

Under the Hood of Genuilder

Under the hood, when you run the Genuilder project, it hooks the end of you project file this way:

XML
<Import Project="StartGenuilder"
   Condition="'Genuilder internal dont touch  or apocalypse will come' == 'true'" />
 <ItemGroup>
   <GenuilderExtension Include="Membership.Genuilder.ProxyGeneratorExtension|
   ..\Membership.Genuilder\bin\Debug\Membership.Genuilder.exe|
   QBdQcm94eUdlbmVyYXRvckV4dGVuc2lvbgg8aHR0cDovL3NjaGVtYXMuZGF0YWNvbnRyYWN0Lm9y
   Zy8yMDA0LzA3L01lbWJlcnNoaXAuR2VudWlsZGVyCQFpKWh0dHA6Ly93d3cudzMub3JnLzIwMDEv
   WE1MU2NoZW1hLWluc3RhbmNlAQ==">
     <Visible>false</Visible>
   </GenuilderExtension>
 </ItemGroup>
 <Import Project="..\Membership.Genuilder\bin\Debug\Genuilder.Extensibility.targets" />
 <PropertyGroup>
   <UseHostCompilerIfAvailable>false</UseHostCompilerIfAvailable>
 </PropertyGroup>
 <Import Project="EndGenuilder" Condition="'Genuilder internal dont touch
   or apocalypse will come' == 'true'" />

Every extension is serialized in your project file (See the GenuilderExtension MSBuild Item). Genuilder uses DataContractSerializer to do that, so only [DataMember] properties of your Extension will be saved when installed.

Conclusion

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.

I forgot to tell you, but you can easily output message to the Error and warning window of Visual Studio.

C#
public void Execute(ExtensionContext extensionContext)
{
    var item = extensionContext.GenItems.FirstOrDefault
            (o => o.Name == "IRoleProvider.cs");
    item.Logger.Error("See you soon :)", 1, 5);

Image 12

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

 
QuestionNice idea, but I think there are better solutions such as Pin
Sacha Barber12-Nov-11 8:34
Sacha Barber12-Nov-11 8:34 
AnswerRe: Nice idea, but I think there are better solutions such as Pin
Nicolas Dorier13-Nov-11 0:01
professionalNicolas Dorier13-Nov-11 0:01 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.