Home > Articles > Programming > Windows Programming

This chapter is from the book

Creating the Code Generator

With the framework for calling my code-generation solution in place, I'm ready to start creating the code-generation code in my DatabaseUtilities class. The constructor for the class accepts the reference to the DTE2 object and moves it to a field in the class. The initial version of the class also contains the GenerateConnectionManager method that's called from my add-in:

using System;
using System.Collections.Generic;
using System.Text;
using EnvDTE;
using EnvDTE80;

namespace ConnectionStringGenerator
{
    class DatabaseUtiltiies
    {
        DTE2 applicationObject;

        public DatabaseUtiltiies(DTE2 ApplicationObject)
        {
            applicationObject = ApplicationObject;
        }

        public void GenerateConnectionManager()
        {

        }
    }
}

At this point, I've got enough code to start testing my solution by adding a line of code to my GenerateConnectionManager method that writes to the status bar:

applicationObject.DTE.StatusBar.Text = "Code generator called.";

I can now check that the menu item appears (with all the text spelled correctly), that I can load my generation class, and that I can successfully call the generation method. If all that works, I'm ready to start thinking about what the solution will do.

Finding the Project

The first step in this code-generation project is to retrieve a reference to the project that the developer wants to modify. At this point, it's worthwhile to think about the problem from the point of view of the developer for whom you're generating the code. When the developer clicks the menu item that starts the code-generation process, what project will the developer expect your code to work with?

For a code-generation solution run from a button on a menu, my first choice is to work with the project for the currently open document. This code retrieves the project for that document:

Project prj = null;
if (applicationObject.ActiveDocument != null)
{
 prj = applicationObject.ActiveDocument.
                                  ProjectItem.ContainingProject;
}

However, if there is no open document, my second choice is to work with the project for the item currently selected in Solution Explorer. This code checks to see if an item is selected in Solution Explorer and if the item has an associated ProjectItem. Then, if both of those conditions are true, it retrieves the associated Project:

else
{
 if (applicationObject.SelectedItems.Count > 0)
 {
  if (applicationObject.SelectedItems.Item(1).ProjectItem
                                                        != null)
  {
   prj = applicationObject.SelectedItems.Item(1).
                                ProjectItem.ContainingProject;
  }
  else
  {
   prj = applicationObject.SelectedItems.Item(1).Project;
  }
 }

Unfortunately, there is a possibility that no document is open, that nothing is selected in Solution Explorer, or that the selected item doesn't return a Project reference. If I can't determine the Project, I give up and exit. However, the decent thing to do in that situation is to tell the developer that no code has been generated. It's tempting to pop up a form telling the developer that no code was generated, but when working through the Add-In Wizard, I promised never to display a modal dialog. So, instead I just update Visual Studio's status bar with code like this. (In the section "Notifying the Developer" later in this chapter, I enhance the messaging to use the TaskList for more serious messages.)

if (prj == null)
{
 applicationObject.DTE.StatusBar.Text =
                                 "Please select a project item.";
 return;
}

Assuming that I get a reference to the Project, I now get references to the Project's ProjectItems collection and the Solution it's part of—I'll need both of these objects later in the solution:

ProjectItems pjis = prj.ProjectItems;
Solution2 sln = (Solution2) applicationObject.Solution;

Does Anything Need to be Done?

In the process I recommended in Chapter 1, "Introducing Code Generation," as part of reading your inputs you should determine whether any code needs to be generated. In this case, that means retrieving the web.config file and determining if it contains any connectionString elements.

This code attempts to retrieve the project's web.config file and, if that fails, the project's app.config file. If neither exists, the code exits:

ProjectItem cfg = null;
try
{
 cfg = pi.Project.ProjectItems.Item("web.config");
}
catch
{
 try
 {
  cfg = pi.Project.ProjectItems.Item("app.config");
 }
 catch {}
}
if (cfg == null)
{
 return;
}

With a configuration file found, the code loads its contents into an XML document by passing the full pathname to the configuration file to an XmlDocument object. The code then uses an XPath expression to search the document for connectionString elements. If none are found, the code exits:

System.Xml.XmlDocument dom;
dom = new System.Xml.XmlDocument();

dom.Load(@cfg.Properties.Item("FullPath").Value.ToString());
System.Xml.XmlNode ndCons =
     dom.SelectSingleNode("//connectionStrings");
if (ndCons == null || ndCons.ChildNodes.Count == 0)
{
 return;
}

Segregating Generated Code

I'm almost ready to start adding code, but I need to decide how to handle the files containing my generated code. Although many code generators attempt to hide generated classes from the developer, my preference is to leave the code visible. (Among other benefits, this makes it easier for me to check that I'm generating the right code during development and debugging.)

However, I do segregate my generated code into special folders. Using the reference to the ProjectItems collection, I can add that folder to hold my generated code using the AddFolder method. For most projects, I create a folder called "Generated Code" to put the class file in. However, for ASP.NET projects, I place the class file in the App_Code folder.

These folders may already be present. (Even my own Generated Code folder may already exist if the developer has run this add-in, or another one of my code-generation utilities, before.) Adding the folder a second time will raise an error; however, rather than check that the folder already exists, I just catch the error and discard it. I'll need to access the folder again, so after adding it I retrieve a reference to the new folder through the ProjectItems' Item method (unfortunately, the AddFolder method doesn't return a reference to the new folder) and store it.

I begin by declaring a field to hold the reference to the folder with the generated code:

ProjectItem codeFolder;

In the following code, I first check to see what kind of project I have by looking at the GUID in the Project object's Kind property. If it's an ASP.NET project, I attempt to add the App_Code folder. For any other kind of project, I add a folder named "Generated Code." As I noted before, if the folders already exist, I just catch the error and discard it. After attempting to add the folder, I get a reference to it:

if (prj.Kind == "{E24C65DC-7377-472b-9ABA-BC803B73C61A}")
{
 try
 {
  pjis.AddFolder("App_Code",
                 "{6BB5F8EF-4483-11D3-8BCF-00C04F8EC28C}");
 }
 catch { };
 codeFolder = pjis.Item("App_Code");
}
else
{
 try
 {
  pjis.AddFolder("Generated Code",
                    Constants.vsProjectItemKindPhysicalFolder);
 }
 catch {};
 codeFolder = pjis.Item("Generated Code");
}

I could simplify the code required to add the App_Code folder by using the VsWebSite objects (described in Chapter 5, "Supporting Project-Specific Features"). However, for this case study, one of my goals is to use as few tools as possible, which means avoiding using the project-specific objects described in that chapter.

With the folder in place, I add the class file that will eventually contain my ConnectionManager code. At this point I have to decide how I want to handle regeneration when the developer is generating the code for the second (or subsequent) time. The simplest strategy for supporting regeneration is to find the file containing the code from the previous generation and delete it. The alternative is to attempt to reconcile the previously generated code against the current environment, a process that is both difficult to implement and error-prone. (One solution is demonstrated in the case study in Chapter 10, "Case Study: Generating Validation Code," where I selectively replace methods in a class to leave the developer's methods in place while replacing my generated methods.)

In a well-designed solution, you should only need to update an existing file occasionally. Typically, solutions end up having to reconcile old code with new code because the solution didn't provide a clean separation between the generated code and the developer's custom code. For this example, I keep most of the generated code in one file and provide a separate file for the developer's custom code.

For this solution, both of the files will have their names begin with "ConnectionManager." The file that holds the generated code will be named "ConnectionManager.Generation," the file holding the developer's code will be named "ConnectionManager.Customization." Initially, all I build into the solution is the ConnectionManager.Generation file.

In this solution, if the ConnectionManager.Generation file already exists, I don't want to try adding it again and catching the error: I always want to delete any existing version of the file in order to start generating the code from a blank slate. To ensure that I'm deleting the right file, I use the full pathname to the file by concatenating together the path to the project and name of the folder I added. The code looks like this:

string ProjectPath;
ProjectPath = System.IO.Path.GetDirectoryName(prj.FullName);

ProjectItem prji = sln.FindProjectItem(@ProjectPath + @"\" +
          codeFolder.Name + @"\ConnectionManager.Generation.cs");
if (prji != null)
{
 prji.Delete();
}

Adding the Template

After ensuring that the file doesn't exist, I now add the Visual Studio template that provides the base for my generation class: a class file in C#. To get this file in the right folder, I use the reference to the folder where I'm going to keep my generated code, which I retrieved earlier. This example adds the template for a non-ASP.NET project:

string ItemTemplatePath = sln.GetProjectItemTemplate(
                                             "Class.zip", "CSharp");
ProjectItem pji = codeFolder.ProjectItems.AddFromTemplate
            (ItemTemplatePath,"ConnectionManager.Generation.cs");

To enable my add-in to support ASP.NET, I need to add a different template. This code checks the project's Kind property and, when the project is a website, adds the correct class. Revising the previous code to handle ASP.NET projects produces the following code:

string ItemTemplatePath;
if (prj.Kind == "{E24C65DC-7377-472b-9ABA-BC803B73C61A}")
{
 ItemTemplatePath = sln.GetProjectItemTemplate("Class.zip",
                                                   @"Web\CSharp");
}
else
{
 ItemTemplatePath = sln.GetProjectItemTemplate("Class.zip",
                                                    "CSharp");
}
ProjectItem pji = codeFolder.ProjectItems.AddFromTemplate
            (ItemTemplatePath,"ConnectionManager.Generation.cs");

It's possible, for a number of reasons, that the AddFromTemplate method will successfully add the class file but not return a ProjectItem. (For instance, if the template is a wizard, you won't get a return value because wizards don't return ProjectItems.) So, after adding the item, I check to see if the reference is null; if it is, I use FindProjectItem to get a reference to the class file. (This also provides a check that the class file was successfully added.)

if (pji == null)
{
 pji = sln.FindProjectItem(@ProjectPath + @"\" +
         codeFolder.Name + @"\ConnectionManager.Generation.cs");
}
if (pji == null)
{
  applicationObject.DTE.StatusBar.Text =
                                  "Unable to add class file.";
  return;
}

InformIT Promotional Mailings & Special Offers

I would like to receive exclusive offers and hear about products from InformIT and its family of brands. I can unsubscribe at any time.

Overview


Pearson Education, Inc., 221 River Street, Hoboken, New Jersey 07030, (Pearson) presents this site to provide information about products and services that can be purchased through this site.

This privacy notice provides an overview of our commitment to privacy and describes how we collect, protect, use and share personal information collected through this site. Please note that other Pearson websites and online products and services have their own separate privacy policies.

Collection and Use of Information


To conduct business and deliver products and services, Pearson collects and uses personal information in several ways in connection with this site, including:

Questions and Inquiries

For inquiries and questions, we collect the inquiry or question, together with name, contact details (email address, phone number and mailing address) and any other additional information voluntarily submitted to us through a Contact Us form or an email. We use this information to address the inquiry and respond to the question.

Online Store

For orders and purchases placed through our online store on this site, we collect order details, name, institution name and address (if applicable), email address, phone number, shipping and billing addresses, credit/debit card information, shipping options and any instructions. We use this information to complete transactions, fulfill orders, communicate with individuals placing orders or visiting the online store, and for related purposes.

Surveys

Pearson may offer opportunities to provide feedback or participate in surveys, including surveys evaluating Pearson products, services or sites. Participation is voluntary. Pearson collects information requested in the survey questions and uses the information to evaluate, support, maintain and improve products, services or sites, develop new products and services, conduct educational research and for other purposes specified in the survey.

Contests and Drawings

Occasionally, we may sponsor a contest or drawing. Participation is optional. Pearson collects name, contact information and other information specified on the entry form for the contest or drawing to conduct the contest or drawing. Pearson may collect additional personal information from the winners of a contest or drawing in order to award the prize and for tax reporting purposes, as required by law.

Newsletters

If you have elected to receive email newsletters or promotional mailings and special offers but want to unsubscribe, simply email information@informit.com.

Service Announcements

On rare occasions it is necessary to send out a strictly service related announcement. For instance, if our service is temporarily suspended for maintenance we might send users an email. Generally, users may not opt-out of these communications, though they can deactivate their account information. However, these communications are not promotional in nature.

Customer Service

We communicate with users on a regular basis to provide requested services and in regard to issues relating to their account we reply via email or phone in accordance with the users' wishes when a user submits their information through our Contact Us form.

Other Collection and Use of Information


Application and System Logs

Pearson automatically collects log data to help ensure the delivery, availability and security of this site. Log data may include technical information about how a user or visitor connected to this site, such as browser type, type of computer/device, operating system, internet service provider and IP address. We use this information for support purposes and to monitor the health of the site, identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents and appropriately scale computing resources.

Web Analytics

Pearson may use third party web trend analytical services, including Google Analytics, to collect visitor information, such as IP addresses, browser types, referring pages, pages visited and time spent on a particular site. While these analytical services collect and report information on an anonymous basis, they may use cookies to gather web trend information. The information gathered may enable Pearson (but not the third party web trend services) to link information with application and system log data. Pearson uses this information for system administration and to identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents, appropriately scale computing resources and otherwise support and deliver this site and its services.

Cookies and Related Technologies

This site uses cookies and similar technologies to personalize content, measure traffic patterns, control security, track use and access of information on this site, and provide interest-based messages and advertising. Users can manage and block the use of cookies through their browser. Disabling or blocking certain cookies may limit the functionality of this site.

Do Not Track

This site currently does not respond to Do Not Track signals.

Security


Pearson uses appropriate physical, administrative and technical security measures to protect personal information from unauthorized access, use and disclosure.

Children


This site is not directed to children under the age of 13.

Marketing


Pearson may send or direct marketing communications to users, provided that

  • Pearson will not use personal information collected or processed as a K-12 school service provider for the purpose of directed or targeted advertising.
  • Such marketing is consistent with applicable law and Pearson's legal obligations.
  • Pearson will not knowingly direct or send marketing communications to an individual who has expressed a preference not to receive marketing.
  • Where required by applicable law, express or implied consent to marketing exists and has not been withdrawn.

Pearson may provide personal information to a third party service provider on a restricted basis to provide marketing solely on behalf of Pearson or an affiliate or customer for whom Pearson is a service provider. Marketing preferences may be changed at any time.

Correcting/Updating Personal Information


If a user's personally identifiable information changes (such as your postal address or email address), we provide a way to correct or update that user's personal data provided to us. This can be done on the Account page. If a user no longer desires our service and desires to delete his or her account, please contact us at customer-service@informit.com and we will process the deletion of a user's account.

Choice/Opt-out


Users can always make an informed choice as to whether they should proceed with certain services offered by InformIT. If you choose to remove yourself from our mailing list(s) simply visit the following page and uncheck any communication you no longer want to receive: www.informit.com/u.aspx.

Sale of Personal Information


Pearson does not rent or sell personal information in exchange for any payment of money.

While Pearson does not sell personal information, as defined in Nevada law, Nevada residents may email a request for no sale of their personal information to NevadaDesignatedRequest@pearson.com.

Supplemental Privacy Statement for California Residents


California residents should read our Supplemental privacy statement for California residents in conjunction with this Privacy Notice. The Supplemental privacy statement for California residents explains Pearson's commitment to comply with California law and applies to personal information of California residents collected in connection with this site and the Services.

Sharing and Disclosure


Pearson may disclose personal information, as follows:

  • As required by law.
  • With the consent of the individual (or their parent, if the individual is a minor)
  • In response to a subpoena, court order or legal process, to the extent permitted or required by law
  • To protect the security and safety of individuals, data, assets and systems, consistent with applicable law
  • In connection the sale, joint venture or other transfer of some or all of its company or assets, subject to the provisions of this Privacy Notice
  • To investigate or address actual or suspected fraud or other illegal activities
  • To exercise its legal rights, including enforcement of the Terms of Use for this site or another contract
  • To affiliated Pearson companies and other companies and organizations who perform work for Pearson and are obligated to protect the privacy of personal information consistent with this Privacy Notice
  • To a school, organization, company or government agency, where Pearson collects or processes the personal information in a school setting or on behalf of such organization, company or government agency.

Links


This web site contains links to other sites. Please be aware that we are not responsible for the privacy practices of such other sites. We encourage our users to be aware when they leave our site and to read the privacy statements of each and every web site that collects Personal Information. This privacy statement applies solely to information collected by this web site.

Requests and Contact


Please contact us about this Privacy Notice or if you have any requests or questions relating to the privacy of your personal information.

Changes to this Privacy Notice


We may revise this Privacy Notice through an updated posting. We will identify the effective date of the revision in the posting. Often, updates are made to provide greater clarity or to comply with changes in regulatory requirements. If the updates involve material changes to the collection, protection, use or disclosure of Personal Information, Pearson will provide notice of the change through a conspicuous notice on this site or other appropriate way. Continued use of the site after the effective date of a posted revision evidences acceptance. Please contact us if you have questions or concerns about the Privacy Notice or any objection to any revisions.

Last Update: November 17, 2020