Tying Generation to Events
Rather than use a menu item to trigger code generation, a better solution for this case study is to tie the code generation to events in Visual Studio. The obvious choice for this case study is to check for changes in the configuration file: Whenever the configuration file is closed, for instance, the code could check for the presence of connection strings and regenerate the ConnectionManager. This is the strategy used for generating the code behind the .NET DataSet designer: The code is generated when the designer is closed (and, in Visual Studio 2008 and 2010, when the focus shifts away from the DataSet's visual designer).
However, the developer also needs a way to force the ConnectionManager to be regenerated if only for those situations where the developer wants to make a change and leave the configuration file open. You could leave the menu item in place (or add a button to the ConnectionManager's Tools | Options dialog). However, a better solution is to tie the code generation into the build process.
Integrating with Builds
Integrating with the build process is the easier of the two events to set up, so I look at that option first. The first step is to declare a class-level variable to hold a reference to the events package that references build events. For a build-related event, that variable is declared with the BuildEvents data type, as this code does:
EnvDTE.BuildEvents BuildE;
In order to tie the code-generation property into Visual Studio events, I have to wire up the events when the add-in is loaded by Visual Studio. I have a couple of choices here: I can either use the add-in's constructor (called from the Connect method in C# or the New method in Visual Basic) or the OnConnection method. I use the OnConnection method because I can check the connectMode parameter passed to the method to ensure that the method is being called in setup mode. Just to be safe, though, I also check that I haven't already set up the event by seeing if my class-level variable is set to null.
This code retrieves the BuildEvents package and then ties a method in the connect class (which I've named BuildE_OnBuildBegin) to the OBuildBegin event:
if (connectMode == ext_ConnectMode.ext_cm_UISetup) { if (BuildE == null) { BuildE = _applicationObject.Events.BuildEvents; BuildE.OnBuildBegin += new _dispBuildEvents_OnBuildBeginEventHandler(BuildE_OnBuildBegin); }
In my OnBuildBegin method, I need to create my generation class (DatabaseUtilities) and call my code-generation method (GenerateConnectionManager) whenever the project is being rebuilt. To determine whether the project is being rebuilt, I can check the two flags passed into the event handler:
- BuildScope—Reports the scope of the build (batch, project, or solution)
- BuildAction—Type of build (Clean, Build, Rebuildall, Deploy)
If I created temporary files or folders that I didn't automatically delete as part of the code-generation process, I should remove those when the event is called with BuildAction set to Clean. However, for this solution, the Clean action is the one BuildAction where I don't want to regenerate ConnectionManager:
void BuildE_OnBuildBegin(vsBuildScope BuildScope, vsBuildAction BuildAction) { if (BuildAction != vsBuildAction.vsBuildActionClean) { DatabaseUtiltiies dbu; dbu = new DatabaseUtiltiies(_applicationObject, _addInInstance); dbu.GenerateConnectionManager(); } }
Integrating with Documents
To catch the events that fire when the configuration file is opened or closed, I first have to catch the events fired when any document is opened or closed. I add another field (named docMaster) to my class to hold the DocumentEvents package for this "master" document event routine. Eventually, I'm going to need a reference for the event that ties to my configuration file, so I also add a field (named docConfig) to hold that reference:
EnvDTE.DocumentEvents docMaster; EnvDTE.DocumentEvents docConfig;
In the OnConnection method, when the method is called in setup mode, I check to see if I've set the event package; if I haven't, I set the reference. Once I've set the reference, I attach a method (which I've called docMaster_DocumentOpened) to the DocumentOpened event. The DocumentOpened event is a filtered event: Because I pass a reference to a document to this event, the event will only fire when that specific document is opened. For my "master" event handler, however, I want to catch events for all documents, so I pass a null as part of wiring up the event:
if (docMaster == null) { docMaster = (EnvDTE.DocumentEvents) _applicationObject.Events.get_DocumentEvents(null); docMaster.DocumentOpened +=new _dispDocumentEvents_DocumentOpenedEventHandler( docMaster_DocumentOpened); }
In my docMaster_DocumentOpened event handler, I want to wire up an event routine that will fire when the configuration file is closed. (This may be just the initial version of this handler: If I expand this solution, I can add more code in this handler to check for other documents that I'm interested in and wire up events for them also.) I first check to see if I've already set an event for the configuration file by checking the field where I hold the reference (docConfig). If that field is null, I then check the Document parameter passed to the event handler to see if the document being opened is either the web.config or app.config file:
void docMaster_DocumentOpened(Document Document) { if (docConfig == null && Document.Name == "app.config") {
If the document being opened is either of the configuration files, I get a reference to the DocumentEvents package as I did in setting up the master event handler. This time, however, I filter the event by passing the Document object that represents the configuration file. In this case study, I want to capture the DocumentClosing event so I wire up the DocumentClosingEventHandler to a routine I've named docConfig_DocumentClosing:
docConfig = (EnvDTE.DocumentEvents) _applicationObject.Events.get_DocumentEvents(Document); docConfig.DocumentClosing +=new _dispDocumentEvents_DocumentClosingEventHandler( docConfig_DocumentClosing); } }
In my docConfig_Closing event handler, as before, I create my code-generation class and call the method that creates the connection manager. In addition, at the end of the routine, I set the reference for this handler to null:
void docConfig_DocumentClosing(Document Doc) { DatabaseUtilities dbu; dbu = new DatabaseUtiltiies(_applicationObject, _addInInstance); dbu.GenerateConnectionManager(); docConfig = null; }