Creating an NHibernate Item Template
Posted Saturday, February 23, 2008 by Nigel Sampson
Visual Studio has some excellent facilities to create simple item and project templates, usually just create the class or project you want and select Export Template from the File Menu. However sometimes we want to do just a bit more than that. For instance when creating an NHibernate mapped class class we typically create the *.cs file and a related *.hbm.xml file, all the things we'd like to automate can't be handled by the Export Template wizard so we'll have to do a bit more work. These are automatically inserting the assembly attribute into the .hbm.xml, setting the build action to "Embedded Resource" and the custom tool to "NHibernateQueryGenerator".
There are a number of great resources out there around creating Project
and Item templates including "Creating Reusable Project and Item Templates for your Development Team" which can get you started in
making the basics. Once you've created your class template you can add
another file to the template simply by adding the file to the zip and
modifying the.vstemplate like so.
<TemplateContent>
<References />
<ProjectItem SubType="Code" TargetFileName="$fileinputname$.cs" ReplaceParameters="true">Class1.cs</ProjectItem>
<ProjectItem SubType="" TargetFileName="$fileinputname$.hbm.xml" ReplaceParameters="true">Class1.hbm.xml</ProjectItem>
</TemplateContent>
And the contents for our .hbm.xml is
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping assembly="$assemblyName$" namespace="$rootnamespace$" xmlns="urn:nhibernate-mapping-2.0">
<class name="$safeitemname$">
</class>
</hibernate-mapping>
Notice that for the assembly attribute I've used a non-standard symbol, we'll be writing some code to populate the symbol soon.
The .vstemplate has a WizardExtension setting to point to a Wizard
class that will handle any extra logic for us. This is where we'll be
doing the extra work, this class inherits from
Microsoft.VisualStudio.TemplateWizard.IWizard, the article linked above
can also give you the basics on this so I'll just focus on the
important parts. To get our symbol $assemblyName$ to work correctly we
need to add it to the replacementsDictionary parameter of the
RunStarted method. The code below determines the project we're adding
the file to and uses the AssemblyName property to create our dictionary
entry.
public void RunStarted(object automationObject, Dictionary<string, string> replacementsDictionary, WizardRunKind runKind, object[] customParams)
{
if(!(automationObject is DTE2))
{
throw new ArgumentException("automationObject is not of type DTE2");
}
// The generated assembly name for the project is not able to be inserted via a built in replacement
// we need to create our own
application = automationObject as DTE2;
// The project item that the item template is being created in, could be a project or a folder
// We will need to handle each case seperatly
SelectedItem item = application.SelectedItems.Item(1); // non-zero index
Project project = null;
if(item.ProjectItem != null) // We are adding the item template to something like a folder
project = item.ProjectItem.ContainingProject;
else if(item.Project != null) // We are adding the item template to a project
project = item.Project;
if(project == null)
throw new ApplicationException("Could not determine project");
replacementsDictionary.Add("$assemblyName$", project.Properties.Item("AssemblyName").Value.ToString());
}
The second part is in ProjectItemFinishedGenerating, we simply modify
two of the properties on the project item representing the .hbm.xml
file to the desired settings like so.
// Set the build action to EmbeddedResource
projectItem.Properties.Item("BuildAction").Value = 3;
projectItem.Properties.Item("CustomTool").Value = "NHibernateQueryGenerator";
Originally I had written some code move the .hbm.xml file so that it was a child of the .cs file in the project tree (in the same way an .aspx.cs file is a child of the .aspx file). However I encountered some strange behavior when I did, the .hbm.xml files were losing their extensions when embedded as a resource, this meant NHibernate wouldn't automatically detect them, still don't know why this behavior exists. I'll post the complete source and templates soon.
