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






4.40/5 (6投票s)
这个 Visual Studio 插件在调试工具栏上放置一个很酷的快捷按钮,用于将你的代码附加到 ASP.NET 调试器 (aspnet_wp.exe)。
引言
随着我们的 ASP.NET 应用程序的增长,我们不能再使用普通的“开始调试”按钮 来调试我们的应用程序,因为应用程序在跳转到我们感兴趣的调试代码之前,可能正在执行大量的其他处理。这时,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 实例中显示插件按钮。
加载插件后,调试器按钮如下所示。
当按下按钮并将代码附加到 aspnet_wp 进程时,调试器按钮如下所示。按下按钮(现在带有叉号)会将代码从调试器分离。
可以通过使用您想要显示的 faceID 更改常量 ATTACH_ICON_FACEID
和 DETACH_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 中引入),如 IDTExtensibility2
、DTE2
等。
有趣的部分是捕获 Visual Studio 事件(例如,当调用 VS 自己的“开始调试”按钮或使用“停止”按钮停止时)。在学习如何捕获这些事件之后,我就能够根据调试器状态修改插件按钮的功能及其图像。您可以在 commandEvents_BeforeExecute
事件中看到这一点。
本文档适用于 Visual Studio 2005,但只需要 DTE 类级别修改即可适用于 Visual Studio 2008。