Click here to Skip to main content
15,867,568 members
Articles / .NET

Genuilder Extensibility

Rate me:
Please Sign up or sign in to vote.
5.00/5 (10 votes)
13 Nov 2011Ms-PL4 min read 33.6K   93   13   8
Generate your own code during compilation without MSBuild knowledge

Image 1.Extensibility

This article is obsolete to use Genuilder Extensibility framework has been refactored take a look at the example on this article 

Table of Contents

Introduction

Maybe some of you remember my last project Genuilder? If you don't remember, I suggest you take a look at my article and also the documentation on CodePlex. Simply said, Genuilder is a bunch of features in Visual Studio (like config file checking) you can add to every project without any installation. It uses MSBuild to do its job.

So today is about a new feature to create your own features and it's called Genuilder.Extensibility. This feature is created to easily generate your own code during compilation. You don't need MSBuild knowledge, you don't need to tweak your project files, you don't need to be a C# guru. You just need to know how to reference an assembly and create a new class library in Visual Studio !

This article is a FAQ on how to use it in your own projects.

How to Use Genuilder in my Project?

For this part, I suggest you read the documentation, I'll quickly explain it here.

Let's create a console application in which you want to use the InterfaceExtractor feature that we are going to create.

Image 2

Copy the Genuilder bin folder in the same folder as your solution.

Image 3

Now we use the Genuilder GUI, browse to our solution and activate Genuilder on our project.

Image 4

It's over !

How Can I Create My Own Feature?

We have to create a new class library and the project name must follow the convention FEATURE_NAME.Gen, this convention is mandatory.

Reference Genuilder, and Genuilder.Extensibility in the feature project, then create a new plugin. It is a class which implements IPlugin (HelloWorldPlugin).

Then, add the feature project as a reference to your project.

Image 5

Generate a file in the implementation of the Initialize method.

C#
public class HelloWorldPlugin : IPlugin
{
	#region IPlugin Members

	public void Initialize(ICodeRepository repository)
	{
		var code = repository.Get("GeneratedCode.gen.cs");
		code.Content = "class HelloWorld{}";
	}

	#endregion
}

Compile and magic ! You can see the class HelloWorld with the intellisense.

C#
class Program
{
	static void Main(string[] args)
	{
		HelloWorld hello; //We have intellisense with the HelloWorld class
	}
}

How Can I Create a Dependency Between a Source File (Source) and a Generated Source File (Target)?

If the source file is deleted, you want the target generated code to be automatically deleted from your project? No problem.

You just have to use the SourceOf and TargetOf methods from the CodeItem class. It returns a CodeDependency, then you just have to register to the ShouldUpdateTarget event.

Image 6

An example worth a thousand words...

Image you want to create a new plugin: For every XXX.cs in my solution, I want to generate a target file named XXX.csgenerated.cs. Each of these target files will contain a class named XXX_cs.

C#
public class DependencyHelloWorldPlugin : IPlugin
{
	#region IPlugin Members

	public void Initialize(ICodeRepository repository)
	{
		repository.CodeItemCreated += 
		   new CodeItemCreatedHandler(repository_CodeItemCreated);
	}

	void repository_CodeItemCreated(ICodeRepository sender, CodeItem item)
	{
		if(!item.Name.EndsWith("generated.cs"))
		{
			var dependency = item.SourceOf(item.Name + "generated.cs");
			dependency.ShouldUpdateTarget += 
			    new CodeDependencyHandler(dependency_ShouldUpdateTarget);
		}
	}

	void dependency_ShouldUpdateTarget(CodeDependency sender, CodeItem target)
	{
		target.Content = String.Format("public class {0}", 
				ToClassName(sender.Source.Name)) + "{}";
	}

	private string ToClassName(string fileName)
	{
		return fileName
			.Replace('.', '_')
			.Replace('/', '_')
			.Replace('\\', '_');
	}

	#endregion
}

How Can I Parse and Generate Code with my Favorite Parser?

Ok, let's take the same previous example.

The code is fine... however I'd like to generate a target file only if the source file contains a class with my custom attribute GenerateAttribute.

Genuilder.Extensibility is shipped with an extension which integrates NRefactory (the parser of SharpDevelop) with Genuilder.

An extension is a class with a CodeItem as constructor's parameter.

Reference Genuilder.Extensibility.NRefactory, ICSharpCode.NRefactory and ICSharpCode.SharpDevelop.Dom in the feature project.

Image 7

Then use CodeItem.GetExtension<CompilationUnitExtension>(); to use the extension.

Image 8
C#
public class DependencyHelloWorldPlugin2 : IPlugin
{
	#region IPlugin Members

	public void Initialize(ICodeRepository repository)
	{
		repository.CodeItemCreated += 
			new CodeItemCreatedHandler(repository_CodeItemCreated);
	}

	void repository_CodeItemCreated(ICodeRepository sender, CodeItem item)
	{
		if(!item.Name.EndsWith("generated2.cs") && 
			HasAClassWithAttribute<GenerateAttribute>(item))
		{

			var dependency = item.SourceOf(item.Name + "generated2.cs");
			dependency.ShouldUpdateTarget += 
			new CodeDependencyHandler(dependency_ShouldUpdateTarget);
		}
	}

	private bool HasAClassWithAttribute<T>(CodeItem item)
	{
		var nRefactoryExtension = item.GetExtension<CompilationUnitExtension>();
		return HasAttribute<T>(nRefactoryExtension.CompilationUnit);
	}

	private bool HasAttribute<T>(INode node)
	{
		if(node is TypeDeclaration)
		{
			TypeDeclaration declaration = (TypeDeclaration)node;
			var hasAttribute = declaration.Attributes
				.SelectMany(attributes => attributes.Attributes)
				.Any(attribute => attribute.Name.EndsWith
					(typeof(GenerateAttribute).Name));
			if(hasAttribute)
				return true;
		}

		foreach(var child in node.Children)
		{
			var hasAttribute = HasAttribute<T>(child);
			if(hasAttribute)
				return true;
		}
		return false;
	}

	void dependency_ShouldUpdateTarget(CodeDependency sender, CodeItem target)
	{
		var nRefactoryExtension = 
			target.GetExtension<CompilationUnitExtension>();
		nRefactoryExtension.CompilationUnit.Children.Clear();
		nRefactoryExtension.CompilationUnit.Children.Add(
		new TypeDeclaration(Modifiers.Public, new List<AttributeSection>())
		{
			Name = ToClassName(sender.Source.Name)
		});
		nRefactoryExtension.Save();
	}

	private string ToClassName(string fileName)
	{
		return fileName
			.Replace('.', '_')
			.Replace('/', '_')
			.Replace('\\', '_') + "2";
	}

	#endregion
}

How Can I Debug my Plugin?

Open the properties of your feature project, then go to the Debug tab. Then set the start program to MSBuild (it depends on your version of .NET). The command parameter must refer to a project which is the plugin.

Image 9

After that, just press F5 when you want to debug.

That's All... Less is More !!

As you can see by the length of this article, Genuilder.Extensibility is really simple to use. Thanks to Genuilder, you'll be able to generate your own code without any knowledge about any technology.

Let me know if you have new ideas, discover bugs -I know you know you will ;)-, on Genuilder's home. I hope you'll like it ! :)

History

  • 11th June, 2010: Initial post

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

 
GeneralI like pictures because I'm thick! Pin
Alan Beasley23-Jun-10 9:10
Alan Beasley23-Jun-10 9:10 
GeneralRe: I like pictures because I'm thick! Pin
Nicolas Dorier23-Jun-10 9:31
professionalNicolas Dorier23-Jun-10 9:31 
GeneralI Agree good to see you back, have another 5 Pin
Sacha Barber13-Jun-10 8:03
Sacha Barber13-Jun-10 8:03 
GeneralRe: I Agree good to see you back, have another 5 Pin
Nicolas Dorier13-Jun-10 8:53
professionalNicolas Dorier13-Jun-10 8:53 
GeneralRe: I Agree good to see you back, have another 5 Pin
Sacha Barber13-Jun-10 11:07
Sacha Barber13-Jun-10 11:07 
GeneralRe: I Agree good to see you back, have another 5 Pin
Alan Beasley23-Jun-10 9:07
Alan Beasley23-Jun-10 9:07 
GeneralGood to see you back! Pin
Daniel Vaughan12-Jun-10 9:21
Daniel Vaughan12-Jun-10 9:21 
Hey Nico!

Happy to see you extending this cool project. 5 from me.
BTW relatedly, and I don't know if you know, but Anders Hejlsberg has been talking a bit lately about compilers as a service. Interesting indeed.

Cheers,
Daniel
Daniel Vaughan
Twitter | Blog | Microsoft MVP | Projects: Calcium SDK, Clog | LinkedIn

GeneralRe: Good to see you back! Pin
Nicolas Dorier12-Jun-10 22:36
professionalNicolas Dorier12-Jun-10 22:36 

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.