Click here to Skip to main content
6,595,444 members and growing! (17,144 online)
Email Password   helpLost your password?
Platforms, Frameworks & Libraries » .NET Framework » Utilities     Intermediate License: The Code Project Open License (CPOL)

Add-in to attach ASP.NET debugger

By Achintya Jha

This visual studio add-in puts a nice-cool shortcut button on your debug toolbar to attach your code to ASP.NET debugger (aspnet_wp.exe).
C# (C# 1.0, C# 2.0, C# 3.0), Windows (WinXP), .NET (.NET 2.0), ASP.NET, Visual Studio (VS2005), Dev, Design
Posted:30 Oct 2008
Views:10,498
Bookmarked:35 times
Unedited contribution
Announcements
Loading...
 
Search    
Advanced Search
Add to IE Search
printPrint   add Share
      Discuss Discuss   Broken Article?Report  
6 votes for this article.
Popularity: 3.42 Rating: 4.40 out of 5

1

2
1 vote, 16.7%
3
1 vote, 16.7%
4
4 votes, 66.7%
5
Download add-in project - 75.56 KB

Download add-in executables - 15.59 KB

Introduction

As our ASP.NET application grows, we can no longer use the normal Start debugging button StartDebug.GIF to debug our application, because the application might be doing a lot of other processing before jumping to our code where we are interested in debugging. This is when, the option of Debug -> Attach to process ... in Visual Studio comes handy. This is in use by many of us where we navigate to the concerned page where there is a problem, and then we attach the debugger to the aspnet_wp.exe process. But, this step, requires, 4 mouse clicks and when you are doing this same thing 100 times a day, its 400 clicks and so on. I was annoyed by this and thats why I created this add-in which places a nice button on the debug toolbar to attach to the asp.net process with a single click.

Background

This was my first add-in development, so I had to digg around on the web a lot and actually I got a lot of help from www.mztools.com.

I got a lot of help from these two articles on their site and of course MSDN helped.

HOWTO: Adding buttons, commandbars and toolbars to Visual Studio .NET from an add-in

HOWTO: Capturing commands events from Visual Studio .NET add-ins

Using the code

To use the add-in, unzip the AttachToASPNETAddinDLL.zip from the Download add-in executables - 15.59 KB section. Copy the two files AttachToASPNETAddin.AddIn and AttachToASPNETAddin.dll to the Addins folder in your <My Documents folder>\Visual Studio 2005\. If that folder is not there, create that folder. This is the folder from where Visual studio picks up the add-ins. So, there is no need for a setup project here. The Add-ins in Visual Studio 2005 are now just an xCopy deployment.

The .Addin file is just an XML file which helps Visual Studio in locating the Add-in executable and various parameters like Company, Author name etc. The important one is Assembly node which keeps the path of the add-in executable. This path can be local, relative or even an URL.

Now, open just one VS 2005 instance and select Tools -> Add-in Manager as shown in this image.

Check the check-box on the left to the Add-in name and also check Startup checkbox.
This operation will bring up the add-in button in this instance and also in all the later VS 2005 instances.

The debugger button looks like this when the add-in is loaded.
AttachImage.PNG

The debugger button looks like this when the button is pressed and the code is attached to the aspnet_wp process. Pressing the button (now with a cross sign) detaches the code from the debugger.
DetachImage.PNG

These buttons can be easily changed by changing the constants ATTACH_ICON_FACEID and DETACH_ICON_FACEID with the faceID you want to show.
There are plenty of web pages flowing on the internet if you 'google' for faceids.

I got a faceID browsing utility as an excel macro from http://peltiertech.com/Excel/Zips/ShowFace.zip

Here is the source code of the backbone of the add-in, the Connect class

using System.Diagnostics; 
using System;
using Extensibility;
using EnvDTE;
using EnvDTE80;
using Microsoft.VisualStudio.CommandBars;
using System.Resources;
using System.Reflection;
using System.Globalization;
using System.Windows.Forms;

namespace AttachToASPNETAddin
{
    /// The object for implementing an Add-in.
    /// 
    public class Connect : IDTExtensibility2, IDTCommandTarget
    {
        private const string ATTACH_TOOLTIP = "Attach ASP.NET debugger";
        private const int ATTACH_ICON_FACEID = 2151;
        private const string DETACH_TOOLTIP = "Detach ASP.NET debugger";
        private const int DETACH_ICON_FACEID = 1088;
        private const string MY_COMMAND_NAME = "AttachToASPNETAddin";
        private DTE2 _applicationObject;
        private AddIn _addInInstance; 
        private CommandBarControl standardCommandBarControl;
        private CommandEvents commandEvents;
        private CommandBarButton commandBarButton;
        
        /// Implements the constructor for the Add-in object. Place your initialization 
    /// code within this method.
        public Connect()
        {
        }

        /// Implements the OnConnection method of the IDTExtensibility2 interface. 
    /// Receives notification that the Add-in is being loaded.
        /// Root object of the host application.
        /// Describes how the Add-in is being loaded.
        /// Object representing this Add-in.
        /// 
        public void OnConnection(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom)
        {
            try
            {
                _applicationObject = (DTE2)application;
                _addInInstance = (AddIn)addInInst;
                
                // get all the command events
                commandEvents = _applicationObject.Events.get_CommandEvents("{00000000-0000-0000-0000-000000000000}", 0);                
                // attach the event handler
                commandEvents.BeforeExecute += new _dispCommandEvents_BeforeExecuteEventHandler(commandEvents_BeforeExecute);

                switch (connectMode)
                {
                    case ext_ConnectMode.ext_cm_Startup:
                        break;
                    // The add-in was marked to load on startup 
                    // Do nothing at this point because the IDE may not be fully initialized 
                    // VS will call OnStartupComplete when ready 

                    case ext_ConnectMode.ext_cm_AfterStartup:
                        Array dummyArr = new string[1];
                        // The add-in was loaded after startup 
                        // Initialize it in the same way that when is loaded on startup calling manually this method: 
                        OnStartupComplete(ref dummyArr);
                        break;

                    case ext_ConnectMode.ext_cm_UISetup:

                        object[] contextGUIDS = new object[] { };
                        Commands2 commands = (Commands2)_applicationObject.Commands;
                        string toolsMenuName;

                        try
                        {
                            //If you would like to move the command to a different menu, change the word "Tools" to the 
                            //  English version of the menu. This code will take the culture, append on the name of the menu
                            //  then add the command to that menu. You can find a list of all the top-level menus in the file
                            //  CommandBar.resx.
                            ResourceManager resourceManager = new ResourceManager("AttachToASPNETAddin.CommandBar", Assembly.GetExecutingAssembly());
                            CultureInfo cultureInfo = new System.Globalization.CultureInfo(_applicationObject.LocaleID);
                            string resourceName = String.Concat(cultureInfo.TwoLetterISOLanguageName, "Tools");
                            toolsMenuName = resourceManager.GetString(resourceName);
                        }
                        catch
                        {
                            //We tried to find a localized version of the word Tools, but one was not found.
                            //  Default to the en-US word, which may work for the current culture.
                            toolsMenuName = "Tools";
                        }

                        //Place the command on the tools menu.
                        //Find the MenuBar command bar, which is the top-level command bar holding all the main menu items:
                        Microsoft.VisualStudio.CommandBars.CommandBar menuBarCommandBar = 
              ((Microsoft.VisualStudio.CommandBars.CommandBars)_applicationObject.CommandBars)["MenuBar"];

                        //Find the Tools command bar on the MenuBar command bar:
                        CommandBarControl toolsControl = menuBarCommandBar.Controls[toolsMenuName];
                        CommandBarPopup toolsPopup = (CommandBarPopup)toolsControl;

                        //This try/catch block can be duplicated if you wish to add multiple commands to be handled by your Add-in,
                        //  just make sure you also update the QueryStatus/Exec method to include the new command names.
                        try
                        {
                            //Add a command to the Commands collection:
                            Command command = commands.AddNamedCommand2(_addInInstance, "AttachToASPNETAddin", "AttachToASPNETAddin", 
                                "Executes the command for AttachToASPNETAddin", true, 59, ref contextGUIDS, 
                (int)vsCommandStatus.vsCommandStatusSupported + (int)vsCommandStatus.vsCommandStatusEnabled, 
                (int)vsCommandStyle.vsCommandStylePictAndText, vsCommandControlType.vsCommandControlTypeButton);

                            //Add a control for the command to the tools menu:
                            if ((command != null) && (toolsPopup != null))
                            {
                                command.AddControl(toolsPopup.CommandBar, 1);
                            }
                        }
                        catch (System.ArgumentException)
                        {
                            //If we are here, then the exception is probably because a command with that name
                            //  already exists. If so there is no need to recreate the command and we can 
                            //  safely ignore the exception.
                        }
                        break;
                }
            }
            catch (Exception e)
            {
                MessageBox.Show(e.ToString());
            }
        }

        private void commandEvents_BeforeExecute(string Guid, int ID, object CustomIn, object CustomOut, ref bool CancelDefault)
        {
            EnvDTE.Command objCommand = default(EnvDTE.Command);
            string sCommandName = null;

            objCommand = _applicationObject.Commands.Item(Guid, ID);

            if ((objCommand != null))
            {
                sCommandName = objCommand.Name;
                if (string.IsNullOrEmpty(sCommandName))
                {
                    sCommandName = "";
                }

                // if the command name matches these commands, modify the button
                if (sCommandName.Equals("Debug.Start") /* Command ID is 295 */ ||
                    sCommandName.Equals("Debug.AttachtoProcess")) /* Command ID is 213 */
                {
                    commandBarButton.FaceId = DETACH_ICON_FACEID;
                    commandBarButton.TooltipText = DETACH_TOOLTIP;
                }
                else if (sCommandName.Equals("Debug.StopDebugging") /* Command ID is 179 */ || 
                    sCommandName.Equals("Debug.DetachAll") ||
                    sCommandName.Equals("Debug.TerminateAll"))
                {
                    commandBarButton.FaceId = ATTACH_ICON_FACEID;
                    commandBarButton.TooltipText = ATTACH_TOOLTIP;
                }
            } 
        }

        /// Implements the OnDisconnection method of the IDTExtensibility2 interface. 
    /// Receives notification that the Add-in is being unloaded.
        /// Describes how the Add-in is being unloaded.
        /// Array of parameters that are host application specific.
        /// 
        public void OnDisconnection(ext_DisconnectMode disconnectMode, ref Array custom)
        {
            try
            {
                if ((standardCommandBarControl != null))
                {
                    standardCommandBarControl.Delete(true);
                }
                commandEvents = null;
            }

            catch (System.Exception e)
            {
                System.Windows.Forms.MessageBox.Show(e.ToString());
            }
        }

        /// Implements the OnAddInsUpdate method of the IDTExtensibility2 interface. 
    /// Receives notification when the collection of Add-ins has changed.
        /// Array of parameters that are host application specific.
        ///         
        public void OnAddInsUpdate(ref Array custom)
        {
        }

        /// Implements the OnStartupComplete method of the IDTExtensibility2 interface. 
    /// Receives notification that the host application has completed loading.
        /// Array of parameters that are host application specific.
        /// 
        public void OnStartupComplete(ref Array custom)
        {
            Command loCommand = null;
            CommandBar loCommandBar = default(CommandBar);
            commandBarButton = default(CommandBarButton);

            CommandBars loCommandBars = default(CommandBars);
            try
            {
                // Try to retrieve the command, just in case it was already created 
                try
                {
                    loCommand = _applicationObject.Commands.Item(_addInInstance.ProgID + "." + MY_COMMAND_NAME, -1);
                }
                catch
                {
                }
                // Add the command if it does not exist 
                if (loCommand == null)
                {
                    object[] dummyObject = new object[1];
                    loCommand = _applicationObject.Commands.AddNamedCommand(_addInInstance, MY_COMMAND_NAME, 
            MY_COMMAND_NAME, "Executes the command for MyAddin", true, 59, ref dummyObject, 
            (int)(vsCommandStatus.vsCommandStatusSupported | vsCommandStatus.vsCommandStatusEnabled));
                }
                // get the collection of commandbars 
                loCommandBars = (CommandBars)_applicationObject.CommandBars;
                // Now get the debug toolbar instance 
                loCommandBar = loCommandBars["Debug"];
                // Now add a button to the built-in "debug" toolbar 
                standardCommandBarControl = (CommandBarControl)loCommand.AddControl(loCommandBar, loCommandBar.Controls.Count + 1);
                standardCommandBarControl.Caption = MY_COMMAND_NAME;
                // Change the button style 
                commandBarButton = (CommandBarButton)standardCommandBarControl;
                commandBarButton.Style = MsoButtonStyle.msoButtonIcon;
                commandBarButton.FaceId = ATTACH_ICON_FACEID;
                commandBarButton.TooltipText = ATTACH_TOOLTIP;
                // attach the button click event
                commandBarButton.Click += new _CommandBarButtonEvents_ClickEventHandler(commandBarButton_Click);
            }
            catch (System.Exception e)
            {
                System.Windows.Forms.MessageBox.Show(e.ToString());
            }
        }

        private void commandBarButton_Click(CommandBarButton pCommBarBtn, ref bool CancelDefault)
        {
            try
            {
                // Get an instance of the currently running Visual Studio IDE.
                EnvDTE80.DTE2 dte2;
                dte2 = (EnvDTE80.DTE2)System.Runtime.InteropServices.Marshal.GetActiveObject("VisualStudio.DTE.8.0"); 
                EnvDTE80.Debugger2 dbg2 = (EnvDTE80.Debugger2)(dte2.Debugger);
                EnvDTE80.Transport trans = dbg2.Transports.Item("Default");
                EnvDTE80.Engine[] dbgeng = new EnvDTE80.Engine[2];
                dbgeng[0] = trans.Engines.Item("Managed");
                EnvDTE80.Process2 proc2 = (EnvDTE80.Process2)dbg2.GetProcesses
                    (trans, System.Environment.MachineName).Item("aspnet_wp.exe");
                if ((proc2.IsBeingDebugged))
                {
                    proc2.Detach(false);
                    pCommBarBtn.FaceId = ATTACH_ICON_FACEID;
                    pCommBarBtn.TooltipText = ATTACH_TOOLTIP;
                }
                else
                {
                    proc2.Attach2(dbgeng);
                    pCommBarBtn.FaceId = DETACH_ICON_FACEID;
                    pCommBarBtn.TooltipText = DETACH_TOOLTIP;
                }
            }
            catch (System.Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        /// Implements the OnBeginShutdown method of the IDTExtensibility2 interface. 
    /// Receives notification that the host application is being unloaded.
        /// Array of parameters that are host application specific.
        /// 
        public void OnBeginShutdown(ref Array custom)
        {
        }
        
        /// Implements the QueryStatus method of the IDTCommandTarget interface. 
    /// This is called when the command's availability is updated
        /// The name of the command to determine state for.
        /// Text that is needed for the command.
        /// The state of the command in the user interface.
        /// Text requested by the neededText parameter.
        /// 
        public void QueryStatus(string commandName, vsCommandStatusTextWanted neededText, 
      ref vsCommandStatus status, ref object commandText)
        {
          if(neededText == vsCommandStatusTextWanted.vsCommandStatusTextWantedNone)
            {
                if(commandName == "AttachToASPNETAddin.Connect.AttachToASPNETAddin")
                {
                    status = (vsCommandStatus)vsCommandStatus.vsCommandStatusSupported|vsCommandStatus.vsCommandStatusEnabled;
                    return;
                }
            }
        }

        /// Implements the Exec method of the IDTCommandTarget interface. 
    /// This is called when the command is invoked.
        /// The name of the command to execute.
        /// Describes how the command should be run.
        /// Parameters passed from the caller to the command handler.
        /// Parameters passed from the command handler to the caller.
        /// Informs the caller if the command was handled or not.
        /// 
        public void Exec(string commandName, vsCommandExecOption executeOption, 
      ref object varIn, ref object varOut, ref bool handled)
        {
            handled = false;
            if(executeOption == vsCommandExecOption.vsCommandExecOptionDoDefault)
            {
                if(commandName == "AttachToASPNETAddin.Connect.AttachToASPNETAddin")
                {
                    handled = true;
                    return;
                }
            }
        }
    }
}

Points of Interest

Building an add-in seems to be complicated in the beginning as VS 2005 Add-in wizard spits out a lot of built-in code which looks like pre-war ones. The object names are also scary which has a prefix 2 (introduced in Visual Studio 2005) like IDTExtensibility2, DTE2 etc.

Fun part was catching the events of Visual Studio events (e.g. when the VS's own Start debugging button is invoked or stopped using the Stop button). After learning how to catch these events, I was able to modify the functionality of the add-in button and its image according to the debugger state. You can see that in the commandEvents_BeforeExecute event.

This article works fine for Visual Studio 2005, but will need only the DTE class level modification to work for Visual Studio 2008.

History

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Achintya Jha


Member
Microsoft Certified Applications Developer
Occupation: Software Developer (Senior)
Location: United States United States

Other popular .NET Framework articles:

Article Top
You must Sign In to use this message board.
FAQ FAQ 
 
Noise Tolerance  Layout  Per page   
 Msgs 1 to 14 of 14 (Total in Forum: 14) (Refresh)FirstPrevNext
GeneralTwo aspnet instances Pinmembercerberiukas21:11 6 Nov '08  
GeneralRe: Two aspnet instances PinmemberAchintya Jha17:55 9 Nov '08  
GeneralMultiple instances of visual studio... Pinmembershadyboxmonster5:20 5 Nov '08  
GeneralRe: Multiple instances of visual studio... PinmemberAchintya Jha17:55 9 Nov '08  
GeneralInvalid Index PinmemberMagic PC0:26 4 Nov '08  
GeneralRe: Invalid Index PinmemberAchintya Jha5:41 4 Nov '08  
GeneralRe: Invalid Index PinmemberMagic PC6:16 4 Nov '08  
GeneralRe: Invalid Index PinmemberAchintya Jha7:06 4 Nov '08  
GeneralRe: Invalid Index PinmemberMagic PC7:37 4 Nov '08  
GeneralPath Add-in problems and VS2008 support PinmemberPeter_J23:25 30 Oct '08  
GeneralRe: Path Add-in problems and VS2008 support PinmemberAchintya Jha4:14 31 Oct '08  
GeneralFormat.... please Pinmemberzitun23:13 30 Oct '08  
GeneralRe: Format.... please PinmemberAchintya Jha4:15 31 Oct '08  
GeneralRe: Format.... please PinmemberAchintya Jha16:29 2 Nov '08  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 30 Oct 2008
Editor:
Copyright 2008 by Achintya Jha
Everything else Copyright © CodeProject, 1999-2009
Web16 | Advertise on the Code Project