Part I: Creating Enterprise Templates

 

Static and Dynamic Content
Technique 1: Static Prototypes
Technique 2: Subproject Wizards
Technique 3: Custom Wizards
Recap: The Three Techniques
Enterprise Template Example
Policy File Entries
Review
Implementing Policy for the Template
Enterprise Templates and Subproject Wizards
Just-in-Time Guidance

Creating an Enterprise Template in Visual Studio .NET is a two-step process. First, you must create an "Enterprise Template Project" project in Visual Studio .NET, which allows you to define the application skeleton developers receive when they create a project with your template. Next, you must expose the template to developers so that it can be accessed by the environment's New Project dialog box. The latter step is accomplished by converting the template into a format understood by Visual Studio .NET.

There are three techniques you can use to determine the initial skeleton of a template. These techniques, which are detailed below, offer a traditional tradeoff between complexity and power. To appreciate this balance you must understand the difference between static and dynamic content.

Static and Dynamic Content

Static content remains the same for every project created with a template. Content such as this is "hard-coded" within the template itself, and is simply regurgitated for every project created with the template. A good example is the Form depicted in Figure 1. Every Windows Application project created in Visual Studio .NET is automatically given an empty form called Form1. Since the form's characteristics (its name, size, color, and so on) do not vary from project to project, it is considered static content.

Dynamic content, on the other hand, changes from project to project depending on run-time variables such as user input, the conditions under which the project was created, and so forth. In other words, content of this type is generated by the template on the fly instead of being created beforehand like static content. A good example of dynamic content is the namespace in which the aforementioned Form class is placed. As can be seen from Figure 1, the namespace varies depending on the name given to the project in the New Project dialog box. Therefore, the namespace of the project is considered dynamic content.

Click here for larger image

Figure 1. Static and dynamic content

The difference between static and dynamic content becomes important when we consider the three approaches you can employ to generate the skeleton of a template.

Technique 1: Static Prototypes

The most straightforward approach in creating a skeleton is to use static prototypes. The premise behind static prototypes is simple: when you create an Enterprise Template project in Visual Studio .NET, the files and settings you institute in the project itself become the base skeleton of the template. For example, if you add a Windows Form called MyForm to the project, all the projects created by the template will contain such a form. Similarly, if you add a reference to the System.Security.dll assembly, all projects will reference this assembly upon creation.

Static prototyping is the simplest approach because it does not require coding. Simply configure the template project directly within the IDE, and those settings become the template skeleton. The disadvantage to this approach is that it does not allow one to create dynamic content. Consider the hypothetical scenario depicted in Listing 1. Suppose you added this file, which contains a C# class named MathUtil located within the MyOrg namespace, to your template.

Listing 1. The hypothetical MathClass.cs file

namespace MyOrg
{
   class MathUtil
   {
      public int Add(int a, int b)
      {
         return a+b;
      }
      public int Subtract(int a, int b)
      {
         return a-b;
      }
   }
}

The important point is that every project instance created with this template receives content identical to that shown in Listing 1. That is, unlike the dynamically changing namespace in Figure 1, every project instance gets a MathUtil class contained within the MyOrg namespace. With the static approach, therefore, content cannot change dynamically based on user input or other conditions. Static prototypes are thus appropriate for only the simplest of templates where content is, well, static.

Another notable limitation with static templates is that they will likely not be compatible with the next version of Enterprise Templates (codenamed Whidbey).

Technique 2: Subproject Wizards

Another way to encode the content of a template is to use subproject wizards. This technique allows you to write Microsoft JScript® code that is executed on the fly when a project instance is created in Visual Studio .NET. In other words, using subproject wizards allows you to insert custom programmatic operations into the template creation process. As you will see, much of the flexibility afforded by Enterprise Templates is a result of this capability.

Subproject wizards are often associated with dynamic content. Although it is true that this technique can be used to create such content (such as namespaces, class names, or other dynamic aspects of code), it is more accurate to recognize that it allows you to generate code and perform other programmatic tasks at run time. For example, a user may receive different content depending on his or her network permissions (for example, different code might be generated for "Administrators" vs. "Guests"). Alternatively, you might record the creation specifics of a project to the system's EventLog in order to get an idea of which templates developers are utilizing. To a large extent, the capabilities of subproject wizards are limited only by your creativity with JScript.

In short, subproject wizards offer all the capabilities of static prototypes, plus the capability to add dynamic content. Moreover, such templates will easily port to the next version of Enterprise Templates with little modification.

Tangent: JScript.NET

It is worth nothing that with the .NET Framework, the JScript language has evolved into a new incarnation, termed JScript.NET. This new version of the language offers significant improvements over its predecessor, such as classes, inheritance, typed variables, and compiled code. With respect to subproject wizards, however, you must use the older "typeless" version of JScript.

After first glance, this seems a curious restriction. Because Enterprise Templates belong to the family of .NET technologies, you might think you could use the newer version of JScript to construct them. The problem, which might seem counterintuitive, is that a large portion of Visual Studio.NET itself is written using pre-.NET technologies. Since the IDE is not written in .NET, extensible aspects of the product, such as add-Ins, plug-ins, and Enterprise Templates, are exposed using older Windows technologies, such as C++, COM, and JScript.

This limitation will arise throughout this booklet when we consider tangential topics such as Visual Studio .NET add-ins, source control customization, and even the third technique, described below, of embedding template content by means of Custom Wizards.

Creating subproject wizards

It is crucial to understand that subproject wizards do not replace, but augment, static prototypes. As illustrated in the upcoming example, a template built using subproject wizards simply extends a static prototype template so as to give it some dynamic operations (which usually generate content on the fly). The process for creating mainstream Enterprise Templates, therefore, is threefold:

  1. Create an Enterprise Template project in Visual Studio .NET to prescribe the application skeleton and static content of the template.
  2. Write (optionally) some JScript code to give the template dynamic content.
  3. Expose the template in Visual Studio .NET by means of some intricate file manipulation.

As you will see in the upcoming example, the most difficult part of working with Enterprise Templates is the arduous nature of this last step. Nevertheless, although it can be laborious, exposing custom templates to developers can be a powerful way to enforce or promote project structures in Microsoft's latest version of Visual Studio.

Technique 3: Custom Wizards

When developers hear the term "wizard" they usually envision a series of graphical screens that collect information in order to perform a complex task. The New Project dialog box shown in 0 can be considered a wizard, albeit one with a single screen. The purpose of this wizard is to collect the name, location, and type of a project to be created in Visual Studio .NET. For well-defined and obvious tasks, wizards offer a simple step-by-step way to obtain information from users.

The previous two template techniques rely on the built-in New Project wizard of Visual Studio .NET. That is, both the static prototype and subproject wizard procedures ascertain the specifics of a project from the wizard inherent in the IDE itself (the latter technique is still termed a wizard, however, as it contains JScript code and executes operations on the fly).

Certain situations can call for more complex and custom wizards. Such wizards must be written in native C++ by means of the Visual Studio .NET's Custom Wizard option. Unlike subproject wizards, custom wizards have a GUI associated with them, which allows the user to make choices that determine the generated output. For information on this more-involved technique, consult the MSDN.

Recap: The Three Techniques

The three techniques with which to create project skeletons for templates are listed in Table 1. As depicted there, each technique offers a balance between implementation complexity and power.

Table 1. Techniques for creating a project structure

Technique Approach Comments
Static Prototypes Create a project structure in Visual Studio .NET and convert it to a template using a few manual steps. Most limited because the template is static (i.e., cannot generate namespaces, filenames, etc., based on project parameters).
Subproject wizards Modify/leverage already existing Visual Studio .NET wizards to execute programmatic code when a project is created. Requires writing custom JScript code for the wizard and some manual boilerplate work, but allows dynamic content.
Custom Wizard Create a custom wizard in native C++ that executes when the project is created. Maximum flexibility, as you are writing versatile C++ code; also the most complex approach.

Enterprise Template Example

The easiest way to illustrate Enterprise Templates is to use an example. To that end, this section of the booklet illustrates how to design an Enterprise Template that is modeled after a three-tier architecture. Because the process is rather arduous, this procedure is illustrated in a step-by-step format with relevant commentary. Remember that the challenge with Enterprise Templates lies in the implementation details. Once mastered, the technology is conceptually and practically straightforward.

Step 1: Display hidden files, folders, and file extensions in Windows Explorer

An important part of this example is identifying the various file types (.etp, .vsdir, .csproj) that constitute an Enterprise Template. The default settings in Windows Explorer can make it difficult to identify the proper files if extensions are not visible or if hidden folders and files are not shown.

To display hidden, files, folders, and file extensions in Windows Explorer

  1. Launch Windows Explorer, go to the Windows Explorer Tools menu and click Folder Options, and then click the View tab.
  2. Find Hidden files and folders, and select Show hidden files and folders.
  3. Find Hide file extensions for known file types and clear the check box.
  4. Click the OK button.
  5. Close Windows Explorer.

Step 2: Create a working directory for your projects

By default, Visual Studio .NET places new projects in a deeply nested directory such as:

%Documents and Settings%\SomeUser\My Documents\Visual Studio Projects\

Because the files of an Enterprise Template must be manually copied and manipulated, it is helpful to place your projects in a more convenient directory. To this end, create a new root directory on the computer named MyProjectFiles.

To create a new root directory

  1. Launch the Windows Explorer and navigate to the drive on the system where the Operating System is installed (usually the C:\ drive)
  2. Go to the File menu and click New.
  3. Rename the newly created folder MyProjectFiles (the folder will automatically be highlighted after the previous step).

Template structure

In this exercise you will develop an Enterprise Template that creates an application skeleton by means of static prototypes. When users create a project instance based on this template, they will get a pre-built project that consists of:

  • A Database tier that houses database logic code (a C# ClassLibrary project)
  • A Business tier that houses business logic code (a C# ClassLibrary project)
  • A Presentation tier that houses a Web-based interface (an ASP.NET project in C#)

This architecture will take the form of a .NET Solution with three subsolutions, each of which contains a C# language project. When users create a project using this template, the Solution Explorer in Visual Studio .NET should appear similar to Figure 2.

Aa302169.vsent_enterprisetemplatesbk02(en-us,MSDN.10).gif

Figure 2. The structure of the Enterprise Template

In addition to the basic project structure, the template will also enforce certain policies. For example, developers will not have the capability to add additional projects to the main Solution (MyTemplate in Figure 2) nor to add Web components to the Business or Database tiers. These rules will be enforced by means of the template's policy file (more on this later).

Step 3. Creating the initial template structure

With static prototyping, a template's initial structure is defined by means of the Enterprise Template Project option in Visual Studio .NET. When you create a project of this type in the IDE, the files you add to the project define the skeleton of the template. For example, when you want users to receive a helloworld.cs file when they create an instance of the template, you would add such a file to the Enterprise Template project (and modify its contents to reflect the code each project instance would inherit). Similarly, when you want the template skeleton to reference the System.XML.dll assembly, simply add a reference to it (you might also reference custom DLLs that you or your company have developed).

It is important to note that this pattern holds only for changes to the files of the template project itself. Any changes to the Visual Studio .NET environment—configuring the toolbox, source control settings, and so on—do not cascade to project instances created with the template. For instance, when you add a custom control to the environment's toolbox, you not are changing the Enterprise Template project itself, but rather instituting a global change in Visual Studio .NET that will persist for all project types you create thereafter. (We will look at special operations such as source control later in this booklet).

After the template skeleton has been configured to your liking, it must be exposed to developers as a project option in Visual Studio .NET by means of some intricate file manipulation (steps you will soon become well acquainted with).

To create the template skeleton

  1. If it is not already opened, open Visual Studio .NET 2003.)

  2. On the File menu, point to New, and click Project.

    The New Project dialog box appears.

  3. In the Project Types pane, expand the Other Projects folder and select the Enterprise Template Projects folder.

  4. The Templates pane displays a number of icons.

  5. Select Enterprise Template Project (do not double-click the icon so that you can assign a custom name and location before creating the project).

  6. Replace the default project name with MyTemplate, change the project location to C:\MyProjectFiles, and click the OK button (see Figure 3).

    Aa302169.vsent_enterprisetemplatesbk03(en-us,MSDN.10).gif

    Figure 3. Creating the Enterprise Template project

    As illustrated earlier in Figure 2, this Enterprise Template consists of three sub solutions.

To give the newly created template the appropriate subsolutions

  1. Select MyTemplate in Solution Explorer (not the root node Solution 'MyTemplate').

  2. On the File menu, point to Add Project, then click New Project

  3. The Add New Project dialog box appears.

  4. Select Enterprise Template Projects in the Project Types pane, and then select Enterprise Template Project in the Templates pane.

  5. Change the project name to "DBLayer", and then click the OK button.

  6. The DBLayer solution appears in Solution Explorer.

  7. Repeat this process twice, changing the name to "BusinessLayer" the first time and "UILayer" the second time.

    Note   be sure to select the MyTemplate solution node in the Solution Explorer (not the root node Solution "MyTemplate") before adding each project.

As it stands, the initial application structure consists of four empty solutions. To give the template some content, each subsolution should be given a language project. In this example, we will use C# language projects, but, in practice, you may use any language you wish.

To give each subsolution a language project

  1. In the Solution Explorer, select the DBLayer solution node.

  2. On the File menu, point to Add Project, and then click New Project.

  3. The Add New Project dialog box appears.

  4. Select Visual C# Projects in the Project Types pane.

  5. In the Templates pane, select Class Library; change the name to DBLayerProj, and then click the OK button.

    The DBLayerProj project appears in the Solution Explorer along with its contents (AssemblyInfo.cs, Class1.cs)

  6. In the Solution Explorer, select the BusinessLayer solution node.

  7. On the File menu, point to Add Project, and then click New Project.

  8. The Add New Project dialog box appears.

  9. Select Visual C# Projects in the Project Types pane.

  10. In the Templates pane, select Class Library; change the name to BusniessLayerProj, and then click the OK button.

    The BusinessLayerProj project appears in the Solution Explorer along with its contents (AssemblyInfo.cs, Class1.cs)

  11. In the Solution Explorer select the UILayer Solution

  12. On the File menu, point to Add Project, and then click New Project.

    The Add New Project dialog box appears.

  13. Select Visual C# Projects in the Project Types pane.

  14. In the Templates pane, select ASP.NET Web Application; change the Location to https://localhost/UILayerProj and click the OK button.

    The UILayerProj project appears in Solution Explorer along with its contents (WebForm1.aspx, Web.config, Global.asax, etc)

  15. Right-click UILayerProj in the Solution explorer, and then click Set as Startup Project.

    Remember that with static prototyping the "skeleton" of a project instance created with your template is based on the structure you have defined here (which currently consists of three subsolutions and three C# language projects). At this point, you make further modifications to reflect any additional requirements you may have. For example, you could reference important Base Class Libraries such as System.Security.DLL, add items such as custom classes to the language projects, and so on.

Associating the template with a policy file

You have almost finished creating the initial template skeleton. The last step is to associate the template with a policy file. A policy file allows one to customize, control, and restrict the Visual Studio .NET environment when an instance of the template is created.

To associate the template with a policy file

  1. Select the MyTemplate node in Solution Explorer (not the root node Solution 'MyTemplate'), right-click, and select Properties.

  2. In the Properties window, locate the Policy File property in the left column of the grid. Click the ellipsis button (...) on the right column of the grid, which allows you to select the policy file. Note: if there is no ellipsis button (...), simply click the right column of the grid.

    The Select a TDL File dialog box appears.

  3. Within the dialog box, choose the DAP.tdl file and press Ctrl+C to copy it.

  4. Press Ctrl+V, and a copy of the file named Copy of DAP.tdl appears in the dialog box.

  5. Right-click on the Copy of DAP file; click Rename, and rename the file MyPolicy.tdl.

  6. Select MyPolicy.tdl and click Open to associate it with your template.

  7. When prompted to reopen the project, click Yes.

  8. Select MyTemplate in Solution Explorer; the Policy File property in the Properties window now displays MyPolicy.tdl (and the directory in which it is located).

You have now created your initial application structure.

To verify that it was created successfully

  1. Go to the Build menu and click Build Solution.

  2. Visual Studio .NET will build all three projects in the template; if there are any errors carefully ensure that you have followed the previous steps.

    Note   there is no stipulation that the project be built in Visual Studio .NET—it is simply performed here to ensure that the initial application structure was successfully created.

Step 4. Exposing the template in Visual Studio .NET

As it exists, the project you just created is simply an Enterprise Template project—it has not been exposed to Visual Studio .NET as a bona fide project type. Exposing a template to Visual Studio .NET is a manual process that requires some intricate file manipulation. Before beginning this process, save the entire solution in Visual Studio .NET and then close the environment to prevent file sharing problems.

Visual Studio .NET looks in the following directory to determine which project types are exposed to developers:

%Program Files%\Microsoft Visual Studio .NET 2003\EnterpriseFrameworks

This directory contains three subdirectories—Policy, Projects, and ProxyProjects—which house the files necessary to expose Enterprise Templates to the development environment. A sophisticated understanding of templates stems from an intimate knowledge of the structure and contents of these directories.

Exposing the project you just developed as an Enterprise Template requires converting the project into a form understandable by Visual Studio .NET. To a large extent, the conversion process involves file modification and copying, as outlined in the following steps:

  1. Begin by using the Windows Explorer to copy the entire directory for the solution you created in the previous Task (remember to first close Visual Studio .NET):

    C:\MyProjectFiles\MyTemplate
    

    to the Enterprise Template Projects directory located at:

    %Program Files%\Microsoft Visual Studio .NET 
      2003\EnterpriseFrameworks\Projects
    

    There is an additional complication that must be taken into account. Unlike other project types, Web-based projects reside within an IIS virtual directory. Thus, the UILayerProj ASP.NET project must be copied from its virtual directory to the template directory of Visual Studio .NET. Perform this operation by copying the following IIS directory:

    C:\Inetpub\wwwroot\UILayerProj
    

    to the project template in Visual Studio .NET:

    %Program Files%\Microsoft Visual Studio .NET 
      2003\EnterpriseFrameworks\Projects\MyTemplate\UILayer
    

    After the copy operation you will have a complete second copy of the template in the following directory:

    …\%VSNETRoot%\EnterpriseFrameworks\Projects\MyTemplate
    

    It is this second copy that you will now manipulate to expose the template to Visual Studio .NET.

  2. Begin the conversion process by navigating to this directory and deleting the *.eto, *.sln, *.suo files from the MyTemplate directory and its subdirectories (these files are extraneous and are not used by Visual Studio .NET for template purposes).

    Note   You can omit this step as the template will still work with these files present. You should remove them, however, when deploying Enterprise Templates in practical situations.

    When Visual Studio .NET creates solutions, it assigns each Enterprise Template project file (.etp file) a Globally Unique Identifier (GUID). Remember that an Enterprise Template is not meant to be accessed as a project itself; rather it functions as a "skeletal outline," which developers use to create their own projects. You must therefore remove the GUID that Visual Studio .NET embedded in each Enterprise Template project (.etp) file.

  3. Using NOTEPAD, edit each of the following files:

    ...\%VSNETRoot%\EnterpriseFrameworks\Projects\MyTemplate\MyTemplate.etp
    ...\%VSNETRoot%\EnterpriseFrameworks\Projects\MyTemplate\DBLayer\ 
      DBLayer.etp
    ...\%VSNETRoot%\EnterpriseFrameworks\Projects\MyTemplate\MiddleLayer\Busin
      essLayer.etp
    ...\%VSNETRoot%\EnterpriseFrameworks\Projects\MyTemplate\UILayer\UILayer.e
      tp
    

    and remove the GUID lines within them, which look something like the following:

    <GUIDPROJECTID>{1DEF4E97-FA5A-4053-9C26-D7F537C8273A}</GUIDPROJECTID>
    

    Note that MyTemplate.etp has three instances of such a line whereas the other files have one.

    There is one last file modification to make. Again, this is related to the Web-based location of the ASP.NET project (UILayerProj). Recall that you were prompted for a virtual directory location when you added this project to your template. In the same fashion, when users create a project instance based on your template, they should be prompted for the URL location of the ASP.NET project. To this end, open the following file in NOTEPAD:

    ...\%VSNETRoot%\EnterpriseFrameworks\Projects\MyTemplate\UILayer\UILayer.e
      tp
    

    and modify it to reflect the highlighted changes in Listing 2.

    Listing 2. Changes to UILayer.etp

    <Views>
       <ProjectExplorer>
          <File>UILayerProj/UILayerProj.csproj</File>
       </ProjectExplorer>
    /Views>
    <References>
       <Reference>
    2   <File>UILayerProj/UILayerProj.csproj</File> 
       <REQUIRESURL>1</REQUIRESURL>
       </Reference>
    </References>
    

    When set to a value of 1 the<REQUIRESURL>element instructs Visual Studio .NET to prompt the user for a virtual root where the preceding<File>element (UILayerProj) should be stored. This is appropriate for UILayerProj, since it is a Web-based application that must reside within IIS.

Policy File Entries

In the next section of the booklet, we institute some rules for the template using the policy file. Before we can do this, each solution and project of the template must be made identifiable to the policy file by means of a unique identifier. For solutions (.etp files), this identifier is specified by embedding a <GLOBALENTRY> element into the .etp file. For projects (.csproj files), the identifier is specified by placing a TDLELEMENTTYPE attribute in the .csproj file.

Table 2 shows the insertions you must make in each file using NOTEPAD (note that the text you must insert has been highlighted. To save some typing and reduce the chance of error, you can paste the text from this document directly into NOTEPAD.)

The paths of the files are listed here for reference (as you can see, you must modify the copy of the files you made in Visual Studio .NET's EnterpriseFrameworks directory).

...\%VSNETRoot%\EnterpriseFrameworks\Projects\MyTemplate\MyTemplate.etp
...\%VSNETRoot%\EnterpriseFrameworks\Projects\MyTemplate\DBLayer\ DBLayer.etp
...\%VSNETRoot%\EnterpriseFrameworks\Projects\MyTemplate\MiddleLayer\Business
     Layer.etp
...\%VSNETRoot%\EnterpriseFrameworks\Projects\MyTemplate\UILayer\UILayer.etp
...\%VSNETRoot%\EnterpriseFrameworks\Projects\MyTemplate\DBLayer\DBLayerProj\
     DBLayerProj.csproj
...\%VSNETRoot%\EnterpriseFrameworks\Projects\MyTemplate\MiddleLayer\MiddleLa
     yerProj\BusinessProj.csproj
...\%VSNETRoot%\EnterpriseFrameworks\Projects\MyTemplate\UILayer\UILayerProj\
     UILayerProj.csproj

Table 2. Adding policy identifiers to the template

File Text to Insert
MyTemplate.etp

<GLOBALS>

<GLOBALENTRY>

<NAME>TDLFILE</NAME>

<VALUE>MyPolicy.TDL</VALUE>

</GLOBALENTRY>

<GLOBALENTRY>

<NAME>TDLELEMENTTYPE</NAME>

<VALUE>etpMyTemplate</VALUE>

</GLOBALENTRY>

</GLOBALS>

DBLayer.etp <GLOBALS>

<GLOBALENTRY>

<NAME>TDLFILE</NAME>

<VALUE>MyPolicy.TDL</VALUE>

</GLOBALENTRY>

<GLOBALENTRY>

<NAME>TDLELEMENTTYPE</NAME>

<VALUE>etpDBLayer</VALUE>

</GLOBALENTRY>

</GLOBALS>

BusinessLayer.etp <GLOBALS>

<GLOBALENTRY>

<NAME>TDLFILE</NAME>

<VALUE>MyPolicy.TDL</VALUE>

</GLOBALENTRY>

<GLOBALENTRY>

<NAME>TDLELEMENTTYPE</NAME>

<VALUE>etpBusinessLayer</VALUE>

</GLOBALENTRY>

</GLOBALS>

UILayer.etp <GLOBALS>

<GLOBALENTRY>

<NAME>TDLFILE</NAME>

<VALUE>MyPolicy.TDL</VALUE>

</GLOBALENTRY>

<GLOBALENTRY>

<NAME>TDLELEMENTTYPE</NAME>

<VALUE>etpUILayer</VALUE>

</GLOBALENTRY>

</GLOBALS>

DBLayerProj.csproj <UserProperties TDLFILE =

"MyPolicy.TDL"

TDLELEMENTTYPE = "projDBLayer"

/>

BusinessLayerProj.csproj <UserProperties TDLFILE =

"MyPolicy.TDL"

TDLELEMENTTYPE =

"projBusinessLayer"

/>

UILayerProj.csproj <UserProperties TDLFILE =

"MyPolicy2.tdl"

TDLELEMENTTYPE =

"projUILayer"

/>

As a result of the additions in Table 2, the solutions and projects in the template can be identified in the policy file, which you will manipulate in the next section of the booklet. For example, the business layer project of the template (BusinessLayerProj.csproj) can now be identified in the policy file as projUILayer, which will allow you to control its characteristics (more on this later).

The conversion process is almost complete. The last step is to expose the template so that it appears in the Add New Project and New Project dialog bBoxes in Visual Studio .NET. This is accomplished by means of a .vsdir file. At first glance, .vsdir files may seem complex, but they are quite straightforward.

Upon initialization, Visual Studio .NET looks in the following directory for .vsdir files:

%Program Files%\Microsoft Visual Studio .NET 
  2003\EnterpriseFrameworks\ProxyProjects

All the .vsdir files within this directory are inspected and their contents are used to populate the project options that appear in the Add New Project and New Project dialog boxes. To expose your template to Visual Studio .NET, you must create a custom .vsdir file in this directory. The process is outlined below:

  • Navigate to the ..\%VSNETRoot%\EnterpriseFrameworks\ProxyProjects directory; create a copy of ProxyProjects.vsdir and call it MyUserTemplate.vsdir (you can copy the file by means of Windows Explorer or using the Command Prompt).

    Note   The actual filename (MyUserTemplate.vsdir) is irrelevant because Visual Studio .NET searches for all the .vsdir files within this directory; however, use the suggested name for consistency with later portions of the example.

  • Using NOTEPAD, delete the contents of MyUserTemplate.vsdir and add the following single line to it (to save some typing, you can directly paste the following line from this document directly into NOTEPAD):

    ..\Projects\MyTemplate\MyTemplate.etp|{AE77B8D0-6BDC-11d2-B354-
        0000F81F0C06}|MyTemplate|0|My user-created template|{AE77B8D0-6BDC-
        11d2-B354-0000F81F0C06}|125|0|MyTemplateProject
    

The .vsdir file might seem complex because of its cryptic format. Note that the fields in the .vsdir file are separated by means of the vertical bar character (|). Table 3 explains the purpose of the important fields in this file.

Table 3. Important fields in .vsdir files

Field Name Value in the Previous Line Purpose
RelPathName ..\Projects\MyTemplate\MyTemplate.etp The file used two instances of the template.
LocalizedName MyTemplate The local name of the template and the name that appears in the Add Item dialog box.
SortPriority 0 An integer representing the sort order and relative priority of the Template in the dialog box. For instance, if this value were "1," then the template would appear next to the other 1s and ahead of all 2s.
Description An example user-created template A description of the template that appears in the Add Item dialog box when the item is selected.
SuggestedBaseName MyTemplateProject The default name for the template that is displayed in the Name field in the dialog box. If the name is not unique, the environment appends the name with an integer. For example, MyTemplate might be changed to MyTemplate1.

The remaining fields—the GUIDs and number (125)—are used to determine the icon that represents the template in the dialog box. More information on these fields can be found in the MSDN.

Close any instances of NOTEPAD and Windows Explorer that may be open.

To test if your template was correctly exposed to Visual Studio .NET

  1. Open Visual Studio .NET 2003.

  2. On the File menu, point to New, and then click Project.

  3. The New Project dialog box appears.

  4. In the Project Types pane, expand the Other Projects folder and select the Enterprise Template Projects folder. The Templates pane displays a number of icons, including your template MyTemplate (see Figure 4).

    Note   The second section of this booklet illustrates how to place the template in a more accessible folder.

    Aa302169.vsent_enterprisetemplatesbk04(en-us,MSDN.10).gif

    Figure 4. The customized template in the New Project dialog

    Accept the default project name and location, and click the OK button. If you made any errors during the conversion process, Visual Studio .NET will display them at this point. Note any errors and carefully ensure that you have accurately followed all the steps in this section (typographical errors are a common problem).

    After prompting you for a URL location, Visual Studio .NET will create a three-tier solution for you based on your template: three solutions named DBLayer, BusinessLayer and UILayer with three C# projects named DBLayerProj, BusinessLayerProj, UILayerProj.

Review

It should be evident by this point that creating an Enterprise Template is more arduous than it is difficult. The emphasis here is on accuracy and attention to detail: you must carefully ensure that files are correctly formatted and in their proper locations. A worthwhile review, therefore, is a succinct reiteration of the required steps in creating a static template:

  1. Create the template skeleton using the Enterprise Template Project option in Visual Studio .NET (Figure 3). The files and settings you institute in the project itself become the base skeleton of the template.
  2. Associate the template with a policy file that can later be used to enforce rules and restrictions when developers use the template.

To expose the template to Visual Studio .NET

  1. Copy all the files of the template to the Visual Studio .NET template directory:

    %Program Files%\Microsoft Visual Studio .NET 
      2003\EnterpriseFrameworks\Projects
    
  2. Remove unnecessary files—*.eto, *.sln, *.suo—from the template directory and its subdirectories.

  3. Remove any <GUIDPROJECTID> tags from the Enterprise Template solution (.etp) files.

  4. Modify any Web-based solution (.etp) files so that they prompt for a virtual directory location when the template is created. This is accomplished by the inclusion of a <REQUIREURL> tag within the solution file.

  5. Add TDLELEMENTYPE variables to the solution (.etp) and language project (.csproj, .vbproj, etc) files so that they can be identified by the template's policy file. For solution (.etp) files this is accomplished via a <GLOBALENTRY> tag, whereas language project files require a <UserProperties> tag.

  6. Make the template available to Visual Studio .NET's Add New Project and New Project dialog boxes by creating a .vsdir file that resides in the following directory:

    %Program Files%\Microsoft Visual Studio .NET 
      2003\EnterpriseFrameworks\ProxyProjects
    

Implementing Policy for the Template

Your template up to this point provides developers with a three-tier application structure for project instances created with the template. After a project is created, however, users can do anything they wish. For example, they can add new projects to the various solutions and add new items (ClassLibraries, Web Forms, and so forth) to the individual projects.

The usefulness of Enterprise Templates goes beyond simply providing an initial application skeleton to developers. Specifically, you can enforce certain rules as developers are working with projects created with the template. To highlight this capability, we will build on the existing template and enforce the following restrictions by means of a policy file:

  • Developers should not have the ability to add additional solutions or projects to the four solutions of the template (MyTemplate, DBLayer, BusinessLayer, UILayer).
  • Developers should be allowed to add only class files (.cs files) to the DBLayerProj or MiddleLayerProj projects of the template.
  • Developers should be allowed to add only HTML Pages to the UILayerProj project of the template. One reason for imposing such a restriction is that you might want to prevent users from adding ASP.NET pages (.aspx files) to the project, while still permitting them to embed plain "vanilla" content to the project using regular HTML.

The motivation for establishing these rules is to enforce and maintain a certain structure within the project. For example, if users could add additional tiers to the top-level solution (MyTemplate), then the three-tier architecture would break. Similarly, if users could add UI components to the DBLayerProj and BusinessLayerProj projects, then the logical division of the application into tiers would make little sense.

Template Description Language (TDL) and the Policy File

The concepts behind the policy file tend to mystify developers. You associated a policy file (MyPolicy.TDL) with your template when you created the latter in Visual Studio .NET. Examine this file using NOTEPAD, and you will see that it contains numerous XML elements. Policy files are written in Template Description Language (TDL), which is an XML-specification that describes the environmental characteristics and project restrictions that are enforced within Visual Studio .NET. There are two ways to manipulate a policy file to institute custom rules for a template:

  • Manually edit the policy file directly (doing so requires an understanding of TDL.).
  • Use the Enterprise Templates Policy Editor add-in for Visual Studio .NET 2003, which provides a design surface for creating and editing design time policy for Enterprise Templates.

It is often tempting to use an automation tool instead of an unfamiliar technology. Nevertheless, while the new Policy Editor for Visual Studio .NET is a welcome addition to the .NET environment, understanding TDL itself will prove far more useful as you experiment with templates. First, it will give you an understanding of what the tool is doing behind the scenes. Second, more complex rules can be established manually only by means of direct TDL. And finally, understanding the technology at the lowest level is invaluable for debugging and deployment scenarios. To that end, we will first illustrate how to enforce the aforementioned rules using raw TDL, followed by the more user-friendly Policy Editor.

To implement our rules using the policy file, invoke Visual Studio .NET 2003 and open to the policy file you associated with the template. The policy file can be found in the following location:

%Program Files%\Microsoft Visual Studio .NET 
  2003\EnterpriseFrameworks\Policy\MyPolicy.TDL

At its root, TDL is based on the concept of an ELEMENT. An ELEMENT is an entity upon which you can prescribe rules. Near the top of MyPolicy.TDL is an example element that you can inspect (it has been commented out and thus appears green in Visual Studio .NET). Before the example element is a <DEFAULTSETTINGS> tag which contains a <POLICYMODE> tag that may have a value of PERMISSIVE. If it does have such a value, change this value to RESTRICTIVE so that the element reflects the setting shown in Listing 3. The purpose of this change will be explained later.

Listing 3. Setting the<PolicyMode>to RESTRICTIVE

   <DEFAULTSETTINGS>
      <DEFAULTACTION>INCLUDE</DEFAULTACTION>
      <ORDER>EXCLUDEINCLUDE</ORDER>
      <POLICYMODE>RESTRICTIVE</POLICYMODE>
   </DEFAULTSETTINGS>

You are now in a position to implement the rules previously stated. To implement the last rule—the provision that only HTML Pages are permissible additions to the UILayerProj project—examine the text in Listing 4.

Listing 4. TDL code to implement first rule

<ELEMENT>
   <ID>projUILayer</ID>
   <IDENTIFIERS>
      <IDENTIFIER>
         <TYPE>PROJECT</TYPE>
         <IDENTIFIERDATA>
            <NAME>GLOBAL:TDLELEMENTTYPE</NAME>
            <VALUE>projUILayer</VALUE>
         </IDENTIFIERDATA>
      </IDENTIFIER>
   </IDENTIFIERS>
   <ELEMENTSET>
      <DEFAULTACTION>EXCLUDE</DEFAULTACTION>
      <ORDER>INCLUDEEXCLUDE</ORDER>
      <INCLUDE>projItemHTMLPage</INCLUDE>
   </ELEMENTSET>
</ELEMENT>

The effect of the code you just added to the policy file is straightforward with a little explanation.

Note that the element in Listing 4 has been given the name of projUILayer by means of the <ID> tag. Also note that to determine if an entity is a projUILayer element, Visual Studio .NET consults the contents of the <IDENTIFIERDATA> tag. As shown in Listing 4, this tag contains two sub-tags: <NAME> and <VALUE> with values of GLOBAL:TDLELEMENTTYPE and projUILayer, respectively.

In other words, if an item is added to the Visual Studio .NET environment so that it has a global identifier named TDLELEMENTTYPE with a value of projUILayer , Visual Studio .NET will consider it to be a projUILayer element. Refer back to Table 2 and you will see that we added these exact parameters to the UILayerProj.csproj file. As a result, UILayerProj projects will be considered projUILayer elements by the Visual Studio .NET environment. Why is this identification important? The answer lies in the next tag: <ELEMENTSET>.

The <ELEMENTSET> tag prescribes which items can be added to the element within the Visual Studio .NET environment. The first subtag within this block, <DEFAULTACTION>, determines the way in which the restrictions for an element are specified. When this tag is set to INCLUDE, you enforce restrictions by explicitly disallowing elements using <EXCLUDE> blocks:

Listing 5. The<ELEMENTSET>block

<ELEMENTSET>
   <DEFAULTACTION>INCLUDE</DEFAULTACTION>
   <ORDER>INCLUDEEXCLUDE</ORDER>
   <EXCLUDE>projItemWebForm</EXCLUDE> 
</ELEMENTSET>

Listing 5 stipulates that any item except Web Forms may be added to this element. In other words, when <DEFAULTACTON> is set to INCLUDE, all items are implicitly allowed and you must disqualify them individually. The reverse behavior can be accomplished by setting <DEFAULTACTION> to EXCLUDE, wherein all items are implicitly disallowed and must be turned on individually by means of <INCLUDE> statements. This more exclusive technique is illustrated in Listing 6:

Listing 6. The<EXCLUDE>technique

<<ELEMENTSET>
  <DEFAULTACTION>EXCLUDE</DEFAULTACTION>
  <ORDER>INCLUDEEXCLUDE</ORDER>
  <INCLUDE>projItemHTMLPage</INCLUDE>        
</ELEMENTSET>

Because <DEFAULTACTION> is set to EXCLUDE, the <INCLUDE> statement that follows stipulates that only HTML Pages may be added to this element. This is of course the third rule we outlined for the template, and thus the TDL code in Listing 6 is actually a snapshot of the more complete code in Listing 4. (The <ORDER> tag is used for those rare scenarios in which an ELEMENTSET block contains both <INCLUDE> and <EXCLUDE> statements; consult the MSDN for information).

A natural question in Listing 6 concerns the projItemHTMLPage text embedded within the <INCLUDE> block; how did we find such text and, furthermore, how do we know that it corresponds to an HTML Page item?

When you browse the policy file, you will find many existing elements for popular .NET items: codeClass, codeUserControl, codeCustomControl, codeInstallerClass, projItemWebForm, projItemHTMLPage, etc. The names of these elements intuitively map to their real .NET counterparts. For example, codeWinForm represents a Windows Form, whereas codeWebService denotes a Web Service component.

To review, when <DEFAULTACTION> is set to EXCLUDE, you start from an empty slate and add items individually with <INCLUDE> tags. When <DEFAULTACTION> is set to INCLUDE, you start from a full slate (that is, all items are allowed) and subtract items individually using <EXCLUDE> tags. In general, the first approach is preferable when an element is inclusive (allows many items; disallows a few); whereas, the second approach is superior when an element is exclusive (allows a few items; disallows the rest).

In this spirit, the TDL code in Listing 7 illustrates how to employ <EXCLUDE> tags to implement the three aforementioned rules of our template. In total, you must add seven ELEMENT definitions to the policy file (four solutions and three language projects).

Listing 7. TDL code the implement the remaining rules

<ELEMENT>
   <ID>etpMyTemplate</ID>
   <IDENTIFIERS>
      <IDENTIFIER>
         <TYPE>PROJECT</TYPE>
         <IDENTIFIERDATA>
            <NAME>GLOBAL:TDLELEMENTTYPE</NAME>
            <VALUE>etpMyTemplate</VALUE>
         </IDENTIFIERDATA>
      </IDENTIFIER>
   </IDENTIFIERS>
   <ELEMENTSET>
      <DEFAULTACTION>EXCLUDE</DEFAULTACTION>
      <ORDER>INCLUDEEXCLUDE</ORDER>
   </ELEMENTSET>
</ELEMENT>
<ELEMENT>
   <ID>etpDBLayer</ID>
   <IDENTIFIERS>
      <IDENTIFIER>
         <TYPE>PROJECT</TYPE>
         <IDENTIFIERDATA>
            <NAME>GLOBAL:TDLELEMENTTYPE</NAME>
            <VALUE>etpDBLayer</VALUE>
         </IDENTIFIERDATA>
         </IDENTIFIER>
      </IDENTIFIERS>
    <ELEMENTSET>
       <DEFAULTACTION>EXCLUDE</DEFAULTACTION>
       <ORDER>INCLUDEEXCLUDE</ORDER>
    </ELEMENTSET>
</ELEMENT>
<ELEMENT>
   <ID>projDBLayer</ID>
   <IDENTIFIERS>
      <IDENTIFIER>
         <TYPE>PROJECT</TYPE>
         <IDENTIFIERDATA>
            <NAME>GLOBAL:TDLELEMENTTYPE</NAME>
            <VALUE>projDBLayer</VALUE>
          </IDENTIFIERDATA>
       </IDENTIFIER>
   </IDENTIFIERS>
   <ELEMENTSET>
     <DEFAULTACTION>EXCLUDE</DEFAULTACTION>
     <ORDER>INCLUDEEXCLUDE</ORDER>
     <INCLUDE>codeClass</INCLUDE>
   </ELEMENTSET>
</ELEMENT>
<ELEMENT>
   <ID>projBusinessLayer</ID>
   <IDENTIFIERS>
      <IDENTIFIER>
         <TYPE>PROJECT</TYPE>
         <IDENTIFIERDATA>
            <NAME>GLOBAL:TDLELEMENTTYPE</NAME>
            <VALUE>projBusinessLayer</VALUE>
        </IDENTIFIERDATA>
      </IDENTIFIER>
   </IDENTIFIERS>
   <ELEMENTSET>
      <DEFAULTACTION>EXCLUDE</DEFAULTACTION>
      <ORDER>INCLUDEEXCLUDE</ORDER>
      <INCLUDE>codeClass</INCLUDE> 
   </ELEMENTSET>
</ELEMENT>
<ELEMENT>
  <ID>projUILayer</ID>
  <IDENTIFIERS>
     <IDENTIFIER>
        <TYPE>PROJECT</TYPE>
        <IDENTIFIERDATA>
           <NAME>GLOBAL:TDLELEMENTTYPE</NAME>
           <VALUE>projUILayer</VALUE>
        </IDENTIFIERDATA>
     </IDENTIFIER>
  </IDENTIFIERS>
  <ELEMENTSET>
    <DEFAULTACTION>EXCLUDE</DEFAULTACTION>
    <ORDER>INCLUDEEXCLUDE</ORDER>
    <INCLUDE>projItemHTMLPage</INCLUDE>
  </ELEMENTSET>
</ELEMENT>

The code in Listing 7 should be straightforward, given your knowledge of TDL. Because all the <DEFAULTACTION> tags have been set to EXCLUDE, allowable items for the element must be added by means of explicate <INCLUDE> tags. Note that the solution elements (etpMyTemplate, etpDBLayer, etpBusinessLayer, etpUILayer) do not contain any <INCLUDE> tags. This exclusion stipulates that NO items may be added to these solutions, which was one of stipulated rules. Furthermore, whereas the projBusinessLayer and projDBLayer elements contain <INCLUDE> tags for codeClass items, the projUILayer element includes such a tag for projItemHTMLPage.

To save your additions to the MyPolicy.tdl file and test the policy file by creating a new project based on the template:

  1. On the File menu, point to New, and then click Project.

    The New Project dialog box appears.

  2. In the Project Types pane, expand the Other Projects folder and select the Enterprise Template Projects folder. The Templates pane displays a number of icons, including your template MyTemplate (see Figure 4).

  3. Accept the default project name and location, and click the OK button. If you made any errors while manipulating the policy file, Visual Studio .NET will display them at this point.

    Visual Studio .NET will create an entire three-tier solution for you based on the template's architecture.

  4. Click the UILayerProj node in the Solution Explorer.

  5. Go to the Project Menu and click Add New Item; because of the policy applied to UILayerProj, only Windows Forms are permissible additions to the project (see Figure 5).

    Aa302169.vsent_enterprisetemplatesbk05(en-us,MSDN.10).gif

    Figure 5. Only HTML pages available

    Note   When Visual Studio .NET creates the project, it generates a number of warnings similar to those that follow. These warnings appear because the initial project skeleton breaks the rules you've enforced in the policy file. For example, one of the stipulated rules was that only HTML Pages could be added to the UILayer project layer. Look at Figure 2, however, and you will see that the UILayer project contains an ASP.NET page named WebForm1.aspx in the project skeleton. Because this inclusion contradicts the TDL code in Listing 7, Visual Studio .NET produces a warning when the project is created. Because the rules are designed be enforced after the initial project skeleton has been created, these warnings can be safely ignored.

    Listing 8. Visual Studio .NET project warnings

    Policy Reminder: Project 'MiddleLayerProj.csproj' (Element 
      projCSharpProject, projMiddleLayer) does not allow File 
      'AssemblyInfo.cs' (Element projItemCSharpFile)
    Policy Reminder: Project 'MiddleLayer.etp' (Element etpMiddleLayer) does 
      not allow Project 'MiddleLayerProj.csproj' (Element projCSharpProject, 
      projMiddleLayer)
    Policy Reminder: Project 'MyTemplateProject1.etp' (Element etpMyTemplate) 
      does not allow Project 'MiddleLayer.etp' (Element etpMiddleLayer)
    Policy Reminder: Project 'UILayerProj.csproj' (Element projCSharpProject, 
      projUILayer) does not allow File 'App.ico' (Element projItemIconFile)
    Policy Reminder: Project 'DBLayerWizard.csproj' (Element 
      projCSharpProject, projDBLayer) does not allow File 'AssemblyInfo.cs' 
      (Element projItemCSharpFile) 
    Policy Reminder: Project 'DBLayer.etp' (Element etpDBLayer) does not allow 
      Project 'DBLayerWizard.csproj' (Element projCSharpProject, projDBLayer)
    

The Enterprise Templates Policy Editor

A common problem with the manual technique of implementing template policies is the possibility of encountering obscure and confusing errors like the one shown in Figure 6.

Aa302169.vsent_enterprisetemplatesbk06(en-us,MSDN.10).gif

Figure 6. An error caused by a bad TDL file

This error, which could arise during the creation of a project instance, is the result of badly formed TDL in the policy file. Although TDL is (hopefully) straightforward, it is, at the same time, based on XML, which is a verbose and sensitive data format. The slightest textual oversight—a missing character or a misspelled word—can result in bizarre and unexpected behavior. Because of this shortcoming, Microsoft has released a more intuitive way to manage policy files by means of the Enterprise Templates Policy Editor, which you can download at msdn.microsoft.com/vstudio/productinfo/enterprise/default.aspx.

This product installs itself as an add-in for Visual Studio.NET 2003 and is thus natively accessible from the development environment. As the following section demonstrates, while the ET Policy Editor provides a nice layer of abstraction on top of TDL, you must still resort to manual code for certain policy implementations.

Implementing Policy with the Editor

In this section, we use the ET Policy Editor to implement the rules of the previous section. To start from a clean slate, make a new copy of MyPolicy.TDL by navigating to the following directory with the Windows Explorer:

%Program Files%\Microsoft Visual Studio .NET 2003\EnterpriseFrameworks\Policy
  1. Delete the existing copy of MyPolicy.TDL you created in the previous section (or rename it so you can examine it later for reference purposes).

  2. Click the DAP.tdl file and press Ctrl+C to copy it.

  3. Press Ctrl+V; a copy of the file named Copy of DAP.tdl appears in the dialog box.

  4. Right-click on the Copy of DAP file, click Rename, and rename the file MyPolicy.tdl.

    Once installed, the ET Editor can be accessed directly from Visual Studio .NET by means of the File -> Open menu option as illustrated in Figure 7.

    Aa302169.vsent_enterprisetemplatesbk07(en-us,MSDN.10).gif

    Figure 7. The Policy Editor in Visual Studio .NET 2003

A policy can be opened in schematic format only by using the Open -> Policy File option. When a policy file is opened using the regular Open -> File option, the IDE displays the file as raw TDL using its XML editor (which would be appropriate for the previous manual section). Open MyPolicy.tdl using the Open -> Policy File option, and the IDE will manifest itself into a graphical format, similar to that shown in Figure 8.

Aa302169.vsent_enterprisetemplatesbk08(en-us,MSDN.10).gif

Figure 8. The ET Policy Editor

Expand the Project node as shown in Figure 8, and you will see numerous predefined ELEMENTS of the policy file (for example, etpWebUi, etWinUI, projDataAccess). It is within this node that you must add the custom ELEMENTS (etpMyTemplate, etpDBLayer, etpBusinessLayer, etc) we manually added in the previous section (Listing 7). Unlike the manual technique of working with raw TDL, adding elements with the editor is comparatively simpler:

To add elements with the editor

  1. Right-click the PROJECT node and select Add New Element.

    An element named _newElement is added to the PROJECT node.

  2. Highlight the _newElement node, and then go to View -> Properties Windows.

    The Property Inspector, depicted in Figure 9, is displayed by Visual Studio .NET.

    Aa302169.vsent_enterprisetemplatesbk09(en-us,MSDN.10).gif

    Figure 9. The Property Inspector

  3. Using the Property Inspector change the ID field to "etpMyTemplate".

Next, you must configure the element so that it can be properly identified by Visual Studio .NET (recall that this is accomplished by means of an <IDENTIFIER> tag, as shown in Listing 4). The following steps described how to accomplish this step with the ET Editor (the process is also shown graphically in Figure 10).

To add identifier data using the ET Editor

  1. Locate the Identifiers property in the Property Inspector, and click the ellipsis button (...) on the right column of the grid.

    The Identifier Collection Editor dialog box appears.

  2. Locate the IdentifierData property in the Property Inspector, and click the ellipsis button (...) on the right column of the grid.

    A second Identifier Collection Editor dialog box appears.

  3. Give the Name property a value of "GLOBAL:TDLELEMENTTYPE" and set the Value property to "etpMyTemplate."

  4. Close all open dialog boxes.

    Click here for larger image

    Figure 10. Adding <Identifier> data using the editor

Now that the element can be properly identified by Visual Studio .NET, we are in a position to prescribe which items can and cannot be added to it. Click etpMyTemplate in the PROJECT node of the editor, and a list of project items will appear in the rightmost grid. As shown in Figure 8, all the project items have a value of "True" next to them, indicating that they are all permissible additions to the etpMyTemplate element.

Remember that our rule states that NO items are valid additions to the topmost solution of the template (etpMyTemplate). One way to enforce this rule is to change every value in the rightmost grid to "False." As there are hundreds of items to change, this would be a very laborious process. (Nevertheless, experiment with the editor by changing some "True" values to "False"). Unfortunately, there is no option in the editor to institute a "global change"; and so, if you wish to use the editor directly, this arduous technique is required. There is a simple workaround, however, which you can employ, given your knowledge of TDL.

Save your changes by going to File -> Save MyPolicy.TDL, and then close the file by going to File -> Close. Next, open the Policy File in raw TDL mode by going to File -> Open -> File and selecting MyPolicy.tdl. Do a search for "etpMyTemplate," and you will find text similar to that in Listing 9.

Listing 9. The<ELEMENT>tag for etpMyTemplate

<ELEMENT>
   <ID>etpMyTemplate</ID>
   <IDENTIFIERS>
      <IDENTIFIER>
         <TYPE>PROJECT</TYPE>
         <IDENTIFIERDATA>
            <NAME>GLOBAL:TDLELEMENTTYPE </NAME>
            <VALUE>etpMyTemplate</VALUE>
         </IDENTIFIERDATA>
      </IDENTIFIER>
   </IDENTIFIERS>
   <ELEMENTSET>
      <DEFAULTACTION>INCLUDE</DEFAULTACTION>
      <ORDER>INCLUDEEXCLUDE</ORDER>
      <EXCLUDE>etpBusinessFacade</EXCLUDE>
      <EXCLUDE>etpWebService</EXCLUDE>
      <EXCLUDE>projBusinessRules</EXCLUDE>
     <EXCLUDE>etpDataAccess</EXCLUDE>
     </ELEMENTSET>
 </ELEMENT>

You can see how your operations in the editor convert into TDL code behind-the-scenes: the identification data you entered translates into <IDENTIFIERDATA>, <NAME>, and <VALUE> tags, and your "True" to "False" changes manifest themselves as individual <EXCLUDE> tags (these tags will differ depending on the items you changed in the editor).

Why have we reverted back to raw TDL if we have the editor at our disposal? The main problem is with respect to the <DEFAULTACTION> tag. Recall that when this tag is set to INCLUDE, every item is a permissible addition to the element unless an <EXCLUDE> tag negates it. The problem is that, as of this writing, there is no way to change <DEFAULTACTION> to EXCLUDE using the editor. Once again, this means that to disallow every item type being added to this element, you must manually switch each item value in the editor to "False".

Thankfully, by reverting to raw TDL, you can employ a simple workaround: you need only modify the element's <ELEMENTSET> tag to reflect the code in Listing 10.

Listing 10. Changing the<DEFAULTACTION>to EXCLUDE

<ELEMENTSET>
   <DEFAULTACTION>EXCLUDE</DEFAULTACTION>
   <ORDER>INCLUDEEXCLUDE</ORDER>
</ELEMENTSET>

Notice that we have manually changed <DEFAULTACTION> to EXCLUDE and also removed any <EXCLUDE> nodes. Save your changes and close the file, and then reopen it in schematic format using the Open -> Policy File option. Navigate to the etpMyTemplate node and you will see that the project items in the rightmost node now all have values of "False." This makes sense, given our changes—no items can be added to the ELEMENT because of the EXCLUDE setting on <DEFAULTACTION>. If you change one of the values to "True" and then examine the file's raw TDL, you will find text similar to the text in Listing 11.

Listing 11. Raw TDL

<ELEMENTSET>
   <DEFAULTACTION>EXCLUDE</DEFAULTACTION>
   <ORDER>INCLUDEEXCLUDE</ORDER>
   <INCLUDE>someProjectItem></INCLUDE>
</ELEMENTSET>

This code is similar to the manual policy we enforced in the previous section—we are now stipulating which items may be added to the element (as opposed to those that may not be added).

This example (hopefully) illustrates how intuitive the ET Policy Editor is with respect to enforcing policy. As was also illustrated, however, the editor must often be used in tandem with raw TDL for certain operations.

Toolbox, Menu and Property Restrictions

In addition to prescribing which items may or may not be added to the given elements of a template, policy files are useful in restricting the toolbox and menu items of Visual Studio .NET. You could, for example, disable the Edit menu for a project instance or prevent certain controls from being used within it. To illustrate the former technique, load MyPolicy.tdl into the ET Editor using the Open -> Policy File menu option.

As usual, a schematic representation of the file appears in Visual Studio .NET. Click the Features tab at the bottom of the editor (see Figure 8 for clarification), and a new screen will appear with three nodes: Menu, Toolbox and Property. These nodes allow you to disable menu items, controls and control properties, respectively. By way of example, let us disable the Edit -> Cut menu option for all the C# files within the DBLayer project.

To disable the Edit -> Cut menu option

  1. To begin, right-click the Menu node and select Add Menu (do not be confused by the name; what you are really doing is adding a Menu restriction).

    The Add Menu Constraint dialog box appears (the top screen in Figure 11).

  2. Double-click the menuEdit.Cut option and click the OK button.

    A new node called menuEdit.Cut appears under the Menu node.

    Aa302169.vsent_enterprisetemplatesbk11(en-us,MSDN.10).gif

    Figure 11. Adding a menu constraint

  3. Click the Elements node in the rightmost pane of the editor and select Add Element.

    The Add Elements for Feature Constraint dialog box appears (the bottom screen in Figure 11).

  4. As shown in the bottom screen in Figure 11, find the projDBLayer element, double-click it, and then click the OK button.

    A node named projDBLayer(0) is added to the Elements node in the rightmost pane.

  5. Right-click the projDBLayer(0) node and select Add Child Element.

    The Add Child Elements for Feature Constraint dialog box appears.

  6. Find the projCSharpFile element in the list, double-click it, and then click the OK button.

    As illustrated in Figure 12, a subnode named projCSharpFile is added to the now renamed node projDBLayer(1).

    Aa302169.vsent_enterprisetemplatesbk12(en-us,MSDN.10).gif

    Figure 12. Adding a menu constraint

It is important to understand the effect of the steps we just performed. In the left pane of the editor, we added a menu constraint to the policy file (specifically, a constraint for the Edit->Cut menu option). By itself, a constraint is useless; it must be associated with those elements on which the constraint will be applied. By adding the two nodes in the right pane of the editor we stipulate the following:

The Edit->Cut menu option is to be disabled for any C# file within a projDBLayer element.

Think of the node layout in Figure 12 as an umbrella of qualifications. The first node, projDBLayer(1), stipulates that the constraint applies only to files within projDBLayer projects. The second node stipulates that within such projects, only CSharp (.cs) files should inherit the constraint.

Save your changes by going to File -> Save MyPolicy.TDL, and then close the file by going to File -> Close.

To test the new restrictions by creating a project based on the template

  1. On the File menu, point to New, and then click Project.

    The New Project dialog box appears.

  2. In the Project Types pane, expand the Other Projects folder and select the Enterprise Template Projects folder. The Templates pane displays a number of icons, including your template MyTemplate (see Figure 4).

  3. Accept the default project name and location, and click the OK button. If you made any errors while manipulating the policy file, Visual Studio .NET will display them at this point.

    Visual Studio .NET will create an entire three-tier solution for you based on the template's architecture.

  4. Expand the DBLayerProj project which is located within the DBLayer solution.

  5. Click one of the C# files within this project (e.g., AssemblyInfo.cs, Class1.cs).

  6. Navigate to the Edit -> Cut menu option, which should now be disabled.

You can see the effect of your modifications to the policy file: the Cut command is not available for C# Files within DBLayer projects. Note, however, that it is accessible to the C# files in the other tiers (that is, BusinessLayer and UILayer projects).

It is worthwhile to examine the TDL code that the editor generated behind the scenes as a result of the added menu constraint. Load MyPolicy.TDL into NOTEPAD using Visual Studio .NET'S Open -> File menu option, search for "etpMyTemplate," and you will find TDL code similar to that in Listing 12.

Listing 12. Resulting TDL code from the added constraints

 <ELEMENT>
   <ID>projDBLayer</ID>
   <IDENTIFIERS>
      <IDENTIFIER>
         <TYPE>PROJECT</TYPE>
         <IDENTIFIERDATA>
            <NAME>GLOBAL:TDLELEMENTTYPE</NAME>
            <VALUE>projDBLayer</VALUE>
         </IDENTIFIERDATA>
      </IDENTIFIER>
   </IDENTIFIERS>
   <CONSTRAINTS>
      <MENUCONSTRAINTS>
         <MENUCONSTRAINT>
            <ID>menuEdit.Cut</ID>
            <ENABLED>0</ENABLED>
         </MENUCONSTRAINT>
      </MENUCONSTRAINTS>
    </CONSTRAINTS>
    <ELEMENTSET>
       <DEFAULTACTION>EXCLUDE</DEFAULTACTION>
       <ORDER>INCLUDEEXCLUDE</ORDER>
       <INCLUDE>codeClass</INCLUDE>
       <MEMBERCONSTRAINTS>
          <MEMBERCONSTRAINT>
             <ID>projItemCSharpFile</ID>
             <MENUCONSTRAINTS>
                <MENUCONSTRAINT>
                   <ID>menuEdit.Cut</ID>
                   <ENABLED>0</ENABLED>
                </MENUCONSTRAINT>
            </MENUCONSTRAINTS>
         </MEMBERCONSTRAINT>
       </MEMBERCONSTRAINTS>
    </ELEMENTSET>
 </ELEMENT>

As evidenced by Listing 12, menu constraints enabled in the ET Editor manifest into <MENUCONSTRAINT> tags in the underlying TDL. Experiment with other constraints, such as toolbox or property constraints, examine the raw file underneath, and you'll get a better feel for the function and structure of TDL code.

Nuances with Constraints

A subtle yet essential point with respect to constraints is that they only take effect when applied to individual project items. This obscure behavior is illustrated in Figure 13.

Aa302169.vsent_enterprisetemplatesbk13(en-us,MSDN.10).gif

Figure 13. Nuances with constraints

In the example on the top, we have applied the menuEdit.Cut constraint to projDBLayer elements. You might reason, therefore, that all files within a projDBLayer project will have the Cut menu option disabled. This is not, however, the case. Instead, as depicted in Figure 12, you must add individual item nodes (projCSharpFile, projHTMLFile, projVBCodeFile, etc) to the project node in order for the constraint to take effect.

The bottom screen in Figure 13 illustrates another situation. Here, the projItemCSharpFile item has been added to the menuEdit.Cut constraint, but without a parent node (that is, it is the root node in the right pane). Because this item has not been qualified with a parent project node, the constraint is applied to all C# files within a template using this policy file. For example, if we were to apply this policy change to our template, all C# files, irrespective of their parent projects (DBLayer, BusinessLayer, ProjectLayer) would have the Cut menu option disabled. Although this might be permissible if you want such global behavior, it is generally better to give an item element a parent project node.

Finally, keep in mind that you can apply a constraint to numerous projects, as illustrated in Figure 14.

Aa302169.vsent_enterprisetemplatesbk14(en-us,MSDN.10).gif

Figure 14. Applying constraints to multiple projects

The settings in Figure 14 institute a policy whereby the Cut menu option is disabled for:

  • Bitmap files within DBLayer projects
  • Text files within BusinessLayer projects
  • Web Forms and JPG files within UILayer projects

Review

The introduction of a policy into a template is a powerful way to enforce project structures and promote design patterns in Visual Studio .NET. The rules prescribed within a policy file allow a developer to:

  • Restrict which items may be added to specific parts of the template
  • Disable menu, toolbox, and control properties for certain parts of the template

Enterprise Templates and Subproject Wizards

Thus far, this booklet has illustrated two powerful aspects of Enterprise Templates: the ability to give developers a predefined application structure and the ability to enforce certain rules within the Visual Studio .NET environment in order to promote design patterns.

One limitation of the template created in the previous section is that it is static. If a developer creates ten project instances based on the template, the content for all ten projects will be identical (with the exception of the utmost solution names, which would be MyTemplate1, MyTemplate2, and so forth). Every project will have three solutions, named DBLayer, MiddleLayer and UILayer; and three C# projects named DBLayerProj, MiddleLayerProj and UILayerProj.

A more versatile option when creating Enterprise Templates is to use wizards instead of static project prototypes. To understand the difference between these two techniques, use NOTEPAD to open the following Enterprise Template file you created in the previous section:

...%VSNetRoot%\EnterpriseFrameworks\Projects\MyTemplate\DBLayer\DBLayer.etp

Within this file you will find the following lines:

Listing 13. Inside the .etp file

      <References>
            <Reference>
                <FILE>DBLayerProj\DBLayerProj.csproj</FILE>
            </Reference>
       </References>

These lines instruct Visual Studio .NET to create a project under the DBLayer solution according to the specifications in the DBLayerProj.csproj file. The latter file, which you also created in the first previous section, is a static C# sharp project that contains two files named AssemblyInfo.cs and Class1.cs. This is why every template instance gets the same files—because the three solutions within the template (DBLayer, MiddleLayer and UILayer) all point to static C# projects (DBLayerProj, MiddleLayerProj, UILayerProj).

To overcome this limitation, you can point a solution to a wizard, which is a packet of executable code that can generate files on the fly and perform other custom actions. (Although developers generally associate the term "wizard" with a series of graphical screens that collect information, in this section we will be working with a "silent" wizard, which executes operations without a GUI or input from the user).

In this section of the booklet we will give our template some dynamic content. Specifically, we will design a wizard that dynamically generates a private key and associates it with the ClassLibrary project in the Database-tier. For information on the role of private keys with respect to assemblies (for instance, shared assemblies), consult CodeNotes for VB.NET, Chapter 5: Assemblies.

Our wizard will be responsible for two dynamic operations:

  • Generating a private key by means of the SN.EXE utility:

    SN.EXE –k DBLayer.snk
    
  • Associating the private key with the AssemblyInfo.cs file in the Database-tier:

    [assembly: AssemblyKeyFile("..\\..\\..\\DBLayer.snk")]
    

Step 1: Creating the Wizard Files

As you will see, because you can leverage and modify the preexisting wizards packaged with Visual Studio .NET, adding dynamic content to a template is mostly a matter of file manipulation. To begin the process, close any open instances of NOTEPAD and Windows Explorer; also close Visual Studio .NET to prevent file sharing problems.

The preexisting wizards that Visual Studio .NET uses to build common .NET projects (Windows Forms applications, ClassLibraries, and so on) can be found in the following directory:

%Program Files%\Microsoft Visual Studio .NET 
  2003\EnterpriseFrameworks\EFWizards

Building a custom wizard for a template is most easily done by modifying an already existing wizard. To that end, navigate to the aforementioned directory using Windows Explorer and use the following procedure:

To build a custom wizard for a template

  1. Select the csharpdllwiz folder and press Ctrl+C to copy it.
  2. Press Ctrl-V; a copy of the folder named Copy of csharpdllwiz will appear in Windows Explorer.
  3. Right-click on the Copy of csharpdllwiz folder, click Rename, and rename the folder MyWizard.

You now have a copy of a preexisting wizard that you can customize for your template. Figure 15 depicts the folder structure of the wizard:

Aa302169.vsent_enterprisetemplatesbk15(en-us,MSDN.10).gif

Figure 15. Wizard folder structure

The Scripts\1033 folder houses a file named default1.js that contains the custom JScript for the wizard (1033 is the language code for English; it will differ for other language versions of Visual Studio .NET). You will modify this script momentarily to implement the desired functionality.

The Templates\1033 folder contains the items that are added to the project by the wizard. Using NOTEPAD, examine the file1.cs file within this folder, and you will find such statements as [!output SAFE_NAMESPACE_NAME] and [!output SAFE_CLASS_NAME]. As you will see, these are replaceable tokens that will be populated by the JScript code during the creation of project instances.

Before you can connect this wizard to your template, you must create initial .csproj and .vsz files. The former is an empty project file that the wizard operates on; the latter points Visual Studio .NET to the wizard files you just copied. (Don't worry if these intricate file details escape you at this moment; all will become clear when you are done). The .csproj and .vsz wizard files can be found in the following directory:

...\EnterpriseFrameworks\Projects\CSharp Building Blocks

Because your wizard will work against the Database layer of the template, copy the DBLayerProj.csproj file from the following directory that you created in the first section of the booklet:

..\%VSNETRoot%\EntepriseFrameworks\Projects\MyTemplate\DBLayer\DBLayerLayerPr
    oj

to the directory just mentioned:

...\EnterpriseFrameworks\Projects\CSharp Building Blocks

Next, you need to create the .vsz file, which is a "pointer" to the wizard we will write. Thankfully, you can leverage an already existing file:

To create the .vsz file

  1. Navigate to the …\EnterpriseFrameworks\Projects\CSharp Building Blocks directory using Windows Explorer

  2. In Windows Explorer, select the BusinessFacade.vsz file, and press Ctrl+C to copy it.

  3. Press Ctrl-V; a copy of the file named Copy of BusinessFacade.vsz will appear in Windows Explorer

  4. Right-click on the Copy of BusinessFacade.vsz file, click Rename and rename the file DBLayerWizard.vsz

  5. Open the DBLayerWizard.vsz file using NOTEPAD and modify its contents to reflect those given in Listing 14 (the required changes have been highlighted).

    Listing 14. Changes to the .vsz file

    VSWIZARD 7.0
    Wizard=VSWizard.VsWizardEngine.7.1
    Param="WIZARD_UI = FALSE"
    Param="WIZARD_NAME = MyWizard"
    Param="PROJECT_TYPE = ETPROJ"
    Param="SCRIPT_COMMON_PROJECT_TYPE = CSPROJ"
    Param="EFT_PROTOTYPE = DBLayerProj.csproj"
    

    As you can see, the .vsz file is really a "pointer" file that specifies the real wizard, MyWizard and the project prototype upon which the wizard operates, DBLayerProj.csproj.

  6. Next, using NOTEPAD, open the DBLayerProj.csproj and remove the following highlighted lines from it:

    Listing 15. Changes to the .csproj file

    <Files>
       <Include>
          <File
             RelPath = "AssemblyInfo.cs"
             SubType = "Code"
             BuildAction = "Compile"
          />
          <File
             RelPath = "Class1.cs"
             SubType = "Code"
             BuildAction = "Compile"
          />
       </Include>
    </Files>
    

The highlighted lines in Listing 15 specify those files that are packaged with the project. Because these files will be built on the fly by the wizard, they must be removed from the project specification (note, however, that you must NOT remove the <Files> and <Include> nodes themselves). By removing the aforementioned lines from the project file, you have converted it into an "empty project" that the wizard will operate on.

Step 2: Connecting the Wizard to the Template

You must now connect the wizard to the Database-tier of your template. Using NOTEPAD, open the DBLayer.etp file, which can be found in the following directory:

..\%VSNETRoot%\EnterpriseFrameworks\Projects\MyTemplate\DBLayer\DBLayer.etp

Remove the <ProjectExplorer> and <Reference> nodes from this file, along with their contents, and add to it the highlighted text in, as well.

Note   The

<ProjectExplorer>

and

<Reference>

nodes are located within the

<Views>

and

<References>

nodes, respectively.

Listing 16. Changes to the .etp file

<GENERAL>
   <BANNER>Microsoft Visual Studio Application Template File</BANNER>
   <VERSION>1.00</VERSION>
   <SUBPROJECTWIZARDS>
      <WIZARD>
         <FILE>..\..\..\Projects\CSharp Building
           Blocks\DBLayerWizard.vsz</FILE>
      </WIZARD>
   </SUBPROJECTWIZARDS>
   <Views>
   </Views>
   <References>
   </References>
</GENERAL>

As a result of these modifications, the template will create the Database-tier by means of the DBLayerWizard.vsz file. Recall from Listing 14 that this file is simply a pointer to the real wizard, whose directory structure is illustrated in Figure 15.

Step 3: Writing the Wizard Code

To implement the desired functionality of the wizard—the generation of the private key and the association of that key with the AssemblyInfo.cs file—two files must be modified:

  • ...\EnterpriseFrameworks\EFWizards\MyWizard\scripts\1033\default.js
  • ...\EnterpriseFrameworks\EFWizards\MyWizard\templates\1033\assemblyinfo.cs

The first file houses the script for the wizard; the second file is used as a model to the build the project's AssemblyInfo.cs file. Open the latter file in NOTEPAD and replace the following line:

[assembly: AssemblyKeyFile("")]

with:

[assembly: AssemblyKeyFile([!output KEYPATH_FOR_ASSEMBLY])]

This change prescribes that the contents of the AssemblyKeyFile attribute are contained in a wizard variable named KEYPATH_FOR_ASSEMBLY. When the wizard runs, it will replace the [!output] marker with the real value, so that when the project is created at run time the line will really read:

[assembly: AssemblyKeyFile("..\\..\\..\\DBLayer.snk")]

The last remaining step is to add the appropriate JScript code to the aforementioned default.js file. Open up this file in NOTEPAD, and you will see a function named OnFinish(). This function is called just before the wizard finishes executing, and is an appropriate place for custom code.

The JScript code to generate a private key and associate it with the AssemblyInfo.cs file is given in Listing 17. The code has been thoroughly commented, and you can scrutinize it to decipher what it is doing. Copy the entire OnFinish() function below into the default.js file (remove the existing OnFinish() function first and do not modify the other methods in this file).

Listing 17. JScript code to generate a private key

function OnFinish(selProj, selObj)
{
  var oldSuppressUIValue = true;
  try
  {
     oldSuppressUIValue = dte.SuppressUI;
     var strProjectPath = wizard.FindSymbol("PROJECT_PATH");
     var strProjectName = wizard.FindSymbol("PROJECT_NAME");
     var strSafeProjectName = CreateSafeName(strProjectName);
     wizard.AddSymbol("SAFE_PROJECT_NAME", strSafeProjectName);

     var bEmptyProject = 0; //wizard.FindSymbol("EMPTY_PROJECT");

    // Our custom code starts here ------------------------------------------
    //
    // This method is called right before the wizard generates the project's
      files.
    // In this section we will generate a private key on-the-fly so that it
      can be used by the class-library. In .NET, private keys are generated
        by means of the SN.EXE utility.  For example:
    //
    // SN.EXE -k mykey.snk  -- generates a private key and stores it in
      mykey.snk  
    //
    // Generating a private key from JScript is complicated by two factors:
    //
    //   1.) The SN.EXE utility must be called externally from JScript, which
    //          requires the use of the Windows Scripting Host. 
    //      2.) The paths for both SN.EXE and the textfile that stores the
      keys must be fully qualified.
    //
    //          Thus the command will really look something like:
    // 
    //    C:\Program Files\Microsoft Visual Studio .NET 
      2003\SDK\v1.1\bin\SN.exe -k SomeDIR\MyTemplate\DBLayer\KeyName.snk
    //
    // To fully qualify both paths we need to determine the directory where
      Visual Studio .NET is installed, as well as the path for the project.
      Thankfully, both pieces of information can be ascertained by means of
      the various objects are exposed to the wizard JScript from VS.NET.

    // Declare variables to store path information:
    var strAbsoluteProjectPath, strVSNETPath, strKeyName;
    var loc,loc1,loc2;
    // Retrieve the Solution name under which this project sits to give the
      key a name:
    strKeyName = selObj.Parent.FullName;

    // Parse the fullpath and add an ".snk" extension to give the key a name:
    loc1 = strKeyName.lastIndexOf("\\");
    loc2 = strKeyName.lastIndexOf(".etp");
    strKeyName = strKeyName.substring(loc1+1,loc2) +".snk"

    // Retrieve the path where the project is being installed:
    strAbsoluteProjectPath = wizard.FindSymbol("TARGET").Parent.FullName;

    // Add the path name to the keyname to fully qualify it
    loc = strAbsoluteProjectPath.lastIndexOf("\\");
    strAbsoluteProjectPath = strAbsoluteProjectPath.substring(0,loc+1) +
      strKeyName;

    // Retrieve the path where VS.NET Installed:
    strVSNETPath = dte.Fullname;

    // Parse the VS.NET path to fully qualify the SN.EXE utility
    loc = strVSNETPath.lastIndexOf("2003");
    strVSNETPath  = strVSNETPath.substring(0,loc+4);
    strVSNETPath = strVSNETPath + "\\SDK\\v1.1\\bin\\sn.exe ";

    // Build the command we will execute through the Windows Scripting Host.
      The command will look something like:
    //
    //    C:\Program Files\Microsoft Visual Studio .NET
      2003\SDK\v1.1\bin\SN.exe -k
    //         SomeDIR\MyTemplate\DBLayer\KeyName.snk

    strMakeKeyCommand = "\""+strVSNETPath+"\" -k \"" + strAbsoluteProjectPath
      + "\"";

    // Execute the command using the Windows Scripting Host, thus building
      the private key:
    oShell = new ActiveXObject("WSCript.Shell");
    oShell.Run(strMakeKeyCommand);

   // Store the keyname in a "Wizard" variable so we can reference it in the
   // AssemblyInfo file. Referencing keyfiles is somewhat odd in C#, so we
     must build a string that specifies the keyfile path relative to the
     class-library (..\..\..\MyKey.snk):

        wizard.AddSymbol("KEYPATH_FOR_ASSEMBLY",
          "\"..\\\\..\\\\..\\\\"+strKeyName+"\"");

   // The key has been built, and so let the boilerplate code take over.  The
     only remaining step is to modify the assemblyinfo.cs file to reference
       the key, which you did in the previous step in the booklet.

   //  Our custom code ends here --------------------------------------------
  var proj = CreateCSharpProject(strProjectName, strProjectPath,
    "..\\projects\\csharp building blocks\\" +
      wizard.FindSymbol("EFT_PROTOTYPE") );

  var InfFile = CreateInfFile();
  if (!bEmptyProject)
  {
     AddReferencesForClass(proj);
     AddFilesToCSharpProject(proj, strProjectName, strProjectPath, InfFile, 
       false);
  }
     proj.Save();
     CollapseProjectNode( proj );
  }
  catch(e)
  {
      if( e.description.length > 0 )
         SetErrorInfo(e);
      return e.number;
  }
  finally
  {
      dte.SuppressUI = oldSuppressUIValue;
      if( InfFile )
     InfFile.Delete();
  }
}
  • After pasting the new code into default1.js, save the file and close any instances of NOTEPAD.

Step 4: Testing the Template

To test the addition of dynamic content by means of wizards to your template

  1. Open Visual Studio .NET.

  2. On the File menu, point to New, and then click Project.

  3. The New Project dialog box appears.

  4. In the Project Types pane, expand the Other Projects folder and select the Enterprise Template Projects folder. The Templates pane displays a number of icons, including your template, MyTemplate (see Figure 4).

  5. Accept the default project name and location, and click the OK button. If you made any errors during the wizard construction process, Visual Studio .NET will display them at this point.

  6. While Visual Studio .NET is creating the project, you may see the quick display of a Command Prompt window; this momentary flash is the JScript code executing the SN.EXE utility to generate the private key.

  7. As before, Visual Studio .NET will create an entire three-tier solution for you based on the template's architecture.

  8. Examine the AssemblyInfo.cs file within the DBLayerproj project, and you will discover that it has been associated with the private key that was generated on the fly:

    [assembly: AssemblyKeyFile("..\\..\\..\\DBLayer.snk")]
    
  9. To test that the key was correctly referenced in the AssemblyInfo file, build the project by going to the Build menu and clicking Build Solution.

  10. Close Visual Studio .NET.

It is important to note that every project instance created with this template gets its own unique private key. Such behavior is in stark contrast to the static prototype approach, in which the contents of project instances did not change.

It is worth repeating that the possibilities with subproject wizards are limited only by the bounds of JScript and your creativity with the language. In fact, even these factors need not be limiting as JScript allows you to use ActiveX controls to go beyond its capabilities. For example, the code in Listing 18 uses the Windows Scripting Host control to execute an external program (SN.EXE) from Visual Studio .NET. Another instructional example is to use this same ActiveX control to log information to the EventLog during the creation of a project instance:

Listing 18. Accessing the EventLog from a wizard

oShell = new ActiveXObject("WSCript.Shell");
oShell.LogEvent(4,"User XXX created a project YYY at ZZZ");

We can also envision more complex setups. For example, perhaps we could modify the code in Listing 18 to save data to the EventLog on a centralized machine for reporting purposes, or use ActiveX Data Objects (ADO) to save necessary information to a relational database. Another intriguing possibility is to introduce Web services into the equation: when a project is created, the wizard uses a Web service to fetch (and leverage) some information from a remote location. Although such an undertaking would not be straightforward (the Web service would have to be exposed to JScript as an ActiveX control, a notable, but nontrivial, capability of the .NET Framework), such a technique could be a very powerful tool in a distributed or geographically disperse organization.

Just-in-Time Guidance

The primary purpose of an Enterprise Template is to provide developers with a predefined project skeleton and enforce environment rules upon that skeleton. As illustrated, the former service is provided by static and subproject wizards, whereas the latter capability is facilitated by the template's policy file (which can be modified manually or by way of Microsoft's new Enterprise Templates Policy Editor).

A template, however, is only as useful as a developer's ability to discern and adhere to its rules. Consider the restrictions we implemented using the policy file of the previous section:

  • Developers should not be given the ability to add additional solutions or projects to the four solutions of the template (MyTemplate, DBLayer, BusinessLayer, UILayer).
  • Developers should be allowed to add only class files (.cs files) to the DBLayerProj and MiddleLayerProj projects of the template.
  • Developers should be allowed to add only HTML Pages to the UILayerProj project of the template.

How do we communicate these restrictions to developers? One could argue that this is done implicitly, insofar as these restrictions are enforced within the Visual Studio .NET environment. Clearly, this approach is unsatisfactory. It would be preferable if developers explicitly understood the restrictions of the template and the motivation behind them. Although such information could be communicated by way of standard documentation, a more powerful and useful approach is provided by an Enterprise Template feature called Just-in-Time Guidance.

Just-in-Time Guidance allows developers to expose custom help information directly within the development environment. In effect, your custom content becomes a natural extension of the MSDN documentation already exposed natively by Visual Studio .NET. In this way, developers can examine a template's documentation—rules, explanations, best practices, patterns, and other recommendations—without leaving the comfortable confines of the IDE.

There are two ways to author custom content for Visual Studio .NET:

  • Author content by means of regular HTML and expose it to Visual Studio .NET.
  • Author content by means of the Visual Studio .NET Help Integration Kit (VSHIK).

In this section of the booklet we concentrate on the first technique, which because of its ease is increasingly becoming the "mainstream" approach, and offer a succinct explanation of the second approach.

Just-in-Time Guidance with HTML

Adding Just-in-Time Guidance to a template by means of regular HTML is a three-step process:

  1. Author custom content in HTML using the tool(s) of your choice (such as FrontPage, ColdFusion, Notepad).
  2. Create a context XML file, which determines how and when such content is displayed within Visual Studio .NET.
  3. Add some entries to the template's policy file for identification purposes.

This process, as well as some of the finer points you should keep in mind while employing it, is best illustrated by example.

Example: Just-in-Time Guidance with HTML

Just-in-Time Guidance is based upon the Dynamic Help window in Visual Studio .NET. This window, which is illustrated in Figure 16, is located under Visual Studio .NET's Solution Explorer.

Aa302169.vsent_enterprisetemplatesbk16(en-us,MSDN.10).gif

Figure 16. The Dynamic Help window

The Dynamic Help window in Figure 16 shows three Topics: "Getting Started," "Custom info on Enterprise Templates," and "My Custom Content."

Topics, which are technically referred to as a "LinkGroups," compartmentalize help files within the Dynamic Help window. As reflected by their names, the first topic contains prebuilt content packaged with Visual Studio .NET, whereas the second two topics house the custom content you will author in a moment. As you are likely aware, when a developer clicks a help file hyperlink in the Dynamic Help window, the associated documentation is displayed in the environment's main window.

As illustrated below, adding custom content to the Dynamic Help window is primarily a matter of configuring Visual Studio .NET to point to your HTML files.

Step 1: Writing the HTML content

Writing custom content for Visual Studio .NET is like writing a Web page—both techniques are based on HTML. Beyond this underlying technology, however, the two procedures have little in common. Unlike a Web based application, help files should be as unadorned and straightforward as possible. The point of such content is not to entertain or dazzle, but to elucidate and clarify the motivations, restrictions, and other specifics of your template. A good example of how to communicate information in a clear and plain manner is the prepackaged documentation of Visual Studio .NET (for example, any link that resides under the "Getting Started" topic in Figure 16).

For illustrative purposes (and to make this example more succinct), you can download the sample content we have prepared at <link>. This content consists of the following four files:

  • MyContent.html   An example HTML page
  • MyFigure.jpg   A sample graphic file referenced by the preceding page
  • WhatisET.html   An HTML page that explains Enterprise Templates
  • WhatisJIT.html   An HTML page that explains Just-in-Time Guidance

Save all these files in the MyProjectFiles directory you created in the previous examples. (In practice, of course, you would author your own content using the tool of your choice.)

Step 2: Creating a context file

The next step, the creation of the context file, is likely the hardest aspect of this entire process—not because context files are especially difficult to create, but because, at first glance, it is difficult to understand exactly what they are doing (not to worry—all will become clear after this example).

Begin the process by saving the text in Listing 19 in a new file, named CustomHelp.xml, and placing it in the following directory:

%Program Files%\Microsoft Visual Studio .NET
  2003\Common7\IDE\HTML\XMLLinks\1033

When Visual Studio .NET loads, it consults the files within this directory to determine how to format its Dynamic Help window.

Note   1033 is the language code for English; it will differ for other language versions of Visual Studio .NET.

Listing 19. Content for CustomHelp.xml

<?xml version="1.0" encoding="utf-8" ?>
 <DynamicHelp xmlns="https://msdn.microsoft.com/vsdata/xsd/vsdh.xsd">
    <Preference AttrName="Locale" AttrValue="kbEnglish"/>
    <LinkGroup ID="CustomContent" Title="My Custom Content" Priority="2">
       <GLYPH Collapsed="5" Expanded="6" />
    </LinkGroup>
    <LinkGroup ID="ET_Info" Title="Custom info on Enterprise Templates" 
      Priority="2">
       <GLYPH Collapsed="1" Expanded="2" />
    </LinkGroup>
    <Context>
       <Keywords>
          <KItem Name="MyETProject"/>
       </Keywords>
   <Links>   
      <LItem URL="C:\MyProjectFiles\MyContent.html"
        LinkGroup="CustomContent">
              My Custom Content
        </LItem> 
      <LItem URL="C:\MyProjectFiles\HTML\WhatisET.html" LinkGroup="ET_Info">
         What are Enterprise Templates?
           </LItem> 
      <LItem URL="C:\MyProjectFiles\WhatisJIT.html" LinkGroup="ET_Info">
         What is Just-in-time Guidance?
          </LItem>
   </Links>
    </Context>
</DynamicHelp>

To understand Just-in-Time Guidance, we must dissect the XML in Table 5 (it will also help to refer to Figure 16 throughout this discussion). First, note that our text has been embedded within a <DynamicHelp> node, which prescribes that these settings apply to the Dynamic Help window.

The first line of code within this block, the <Preference> node, sets the language configuration for Visual Studio .NET (which determines how help content is displayed).

Recall from Figure 16 that help files are compartmentalized into topics. In Visual Studio .NET, a topic is defined by a <LinkGroup> node:

<LinkGroup ID="ET_Info" Title="Custom info on Enterprise Templates"
  Priority="2">
   <GLYPH Collapsed="1" Expanded="2" />
</LinkGroup>

The preceding XML creates a topic named ET_Info, as denoted by the ID attribute. The second attribute, Title, is used to describe the topic heading in the Help window (see Figure 16). Finally, the last attribute, Priority, determines the position of the topic relative to other topics in the Help window.

And what does the <Glyph> node do (pronounced "Gliff")? It may not be intuitive, but this subnode is used to determine the icon that is placed next to the topic when it is expanded or collapsed within the Help window. Although the code in Table 5 specifies that these icons use the predefined ID numbers of Visual Studio .NET, you can alternatively specify graphic files:

<Glyph Expanded="c:\someDir\ctxhelp_opn.gif"
  Collapsed="c:\someDir\ctxhelp_cls.gif"/>

Look closely, and you'll see that the code in Table 5 defines two topics: ET_Info and CustomContent. These topics correspond to the headings illustrated in Figure 16. The next step is to place the appropriate "help hyperlinks" under these topics; and it is this process that can become somewhat convoluted.

Contexts

By themselves, topics are meaningless—they are simply empty headings without purpose. A topic only becomes significant when a number of help items, termed "Links," are added to it. Help items (Links) are added to topics (LinkGroups) within a "context."

Any time an item (a control, form, solution, language project, and so on) receives focus in the development environment, the Visual Studio .NET Help Engine creates a context for that item. That is, it looks through all the help files at its disposal, determines which ones are relevant to the specific item, and displays them in the Dynamic Help window. This collection of relevant files represents a "context." For example, the context for a C# language project would not include help files specific to Visual Basic .NET. Likewise, the context for an ASP.NET application would exclude content particular to Windows Forms.

When you expose custom content in Visual Studio .NET, what you are really doing is creating "contexts" for the items of your template. This may sound complex, but it is quite straightforward once you learn the conventions of the <Context> node, Table 5.

The most important step in specifying a context is determining the conditions under which that context is displayed in the Help window. This process is based on the concept of a "Keyword," as illustrated in the <Context> node below.

Listing 20. The<Context>node

<Context>
   <Keywords>
      <KItem Name="MyETProject"/>
   </Keywords>
   <Links>   
      <LItem URL="C:\MyProjectFiles\MyContent.html" 
        LinkGroup="CustomContent">
         My Custom Content
      </LItem> 
      <LItem URL="C:\MyProjectFiles\HTML\WhatisET.html" LinkGroup="ET_Info">
         What are Enterprise Templates?
      </LItem> 
      <LItem URL="C:\MyProjectFiles\WhatisJIT.html" LinkGroup="ET_Info">
         What is Just-in-time Guidance?
      </LItem>
   </Links>
</Context>

The highlighted text in Listing 20 prescribes that this context will be displayed in the Help window when a focused item is associated with the MyETProject keyword (in the next step, we will see how to associate an item with a keyword). In addition, the text within the <Links> node specifies the makeup of the context (that is, those help files that constitute the context). There are three <LItem> tags within this node, each of which corresponds to a help-file hyperlink. For example, the following code:

      <LItem URL="C:\MyProjectFiles\WhatisJIT.html" LinkGroup="ET_Info">
         What is Just-in-time Guidance?
      </LItem>

stipulates that this context contains a help-file hyperlink named "What is Just-in-time Guidance?"

The HTML for this hyperlink will be loaded from c:\MyProjectFiles\WhatisJIT.html, as specified by the URL attribute. The last attribute, LinkGroup, determines the topic ("ET_Info" or "Custom Content") under which the hyperlink is displayed.

Look carefully at the code in Listing 21, and you will see that the <LinkGroup> and <LItem> nodes result in the exact context depicted in Figure 16. The last remaining step is to associate your template's items with the help context you just created. Before moving on to this step, however, it is important to note that a <Context> node can have multiple keywords qualifications as the following code illustrates:

Listing 21. Multiple keywords in a single context

<Context>
   <Keywords>
      <KItem Name="Foo"/>
      <KItem Name="Bar"/>
      <KItem Name="ACME"/>
   </Keywords>

Keywords work in an OR-like fashion. That is, in Listing 21 an item must have one of a Foo, Bar or ACME keyword for the context to be displayed.

Step 3: Setting up keywords

The context described in Listing 21 displays whenever an item with a MyETProject keyword receives focus in the Visual Studio .NET environment. To give an item a keyword, you must add a <CONTEXT> node to the item's ELEMENT definition in the policy file (MyPolicy.tdl).

Listing 22. The<CONTEXT>element

<ELEMENT>
   <ID>projDBLayer</ID>
   <CONTEXT>
      <CTXTKEYWORD>MyETProject</CTXTKEYWORD>
   </CONTEXT>
   <IDENTIFIERS>
   ...

As illustrated in Listing 22, the <CONTEXT> node must be placed directly after the element's <ID> tag. Located within the <CONTEXT> node is the <CTXTKEYWORD> subnode that specifies the keyword itself.

Add identical <CONTEXT> nodes to the projUILayer and projBusinessLayer elements, save your changes, and close Visual Studio .NET so that your changes will take effect.

Step 4: Testing content availability

To test the effects of Just-in-Time Guidance, open the development environment and create a project instance based on your template (see Figure 4). As usual, Visual Studio .NET will create a three-tier project modeled after the template structure (Figure 2). Select one of the solution nodes in the Solution Explorer (that is, BusinessLayer, DBLayer or UILayer in Figure 2), and invoke the Dynamic Help window by going to Help -> Dynamic Help (or press CONTROL-F1).

Examine the contents of the Help window, and you will find it does not display the custom content you added. Select one of the language projects (BusinessLayerProj, DBLayerProj, UILayerProj), however, and you will discover that the custom content is displayed in accordance with Figure 16. Finally, select one of the individual files of a language project (for example, WebForm1.aspx of UILayerProj), and you will see that the custom content is still displayed.

You might have expected some of this behavior. It makes sense, for example, that custom content is not displayed for a solution node, as solution elements were not associated with a MyETProject keyword in the policy file. However, because this association was made for language projects (Listing 22), the custom content is made visible. The pattern does not hold, however, for individual files within the language projects (such as WebForm1.aspx). These files are not associated with the MyETProject keyword, but the custom content is still displayed. What's behind this unexpected behavior?

The crucial point is that items inherit keywords from their parents. Therefore, in our example, although the WebForm has not been given a MyEtProject keyword, it inherits it from the UILayerProj language project and our custom content is still displayed.

Step 5: Examining custom content

To inspect the custom content you exposed to Visual Studio .NET, select one of the language projects and click the My Custom Content hyperlink in the Dynamic Help window (Figure 16). As illustrated in Figure 17, Visual Studio .NET will display the formatted HTML in its main window.

Click here for larger image

Figure 17. Custom content in Visual Studio .NET

As you can see from the previous figure, the possibilities with custom content are limited only by one's creativity with HTML. Custom documentation can consist of text, tables, graphics, frames, and so on. In fact, even HTML need not be a limiting factor. Scroll down to the bottom of the "My Custom Content" page, shown in Figure 17, and you will find a hyperlink to Microsoft's homepage. Click this link and the results may surprise you: Visual Studio .NET will render the Microsoft homepage directly within the development environment (Figure 18).

Click here for larger image

Figure 18. Rendering a regular Web page in Visual Studio .NET

This seemingly pointless exercise illustrates two very powerful aspects of Just-in-Time Guidance:

  • Because Visual Studio .NET uses the Internet Explorer Engine to display help documents, they can be constructed in any technology that renders IE-compatible HTML (ASP, ASP.NET, JSP, and so forth).
  • Custom content does not have to reside on the local machine; it can reside anywhere accessible over HTTP.

These two principles are illustrated in the following hypothetical <Links> node, which would be embedded in a context file similar to that in Listing 20.

<Links>
   <LItem URL="http:\\SomeServer\\MyHelpPage.aspx" LinkGroup="ServerContent">
      My Custom Content
   </LItem>
</Links>

This code stipulates that the content for this help hyperlink is generated by the ASP.NET page MyHelpPage.aspx, located on the SomeServer machine.

A scenario such as this leads to some interesting and powerful possibilities. First, an architect can store the custom content for a template in a centralized location, which might be appropriate for Intranet setups. Unlike content that resides on the local machine, centralized documentation would be easier to maintain and update. Second, the ability to author content using powerful technologies such as ASP.NET allows documentation to take on an interactive or dynamic flavor. Instead of simply communicating static information, a help page could query the user for information and provide useful results on the fly. Therefore, the possibilities with custom content are not limited by your creativity with static HTML, but, more accurately, with the Web technologies at your disposal.

There is a downside, however, with this more versatile approach. Content that is stored remotely would become unavailable in offline situations (or if the network went down), which can be extremely frustrating for developers. In addition, authoring and hosting dynamic content is much more complex than writing simple HTML pages. Nonetheless, while static HTML is the recommended approach to creating custom content, remote and dynamic documentation can be useful in special and demanding scenarios.

Attributes

The CustomHelp.xml file in Listing 20 stipulates that its help context be displayed for all items associated with a MyETProject keyword. Because this association was established for language projects by means of the template's policy file (Listing 22), the custom context is displayed whenever a language project receives focus in the Visual Studio .NET environment.

It is sometimes useful to display different help contexts for different items. For example, the DBLayerProj and UILayerProj nodes might each elicit different contexts in the Help window when they receive focus. One way to achieve this behavior is to create two <Context> nodes with different keyword qualifications. Another way is to leverage attributes.

Attributes (not to be confused with language attributes found in .NET languages such as C# and VB.NET) offer another way to determine the manner in which a help context is displayed. It is important to note that attributes work in combination with keywords, as illustrated by the following XML code.

Listing 23. Using attributes with keywords

<Context>
   <Keywords>
      <KItem Name="MyETProject"/>
   </Keywords>
   <Attributes>
      <AItem Name="WhatLayer" Value="Business" />
   </Attributes>
   <Links>   
      <LItem URL="C:\MyProjectFiles\MyContent.html" LinkGroup="ET_Info">
         My Custom Content
      </LItem>
      <LItem URL="C:\MyProjectFiles\WhatisET.html" LinkGroup="ET_Info">
         What are Enterprise Templates?
      </LItem>
      <LItem URL="C:\MyProjectFiles\WhatisJIT.html" LinkGroup="ET_Info">
         What is Just-in-time Guidance?
      </LItem>
   </Links>
</Context>
<Context>
   <Keywords>
      <KItem Name="MyETProject"/>
   </Keywords>
   <Attributes>
      <AItem Name="WhatLayer" Value="UI" />
   </Attributes>
   <Links>
      <LItem URL="C:\MyProjectFiles\MyContent.html" 
        LinkGroup="CustomContent">
         My Custom Content
      </LItem>
      <LItem URL="C:\MyProjectFiles\WhatisET.html" LinkGroup="ET_Info">
         What are Enterprise Templates?
      </LItem> 
      <LItem URL="C:\MyProjectFiles\WhatisJIT.html" LinkGroup="ET_Info">
         What is Just-in-time Guidance?
      </LItem>
   </Links>
</Context>

Unlike the code in Table 5, the XML code above contains two <Context> definitions. Replace the single <Context> node in CustomHelp.xml with the two <Context> definitions in Listing 23.

Recall that this file can be found in the following directory:

%Program Files%\Microsoft Visual Studio .NET 
  2003\Common7\IDE\HTML\XMLLinks\1033

The attribute definitions in Table 6 serve as additional qualifiers for their respective contexts. Consider the first <Context> node. In order for this context to be displayed, an item must have both a MyETProject keyword as well as a WhatLayer attribute with a value of Business. Like a keyword, an item is given an attribute through an entry in the template's policy file:

Listing 24. Adding attribute identifiers to the policy file

<ELEMENT>
   <ID>projBusinessLayer</ID>
   <CONTEXT>
      <CTXTKEYWORD>MyETProject</CTXTKEYWORD>
      <CTXTATTRIBUTE>
         <NAME>WhatLayer</NAME>
         <VALUE>Business</VALUE>
      </CTXTATTRIBUTE>
   </CONTEXT>
   <IDENTIFIERS>
...
<ELEMENT>
   <ID>projUILayer</ID>
   <CONTEXT>   
      <CTXTKEYWORD>MyETProject</CTXTKEYWORD> 
      <CTXTATTRIBUTE>
         <NAME>WhatLayer</NAME>
    <VALUE>UI</VALUE>
      </CTXTATTRIBUTE>
   </CONTEXT>
   <IDENTIFIERS>
...

The highlighted TDL code in Listing 24, which you should add to the template's policy file (MyPolicy.tdl), gives the projBusinessLayer and projUILayer elements attributes with values of Business and UI, respectively. Note that we have not given the third language project, projDBLayer, a similar entry (an omission whose effects you will see momentarily).

Save your changes to both MyPolicy.TDL and CustomHelp.xml and close Visual Studio .NET.

Note   Because Visual Studio .NET only reads context files such as CustomHelp.xml upon startup, you must restart the program for your changes to take effect.

To see the effects of your added attributes, reopen the development environment and create a project instance based on your template (see Figure 4). Select the BusinessLayerProj node in the Solution Explorer and examine the Dynamic Help window by pressing Control-F1. Repeat the procedure, but this time select the UILayerProj node, and you will see an interesting occurrence. Although our custom content is displayed in both cases, it is formatted differently, as shown in Figure 19. Whereas there are two topics when you click the UILayerProj node (the figure on the left), there is one topic when you click the BusinessLayerProj node (the figure on the right).

Aa302169.vsent_enterprisetemplatesbk19(en-us,MSDN.10).gif

Figure 19. The effect of attributes

To understand the differing conventions, carefully examine the <Context> nodes in Table 6. The first node, which displays when the WhatLayer attribute is set to "Business," groups all the hyperlinks under a single topic, ET_Info. The second node, which displays when the WhatLayer attribute is set to "UI," compartmentalizes the hyperlinks into two topics: ET_Info and CustomContent. The different appearances, therefore, are a result of the two <Context> nodes in Table 6 and the attribute identifiers (Listing 24) you added to the policy file for each project type.

The TDL code in Listing 24 adds attribute identifiers only for the projBusinessLayer and projUILayer elements; the projDBLayer has remained untouched. Nevertheless, if you select the DBLayer node in the Solution Explorer, the Dynamic Help window will display the custom content formatted under one topic (for example., the right picture in Figure 19).

Why does our custom content still display for the DBLayer node even though it hasn't been given an attribute value in the policy file? The unfortunate fact is that with Visual Studio .NET 2003, if an item is not given an attribute value the IDE ignores the attribute qualification altogether (the same is not true, of course, for keywords).

When you select the DBLayer node, therefore, Visual Studio .NET finds the first <Context> match in Table 6, which places the custom content within a single topic. If you were to reverse the order of the <Context> nodes in Table 6, then the opposite would occur—our content would be split into two topics (the left picture in Figure 19). To make matters more complex, in an "attributeless" case such as this, Visual Studio .NET will not always default to the first matching <Context> node. If different <Context> nodes contain different help hyperlinks, it will actually combine or superimpose them. An easy way around this convoluted and obscure behavior is to always give the items of your template values for the attributes you are using. In our example, this would equate to giving the projDBLayer item a WhatLayer attribute with an appropriate value:

Listing 25. Giving projDBLayer an attribute

<ELEMENT>
   <ID>projDBLayer</ID>
   <CONTEXT>
      <CTXTKEYWORD>MyETProject</CTXTKEYWORD>
      <CTXTATTRIBUTE>
         <NAME>WhatLayer</NAME>
          <VALUE>DB</VALUE>
      </CTXTATTRIBUTE>
   </CONTEXT>
   <IDENTIFIERS>
   ...

Because of this added attribute, our custom content will not be displayed when the DBLayer item is clicked in the solution explorer.

Attributes vs. keywords

Attributes might seem very much like keywords. However, whereas a keyword is binary (an item either has it or does not), attributes can take on many values. Furthermore, Visual Studio .NET sets its own predefined attributes, depending on the characteristics of your project. Listing 26 shows a worthwhile example.

Listing 26. Combining attributes with keywords

<Context>
    <Keywords>
      <KItem Name="FooBar" />    
    </Keywords>
    <Attributes>
      <AItem Name="DevLang" Value="VB" /> 
      <AItem Name="DevLang" Value="VC" /> 
    </Attributes>

The code in Listing 26 uses the predefined DevLang attribute to display custom content only for template items with a FooBar keyword that are authored in VB.NET or C#.

Example: Just-in-Time Guidance and the F1 key

Just-in-Time Guidance is useful as it allows you to embed custom content within Visual Studio .NET's Dynamic Help window. Many developers, however, will not peruse this window when in need of template guidance. Like end users, developers are conditioned to press F1 when they require assistance or clarification. Thankfully, Just-in-Time Guidance allows you to "hardwire" a focused item of your template to custom content when the F1 key is pressed. The process is simpler than you might expect, and requires a keyword and attribute addition to the context file as shown below.

Listing 27. Hardwiring custom content to the F1 key

<Context>
   <Keywords>
      <KItem Name="MyETProject"/>
      <KItem Name="VS.SolutionExplorer.EnterpriseTemplate" />
   </Keywords>
   <Attributes>
      <AItem Name="WhatLayer" Value="Business" />
      <AItem Name="EnterpriseTemplateProject" Value="YES" />
   </Attributes>
   <Links>   
      <LItem URL="C:\MyProjectFiles\MyContent.html" LinkGroup="ET_Info">
         My Custom Content
      </LItem> 
      <LItem URL="C:\MyProjectFiles\WhatisET.html" LinkGroup="ET_Info">
         What are Enterprise Templates?
      </LItem> 
      <LItem URL="C:\MyProjectFiles\WhatisJIT.html" LinkGroup="ET_Info">
         What is Just-in-time Guidance?
      </LItem>
   </Links>
</Context>

These two simple additions stipulate that when an item to which this context applies (that is, one with a WhatLayer attribute of value "Business" and a MyETProject keyword) receives focus in the Visual Studio .NET environment, pressing the F1 key will load the first hyperlink in the context—MyContent.html, in this case.

Thus, if you create a project instance based on your template, select the BusinessLayer node in the Solution Explorer and press F1, Visual Studio .NET will load MyContent.html in its main window (similar to Figure 17). Note that this behavior applies only to BusinessLayer nodes; to institute the change for UILayer nodes, you would make identical highlighted entries to the second <Context> node in Table 6.

Review

As illustrated by the section, adding custom help to Visual Studio .NET by means of regular HTML is a powerful way to provide online guidance for the users of your template. Although the nomenclature and technology can be confusing, once the conventions are mastered the process is relatively straightforward:

  1. Author HTML content and place it anywhere on the system.

  2. Author a custom XML file using the aforementioned concepts of attributes and keyword, and place it in the following directory:

    %Program Files%\Microsoft Visual Studio .NET
      2003\Common7\IDE\HTML\XMLLinks\1033
    

Just-in-Time Guidance with the Visual Studio .NET Help Integration Kit (VSHIK)

Another way to add custom help content to the Visual Studio .NET environment is to use the Visual Studio .NET Help Integration Kit (VSHIK). This technique is exposed by means of the New Help Project template in Visual Studio .NET, which is illustrated in Figure 20.

Aa302169.vsent_enterprisetemplatesbk20(en-us,MSDN.10).gif

Figure 20. New Help project template

When you build a Help Project in Visual Studio .NET, the environment creates a compiled Microsoft Help (.HxS) file. Because such files are compiled, they generally consume less hard-drive space than their HTML equivalents. Furthermore, much like the MSDN itself, compiled help files can contain pre-built Tables of Contents, Keyword Indexes, and Attribute definitions.

A thorough examination of creating compiled files using Visual Studio .NET can be found at: ms-help://MS.VSCC.2003/ms.vshik.2003/vshikcommon/html/vsoriVSHIK.htm