Table of Contents
Introduction
My last article on Genuilder.Extensibility did not have much success, a reader on my blog told me it was because there is no real use case shown in it. So here it is!
The goal is to create a code generator which allows you to use AppSettings in a safely typed manner. I already have done an article on that and integrated it to Genuilder. The problem is that to understand how I have done it, you have to understand how MSBuild works. So today, I'll show you how to implement this feature yourself with Genuilder.Extensibility.
Installation
I won't explain again how to install Genuilder, since it's fully described on CodePlex and in my last articles.
Create the plug-in
So let's create two projects: the first one is a console application with a config file and some appSettings inside. The second one is our feature project, a Class Library.
Do not forget, the feature project mus end with .Gen.
Let's create the plug-in StronglyTypedAppSettingsPlugin in the feature project.
In this code snippet, I subscribe to the CodeItemCreated
event of the CodeRepository
. This event is fired for every source code file + config file in the project.
Then I create a dependency between my config file and a target file, named StronglyTypedSettings.cs.
public class StronglyTypedAppSettingsPlugin : 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(".config"))
{
var dependency = item.SourceOf("StronglyTypedSettings.cs");
dependency.ShouldUpdateTarget +=
new CodeDependencyHandler(dependency_ShouldUpdateTarget);
}
}
dependency_ShouldUpdateTarget
generates the code in StronglyTypedSettings.cs based on the config file.
void dependency_ShouldUpdateTarget(CodeDependency sender, CodeItem target)
{
var configuration =
ConfigurationManager.OpenMappedExeConfiguration(new ExeConfigurationFileMap()
{
ExeConfigFilename = ((FileCodeItem)sender.Source).File.FullName
}, ConfigurationUserLevel.None);
string generatedClass = "public static class AppConfigSettings{";
foreach(KeyValueConfigurationElement setting in configuration.AppSettings.Settings)
{
string getter = "public static System.String " + setting.Key +
" { get{ return System.Configuration.ConfigurationManager.AppSettings[\"" +
setting.Key + "\"]; } }";
generatedClass += getter;
}
generatedClass += "}";
target.Content = generatedClass;
To show how to send warning and error notification to the build, let's send a warning each time StronglyTypedSettings.cs is updated.
target.Logger.Send("Settings generated !!",
Genuilder.Extensibility.Utilities.LogType.Warning);
}
#endregion
}
Here is the config file in the console application:
="1.0"
<configuration>
<appSettings>
<add key="SomeKey" value="SomeValue"/>
</appSettings>
</configuration>
Build ConsoleApplication1 and enjoy the show.
You can even see the warning message.
As you can see, when you rebuild, the warning does not appear because the source file (App.config) has not changed. If you modify your feature project, you must clean the project prior to build.
This plug-in works only in C#, how can I make it work in VB.NET too?
We will just modify dependency_ShouldUpdateTarget
to use NRefactory extension. But before that, let's reference the NRefactory integration assembly in our project.
Here is the code of dependency_ShouldUpdateTarget
using CompilationUnitExtension
(NRefactory extension)... A bit more complicated, but works in both C# and VB.NET !!
void dependency_ShouldUpdateTarget(CodeDependency sender, CodeItem target)
{
var configuration =
ConfigurationManager.OpenMappedExeConfiguration(
new ExeConfigurationFileMap()
{
ExeConfigFilename = ((FileCodeItem)sender.Source).File.FullName
}, ConfigurationUserLevel.None);
var targetNRefactoryExtension =
target.GetExtension<Genuilder.Extensibility.NRefactory.CompilationUnitExtension>();
targetNRefactoryExtension.CompilationUnit.Children.Clear();
var typeDeclaration = new TypeDeclaration(Modifiers.Public | Modifiers.Static, null)
{
Name = "AppConfigSettings"
};
targetNRefactoryExtension.CompilationUnit.Children.Add(typeDeclaration);
foreach(KeyValueConfigurationElement setting in configuration.AppSettings.Settings)
{
var propertyDeclaration = new PropertyDeclaration(
Modifiers.Public | Modifiers.Static, null, setting.Key, null)
{
TypeReference = new TypeReference(typeof(String).FullName)
};
var indexer = new IndexerExpression(
new MemberReferenceExpression(
new TypeReferenceExpression(
new TypeReference(typeof(ConfigurationManager).FullName)), "AppSettings")
, new List<Expression>()
{
new ICSharpCode.NRefactory.Ast.PrimitiveExpression(setting.Key)
});
propertyDeclaration.GetRegion = new PropertyGetRegion(new BlockStatement(), null);
propertyDeclaration.GetRegion.Block.Children.Add(new ReturnStatement(indexer));
typeDeclaration.Children.Add(propertyDeclaration);
}
targetNRefactoryExtension.Save();
target.Logger.Send("Settings generated !!",
Genuilder.Extensibility.Utilities.LogType.Warning);
}
Conclusion
Well, that's all. I'm proud that my articles are short and fast to do, it shows that they are not complicated!!
I'm very interested to hear about new plug-in ideas from you. I'll be glad to implement them if I'm motivated! ;)