.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;
}
}
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 ;)-, on Genuilder's home. I hope you'll like it ! :)
- 11th June, 2010: Initial post