Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / .NET

Genuilder Extensibility

5.00/5 (10 votes)
13 Nov 2011Ms-PL4 min read 34.7K   93  
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)