Jelle Druyts .NET Consultant
Just another ignorant weirdo from Antwerp, Belgium trying to make sense out of it all
[This is episode 3 of the Guidance Automation Series]
Last time, we left off with an empty guidance package that we will now be using to build the Enterprise Library guidance package. To scope it down a bit, we'll only be supporting C# in this example - but of course the same principles apply to other languages. As a first step, we'll set up the package so that it will generate two projects: the runtime project that contains the logic for the application block, and the design-time project that contains the user interface logic for the configuration tool.
Templates And Recipes
Before diving in, you need to know that a guidance package is basically a combination of Visual Studio Templates and Guidance Automation Recipes.
The two are linked by adding a recipe reference to the vstemplate file that will make sure the recipe is executed at the time the solution is "unfolded" (which is just a fancy term to say "created" in Visual Studio).
The Solution Template
Armed with this knowledge, we can now create a basic vstemplate file in the Templates\Solutions directory that contains the template for the Application Block solution:
<VSTemplate Version="2.0" Type="ProjectGroup" xmlns="http://schemas.microsoft.com/developer/vstemplate/2005"> <TemplateData> <Name>Application Block</Name> <Description>Guidance Package that creates a new Enterprise Library Application Block.</Description> <ProjectType>CSharp</ProjectType> <SortOrder>91</SortOrder> <Icon>ApplicationBlock.ico</Icon> <CreateNewFolder>false</CreateNewFolder> <DefaultName>ApplicationBlock</DefaultName> <ProvideDefaultName>true</ProvideDefaultName> </TemplateData> <TemplateContent> <ProjectCollection> <ProjectTemplateLink ProjectName="$ApplicationBlockNamespace$.$ApplicationBlockName$">Projects\Runtime\Runtime.vstemplate</ProjectTemplateLink> </ProjectCollection> </TemplateContent> <WizardExtension> <Assembly>Microsoft.Practices.RecipeFramework.VisualStudio, Version=1.0.51206.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</Assembly> <FullClassName>Microsoft.Practices.RecipeFramework.VisualStudio.Templates.UnfoldTemplate</FullClassName> </WizardExtension> <WizardData> <Template xmlns="http://schemas.microsoft.com/pag/gax-template" SchemaVersion="1.0" Recipe="CreateApplicationBlock"> </Template> </WizardData> </VSTemplate>
There are a few interesting things to note here:
TemplateData
TemplateContent
"$ApplicationBlockNamespace$.$ApplicationBlockName$"
Template
WizardData
Guidance Automation Tip #2: All Visual Studio template files (vstemplates and their contents such as project files, code files and images) must have their "Build Action" set to "Content" and "Copy to Output Directory" to "Copy Always". The Guidance Automation Toolkit adds an item in the Templates directory's context menu that will do this for you.
The Project Template
At this point, we have a template for the solution that contains a link to a project template and to a recipe. The Runtime.vstemplate project template looks like this:
<VSTemplate Version="2.0.0" Type="Project" xmlns="http://schemas.microsoft.com/developer/vstemplate/2005"> <TemplateData> <Name>Application Block Runtime Project</Name> <Description>A project template for the runtime part of an Application Block.</Description> <Icon Package="{FAE04EC1-301F-11d3-BF4B-00C04F79EFBC}" ID="4547" /> <ProjectType>CSharp</ProjectType> <SortOrder>20</SortOrder> <CreateNewFolder>true</CreateNewFolder> <DefaultName>Runtime</DefaultName> <ProvideDefaultName>true</ProvideDefaultName> </TemplateData> <TemplateContent> <Project File="Runtime.csproj" TargetFileName="$ApplicationBlockNamespace$.$ApplicationBlockName$.csproj" ReplaceParameters="true"> <ProjectItem ReplaceParameters="true">Properties\AssemblyInfo.cs</ProjectItem> </Project> </TemplateContent> <WizardExtension> <Assembly>Microsoft.Practices.RecipeFramework.VisualStudio, Version=1.0.51206.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</Assembly> <FullClassName>Microsoft.Practices.RecipeFramework.VisualStudio.Templates.UnfoldTemplate</FullClassName> </WizardExtension> <WizardData> <Template xmlns="http://schemas.microsoft.com/pag/gax-template" SchemaVersion="1.0"> </Template> </WizardData> </VSTemplate>
Some highlights from this file:
ReplaceParameters
true
The Project File
This is the content of the Runtime.csproj file:
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <ProductVersion>8.0.50727</ProductVersion> <SchemaVersion>2.0</SchemaVersion> <ProjectGuid>$guid1$</ProjectGuid> <OutputType>Library</OutputType> <AppDesignerFolder>Properties</AppDesignerFolder> <RootNamespace>$safeprojectname$</RootNamespace> <AssemblyName>$safeprojectname$</AssemblyName> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols> <DebugType>full</DebugType> <Optimize>false</Optimize> <OutputPath>bin\Debug\</OutputPath> <DefineConstants>DEBUG;TRACE</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> <TreatWarningsAsErrors>true</TreatWarningsAsErrors> <DocumentationFile>bin\Debug\$safeprojectname$.xml</DocumentationFile> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <DebugType>pdbonly</DebugType> <Optimize>true</Optimize> <OutputPath>bin\Release\</OutputPath> <DefineConstants>TRACE</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> <TreatWarningsAsErrors>true</TreatWarningsAsErrors> <DocumentationFile>bin\Release\$safeprojectname$.xml</DocumentationFile> </PropertyGroup> <ItemGroup> <Reference Include="System" /> <Reference Include="System.Data" /> <Reference Include="System.Xml" /> </ItemGroup> <ItemGroup> <Compile Include="Properties\AssemblyInfo.cs" /> </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSHARP.Targets" /> </Project>
Again, some interesting points to look at here:
$ApplicationBlockName$
$guid1$
$safeprojectname$
TreatWarningsAsErrors
ItemGroup
The Recipe
Now that we have all the templates in place, we still need to define the recipe that will enable the user to automate the creation of the solution and its projects. In its simplest form, you can think of a recipe as a wizard that collects user data - but as we will see later on in this series, it can do a lot more. We've currently defined two custom parameters in the template files: $ApplicationBlockName$ and $ApplicationBlockNamespace$. This means we need to collect this information from the user when the solution is being created. The following recipe definition does just that:
$ApplicationBlockNamespace$
<?xml version="1.0" encoding="utf-8" ?> <GuidancePackage xmlns="http://schemas.microsoft.com/pag/gax-core" Name="JelleDruyts.EnterpriseLibraryGuidance" Caption="Enterprise Library Guidance" Description="Provides guidance around the creation of Enterprise Library Application Blocks" Guid="2cac5b9c-a04f-4a49-8a56-3ee5d63bd83f" SchemaVersion="1.0"> <Recipes> <Recipe Name="CreateApplicationBlock"> <Caption>Create a new Enterprise Library Application Block</Caption> <Arguments> <Argument Name="ApplicationBlockName" Required="true"> <Converter Type="Microsoft.Practices.RecipeFramework.Library.Converters.CodeIdentifierStringConverter, Microsoft.Practices.RecipeFramework.Library" /> </Argument> <Argument Name="ApplicationBlockNamespace" Required="true"> <Converter Type="Microsoft.Practices.RecipeFramework.Library.Converters.NamespaceStringConverter, Microsoft.Practices.RecipeFramework.Library" /> </Argument> </Arguments> <GatheringServiceData> <Wizard xmlns="http://schemas.microsoft.com/pag/gax-wizards" SchemaVersion="1.0"> <Pages> <Page> <Title>Application Block Information</Title> <LinkTitle>Application Block</LinkTitle> <Help> Enter the Application Block name and namespace. </Help> <Fields> <Field ValueName="ApplicationBlockName" Label="Application Block Name" InvalidValueMessage="Must be a valid .NET identifier (e.g. it shouldn't contain spaces or special characters)." /> <Field ValueName="ApplicationBlockNamespace" Label="Application Block Namespace" InvalidValueMessage="Must be a valid .NET namespace identifier (e.g. it shouldn't contain spaces or special characters)." /> </Fields> </Page> </Pages> </Wizard> </GatheringServiceData> </Recipe> </Recipes> </GuidancePackage>
Some remarks on this file:
Arguments
ApplicationBlockName
ApplicationBlockNamespace
GatheringServiceData
Running The Guidance Package
Now that we have all the parts covered, we can finally register and test our package. It will show up as the "Application Block" project type in Visual Studio (with the proper icon and description):
When we create the solution, a wizard will pop up that allows us to enter the information required to generate the projects. Notice that the fields have a light yellow background and that we get an error icon and a tooltip if some argument is invalid:
When we correct the information and press Finish, our first skeleton project for the Enterprise Library Application Block has been created:
Note that the project was correctly named using the combination of the namespace (JelleDruyts.EnterpriseLibrary) and name (ServiceAgents) that was entered in the wizard.
Where Are We
At this point, we've seen how to define Visual Studio Templates that accept parameters and create solution files, project files and project items. We can already impose some guidance and best practices by tweaking the projects (e.g. to treat warnings as errors). We've also seen how to define a recipe that gathers information from the user to pass along to the templates.
You might say, "well, that's an awful lot of work to create a blank project", and you would be absolutely right. But of course, this is just the beginning. And now that we have the basics covered, the fun can really start! Stay tuned for the next episode, in which we'll take this guidance package a step further.
Download the source code for the current state of the Guidance Package.