65.9K
CodeProject 正在变化。 阅读更多。
Home

用于附加 ASP.NET 调试器的插件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.40/5 (6投票s)

2008年10月30日

CPOL

3分钟阅读

viewsIcon

45927

downloadIcon

172

这个 Visual Studio 插件在调试工具栏上放置一个很酷的快捷按钮,用于将你的代码附加到 ASP.NET 调试器 (aspnet_wp.exe)。

引言

随着我们的 ASP.NET 应用程序的增长,我们不能再使用普通的“开始调试”按钮 StartDebug.GIF 来调试我们的应用程序,因为应用程序在跳转到我们感兴趣的调试代码之前,可能正在执行大量的其他处理。这时,Visual Studio 中的“调试”->“附加到进程...”选项就派上用场了。当我们在出现问题的相关页面中导航时,我们中的许多人会使用它,然后我们将调试器附加到 aspnet_wp.exe 进程。但是这个步骤需要点击四次鼠标,当你每天这样做 100 次时,就是 400 次点击,等等。我对此感到厌烦,所以创建了这个插件,它在调试工具栏上放置了一个漂亮的按钮,只需单击一下即可附加到 ASP.NET 进程。

背景

这是我的第一个插件开发,所以我必须在网上四处搜索,实际上我从 www.mztools.com 获得了很大的帮助。

我从他们网站上的这两篇文章中得到了很多帮助,当然 MSDN 也提供了帮助

Using the Code

要使用该插件,请从上面的链接解压缩 *AttachToASPNETAddinDLL.zip*。将文件 *AttachToASPNETAddin.AddIn* 和 *AttachToASPNETAddin.dll* 复制到 <我的文档文件夹>\Visual Studio 2005\ 中的 *Addins* 文件夹。如果该文件夹不存在,请创建该文件夹。这是 Visual Studio 从中选取插件的文件夹。因此,这里不需要安装项目。Visual Studio 2005 中的插件现在具有 xCopy 部署。

*.Addin* 文件只是一个 XML 文件,它帮助 Visual Studio 定位插件可执行文件和各种参数,如公司、作者姓名等。最重要的是 Assembly 节点,它保存插件可执行文件的路径。此路径可以是本地路径、相对路径,甚至是 URL。

现在,只打开一个 VS 2005 实例,然后选择“工具”->“插件管理器”,如下图所示。

选中插件名称左侧的复选框,并选中“启动”复选框。此操作将在该实例以及所有后续的 VS 2005 实例中显示插件按钮。

加载插件后,调试器按钮如下所示。

AttachImage.PNG

当按下按钮并将代码附加到 aspnet_wp 进程时,调试器按钮如下所示。按下按钮(现在带有叉号)会将代码从调试器分离。

DetachImage.PNG

可以通过使用您想要显示的 faceID 更改常量 ATTACH_ICON_FACEIDDETACH_ICON_FACEID 来轻松更改这些按钮。如果您在 Google 上搜索 face ID,互联网上会涌现出大量的网页。

我从 http://peltiertech.com/Excel/Zips/ShowFace.zip 获得了一个作为 Excel 宏的 face ID 浏览实用程序。

以下是插件主干 Connect 类的源代码

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
{
    /// <summary>The object for implementing an Add-in.</summary>
    /// <seealso class="IDTExtensibility2" >
    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;
        
        /// <summary>Implements the constructor
        /// for the Add-in object. Place your initialization 
        /// code within this method.</summary>
        public Connect()
        {
        }

        /// <summary>Implements the OnConnection method of the IDTExtensibility2 interface. 
        /// Receives notification that the Add-in is being loaded.</summary>
        /// <param term="application">Root object of the host application.</param>
        /// <param term="connectMode">Describes how the Add-in is being loaded.</param>
        /// <param term="addInInst">Object representing this Add-in.</param>
        /// <seealso class="IDTExtensibility2">
        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;
                }
            } 
        }

        /// <summary>Implements the OnDisconnection method of the IDTExtensibility2 interface. 
        /// Receives notification that the Add-in is being unloaded.</summary>
        /// <param term="disconnectMode" />Describes how the Add-in is being unloaded.</param>
        /// <param term="custom">Array of parameters that are host application specific.</param>
        /// <seealso class="IDTExtensibility2" />
        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());
            }
        }

        /// <summary>Implements the OnAddInsUpdate
        /// method of the IDTExtensibility2 interface. 
        /// Receives notification when the collection
        /// of Add-ins has changed.</summary>
        /// <param term="custom">Array of parameters
        /// that are host application specific.</param>
        /// <seealso class="IDTExtensibility2" />
        public void OnAddInsUpdate(ref Array custom)
        {
        }

        /// <summary >Implements the OnStartupComplete method of the IDTExtensibility2 interface. 
        /// Receives notification that the host application has completed loading.</summary >
        /// <param term="custom" >Array of parameters that are host application specific.</param >
        /// <seealso class="IDTExtensibility2" >
        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);
            }
        }

        /// <summary >Implements the OnBeginShutdown method of the IDTExtensibility2 interface. 
        /// Receives notification that the host application is being unloaded.</summary >
        /// <param term="custom" >Array of parameters that are host application specific.</param >
        /// <seealso class="IDTExtensibility2" >
        public void OnBeginShutdown(ref Array custom)
        {
        }
        
        /// <summary >Implements the QueryStatus method of the IDTCommandTarget interface. 
        /// This is called when the command's availability is updated</summary >
        /// <param term="commandName" >The name of the command to determine state for.</param >
        /// <param term="neededText" >Text that is needed for the command.</param >
        /// <param term="status" >The state of the command in the user interface.</param >
        /// <param term="commandText" >Text requested by the neededText parameter.</param >
        /// <seealso class="Exec" >
        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;
                }
            }
        }

        /// <summary >Implements the Exec method of the IDTCommandTarget interface. 
        /// This is called when the command is invoked.</summary >
        /// <param term="commandName" >The name of the command to execute.</param >
        /// <param term="executeOption" >Describes how the command should be run.</param >
        /// <param term="varIn" >Parameters passed from the caller to the command handler.</param >
        /// <param term="varOut" >Parameters passed from the command handler to the caller.</param >
        /// <param term="handled" >Informs the caller if the command was handled or not.</param >
        /// <seealso class="Exec" >
        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;
                }
            }
        }
    }
}

关注点

构建插件一开始看起来很复杂,因为 VS 2005 插件向导会生成大量内置代码,这些代码看起来像是战前的代码。对象名称也很可怕,它们也有一个前缀(在 Visual Studio 2005 中引入),如 IDTExtensibility2DTE2 等。

有趣的部分是捕获 Visual Studio 事件(例如,当调用 VS 自己的“开始调试”按钮或使用“停止”按钮停止时)。在学习如何捕获这些事件之后,我就能够根据调试器状态修改插件按钮的功能及其图像。您可以在 commandEvents_BeforeExecute 事件中看到这一点。

本文档适用于 Visual Studio 2005,但只需要 DTE 类级别修改即可适用于 Visual Studio 2008。

© . All rights reserved.