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操做系統)。

(本章完)

相關文章
相關標籤/搜索