Visual Studio 2015高级编程(第6版)
上QQ阅读APP看书,第一时间看更新

9.1 Servers连接

Servers节点更适合被命名为Computers,因为它可以连接和查询有权访问的计算机(无论是服务器还是台式工作站)。每台计算机都在Servers节点下列为一个独立的节点。在每个计算机节点下都列出了属于该计算机的硬件、服务以及其他组件,并包含很多可以执行的活动和任务。某些软件供应商还提供了可以插入Server Explorer中的组件来扩展Server Explorer提供的功能。

要访问Server Explorer,可以在View菜单中选择Server Explorer命令。本地计算机会默认出现在Servers列表中。要添加其他计算机,可以右击Servers节点,然后从弹出的上下文菜单中选择Add Server命令。

输入计算机名或者IP地址,就可以使用凭据尝试连接到相应的计算机了。如果当前用户没有足够的权限,可以单击要连接的计算机,使用另一个用户名登录。此时该链接会显示为禁用,但单击它就会弹出如图9-2所示的对话框,在其中可以输入备选的用户名和密码。

图9-2

要通过Server Explorer访问服务器,必须拥有Administrator权限。

9.1.1 Event Logs节点

Event Logs节点用于访问计算机事件日志。从该节点的上下文菜单中可以启动Event Viewer。或者,如图9-3所示,也可以展开事件日志列表,以查看特定应用程序的事件。单击任意一个事件, Properties窗口都会显示与其相关的全部信息。

图9-3

在编写代码时,可以使用Server Explorer查询计算机,但Server Explorer真正的强大之处是只要把资源节点拖放到Windows Form上,就能自动创建相应的组件。例如,如果将Application节点拖放到一个Windows Form上,就会在设计器的非可视化区域添加System.Diagnostic.Eventlog类的一个实例。在Server Explorer中右击日志,从上下文菜单中选择Add to Designer,也可以添加相同的代码。然后,可以使用下面的代码为该事件日志添加一个条目。

C#

      this.eventLog1.Source = "My Server Explorer App";
      this.eventLog1.WriteEntry("Something happened",
                                System.Diagnostics.EventLogEntryType.Information);

VB

      Me.EventLog1.Source = "My Server Explorer App"
      Me.EventLog1.WriteEntry("Something happened",
                              System.Diagnostics.EventLogEntryType.Information)

因为上面的代码在Application Event Log中创建了一个新的Source,所以需要管理员权限才能执行。如果运行Windows Vista、Windows 7或Windows 8时启用了User Account Control,就应该创建一个应用程序清单,这些内容参见第6章。

运行这段代码后,可在Server Explorer中直接查看结果。单击Server Explorer工具栏上的Refresh按钮,确保在Application Event Log节点下显示新的Event Source。

Visual Basic程序员有另一种向代码添加EventLog类的方法:使用My名称空间提供的内置日志功能。例如,可以修改前面的代码片段,使用My.Application.Log.WriteEntry方法编写一个日志条目。

VB

      My.Application.Log.WriteEntry("Button Clicked", TraceEventType.Information)

也可以使用My.Application.Log.WriteException方法编写异常信息,该方法的参数是一个异常和两个提供其他信息的可选字符串。

使用My名称空间编写日志信息还有其他很多好处。下面的配置文件指定了一个把日志信息传送到事件日志的EventLogTraceListener。也可以指定其他的跟踪侦听器——如FileLogTraceListener,通过在SharedListeners和Listeners集合中添加信息,以向日志文件中写入信息。

      <?xml version="1.0" encoding="utf-8" ?>
      <configuration>
          <system.diagnostics>
              <sources>
                  <source name="DefaultSource" switchName="DefaultSwitch">
                      <listeners>
                          <add name="EventLog"/>
                      </listeners>
                   </source>
           </sources>
           <switches>
               <add name="DefaultSwitch" value="Information"/>
           </switches>
           <sharedListeners>
                  <add name="EventLog"
                       type="System.Diagnostics.EventLogTraceListener"
                       initializeData="ApplicationEventLog"/>
              </sharedListeners>
          </system.diagnostics>
      </configuration>

这段配置信息还指定了一个名为DefaultSwitch的开关。DefaultSwitch开关定义了发送给侦听器的最小事件类型,并且通过switchName特性与跟踪信息源关联在一起。例如,如果DefaultSwitch的值为Critical,Information类型的事件就不会写入事件日志。DefaultSwitch的可能值见表9-1。

表9-1 DefaultSw itch的值

注意,WriteEntry和WriteException的重载可以默认分别使用Information或者Error事件类型,因此不需要为这种方法指定事件类型。

9.1.2 Management Classes节点

图9-4显示了所有可通过Server Explorer访问的管理类。每个节点都提供了它代表的设备或者应用程序的一组功能。例如,右击Printers节点可以添加一个新的打印机连接,而右击My Computer节点可以在域或者工作组中添加计算机。这些节点有一个共同特性,即它们都为Windows Management Instrumentation(WMI)提供了强类型的包装。大多数情况下,开发人员只要把感兴趣的节点拖放到窗体上,就可以从代码中访问和操作节点对应的信息了。

图9-4

为说明这些包装器的用法,本节创建一个使用管理类检索计算机信息的例子。在My Computer节点下有一个本地计算机节点,选择这个节点并把它拖放到窗体上,在窗体的非可视化区域就会创建一个ComputerSystem组件,再把一个Label控件、一个TextBox控件、一个Button控件和一个PropertyGrid控件从Toolbox的All Windows Forms选项卡拖放到窗体上,其布局如图9-5所示。

图9-5

在Solution Explorer中还添加了一个定制组件root.CIMV2.Win32_ComputerSystem(具体的名称可能因计算机配置而异)。该定制组件由Management Strongly Typed Class Generator(Mgmtclassgen.exe)创建,它包含的ComputerSystem等类可以提供WMI信息。

如果单击窗体上的computerSystem1对象,就可以在Properties窗口中看到相关计算机的信息。但是在本例中,我们对该计算机的信息并不感兴趣,因为它只是用作一个创建ComputerSystem类的模板。可以删除ComputerSystem1对象,但是在删除前请先复制对象的Path属性。后面会使用Path属性和图9-5所示窗体中输入的计算机名来加载该计算机的信息。在Load按钮的单击事件处理程序中添加下面的代码。

C#

      const string compPath = "\\\\{0}\\root\\CIMV2:Win32_ComputerSystem.Name=\"{0}\"";
      if (!string.IsNullOrEmpty(this.textBox1.Text))
      {
          string computerName = this.textBox1.Text;
          string pathString = string.Format(compPath, computerName);
          var path = new System.Management.ManagementPath(pathString);
          ROOT.CIMV2.ComputerSystem cs = new ROOT.CIMV2.ComputerSystem(path);
          this.propertyGrid1.SelectedObject = cs;
      }

VB

      Const compPath As String = "\\{0}\root\CIMV2:Win32_ComputerSystem.Name=""{0}"""
      If Not Me.TextBox1.Text = "" Then
          Dim computerName As String = Me.TextBox1.Text
          Dim pathString As String = String.Format(compPath, computerName)
          Dim path As New System.Management.ManagementPath(pathString)
          Dim cs As New ROOT.CIMV2.ComputerSystem(path)
          Me.PropertyGrid1.SelectedObject = cs
      End If

在上面的代码中,Path属性来自ComputerSystem1对象,而计算机名组件被替换为字符串替代标记{0}。单击按钮时,代码通过String.Format操作把文本框中输入的计算机名和Path属性结合为完整的WMI路径。然后,程序使用WMI路径实例化一个新的ComputerAccount对象,并把它传递给ComputerPropertyGrid控件,运行结果如图9-6所示。

图9-6

生成的代码不能优雅地处理不支持的功能。例如,如果在不支持电源管理的计算机上运行应用程序,computerSystem1组件就会在被实例化时引发一个异常。这可能需要修改生成的代码,然后才可以成功地运行应用程序。

尽管大多数属性都是只读的,但对于这些可编辑的字段,在PropertyGrid中的更改会立刻提交给计算机。如果不想自动应用更改,可以改变ComputerSystem类的AutoCommit属性。

9.1.3 Management Events节点

如前所述,可以把Server Explorer中的管理类拖放到窗体上,然后对自动生成的类进行操作。另一种使用WMI接口的方法是通过Management Events节点。管理事件可以监视任意类型的WMI数据,并在创建、修改或者删除特定类型的WMI数据对象时引发事件。默认情况下,Management Events节点是一个空的列表,可以右击该节点并选择Add Event Query,在如图9-7所示的对话框中创建自己的管理事件。

可以在该对话框中选择自己感兴趣的WMI数据类型。对话框内显示了成百上千个数据类型,为快速定位,可使用Find搜索框。如图9-7所示,输入搜索条件process,就会在root\CIMV2节点下找到CIM Processes类。这个类的每个实例都表示一个运行在系统上的进程。如果希望在创建新过程时获得通知,可以从下拉菜单中选择Object Creation命令。

单击OK按钮,Visual Studio会在Management Events节点下添加一个CIM Processes Event Query节点。如果在系统上打开应用程序(如Notepad)的一个新实例,就会把相关的事件添加到这个节点下。在如图9-7所示的Build Management Event Query对话框中,默认的检测间隔设置为60s,因此在进行这个修改后,需要等待60s才能在树状视图中看到生成的事件。

图9-7

该事件最初显示在Output窗口中。然后,可以刷新Event Query节点(右击该节点并从上下文菜单中选择Refresh),在Server Explorer中查看事件。输出如图9-8所示。选中一个事件后,Properties窗口会显示大量的属性。这些属性目前没有什么用,但一旦知道要查询的属性,就很容易捕获、过滤和响应系统事件。

图9-8

下面继续前面的例子。把一个CheckBox控件和一个ListBox控件从Toolbox拖放到新的Windows Form上。

把CIM Processes Event Query节点从Server Explorer拖放到新窗体上,这会创建System.Management.ManagementEventWatcher类的一个实例,并把实例的属性配置为侦听新进程的创建。使用内嵌ManagementQuery对象的QueryString属性可以访问查询。和大多数监视类一样,在满足监视条件时ManagementEventWatch类会触发事件(这里是EventArrived事件)。为处理该事件,添加如下代码:

C#

      private void managementEventWatcher1_EventArrived(System.Object sender,
                                           System.Management.EventArrivedEventArgs e)
      {
          foreach (System.Management.PropertyData p in e.NewEvent.Properties)
          {
              if (p.Name == "TargetInstance")
              {
                  var mbo = (System.Management.ManagementBaseObject)p.Value;
                  string[] sCreatedProcess = {(string)mbo.Properties["Name"].Value,
                                              (string)mbo.Properties["ExecutablePath"].
                                              Value };
                  this.BeginInvoke(new LogNewProcessDelegate(LogNewProcess),
                                   sCreatedProcess);
              }
          }
      }
      delegate void LogNewProcessDelegate(string ProcessName, string ExePath);
      private void LogNewProcess(string ProcessName, string ExePath)
      {
          this.listBox1.Items.Add(string.Format("{0}—{1}", ProcessName, ExePath));
      }
      private void checkBox1_CheckedChanged(System.Object sender, System.EventArgs e)
      {
          if (this.checkBox1.Checked)
          {
              this.managementEventWatcher1.Start();
          }
          else
          {
              this.managementEventWatcher1.Stop();
          }
      }

VB

      Private Sub ManagementEventWatcher1_EventArrived(ByVal sender As System.Object, _
                                    ByVal e As System.Management.EventArrivedEventArgs)
          For Each p As System.Management.PropertyData In e.NewEvent.Properties
              If p.Name = "TargetInstance" Then
                  Dim bo As System.Management.ManagementBaseObject = _
                                 CType(p.Value, System.Management.ManagementBaseObject)
                  Dim sCreatedProcess As String()= {mbo.Properties("Name").Value, _
                                                mbo.Properties("ExecutablePath").Value}
                  Me.BeginInvoke(New LogNewProcessDelegate(AddressOf LogNewProcess), _
                                 sCreatedProcess)
              End If
          Next
      End Sub
      Delegate Sub LogNewProcessDelegate(ByVal ProcessName As String, _
                                         ByVal ExePath As String)
      Private Sub LogNewProcess(ByVal ProcessName As String, ByVal ExePath As String)
          Me.ListBox1.Items.Add(String.Format("{0}—{1}", ProcessName, ExePath))
      End Sub
      Private Sub CheckBox1_CheckedChanged(ByVal sender As System.Object, _
                                                   ByVal e As System.EventArgs)_
                                                   Handles CheckBox1.CheckedChanged
          If Me.CheckBox1.Checked Then
              Me.ManagementEventWatcher1.Start()
          Else
              Me.ManagementEventWatcher1.Stop()
          End If
      End Sub

在事件处理程序中,需要迭代NewEvent对象上的Properties集合。在对象发生改变时,方法将返回两个实例:PreviousInstance(该实例保存了轮询间隔开始时的状态)和TargetInstance(该实例保存了轮询周期结束时的状态)。在同一个轮询周期中,对象可能多次改变状态。因此,只有在起始状态与结束状态不一样的情况下事件才会触发。例如,如果在同一个轮询周期中启动并停止了一个进程,就不会触发任何事件。

事件处理程序从传送给事件参数的值中生成了一个新的ManagementBaseObject,以获得新进程的显示名称和可执行文件的路径。UI控件只能在UI线程上更新,所以不能直接更新ListBox,而必须调用BeginInvoke ,在UI线程上执行LogNewProcess功能。最后需要执行的操作是关联CheckChanged事件与CheckBox控件、EventArrived事件与ManagementEventWatcher。在设计器中选择控件,单击Properties窗口中的Events按钮,然后在事件下拉列表中选择Event Handler。图9-9显示了操作中的窗体。

图9-9

9.1.4 Message Queues节点

展开的Message Queues节点如图9-10所示,它用于访问计算机上的消息队列。消息队列可以分为3种:私有的(一台计算机查询另一台计算机时,这种类型的队列不会显示);公有的(显示给所有计算机);系统的(用于保存未发送的消息和其他异常报告)。

图9-10

为了顺利展开Message Queues节点,必须确保计算机上安装了MSMQ。要安装MSMQ,可以在Control Panel上选择Programs and Features,然后选择Turn Windows On or Off任务菜单项,选择复选框以启用Microsoft Message Queue (MSMQ)Server功能。

在图9-10中,通过从右击上下文菜单中选择Create Queue命令,以在Private Queues节点中添加一个消息队列samplequeue。创建队列以后,可以把队列拖放到新的Windows Form上,创建一个正确配置的MessageQueue类。为了展示MessageQueue对象的功能,在窗体上添加两个文本框和一个Send按钮,其布局如图9-11所示。Send按钮通过MessageQueue对象发送在第一个文本框中输入的消息。在窗体的Load事件中,后台线程不断轮询队列以检索消息,然后把它显示在第二个文本框中。

图9-11

C#

      public Form4()
      {
          InitializeComponent();
          var monitorThread = new System.Threading.Thread(MonitorMessageQueue);
          monitorThread.IsBackground = true;
          monitorThread.Start();
          this.button1.Click +=new EventHandler(btn_Click);
      }
      private void btn_Click(object sender, EventArgs e)
      {
          this.messageQueue1.Send(this.textBox1.Text);
      }
      private void MonitorMessageQueue()
      {
          var m = default(System.Messaging.Message);
          while (true)
          {
              try
             {
                 m =this.messageQueue1.Receive(new TimeSpan(0, 0, 0, 0, 50));
                 this.ReceiveMessage((string)m.Body);
             }
              catch (System.Messaging.MessageQueueException ex)
              {
                  if (!(ex.MessageQueueErrorCode ==
                        System.Messaging.MessageQueueErrorCode.IOTimeout))
                  {
                      throw ex;
                  }
              }
              System.Threading.Thread.Sleep(10000);
          }
      }
      private delegate void MessageDel(string msg);
      private void ReceiveMessage(string msg)
      {
          if (this.InvokeRequired)
          {
              this.BeginInvoke(new MessageDel(ReceiveMessage), msg);
              return;
          }
          this.textBox2.Text = msg;
      }

VB

      Private Sub Form_Load(ByVal sender As Object, ByVal e As System.EventArgs)_
                            Handles Me.Load
          Dim monitorThread As New Threading.Thread(AddressOf MonitorMessageQueue)
          monitorThread.IsBackground = True
          monitorThread.Start()
      End Sub
      Private Sub btn_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)_
                                Handles Button1.Click
          Me.MessageQueue1.Send(Me.TextBox1.Text)
      End Sub
      Private Sub MonitorMessageQueue()
          Dim m As Messaging.Message
          While True
              Try
                  m =Me.MessageQueue1.Receive(New TimeSpan(0, 0, 0, 0, 50))
                  Me.ReceiveMessage(m.Body)
              Catch ex As Messaging.MessageQueueException
                  If Not ex.MessageQueueErrorCode = _
                          Messaging.MessageQueueErrorCode.IOTimeout Then
                      Throw ex
                  End If
              End Try
              Threading.Thread.Sleep(10000)
          End While
      End Sub
      Private Delegate Sub MessageDel(ByVal msg As String)
      Private Sub ReceiveMessage(ByVal msg As String)
          If Me.InvokeRequired Then
              Me.BeginInvoke(New MessageDel(AddressOf ReceiveMessage), msg)
              Return
          End If
          Me.TextBox2.Text = msg
      End Sub

注意,这段代码没有显式地关闭后台线程。由于线程的IsBackground属性设置为True,因此它会在退出应用程序时自动终止。与前面的例子一样,消息处理是在后台线程上进行的,因此在更新用户界面时还需要通过BeginInvoke方法不断地在线程之间切换。最终窗体如图9-11所示。

消息发送到消息队列中时,它们会出现在Server Explorer的相应队列中。单击一条消息, Properties窗口就会显示消息的内容。

9.1.5 Performance Counters节点

开发人员在构建应用程序时经常会忘记如何维护和管理应用程序。例如,假设一个程序在安装后一年以来都正常运行,但是突然之间它的响应请求效率变得异常低下。显然,应用程序出了问题,但是无法判断究竟是什么地方出了问题。为了找到性能问题的根源,一个常见的策略就是使用性能计数器。Windows内置了很多可用于监视操作系统运行的性能计数器,很多第三方软件也安装了性能计数器,系统管理员可以通过这些性能计数器对程序的异常行为进行分析。

Server Explorer树中展开的Performance Counters节点如图9-12所示,有两个主要功能。第一,它允许查看和检索已经安装的计数器信息。也可以在这里创建新的性能计数器,编辑或者删除已存在的计数器。如图9-12所示,Performance Counters节点包含了多个分类,每个分类下都有很多计数器。

图9-12

必须用管理员权限运行Visual Studio,Server Explorer才能显示Performance Counters节点。此外,只能编辑自己创建的分类。

要编辑一个分类或者计数器,从分类的右击上下文菜单中选择Edit Category命令;要添加一个新的分类和关联的计数器,右击Performance Counters节点,然后从上下文菜单中选择Create New Category命令。这两种操作都会打开Performance Counter Builder对话框,如图9-13所示。本例创建了一个新的性能计数器分类,它可以跟踪窗体的打开和关闭事件。

图9-13

Performance Counters节点的第二个功能是为开发人员提供一种在代码中快速访问性能计数器的方式。只要把性能计数器分类拖放到窗体上,就可以对性能计数器执行读写操作了。下面继续前面的例子。把新创建的My Application性能计数器Form Open和Form Close拖放到一个新的Windows Form上。然后,添加一些用于显示性能计数器值的文本框和按钮。最后,为性能计数器指定一个友好的名称。最终窗体如图9-14所示。

图9-14

从Properties窗口中可以看到,窗体上当前选中的计数器——Form Open来自于My Application分类。还可以看到一个MachineName属性(说明计数器信息的来源计算机)和一个ReadOnly属性(默认情况下为True。如果希望更新计数器,就可以把它设置为False)。最后,为Retrieve Counters按钮的单击事件处理程序添加如下代码。

C#

      this.textBox1.Text = this.perfFormOpen.RawValue.ToString();
      this.textBox2.Text = this.perfFormClose.RawValue.ToString();

VB

      Me.textBox1.Text = Me.perfFormOpen.RawValue
      Me.textBox2.Text = Me.perfFormClose.RawValue

如果要更新性能计数器,还需要在应用程序中添加一些代码。例如,在Form中的Load事件处理程序有如下代码:

C#

      this.perfFormOpen.Increment();

VB

      Me.perfFormOpen.Increment()

把性能计数器拖放到窗体上时,性能计数器的智能标签(选择一个控件时,其右上角会出现一个小箭头)上会出现一个Add Installer项。选中该组件时,也可以在Properties窗口的底部看到该项。Add Installer操作可以在解决方案中添加一个Installer类,用于在应用程序的安装过程中安装性能计数器。当然,为调用这个Installer,必须把它所在的程序集作为一个定制操作添加到部署项目中。

要创建多个性能计数器,只需要选择每个额外的性能计数器,并单击Add Installer。Visual Studio 2015会返回前面创建的第一个Installer,在PerformanceCounterInstaller组件的Counters集合中自动添加第二个计数器,如图9-15所示。

图9-15

在设计界面上添加其他的PerformanceCounterInstaller组件,可以添加其他分类中的计数器。这样,就可以使用PerfMon等工具监控应用程序的行为。

9.1.6 Services节点

展开的Services节点如图9-16所示,显示了计算机中已经注册的服务。每个节点都在其图标的右下角显示了服务的状态。可能的状态包括Stopped、Running或Paused。选择一个服务可以在Properties窗口中显示服务的其他信息,如服务的其他依赖关系。

图9-16

和Server Explorer中的其他节点一样,每个服务都可以拖放到窗体的设计界面上。这会在窗体的非可视化区域创建一个ServiceController组件。默认情况下,ServiceName属性设置为在Server Explorer中选中的服务名称,但也可以修改该属性,访问信息和控制任意的服务。与此类似,也可以修改MachineName属性,以连接到有权访问的任意计算机。下面的代码展示了如何使用ServiceController组件停止服务。

C#

      this.serviceController1.Refresh();
      if (this.serviceController1.CanStop)
      {
          if (this.serviceController1.Status ==
                   System.ServiceProcess.ServiceControllerStatus.Running)
          {
              this.serviceController1.Stop();
              this.serviceController1.Refresh();
          }
      }

VB

      Me.ServiceController1.Refresh()
      If Me.ServiceController1.CanStop Then
         If Me.ServiceController1.Status = _
               ServiceProcess.ServiceControllerStatus.Running Then
            Me.ServiceController1.Stop()
            Me.ServiceController1.Refresh()
         End If
      End If

除了3个主要的状态(Running、Paused或Stopped)以外,Windows服务还有其他一些过渡状态:ContinuePending、PausePending、StartPending以及StopPending。如果要启动的服务依赖于另一个处于过渡状态的服务,可以调用WaitForStatus方法,保证服务的顺利启动。