![]() |
Platforms, Frameworks & Libraries »
.NET Framework »
Utilities
Intermediate
License: The Code Project Open License (CPOL)
Add-in to attach ASP.NET debuggerBy Achintya JhaThis 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
|
||||||||
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
As our ASP.NET application grows, we can no longer use the normal Start debugging button 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.
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
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.
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.
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; } } } } }
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.
| You must Sign In to use this message board. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
General
News
Question
Answer
Joke
Rant
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 |