Click here to Skip to main content
15,868,016 members
Articles / Programming Languages / C#

NUnit Starter

Rate me:
Please Sign up or sign in to vote.
4.92/5 (6 votes)
13 Oct 2009Zlib34 min read 42.3K   470   39   8
How to create Visual Studio 2008 Add-in compatible with Visual Studio 2003
Image 1

Contents

Introduction

Motivation

A few years ago I wrote Versioning Controlled Build add-in for Visual Studio 2002. Since its initial release, the add-in has been updated for newer versions of Visual Studio, including VS 2008. However, recently several users complained about being unable to start it on Visual Studio 2008, receiving unhandled COM Exception error. Since the current version of add-in still uses COM-based registration which has been “banned” from Visual Studio 2005, I thought that this problem probably could be avoided using new XML-based add-in registration. As I had no experience with XML-based registration, I decided to create and test an add-in with Visual Studio 2008 and then adopt it to be compatible with VS 2003. During work on this add-in, I found a lot of useful information and hints on the Internet, which I used in this tool and I hope I'll credit all those resources correctly. Definitively, the number one resource for add-in developers is Carlos Quintero’s MZ-Tools page, especially Resources about Visual Studio .NET extensibility section. I often find those pages more valuable than even MSDN pages.

Rationale for the NUnit Starter Add-in

Since I use NUnit for unit tests, the idea for this project was to create an add-in that would simplify starting NUnit tests. Nunit has graphical interface that very clearly shows which tests have been processed and distinguishes passed from failed ones. You can start NUnit as a separate application, select assembly that contains tests and run them, but I thought it would be nice to start tests directly from within Visual Studio, without leaving the IDE. Individual test fixtures are usually placed in separate files, and if the user wants to run that test fixture only, she/he has just to click with the right mouse button on the corresponding item in Solution Explorer and start test from the context menu. If user wants to run all tests in an assembly, she/he would do it in the same way, clicking corresponding project item.

If the test fails for some mysterious reason, the user has to debug it, placing a breakpoint at the appropriate command within a test. Debugging NUnit tests from Visual Studio is pretty awkward: the user must start NUnit GUI, then attach Visual Studio debugger to the corresponding process and finally start the test. Another nice feature of my add-in would be to automate this process as much as possible.

During development I discovered that there are other (maybe even better) solutions already available, just two free tools to mention:

None of these tools did completely meet my requirements: the first one seemed a little bit of an overkill for my needs since it has a lot of additional features; the second one lacks the ability to run individual tests.

Structure of the Article

This article consists of two main parts:

  • General description of creating and testing add-ins, and
  • Description of NUnit Starter add-in

The first part is aimed to those not familiar with add-ins. Those familiar with add-in development will very probably skip it, but I hope the last two sections dealing with backward compatibility and installation issues could be interesting to them too. The second part deals with NUnit Starter implementation details.

Creating an Add-in

Add-in is a class library which can be run from the host application (e.g. Visual Studio or Office application), providing additional or modified functionality. Creating a skeleton for add-in is pretty straightforward if you start with Add-in Wizard. However, there are several options in Add-in Wizard that may be confusing to developers not familiar with add-ins. In this section, I'll try to explain these options and how to correct them afterwards. A reader familiar with this topic may skip this section.

In addition to the initial code, Add-in Wizard creates .Addin configuration file described in the next subsection. Settings in this file are set according to options selected in Add-in Wizard as referenced in the description that follows.

To start the Add-in wizard, in “New Project” dialog inside left-hand side “Project types” pane open “Other Project Types” group and select “Extensibility” subgroup. On the right-hand side “Templates” pane, select “Visual Studio Add-in” and type the name of the add-in project. Note that .NET Framework 2.0 has been selected as a target platform in the upper right corner for backward compatibility reasons. You can always change the target platform later in project settings.

Add New Project dialog

After the Welcome screen, you'll have to choose programming language in which code for the add-in will be written, i.e., in which programming language the code skeleton will be generated. I've chosen C# for this project.

The next dialog provides a list of hosts for which the add-in will be available. These options generate a collection of HostApplication tags in .Addin file. For add-ins, I usually uncheck “Macros” options. Note that any option in this and the following pages can be easily changed even after wizard completion since they are directly related to the content of generated .Addin file or code skeleton.

On entering your Add-in name (FriendlyName tag in .Addin file) and brief description (Description tag), the next page provides add-in options. Let us overview these options. “Would you like to create command bar...” option defines if the wizard will also generate code required for add-in to interact with Visual Studio GUI. If this option is unchecked, Connect class that is generated by Add-in Wizard will contain only methods implementing the IDTExtensibility2 interface:

  • OnAddInsUpdate
  • OnBeginShutdown
  • OnConnection
  • OnDisconnection
  • OnStartupComplete

As you may conclude from their names, methods only represent handlers processed during individual phases of add-in state change. None of these methods allow the user to interact directly with add-in code – the user would have to add controls manually in order to bind them with methods inside the add-in.

Hence, if you want to create an add-in with GUI controls that allow add-in code invocation, the simplest approach is to check the above mentioned option. In that case, Connect class will implement IDTCommandTarget interface as well, with Exec and QueryStatus methods. Exec method is used to launch a command while QueryStatus method controls the state of command, i.e., of corresponding controls. Last, but not the least, with this option checked, the generated Connect class will contain code that creates a command and corresponding item at the top of Tools menu. This menu item is intended to start a command from our add-in.

Of course, if you forget to check this option, you can add Exec and QueryStatus methods later manually. But writing code to create command and GUI element from scratch is pretty complex for a beginner, so do not forget to check this option if you want your add-in to provide user interaction.

With the second option on this page (“I would like my Add-in to load...”), we can control if add-in will be loaded automatically when Visual Studio is started (LoadBehavior). If you leave this option unchecked, add-in has to be loaded through Add-in Manager that is started from Tools menu in Visual Studio. For debugging purposes, especially if something goes wrong with add-in registration, such option is a better solution, so I usually leave it off. You can always change this option later in Add-in Manager.

The third option (“My Add-in will never put up modal UI...”) (CommandLineSafe) should be checked if you intend to create add-ins that should perform unattended operation, e.g., from command-line builds.

The last page with options allows entering optional information that will be included into “Help About Visual Studio” dialog (AboutBoxDetails and AboutIconData tags).

After Add-in Wizard is finished, the project structure will look like in the figure below:

Solution Explorer after Add-In Wizard

.Addin Files

Besides the already mentioned Connect class, you may notice two .Addin files: one stored in the project folder, the other (with “For Testing” suffix in its name) is attached as a link to the project and is stored in Addin folder for the current user. This folder is in “Visual Studio 20xx” folder inside current user’s documents folder (e.g. “C:\Users\UserName\Documents\Visual Studio 2008\Addins”). If you open both files, you'll notice that their contents are identical, except for Assembly tag – linked .Addin file (one with “For Testing” suffix) contains the actual name of the add-in DLL with full path to it as defined by initial settings for generated project.

This .Addin file is used during add-in testing and debugging: if you run the add-in project from Visual Studio, it will open another instance of Studio. During startup, Studio checks contents of several predefined folders and gathers information from all .Addin files found in these folders. If any of those add-ins is set to be loaded during host (i.e. Studio) startup, it will be activated automatically – otherwise the user will have to activate it from Add-in Manager.

Let’s peek into the content of the .Add-in file:

XML
<?xml version="1.0" encoding="UTF-16" standalone="no"?>
   <Extensibility xmlns="http://...">
     <HostApplication>
       <Name>Microsoft  Visual Studio</Name>
       <Version>9.0</Version>
     </HostApplication>
     <HostApplication>
       <Name>Microsoft  Visual Studio</Name>
       <Version>8.0</Version>
     </HostApplication>
     <Addin>
       <FriendlyName>NUnit  Starter</FriendlyName>
       <Description>Launch  NUnit tests from Visual Studio IDE.</Description>
       <AboutBoxDetails>For more  information ...</AboutBoxDetails>
       <AboutIconData>000001000600202010...</AboutIconData>
       <Assembly>C:\Users\  ... \NUnitStarter.dll</Assembly>
       <FullClassName>NUnitStarter.Connect</FullClassName>
       <LoadBehavior>0</LoadBehavior>
       <CommandPreload>1</CommandPreload>
       <CommandLineSafe>0</CommandLineSafe>
     </Addin>
   </Extensibility>

First, you notice a list of host applications for which the add-in is intended. Currently, this list may include Visual Studio 2005 (i.e. version 8.0), 2008 (version 9.0) and 2010 (version 10.0). Inside Addin tag are settings for add-in we actually entered in Add-in Wizard. So if you missed some setting in Add-in Wizard, you can correct it in .Add-in file manually. Since most of these settings are documented in MSDN, I'll skip them.

AboutBoxDetails and AboutIconData are shown when you open Help About dialog in Visual Studio. AboutIconData is binary icon data in hexadecimal form. Hence, if you copy those data into a file by using some hex editor, you will get an ICO file. Default array generated by Add-in Wizard contains small (16x16) and large (32x32) icons in 16, 256 and 16.7 million colors. In order to change the icon, you'll have to use some icon editor (or Visual Studio), create ICO file, then open that file with a hex editor, copy its hexadecimal presentation and paste it into .Addin file.

CommandPreload tag is set to 1 by Add-in Wizard if option “Would you like to create command bar...” has been checked. This will force Visual Studio to “preload” the add-in, i.e. to load it immediately after startup, in order to perform first-time initialization. After initialization, add-in is unloaded to be reloaded either automatically or through Add-in Manager and this “preload” is not performed anymore. The meaning of this flag will be clarified when we'll discuss creating commands and controls.

It should be noted that if you ever change add-in assembly name or its location (e.g. if you change output folder in project settings), you must not forget to synchronize Assembly tag in .Addin file that is used for debugging. Otherwise, Studio won't be able to find the corresponding assembly and load it. Similar problem exists with add-ins created in Visual Studio 2002/2003: when developer initially creates add-in project, corresponding DLL and Connect class are registered by Add-in Wizard. However, the very moment any of these have been modified, the add-in stops working and it has to be re-registered manually or running installation procedure.

Order of Add-in Methods Execution

After Add-in Wizard has created Connect class skeleton, you may run the add-in either by starting a new instance of Visual Studio or starting a debugging session for the project. Although automatically generated code does not implement any functionality, this could be helpful for a beginner in order to learn the sequence in which methods are called by the host application. If you are going to start the add-in from add-in project, just insert breakpoints into each method. Otherwise, if you intend to run it from externally started instance of Visual Studio, you may simply insert message box calls into each method.

We can resolve three different add-in initialization sequences (each preceded with a call to Connect constructor!):

  1. So-called “preload” sequence in the following methods are called:
    • OnConnection with parameter connectMode = ext_cm_UISetup
    • OnDisconnection with disconnectMode = ext_dm_UISetupComplete

    Note that this sequence is executed only once when the add-in is run for the first time. As you may note (from OnDisconnection method call), the add-in is immediately unloaded and then re-initialized by one of two sequences below. This sequence is used for the first-time initialization only as it will be described later.

  2. If the add-in is configured to be loaded automatically with Visual Studio (i.e. LoadBehavior is set to 1) then the following methods are called:
    • OnConnection with connectMode = ext_cm_Startup,
    • OnAddInsUpdate
    • OnStartupComplete
  3. If add-in is initialized from Add-in Manager, the following methods are invoked:
    • OnConnection with connectMode = ext_cm_AfterStartup
    • OnAddInsUpdate

There are two possible unload scenarios:

  1. If add-in is unloaded from Add-in Manager, then only the following method is called:
    • OnDisconnection with disconnectMode = ext_dm_UserClosed
  2. If Visual Studio is exited while add-in is still loaded, the following methods are called:
    • OnBeginShutdown, followed by
    • OnDisconnection with disconnectMode = ext_dm_HostShutdown

As you can see, OnConnection and OnDisconnection methods are called always, independently of how the add-in was loaded or unloaded, differing in connectMode and disconnectMode parameters respectively.

Exec and QueryStatus methods are not called by the host application as long as we do not invoke a command added to the add-in as described in the next section.

Creating Commands and UI Controls

If you selected to create command bar in Add-in Wizard, the generated OnConnection method will contain code to create a command and corresponding item in Tools menu. Command is identified by its name, provided as a second parameter to AddNamedCommand2 method. This name (prefixed with Connect class full name) is verified in Exec and QueryStatus methods to find which command has been launched. Since this code is pretty well commented, I believe there is no need to analyze it in detail. Let’s focus on some not so obvious facts.

As evident from the generated code, the add-in can create its own controls required to launch add-in commands. These controls can be tied to existing controls (menus or toolbars) or can be created on own toolbars. In both cases, controls can be temporary or permanent. Temporary controls are recreated each time the add-in is activated and should be deleted on add-in deactivation, inside OnDisconnection method. Add-in with temporary controls is easier to uninstall because there is no need for additional cleanup. However, if you want to assign a shortcut to some command/control, or need to keep toolbar layout between sessions, permanent controls must be used. A detailed description on how to create temporary or permanent controls can be found here. Note that code generated by Add-in Wizard creates a permanent item in Tools menu.

Back to OnConnection method, it is important to remember that this method is the actual “entry point” for the add-in. It is called by the host application whenever the add-in is activated. This may happen when the user activates it from Add-in Manager or (if configured so) during Visual Studio startup. But as we saw in the previous section, before these invocations OnConnection method will be also executed once for the first time add-in is detected, if CommandPreload tag is set to 1. This call should be used to create permanent controls.

How can we figure out which phase is OnConnection method called in? The answer is given by connectMode parameter:

  • when add-in is first run, this enumeration parameter has ext_cm_UISetup value
  • if add-in is started automatically with Visual Studio startup, its value is ext_cm_Startup
  • if add-in has been activated from Add-in Manager, its value is ext_cm_AfterStartup

Consequently, if we want to create permanent controls, the best approach is to check if connectMode parameter has ext_cm_UISetup value and then initialize controls:

C#
if (connectMode ==  ext_ConnectMode.ext_cm_UISetup)
{
  // initialize permanent controls
}   

Since OnConnection is called with this value for connectMode only once, controls initialization code won't be executed any more. On the other side, to create temporary controls, the user should check if connectMode parameter has ext_cm_AfterStartup value and initialize temporary controls:

C#
if (connectMode ==  ext_ConnectMode.ext_cm_AfterStartup)
{
  // initialize temporary controls
}  

The reader will probably notice that we are missing the case when the add-in is set to load automatically with Visual Studio startup, i.e. when connectMode is equal to ext_cm_Startup. Theoretically, we could add this condition to the above code too, but a better approach is to support that case from OnStartupComplete method. Thus we make sure that Visual Studio GUI has been fully initialized before we create our controls.

OnConnection method’s first argument (object application) is a reference to Visual Studio host. This reference provides a link to Visual Studio automation and through it we can access objects like controls in Visual Studio IDE and current solution. Usually add-in needs to communicate with Visual Studio, so it is vital to store this reference into a class member, as it is done by automatically generated code inside OnConnection method.

You must not forget that each call to OnConnection method is made on separate instance of Connect class and for this reason all necessary data must be (re)initialized. But how can we then reach permanent commands and controls that have been created during preload phase? They have been attached to Visual Studio and cached in CmdUI.prf file inside user’s profile and will be available as long as the add-in is not uninstalled.

Executing Add-in Command

OnConnection method code generated by Add-in Wizard adds command to Commands collection of the EnvDTE object (i.e. Visual Studio) and attaches control (item in Tools menu) to it. When the user clicks that control, Visual Studio will call Exec method. It is up to the user to implement the appropriate actions in this method (for a test, you can just place a statement that will show a message box). Since every add-in may have more than one command, command launched is identified by its full name that is provided as the first parameter to the method. In such case, the code must distinguish which command has been launched by comparing its name first and then perform appropriate code.

QueryStatus method is called whenever state of the command needs to be updated. So, inside this method, the user can enable/disable or hide/show add-in controls.

Debugging Add-in

Add-in can be debugged as any other application. For a simple test, place a breakpoint into the constructor of the Connect class and start the add-in application in Debug mode. First, a new instance of Visual Studio will load and then execution will stop on breakpoint set. Exiting the second instance of Visual Studio closes the debugging session.

To understand how the second instance of Visual Studio is started, you should check Debug settings for the add-in project:

Debug settings for an add-in project

You will notice that under “Start Action” option to start Visual Studio as an external application is set and that its directory is set as working directory. It is important to notice “Command line arguments” value /resetaddin NUnitStarter.Connect. Briefly, this command line argument resets add-in to initial state as if it has never been run. Therefore, each time you run debug session OnConnection method will be called with ext_cm_UISetup value for connectMode parameter first as if the add-in has just been installed for the first time. This may confuse an inexperienced developer who may get the impression that this will happen each time Visual Studio is started even after the add-in is installed on the target machine. In real life, this call happens only for the first time Visual Studio is run after add-in installation.

If you want to test the add-in in another version of Visual Studio, just correct content of “Start external program” and “Working directory” boxes and copy .Addin file to the location corresponding to that version of Visual Studio. Be aware that Visual Studio 2002/2003 require COM registration of the add-in and changes in registry before debugging it.

Add-in Registration

There are two ways for add-in registration with Visual Studio:

  1. COM-registration
  2. XML based registration

COM registration was the only way to register add-in with older versions of Visual Studio (including versions 2002 and 2003). Entry (i.e. Connect) class had to be registered during add-in installation and new key in Visual Studio registry branch had to be added. During startup, Visual Studio reads registry keys in Software\Microsoft\VisualStudio\x.x\Addins branch first (where x.x stands for actual version of Visual Studio, like “7.0” or “7.1” for VS 2002 or VS 2003 respectively). This branch may be in HKEY_LOCAL_MACHINE or HKEY_CURRENT_USER, depending if the add-in has been installed for all users or current user only, respectively. Then it searches the list of registered COM objects to locate the actual location of the DLL with add-in.

Registry content for Add-in

Although COM-registration can still be used with newer versions of Visual Studio, version 2005 introduced much simpler XML based registration in which no COM-object registration neither registry change is required. To register an add-in, the corresponding .Addin file with correct information must be placed in one of the preconfigured folders only. As already mentioned, during startup, Visual Studio scans all these folders and records all corresponding add-ins. List of (pre)configured folders can be found in Options dialog, Environment-Add-in/Macros Security page, as shown below:

Add-in file paths

Keeping Backward Compatibility

One of my intentions was to keep the add-in compatible with previous versions of Visual Studio, specifically with version 2003. This constraint raises several problems that should be clarified.

The first problem is related to the CLR version which will run the code. As the reader probably knows, a standalone application will run on the CLR for which it was targeted during compilation (except if specified differently in .config file). If that CLR is not available on the target machine, later compatible runtime is used. However, for add-ins, CLR version is ruled by the host application: Visual Studio 2003 instantiates CLR 1.1, while VS 2005 and VS 2008 use CLR 2.0. You should be aware of the difference between .NET Framework and CLR versions – .NET Framework 3.0 and 3.5 use CLR 2.0; for details you're advised to check this article.

To write a common code that will run on different CLRs, it must be compatible with the lowest version. When coding for CLR 1.1 and CLR 2.0 this means that we must avoid constructs like partial classes or generics as they are not supported by CLR 1.1. This may cause headaches when generating forms in designer of VS 2005 or 2008 – the generated code has to be merged manually into a single file to circumvent partial class definitions.

Even larger problem than CLRs’ version compatibility is .NET Framework (here I mean class library) versions discrepancy. If .NET Frameworks would be completely backward compatible, then all we need to care is to use classes available in the lowest version. Unfortunately, this is not the case. For example, CommandBar and related controls are defined in Microsoft.Office.Core assembly in .NET Framework 1.1. On the other side, .NET Framework 2.0 (and higher) defines these controls in a new assembly (and in different namespace, of course): Microsoft.VisualStudio.CommandBars.

The only solution to this problem I can think of is to create different versions of add-in assembly for each .NET Framework version from the same source code. It is necessary to create two different projects, each targeting different .NET Frameworks, but both including the same set of source code files and referencing compatible .NET Framework assemblies. Using preprocessor statements, we can conditionally include/exclude parts of code that differ for each target. Conditional code inclusion gives an opportunity to optimize parts of code or provide functionality for newer version of .NET Framework that is not supported with former version, as discussed later in this section.

The main drawback of this approach is the need for manual synchronization of both projects. Each time a file is added or removed in one project, the action must be repeated in the other as well. This could be avoided if the same project could have different build configurations for each target version (which is actually possible as described below) and reference assemblies from distinct .NET Framework for each configuration. Since I am not aware of a simple way to conditionally reference assemblies for each configuration, I use different projects. On the other side, different projects can build outputs for different targets simultaneously, without configuration change.

Suppose that .NET Framework 1.1 targeted project defines TARGETTING_FX_1_1 symbol. In such circumstances, for the code dealing with command bars following condition solves namespace inconsistency:

C#
#if  TARGETTING_FX_1_1
  using Microsoft.Office.Core;
#else
  using Microsoft.VisualStudio.CommandBars;
#endif

Similarly, the preprocessor directives...

C#
#if !TARGETTING_FX_1_1
  using EnvDTE80;
  using _DTE = EnvDTE80.DTE2;
#endif

...make _DTE an alias for richer DTE2 interface.

MSBee for Building .NET 1.1 Projects in Visual Studio 2005/2008

Visual Studio 2005 and 2008 natively support .NET Framework 2.0 (and higher) projects only so if we have chosen to create an add-in for .NET 1.1, we would need to build the corresponding project in Visual Studio 2003. Fortunately, you can get around this limitation using MSBee and build .NET 1.1 projects from VS 2005 or 2008. Detailed instructions how to setup a .NET 1.1 projects in Visual Studio 2008 can be found here.

Summary: How to Create Multiplatform Project

This subsection provides a brief summary of how to create and handle add-in solution targeted for both .NET Framework 1.1 and 2.0. A prerequisite is that you have MSBee (and .NET Framework 1.1) installed.

  • Create an add-in skeleton using Add-in Wizard. To make later compatibility changes easier, chose .NET Framework 2.0 as the target platform.
  • Write the code for the add-in, keeping in mind that code must be compatible with .NET 1.1 (and corresponding CLR) as much as possible, so that the same code can be used for both targets.
  • After you have finished writing the code and your add-in works as expected, create a copy of project (.csproj or .vbproj) file, make necessary modifications in the file as described here and add it to the add-in solution. In the properties of the newly added project, change “Active solution platform” to .NET 1.1 as described in the link mentioned. This will add TARGETTING_FX_1_1 symbol to the project definition; this symbol can be used for conditional compilation.
  • Make the necessary modifications in the code to make it compatible with .NET 1.1. If there is a code for forms generated by Designer, you'll have to merge partial class definitions into a single file class definition and remove superfluous files (files with Designer extension to the filename). Some controls’ properties that are supported in .NET 2.0 only have to be excluded by conditional compilation – otherwise the compiler will report errors. For example, Button control in .NET Framework has UseVisualStyleBackColor property which does not exist in .NET 1.1. Consequently, we have to add conditional compilation around statement that assigns its value in order to make compilation for .NET 1.1 possible:
C#
  // .NET  2.0 specific property
#if !TARGETTING_FX_1_1
  this.m_button.UseVisualStyleBackColor = true;
#endif

Although such merged code can be opened in the Designer without a problem, you must be aware that if you make any changes in the form, Designer will overwrite automatically generated code and all your manual modifications (including conditional compilation statements) will be lost. Therefore, I prefer moving such excluded code outside section that is automatically generated by Designer into Form constructor, just below call of InitializeComponents method.

  • In the project targeted for .NET 1.1, replace references to .NET Framework 2.0 assemblies with references to corresponding assemblies defined in Windows\Microsoft.NET\Framework\v1.1.4322 folder. As already mentioned, reference to Microsoft.VisualStudio.CommandBars must be replaced by reference to Microsoft.Office.Core and you'll have to abandon .NET 2.0 specific assemblies like EnvDTE80.
  • If there is .NET 2.0 specific code that you do not want to sacrifice, you'll have to write .NET 1.1 compatible surrogate and use conditional compilation statements to include or exclude corresponding code sections.
  • Since .NET 1.1 add-in uses COM based registration, you do not need .Addin files in the corresponding project so you can exclude (not delete!) them from the project.

Add-in Installation

Installation of the add-in with XML based registration is pretty straightforward:

  1. Add-in assemblies have to be copied to target folder
  2. Path inside <Assembly> tag in .Addin file must be adjusted to point on this target folder
  3. .Addin file has to be placed in one of predefined folders as described here

These steps can be done manually, but if you want to distribute your software, installation package is what customers expect you to deliver. Carlos Quintero has provided a detailed description of how to create a setup package with Inno Setup tool but I'll stick with Microsoft Installer I am used to.

To create a setup, you need to add a setup project to your Visual Studio solution: in “Other Project Types”, select “Setup and Deployment”. Then you need to add output files from add-in project and that practically accomplishes point one mentioned above.

Next we must assure that the .Addin file will be copied to the correct folder. It should be one of the folders already listed in Environment – Add-in/Macro Security section of Options dialog. Note that there are several options available, but they are different for various versions of Visual Studio. It is reasonable to select a folder that is common for different versions of Visual Studio if we intend to support them all.

When add-in is being installed, the user has the option to install it for all users or for herself/himself (i.e. current user) only. For all users, the best destination for .Addin file would be...

%ALLUSERSPROFILE%\Application Data\Microsoft\MSEnvShared\AddIns

...since both VS 2005 and 2008 have this folder listed. However, “Application Data” is hardcoded even for localized versions of Visual Studio but this by no means corresponds to localized name for Application Data folder in localized Windows versions! For detailed explanation, you may check article by Carlos Quinteros. The second candidate is...

%VSCOMMONAPPDATA%\AddIns

...folder with subfolder for each version to support, i.e., ...

%VSCOMMONAPPDATA%\AddIns\8.0 
%VSCOMMONAPPDATA%\AddIns\9.0

...for Visual Studio 2005 and 2008, respectively.

For the current user selection is easier as there is no “localization bug”:

C#
%APPDATA%\Microsoft\MSEnvShared\AddIns

But how to force the installer to copy .Addin file to the adequate folder (remembering to remove it during uninstallation) and write the correct path into it? Installer does not support these operations directly, so we need to implement them ourselves. To do that, we will create a class derived from Installer class (defined in System.Configuration.Install.dll assembly) simply adding a new Installer class from Visual Studio environment. Installer class has several virtual methods that may be invoked by the installer utility in different phases of installation or uninstallation. For our installation, we have to override Install and Uninstall methods. Install method in our derived class will call implementation from the base class first, change the content of the file to contain correct path to add-in assembly, copy .Addin file to corresponding destination folders and finally save paths to destination folders so that uninstallation can delete them. Uninstall method will delete all .Addin files.

To select the destination folder for .Addin file, we must know if user chose to install add-in for all users or for current one only. This will be provided by the installer utility in “allusers” parameter as described at the end of this section. Our Installer class will get this parameter from Context property:

C#
bool allUsers =  (string)Context.Parameters["allusers"] == "1"; 

Now as we know where to copy .Addin file, the last hurdle to jump is to find out where the file is copied from? One simple solution would be to set the installer to copy this file into output folder where other assembly files are installed, then modify it, copy modified version to destination folders for .Addin files and finally delete the original file. However, this approach could be risky since the .Addin file may be locked by the installer while we are trying to modify it. Therefore I chose the approach proposed here: .Addin file is included into assembly as a resource and our Installer class extracts it, adjusts its content and saves it to appropriate locations.

To activate installer class, it must be included into “Custom Actions”: click with right mouse button on setup project, select View – Custom Actions. In Custom Actions view, add output from the project that contains installer class to Install and Uninstall methods.

Custom Actions

We can add a condition to install a particular add-in version (for .NET 2.0 or .NET 1.1) only if the corresponding version of Visual Studio is installed. To implement this, the installer must first check if Visual Studio is installed looking for VisualStudio.DTE.x.y registry key in HKEY_CLASSES_ROOT registry hive, where x.y represents the version: 7.1 for VS 2003, 8.0 for VS2005, etc. For example, to verify if Visual Studio 2008 is installed, click with right mouse button on setup project, select View – Launch Conditions and “Add registry search condition”. Rename condition, e.g. to “Search for VS2008”, define a property that holds result of search (e.g. VS2008_INSTALLED), enter registry key to search for (“VisualStudio.DTE.9.0”) and Root (“vsdrrHKCR” for HKEY_CLASSES_ROOT). If registry key is not found property will be an empty string.

Launch Conditions

Now, to install .NET 2.0 version of add-in only if Visual Studio 2008 is installed, open File System view for setup project and add condition...

VS2008_INSTALLED <> ""

... for primary output of the corresponding add-in project.

If you add verifications for other versions of Visual Studio in the same way, you can combine conditions using logical Or operator as shown below:

Conditional setup

Finally, as described above, Installer class must know the ALLUSERS property in order to know where .AddIn file should be copied. We pass this value to our Installer class as CustomActionData – just enter...

/allusers=[ALLUSERS]

... in Custom Actions view of the setup project (c.f. Custom Actions screenshot above).

Installation for Visual Studio 2003

There are two issues that must be taken care of when creating add-in for Visual Studio 2003 (or older):

  1. Add-in assembly must be registered as a COM object.
  2. Visual Studio checks available add-ins from registry, so appropriate registry keys must be set.

Add-in COM registration should be done by installer application. To facilitate that, after adding output from .NET 1.1 project, open properties window for that output, find “Register” property and change it to “vsdrpCOM”. It is worthwhile to note that this will register all public classes in the assembly. Therefore, you should declare classes internal whenever possible.

Adding required registry keys and values to installation procedure is pretty straightforward: click with right mouse button on setup project and select View – Registry. In Registry View, add key with full name (i.e. with namespace) of the Connect class into Visual Studio AddIns key. You will need to create the entire path to the key in “User/Machine Hive” and the installer will automatically select if this key should be created in HKEY_CURRENT_USER or HKEY_LOCAL_MACHINE branch, depending on what user selects during installation. Registry key contains practically the same entries found inside the .Addin file, so you can just copy them.

Registry settings in setup

About NUnit Starter

Implementation

NUnitStarter add-in launches NUnit providing command-line parameters to start a specific test. Since NUnit does not support multiple arguments, it is possible to start single test frame or start tests in one assembly only. There are two supported scenarios:

  1. When the user clicks with right mouse button on an item in the Solution Explorer, add-in checks if the corresponding item is a file with .NET compatible (C#, VB.NET or J#) code and if there is TextFixture attribute applied to any class in that file. If former condition is satisfied, command is shown in context menu but is enabled only if the latter condition is fulfilled.
  2. When the user clicks on a project, verification is made if it is a .NET project and command is shown on context menu. There are no other checks made since they could take quite a long time.

In both cases, after the user has selected the command, add-in creates appropriate command-line parameters based on current selection and launches NUnit test. For a single item, only the first test fixture found in the file is run. For an assembly, all available tests in the assembly will be run.

The figure below shows simplified class diagram. Connect class contains an instance of NUnitExecutor that is responsible to start a process with NUnit test framework when user selects a command. NUnitExecutor contains two public methods for loading and running GUI tests and for debugging tests. Methods are invoked from Exec method of the Connect class. To make sure that NUnit GUI will be closed when Visual Studio is being closed and to prevent more than one instance of NUnit process to be running simultaneously, NUnitExecutor implements IDisposable interface, in which Dispose method is responsible to close active NUnit instance.

Class Diagram

SelectedItemInfo provides the necessary information to NUnitExecutor on currently selected item in Solution Explorer. CommandStatus class contains two public methods: IsAvailable and IsEnabled. They are used to show/hide and enable/disable controls for launching tests and are called from QueryStatus method of Connect class. UiElements class creates and destroys commands and corresponding controls. SelectedItemType is a utility class used to determine the type of selected item: is it .NET or some other type of project, is it .NET source file or some other type of files.

In addition to starting tests, add-in allows debugging test units. Since EnvDTE.Process property can attach running process only (which is equivalent to attaching a process from Visual Studio IDE), my initial idea was to implement debugging as suggested by NUnit documentation:

  1. Start NUnit GUI test frame, but contrary to test command, without running test(s) automatically
  2. Attach NUnit process to Visual Studio – the tool does this automatically searching for process ID
  3. User has to click Run button in NUnit GUI to start debugging.

Code implementing this approach looks like:

C#
System.Diagnostics.Process proc = new  System.Diagnostics.Process();
string cla = ...;  // command line  arguments
proc.StartInfo = CreateProcessStartInfo(nUnitExecutable, cla);
proc.Start();
proc.WaitForInputIdle();  // wait until  process is fully initialized
EnvDTE.Process proc2Attach =  GetProcessById(applicationObject, proc.Id);
proc2Attach.Attach();

private EnvDTE.Process  GetProcessById(_DTE applicationObject, int procId)
{
  EnvDTE.Processes  processes = applicationObject.Debugger.LocalProcesses;
  foreach  (EnvDTE.Process proc in processes)
  {
    if  (proc.ProcessID == procId)
      return  proc;
  }
}

Meanwhile, I have come upon the brilliant solution given in Unitit add-in which allows fully automatic debugging without subsequent user interaction. In short, the idea is as follows:

  1. Create suspended process using WinApi CreateProcess function
  2. Create a common language runtime debugging engine for default debugging transport
  3. Attach debugger to suspended process, and finally
  4. Resume the suspended process

Simplified code implementing the above procedure looks like:

C#
Win32Api.StartupInfo si =  new Win32Api.StartupInfo();
Win32Api.ProcessInformation pi;
bool success = Win32Api.CreateProcess(null,  cmdLine, IntPtr.Zero, IntPtr.Zero, true,  
    Win32Api.CreationFlags.CREATE_SUSPENDED |  Win32Api.CreationFlags.CREATE_NO_WINDOW, 
    IntPtr.Zero, null, ref si, out pi);
Process2 p2 = (Process2)GetProcessById(applicationObject, pi.ProcessId);
Engine[] engines = new  Engine[1];
Debugger2 debugger = (Debugger2)applicationObject.Debugger;
Transport transport = debugger.Transports.Item(1);
engines[0] = transport.Engines.Item("Managed");
p2.Attach2(engines);
Win32Api.ResumeThread(pi.ThreadHandle);

For debugging purposes, we do not need fancy NUnit GUI window so console is started instead with window hidden.

Unfortunately, this approach is supported only by Visual Studio 2005 (and later) automation model and thus for Visual Studio 2003, we are left with the former approach.

Using NUnit Starter

In order to use NUnit Starter add-in, in addition to one of the supported versions of Visual Studio (2003/2005/2008) and .NET Framework 2.0, you must have NUnit installed. For running tests, any recent version of NUnit (2.4.x or 2.5.x) can be used. However, to debug tests, version of NUnit runtime must match CLR version used by Visual Studio. This means that if you want to debug tests on Visual Studio 2003, NUnit 2.4.x must be used, while to debug tests on Visual Studio 2005 or 2008, NUnit 2.5.x must be installed. Note that different versions of NUnit may coexist on the same machine.

If you have the above version(s) of NUnit installed, the add-in should automatically configure itself when it is loaded for the first time, searching for the latest compatible version of NUnit installed. It should be pointed out that the add-in is not configured to load automatically, but the user has to load it and configure startup behavior from Add-in Manager dialog.

You may re-configure NUnit settings (e.g. if you install a newer version of NUnit) running Configurator application. Command line parameters in configuration may contain placeholders {0} and {1} representing the assembly name and (optional) file name that contain test(s) to be run, respectively.

Additional details may be found in Readme.txt file installed with add-in.

Add-in has been tested with all versions of NUnit 2.4.x and 2.5.x, up to 2.5.2.

Add-in does not work with Visual Studio 2010.

History

  • 1.0 (October 12, 2009) - Initial version running on Visual Studio 2003/2005/2008 and supporting NUnit 2.4.x and 2.5.x

License

This article, along with any associated source code and files, is licensed under The zlib/libpng License


Written By
Software Developer (Senior)
Croatia Croatia
Graduated at the Faculty of Electrical Engineering and Computing, University of Zagreb (Croatia) and received M.Sc. degree in electronics. For several years he was research and lecturing assistant in the fields of solid state electronics and electronic circuits, published several scientific and professional papers, as well as a book "Physics of Semiconductor Devices - Solved Problems with Theory" (in Croatian).
During that work he gained interest in C++ programming language and have co-written "C++ Demystified" (in Croatian), 1st edition published in 1997, 2nd in 2001, 3rd in 2010, 4th in 2014.
After book publication, completely switched to software development, programming mostly in C++ and in C#.
In 2016 coauthored the book "Python for Curious" (in Croatian).

Comments and Discussions

 
GeneralMy vote of 5 Pin
Morries13-Dec-11 20:07
Morries13-Dec-11 20:07 
GeneralCOM add-in registration Pin
Anna-Jayne Metcalfe15-Oct-09 23:38
Anna-Jayne Metcalfe15-Oct-09 23:38 
COM add-in registration actually works quite well in all versions of Visual Studio (even the upcoming VS2010). Like every piece of software, individual versions have their own "party quirks", e.g.:

  • Per user COM registration in VS2002/2003 is buggy (but local machine registration works just fine)
  • The load state of add-ins registered by COM in the local machine hive in VS2005 cannot be changed in the Add-in Manager (this was fixed in VS2008).
Hence it's perfectly plausible to write a single add-in which runs in all versions of Visual Studio (even the pre-VS2002 versions if you get the interfaces right). If the add-in uses native code exclusively (the restriction is an unfortunate result of versioning restrictions in the .NET Framework) you can even do this in a single binary without recompilation.

But I digress. Great article!

Anna Rose | [Rose]

Having a bad bug day?

Tech Blog | Anna's Place | Tears and Laughter

"If mushy peas are the food of the devil, the stotty cake is the frisbee of God"

modified on Friday, October 16, 2009 6:17 AM

AnswerRe: COM add-in registration Pin
Julijan Sribar16-Oct-09 8:15
Julijan Sribar16-Oct-09 8:15 
GeneralRe: COM add-in registration Pin
Anna-Jayne Metcalfe16-Oct-09 8:34
Anna-Jayne Metcalfe16-Oct-09 8:34 
AnswerRe: COM add-in registration Pin
Julijan Sribar16-Oct-09 9:58
Julijan Sribar16-Oct-09 9:58 
GeneralRe: COM add-in registration Pin
Anna-Jayne Metcalfe16-Oct-09 10:56
Anna-Jayne Metcalfe16-Oct-09 10:56 
AnswerRe: COM add-in registration Pin
Julijan Sribar16-Oct-09 11:26
Julijan Sribar16-Oct-09 11:26 
GeneralRe: COM add-in registration Pin
Anna-Jayne Metcalfe16-Oct-09 11:35
Anna-Jayne Metcalfe16-Oct-09 11:35 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.