C# 6 与 .NET Core 1.0 高级编程 - 39 章 Windows 服务(下) Professional C# 6 and .NET Core 1.0 - Chapter 39 Wind

 译文,我的原创,转载请注明出处(C# 6 与 .NET Core 1.0 高级编程 - 39 章 Windows 服务(下)),不对的地方欢迎指出与交流。  html

章节出自《Professional C# 6 and .NET Core 1.0》。水平有限,各位阅读时仔细分辨,惟望莫误人子弟。  编程

附英文版原文:Professional C# 6 and .NET Core 1.0 - Chapter 39 Windows Services数组

本章节译文分为上下篇,上篇见:C# 6 与 .NET Core 1.0 高级编程 - 39 章 Windows 服务(上) 服务器

(接下来会翻译本书的重点 asp.net mvc 和 asp.net core,这是译者找到此书的动机之一。架构

这两章篇幅也较长,对于每章除了翻译,译者发表中英文版前都要整理排版,效率不算高,请你们耐心等待。mvc

固然若是有园友愿意义务帮忙排版的请大意大胆留言或私信我吧,热烈欢迎!app

目前能找到关于 .net core的资料特别是中文资料太少了,听说asp.net core 2.0 今年4月份前相关书籍会发表,但愿一样有机会翻译,为技术更新换代的中文资料贡献一点绵薄之力。)asp.net

------------------------------------------------------------------------------函数

监视和控制Windows服务工具

要监视和控制Windows服务,可使用属于计算机管理管理工具的服务MMC管理单元。每一个Windows系统还有一个命令行实用程序net.exe,它可以控制服务。另外一个Windows命令行实用程序是sc.exe,此实用程序具备比net.exe更多的功能。还能够直接从Visual Studio Server Explorer 控制服务。在本节中建立了一个小型Windows应用程序,它使用System.ServiceProcess.ServiceController类来监视和控制服务。

MMC管理单元

MMC的服务管理单元能够查看全部服务的状态(见图39.11)。还能够向服务发送控制请求以中止、启用或禁用它们,以及改变它们的配置。服务管理单元是服务控制程序以及服务配置程序。

图39.11

双击QuoteService以打开属性对话框,如图39.12所示。从这里能够查看服务名称、描述、可执行文件的路径、启动类型和状态。服务当前是启动状态。经过选择此对话框中的“Log On”选项卡,能够更改服务过程的账户。

图39.12

net.exe实用程序

“服务”管理单元虽然易于使用,但系统管理员没法自动执行,由于它没法使用脚本管理。要使用 自动执行脚本的工具来控制服务,可使用命令行实用程序net.exe。 “net start” 命令显示全部正在运行的服务,“net start servicename”启动服务,“net stop servicename” 向服务发送中止请求。也能够用“net pause”和“net continue”(若是服务容许的话)暂停和继续服务。

sc.exe实用程序

操做系统操做的另外一个不为人知的实用程序是sc.exe。这是操做服务的一个很好的工具。使用 sc.exe 比net.exe实用程序能够作更多事情。sc.exe 能够检查服务的实际状态,或配置、删除和添加服务。若是服务没法正常工做,该工具还能够很方便地卸载服务。

Visual Studio服务器资源管理器

在Visual Studio中使用服务器资源管理器监视服务,能够从树视图中选择服务器,而后选择您的计算机,而后选择服务元素。能够看到全部服务的状态,如图39.13所示。经过选择服务,能够看到服务的属性。

图39.13

编写自定义服务控制器

本节中建立一个使用ServiceController类来监视和控制Windows服务的小型WPF应用程序。

使用用户界面建立一个WPF应用程序,如图39.14所示。此应用程序的主窗口有一个列表框去显示全部服务,四个文本框显示服务的显示名称、状态、类型和名称,还有六个按钮:四个按钮用于发送控制事件,一个按钮用于刷新列表,一个按钮用于退出应用程序。

图39.14

注意 您能够在第29到35章中了解有关WPF和XAML的更多信息。

监控服务

ServiceController类能够获取有关每一个服务的信息。下表显示了ServiceController类的属性:

属性

描述

CanPauseAndContinue

若是能够发送暂停和继续请求到服务,则返回true

CanShutdown

若是服务有系统关机的处理事件,则返回true

CanStop

若是服务可中止,则返回true。

DependentServices

返回依赖服务的集合。若是服务要中止,则全部依赖服务必须在该服务中止前中止。

ServicesDependentOn

返回该服务的依赖服务集合。

DisplayName

该服务显示的名称。

MachineName

服务运行所在的计算机的名称

ServiceName

指定服务的名称。

ServiceType

服务的类型。服务能够运行在一个共享进程内,多个服务使用相同的进程(Win32ShareProcess),或在一个进程中只有一个服务的方式(Win32OwnProcess)。若是服务能够在桌面进行交互,类型为InteractiveProcess。

Status

服务的状态,能够是运行、中止、暂停或在某些中间模式,例如开始挂起、中止挂起等。状态值在枚举类ServiceControllerStatus中定义。

示例应用程序中使用属性DisplayName,ServiceName,ServiceType和Status来显示服务信息。 CanPauseAndContinue 和 CanStop 用于启用或禁用暂停,继续和中止按钮。

要获取用户交互所须要的信息,能够建立ServiceControllerInfo类。该类可用于数据绑定,并提供状态信息,服务名称、服务类型以及按钮应启用或禁用的服务控制的信息。

注意 因为使用了System.ServiceProcess.ServiceController类,必须引用程序集System.ServiceProcess。

ServiceControllerInfo包含一个嵌入的ServiceController,它使用ServiceControllerInfo类的构造函数设置。还有一个只读属性Controller能够访问嵌入式ServiceController(代码文件
ServiceControlWPF/ServiceControllerInfo.cs):

public class ServiceControllerInfo { public ServiceControllerInfo(ServiceController controller) { Controller = controller; } public ServiceController Controller { get; } // etc.
}

要显示有关服务的当前信息,ServiceControllerInfo类有只读属性DisplayName,ServiceName,ServiceTypeName和ServiceStatusName。属性DisplayName和ServiceName的实现只访问基础ServiceController类的属性。属性ServiceTypeName和ServiceStatusName的实现须要更多的工做 - 服务的状态和类型不容易返回,由于显示的应该一个字符串而不是一个数字,这是ServiceController类返回的。属性ServiceTypeName返回一个表示服务类型的字符串。从属性ServiceController.ServiceType获取的ServiceType表示一组可使用按位或运算符组合的标志。 InteractiveProcess位能够与Win32OwnProcess和Win32ShareProcess一块儿设置。所以,第一次在继续检查其余值以前先肯定是否设置InteractiveProcess位。服务返回的字符串将是“Win32 Service Process”或“Win32 Shared Process”(代码文件ServiceControlWPF/ServiceControllerInfo.cs):

public class ServiceControllerInfo { // etc.
  public string ServiceTypeName { get { ServiceType type = controller.ServiceType; string serviceTypeName =""; if ((type & ServiceType.InteractiveProcess) != 0) { serviceTypeName ="Interactive"; type -= ServiceType.InteractiveProcess; } switch (type) { case ServiceType.Adapter: serviceTypeName +="Adapter"; break; case ServiceType.FileSystemDriver: case ServiceType.KernelDriver: case ServiceType.RecognizerDriver: serviceTypeName +="Driver"; break; case ServiceType.Win32OwnProcess: serviceTypeName +="Win32 Service Process"; break; case ServiceType.Win32ShareProcess: serviceTypeName +="Win32 Shared Process"; break; default: serviceTypeName +="unknown type" + type.ToString(); break; } return serviceTypeName; } } public string ServiceStatusName { get { switch (Controller.Status) { case ServiceControllerStatus.ContinuePending: return"Continue Pending"; case ServiceControllerStatus.Paused: return"Paused"; case ServiceControllerStatus.PausePending: return"Pause Pending"; case ServiceControllerStatus.StartPending: return"Start Pending"; case ServiceControllerStatus.Running: return"Running"; case ServiceControllerStatus.Stopped: return"Stopped"; case ServiceControllerStatus.StopPending: return"Stop Pending"; default: return"Unknown status"; } } } public string DisplayName => Controller.DisplayName; public string ServiceName => Controller.ServiceName; // etc.
}

ServiceControllerInfo类具备一些其余的属性,用来启用 启动、中止、暂停和继续按钮:EnableStart,EnableStop,EnablePause和EnableContinue。这些属性根据服务的当前状态返回一个布尔值(代码文件ServiceControlWPF/ServiceControllerInfo.cs):

public class ServiceControllerInfo { // etc.
  public bool EnableStart => Controller.Status == ServiceControllerStatus.Stopped; public bool EnableStop => Controller.Status == ServiceControllerStatus.Running; public bool EnablePause => Controller.Status == ServiceControllerStatus.Running && Controller.CanPauseAndContinue; public bool EnableContinue => Controller.Status == ServiceControllerStatus.Paused; }

ServiceControlWindow 类的方法 RefreshServiceList 获取全部使用ServiceController.GetServices的服务显示到列表框中。 GetServices 方法返回一个ServiceController实例的数组,表示操做系统上安装的全部Windows服务。 ServiceController 类也有静态方法GetDevices,它返回一个表示全部设备驱动程序的ServiceController数组。返回的数组在扩展方法OrderBy的帮助下进行排序。这是按传递给 OrderBy 方法的 lambda 表达式定义的DisplayName 来进行排序的。使用Select语句 将 ServiceController 实例转换为 ServiceControllerInfo 类型。在一句中,传递了一个lambda表达式,为每一个ServiceController对象调用ServiceControllerInfo构造函数。最后,将结果分配给用于数据绑定的窗口的DataContext属性(代码文件ServiceControlWPF/MainWindow.xaml.cs):

protected void RefreshServiceList() { this.DataContext = ServiceController.GetServices(). OrderBy(sc => sc.DisplayName). Select(sc => new ServiceControllerInfo(sc)); }

获取列表框中的全部服务的方法 RefreshServiceList 在类 ServiceControlWindow 的构造函数内调用。构造函数还定义了按钮的Click事件:

public ServiceControlWindow() { InitializeComponent(); RefreshServiceList(); }

如今能够定义XAML代码将信息绑定到控件。首先,为ListBox中显示的信息定义DataTemplate。 ListBox包含一个Label,其中Content绑定到数据源的DisplayName属性。绑定 ServiceControllerInfo 对象数组时,DisplayName属性在ServiceControllerInfo类定义(代码文件ServiceControlWPF/MainWindow.xaml):

<Window.Resources>
  <DataTemplate x:Key="listTemplate">
    <Label Content="{Binding DisplayName}"/>
  </DataTemplate>
</Window.Resources>

放置在窗口左侧的ListBox将ItemsSource属性设置为{Binding}。这样,列表中显示的数据从在RefreshServiceList方法中设置的DataContext属性中检索。 ItemTemplate属性引用前面所示的DataTemplate 资源定义的listTemplate。属性 IsSynchronizedWithCurrentItem 设置为True,以便在同一窗口内的TextBox和Button控件绑定到使用ListBox选择的当前项:

<ListBox Grid.Row="0" Grid.Column="0" HorizontalAlignment="Left" Name="listBoxServices" VerticalAlignment="Top" ItemsSource="{Binding}" ItemTemplate="{StaticResource listTemplate}" IsSynchronizedWithCurrentItem="True">
</ListBox>

要用Button控件区分 启动/中止/暂停/继续 服务,定义如下枚举(代码文件ServiceControlWPF/ButtonState.cs):

public enum ButtonState
{
  Start,
  Stop,
  Pause,
  Continue
}

TextBlock控件的Text属性绑定到ServiceControllerInfo实例的相应属性。启用或禁用Button控件也经过将IsEnabled属性绑定到ServiceControllerInfo实例的相应属性,对应属性返回一个布尔值。按钮的Tag属性被分配给以前定义的ButtonState枚举类型的值,以便在同一处理事件OnServiceCommand中区分按钮(代码文件ServiceControlWPF/MainWindow.xaml): 

<TextBlock Grid.Row="0" Grid.ColumnSpan="2"
  Text="{Binding /DisplayName, Mode=OneTime}" />
<TextBlock Grid.Row="1" Grid.ColumnSpan="2"
  Text="{Binding /ServiceStatusName, Mode=OneTime}" />
<TextBlock Grid.Row="2" Grid.ColumnSpan="2"
  Text="{Binding /ServiceTypeName, Mode=OneTime}" />
<TextBlock Grid.Row="3" Grid.ColumnSpan="2" 
  Text="{Binding /ServiceName, Mode=OneTime}" />
<Button Grid.Row="4" Grid.Column="0" Content="Start"
  IsEnabled="{Binding /EnableStart, Mode=OneTime}"
  Tag="{x:Static local:ButtonState.Start}"
  Click="OnServiceCommand" />
<Button Grid.Row="4" Grid.Column="1" Name="buttonStop" Content="Stop"
  IsEnabled="{Binding /EnableStop, Mode=OneTime}"
  Tag="{x:Static local:ButtonState.Stop}"
  Click="OnServiceCommand" />
<Button Grid.Row="5" Grid.Column="0" Name="buttonPause" Content="Pause"
  IsEnabled="{Binding /EnablePause, Mode=OneTime}"
  Tag="{x:Static local:ButtonState.Pause}"
  Click="OnServiceCommand" />
<Button Grid.Row="5" Grid.Column="1" Name="buttonContinue"
  Content="Continue"
  IsEnabled="{Binding /EnableContinue,
  Tag="{x:Static local:ButtonState.Continue}"
  Mode=OneTime}" Click="OnServiceCommand" />
<Button Grid.Row="6" Grid.Column="0" Name="buttonRefresh"
  Content="Refresh"
  Click="OnRefresh" />
<Button Grid.Row="6" Grid.Column="1" Name="buttonExit"
  Content="Exit" Click="OnExit" />

控制服务

使用ServiceController类还能够向服务发送控制请求。 下表描述了能够应用的方法。

方法

描述

Start

告诉SCM要启动服务。 在示例中服务程序,OnStart被调用。

Stop

示例服务程序中,若是属性CanStop在服务类中为true,在SCM的帮助下调用OnStop方法

Pause

若是属性CanPauseAndContinue 为 true,则调用OnPause 方法。

Continue

若是属性CanPauseAndContinue为true,则调用 OnContinue 方法。

ExecuteCommand

启用向服务发送自定义命令。

如下代码控制服务。 由于启动,中止,挂起和暂停的代码是相似的,因此四个按钮能够用同一个处理事件(代码文件ServiceControlWPF/MainWindow.xaml.cs)译者注:学习时不要“惟书论”,书中的观点不必定所有正确,代码也不必定是最好的,例如如下 判断 currentButtonState 用了四个 if 语句,至少能够优化为 switch 语句):

protected void OnServiceCommand(object sender, RoutedEventArgs e) { Cursor oldCursor = this.Cursor; try { this.Cursor = Cursors.Wait; ButtonState currentButtonState = (ButtonState)(sender as Button).Tag; var si = listBoxServices.SelectedItem as ServiceControllerInfo; if (currentButtonState == ButtonState.Start) { si.Controller.Start(); si.Controller.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromSeconds(10)); } else if (currentButtonState == ButtonState.Stop) { si.Controller.Stop(); si.Controller.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromSeconds(10)); } else if (currentButtonState == ButtonState.Pause) { si.Controller.Pause(); si.Controller.WaitForStatus(ServiceControllerStatus.Paused, TimeSpan.FromSeconds(10)); } else if (currentButtonState == ButtonState.Continue) { si.Controller.Continue(); si.Controller.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromSeconds(10)); } int index = listBoxServices.SelectedIndex; RefreshServiceList(); listBoxServices.SelectedIndex = index; } catch (System.ServiceProcess.TimeoutException ex) { MessageBox.Show(ex.Message,"Timout Service Controller", MessageBoxButton.OK, MessageBoxImage.Error); } catch (InvalidOperationException ex) { MessageBox.Show(String.Format("{0} {1}", ex.Message, ex.InnerException != null ? ex.InnerException.Message: String.Empty), MessageBoxButton.OK, MessageBoxImage.Error); } finally { this.Cursor = oldCursor; } } protected void OnExit(object sender, RoutedEventArgs e) => Application.Current.Shutdown(); protected void OnRefresh_Click(object sender, RoutedEventArgs e) => RefreshServiceList();

由于控制服务的操做可能须要一些时间,因此在第一条语句中将光标切换到等待状态。而后根据按下的按钮调用ServiceController方法。WaitForStatus方法表示正在等待确认服务将状态更改成请求的值,但等待最长时间仅为10秒。以后将刷新列表框中的信息,并将所选索引设置为与以前相同的值。而后显示该服务的最新状态。

因为应用程序须要管理权限,正如大多数服务须要权限去启动和中止,将设置为 requireAdministrator 的requestedExecutionLevel的应用程序清单添加到项目(应用程序清单文件ServiceControlWPF/app.manifest)中:

<?xml version="1.0" encoding="utf-8"?>
<asmv1:assembly manifestVersion="1.0" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
    <security>
      <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
        <requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
      </requestedPrivileges>
    </security>
  </trustInfo>
</asmv1:assembly>

图39.15显示了已完成及正在运行的应用程序。

图39.15

故障排除和事件日志

故障排除服务与对其余类型的应用程序进行故障排除不一样。本部分涉及一些服务问题,交互式服务特有的问题和事件日志。

开始构建服务的最佳方法是在实际建立服务以前建立具备所需功能和测试客户端的程序集。这里能够作正常的调试和错误处理。只要应用程序正在运行,就可使用该程序集来建立服务。固然,服务可能还有问题:

  • 不要在服务(在客户端系统上运行的交互式服务除外)的消息框中显示错误。而应使用事件日志记录服务将错误写入事件日志。固然,在使用该服务的客户端应用程序中,能够在消息框显示以通知用户有关错误。
  • 从调试器中没法启动服务,但能够将调试器附加到正在运行的服务进程。打开服务源代码的解决方案并设置断点。在 Visual Studio的“Debug”菜单中,选择进程并附加服务的运行进程。
  • 性能监视器可用于监视服务的活动,还能够将自定义的性能对象添加到服务。还能够添加一些有用的信息进行调试。例如,使用Quote服务,能够设置一个对象,以提供返回的引用总数,初始化所需的时间等。

服务能够经过向事件日志中添加事件来报告错误和其余信息。当AutoLog属性设置为true时,从ServiceBase派生的服务类自动记录事件。 ServiceBase类检查此属性,并在开始,中止,暂停和继续请求时写入日志条目。

图39.16显示了示例来自服务的日志。

图39.16

注意 有关事件日志以及如何编写自定义事件的详细信息,请阅读第20章“诊断和应用程序分析”。

总结

本章中了解了Windows服务的架构以及如何使用.NET Framework建立服务。Windows 服务应用程序能够在开机时自动启动,可使用特权系统账户做为服务的用户。 Windows服务是从主函数,主服务函数和处理事件建立的。还查看了有关Windows服务的其余相关程序,例如服务控制程序和服务安装程序。

.NET Framework对Windows服务有很大的支持。建立,控制和安装服务所需的全部管道代码都内置到System .ServiceProcess命名空间中的.NET Framework类中。经过从ServiceBase派生类,能够重写服务在暂停、恢复或中止时调用的方法。对于服务的安装,ServiceProcessInstaller和ServiceInstaller类处理服务所需的全部注册表配置。还可使用ServiceController控制和监视服务。

在下一章中,能够了解有关ASP.NET Core 1.0的技术,它利用Web服务器,且一般运行在Windows服务上(若是服务器使用Windows操做系统)。

(本章完)

相关文章
相关标签/搜索