Table of contents
This article has almost the same content as Genuilder's documentation. But CodeProject members are
not developers like other... We are curious, we hack, combine, create, criticize ideas in order to find better solutions, for ourselves and for others. That's why I'll also talk about
the implementation details of Genuilder at the end of this article.
In one sentence, Genuilder is a Build time development framework... Remember, it took me a long time to find how to describe Genuilder in one sentence.
It means that you will be able to run your own code when developers build your project, emit error messages, generate code on the fly (for example, it is how WCF RIA services works), and more.
...or you can just use the built-in features, like
AppConfig compile time verification, or async contract generators for WCF.
Go to genuilder.codeplex.com and download the Genuilder Visual Studio extension.
Restart Visual Studio, you should then see the new project item.
You have to uninstall the previous version of Genuilder in the Extension Manager (in Visual Studio "Tools/Extension manager").
Common features are accessible through the
CommonFeature enumeration in your Genuilder project.
You need to run your Genuilder project each time you modify it to install or remove features from your projects.
AppConfigVerifier is an integration in Genuilder of this project.
It scans all
'type=".*"' in the config file and checks if the type is referenced in the project. The documentation
can be found on CodeProject. Here is a quick example, where in the config file you misspelled the MembershipProvider type:
Genuilder will find the error at compile time:
You can exclude types or assemblies from the verification, just check this article.
This feature comes from one of my articles on CodeProject.
With it you can access your
ConnectionStrings in a strongly typed way. For example, the
Auto completion on
Auto completion on
EnvironmentFile is just a pre-processor for your config file. For example, you have this app.config with a token:
You also have this EnvironmentFile.env file which is just a key=value pair list delimited by line breaks:
After compilation, the output config file (for example, MyProject.exe.config) will be:
This is particularly useful when developers in a team do not have the same development environment, they don't need to checkout their own version
of the app.config. (EnvironmentFile should be unique for each developer and should not be in your source control.)
If one of your fellow developers changes the config file like that:
After the next Get latest version, the project will not compile because the new token is not set in EnvironmentFile.env:
InterfaceExtractor is a code generator which extracts an interface from public members of classes and structs.
This feature is really useful when you want to create static or dynamic proxies around a service or a controller for authentication or logging
for example. Here is some other cool things to do with an interface. For example, this class:
will create extract this interface:
Note that the parameter of
ExtractInterface is optional, the default interface name is
IClassName. Also, you don't have to implement the interface on your class.
[ExtractInterface(typeof(SomeNamespace.Interface))] is currently not allowed, you cannot specify the namespace of the extracted
interface... not a big deal to add though.
Like InterfaceExtractor, PropertiesExtractor is a class generator which generates a static class to access property names
in a strongly typed way. For example, this class implements
INotifyPropertyChanged and does not hard code the property name with a string:
Because PropertiesExtractor will generate this other class:
This way if you refactor the property name, the code will break at compile time instead of during runtime.
As with InterfaceExtractor, the parameter of the ExtractProperties is optional, the default class name is
[ExtractProperties(typeof(SomeNamespace.MyClassProperties))] is currently not allowed, you cannot specify the namespace
of the extracted class properties... not a big deal to add though.
This feature generates a class to access your configuration file in a strongly typed way. If you remove a section from your config file
and somewhere your code has a dependency on it, it will break at compile time.
For example, for this config file:
You will be able to access sections like this:
Installing an extension in your project
An article has been written on CodeProject to introduce the Extensibility framework of Genuilder,
but I will refresh your memory. To install an extension, you need to use
ExtensibilityFeature, add extensions, and install it on the project. In this example,
AsyncServiceContractsExtension on the file Program.cs of every project.
This extension creates a
ServiceContract given a synchronous
ServiceContract, useful for Silverlight projects. Given:
An example is given here.
It generates classes to access a type's member in a strongly typed way. Given:
It will generate a class to access
EventInfo this way:
The goal and usage of this
IExtension is explained in this CodeProject article.
To create your own extension, you have to implement
IExtension, here is the object model of
GenItems represents code files of your project, you can use
GenItem.CreateChild(string name) to create new ones. When you use
GenItem.CreateChild, the child will be deleted when the parent is deleted.
You can easily send warning, error, and info messages to Visual Studio by using
For example, in this example, I output an error on Program.cs line 5 column 6 in Visual Studio.
Once installed on my project, here is what happens at compile time:
Debugging an Extension
You can easily debug your extensions by calling the
Project.Build() method on your project.
In this case, you will be able to break in the
MyExtension class to see what is going on during the build.
Easy code parsing and generation
Genuilder has a built-in object extension to easily parse
CodeItems with NRefactory.
You can get it this way:
var compilationExtension = genItem.GetExtension<CompilationUnitExtension>()
Then you can use the
CompilationUnit property to walk through the Abstract Syntax Tree (AST).
Or, more easily, use the Visitor pattern by implementing
AbstractASTVisitor, overriding methods called during the visit
of the tree, then calling
CompilationUnit.Visit(visitor,null);. Take a look at the code source
In this example, you'll see how to use the
CodeWriter class to generate readable code.
Currently, the intellisense of Resharper doesn't work on generated classes. But the code will compile.
Under the hood: All is feature
In Genuilder philosophy, all is feature and features are... Chunk of MSBuild XML in your project, nothing more, nothing else. And there is actually two types of features.
Under the hood, a
CommonFeature is just a
TargetFeature, as you can see in the implementation of
public void InstallCommonFeature(CommonFeature feature)
For example, when you install a
CommonFeature and an
IExtension in your project:
foreach(var project in Projects.InSubDirectories("../..").ExceptForThisAssembly())
var ex = new ExtensibilityFeature();
You will see in the csproj file the following XML :
Red elements are the delimiters of Genuilder's sections. Genuilder guarantees that everything it adds to your csproj is between these two elements.
Blue is the extensibility feature's section.
Cyan is your extension section, composed in three parts delimited by pipe '|' parts:
- The full class name of your extension
- The assembly of your extension
- The DataContract serialization of your extension
Orange we ask to not use the Visual Studio compiler. Without it, auto completion on generated classes is buggy.
Green is the CommonFeature's section, as you can see it only includes an import to a target file.
A feature is really easy to implement, I use the MSBuild framework along with some of my own Extension Methods to modify the csproj file easily.
You can use the
msBuildData parameter to add things just after the Genuilder's section delimiter; for example, this is how
TargetFeature is implemented:
public override void LoadFeature(Project project, MSBuildData msBuildData)
var importPath = PathUtil.RelativePathTo(
MSBuild integration of Extensions
As you can see in the Blue section, the Extensibility feature imports
It just inserts its own Msbuild
Task during compilation, and during cleaning.
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">
The implementation of this task has been kept as small as possible, because it was really hard to test. The core logic
Genuilder.Extensibility is in the
I'll explain some lines of the
GenEngine needs an
IGenLogger to add logging capability to
just provides an implementation of this interface for MSBuild logging, the
That's what we find in the implementation of
GenEngine engine = new GenEngine();
engine.Logger = new MSBuildLogger(this.Log);
GenEngine needs your extensions.
GetExtensions loads assemblies of every
IExtension, and deserializes every extension. The problem is that these extensions were loaded
in the Visual Studio process when compiling.
The implication is that when you try to debug an
IExtension and recompile it, you got an error like this one:
Unable to copy file reference.dll to bin/reference.dll.
The process cannot access the file reference.dll because it is being used by another process
The fix is simple, I just need to load my
ExtensibilityTask in a separate AppDomain, and unload it when the task is executed. That's why
AppDomainIsolatedTask instead of
GenEngine.StartInfo.GenerationDirectory is the directory where new
GenItems are generated.
engine.StartInfo.GenerationDirectory = OutputPath;
As you see in the Target file, this maps to
IntermediateOutputDirectory. That is, by default, the "obj/Debug|Release" folder.
RunHistory is a file which keeps track of generated files after every
GenEngine.Run, it keeps track of generated files and timestamp. Its goal is to see if files
were modified between two runs so that
GenEngine can set the value of
GenItem.Modified before every run.
The other goal is to include the generated files of the last run to the list of
GenEngine.SourceFiles before each run.
I worked hard to make the simplest design possible to implement Genuilder. And I'm happy to see there is not much lines of code.
I hope that Genuilder will make your projects a little more simple, and that its design is simple enough so that you don't need to know MSBuild to understand how Genuilder works.