Click here to Skip to main content
Click here to Skip to main content

Tagged as

Go to top

Genuilder Extensibility

, 13 Nov 2011
Rate this:
Please Sign up or sign in to vote.
Generate your own code during compilation without MSBuild knowledge

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

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

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

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.

Generate a file in the implementation of the Initialize method.

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.

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.

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.

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.

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

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.

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 Wink | ;) -, on Genuilder's home. I hope you'll like it ! Smile | :)

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)

Share

About the Author

Nicolas Dorier
Software Developer Freelance
France France
I am a trainer and a curious developer.
 
CEO of AO-IS, we created a tool to make IaaS on Azure more easy IaaS Management Studio.
 
If you are interested for working with me, for fun coding stuff, for freelance stuff, or interested in using our cloud training infrastructure freely for a kickass presentation for the dev community ? this way Smile | :)

Comments and Discussions

 
GeneralI like pictures because I'm thick! PinmemberAlan Beasley23-Jun-10 9:10 
GeneralRe: I like pictures because I'm thick! PinmemberNicolas Dorier23-Jun-10 9:31 
GeneralI Agree good to see you back, have another 5 PinmvpSacha Barber13-Jun-10 8:03 
GeneralRe: I Agree good to see you back, have another 5 PinmemberNicolas Dorier13-Jun-10 8:53 
GeneralRe: I Agree good to see you back, have another 5 PinmvpSacha Barber13-Jun-10 11:07 
GeneralRe: I Agree good to see you back, have another 5 PinmemberAlan Beasley23-Jun-10 9:07 
GeneralGood to see you back! PinmvpDaniel Vaughan12-Jun-10 9:21 
GeneralRe: Good to see you back! PinmemberNicolas Dorier12-Jun-10 22:36 

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

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

| Advertise | Privacy | Mobile
Web01 | 2.8.140921.1 | Last Updated 13 Nov 2011
Article Copyright 2010 by Nicolas Dorier
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid