2016 年 10 月

第 31 卷,第 10 期

本文章是由機器翻譯。

Windows 服務 - 建立可自訂的 FileSystemWatcher Windows 服務

Diego Ordonez

FileSystemWatcher 類別是一個非常強大的工具,已經過 Microsoft.NET Framework 的一部分,自 1.1 版,並根據其正式定義 (bit.ly/2b8iOvQ),它 [接聽檔案系統變更通知並引發事件時在目錄中,變更檔案或目錄中。

這個類別是能夠在檔案系統中偵測事件,例如建立、 修改或刪除檔案和資料夾。它可完全自訂。而其建構函式會接受參數,例如資料夾位置和檔案延伸模組來接聽,並且布林參數來指定要接聽的處理程序是否應該執行遞迴地資料夾結構。然而,在您的程式碼中包含這些參數並不理想的方法因為它們不會幫助時必須包含新的資料夾和檔案的副檔名,此外,將會需要撰寫程式碼、 建置和重新部署應用程式。除非您確定您的應用程式將來信變更這些設定,進一步了解是實作一種機制,可以變更組態,而修改的原始程式碼。

在本文中我將探討如何撰寫的應用程式使用 FileSystemWatcher 類別只執行一次,但接著,透過 XML 序列化時,可以進一步修改應用程式的設定,例如資料夾名稱、 副檔名和引發事件時要執行的動作。如此一來,所有的變更就能輕鬆達成只要更新 XML 檔案,然後重新啟動 Windows 服務。

為了簡單起見,我不打算說明有關如何為 Windows 服務,執行這個 C# 主控台應用程式的詳細資訊,但許多資源可從線上關於這件事。

結構的自訂的資料夾設定

由於我想結構完善的 C# 類別還原序列化的 XML 設定檔,應用程式的第一個元件必須 FileSystemWatcher 運作所需的參數定義。[圖 1 顯示定義該類別的程式碼。

[圖 1 CustomFolderSettings 類別定義

/// <summary>
/// This class defines an individual type of file and its associated
/// folder to be monitored by the File System Watcher
/// </summary>
public class CustomFolderSettings
{
  /// <summary>Unique identifier of the combination File type/folder.
  /// Arbitrary number (for instance 001, 002, and so on)</summary>
  [XmlAttribute]
  public string FolderID { get; set; }
  /// <summary>If TRUE: the file type and folder will be monitored</summary>
  [XmlElement]
  public bool FolderEnabled { get; set; }
  /// <summary>Description of the type of files and folder location –
  /// Just for documentation purpose</summary>
  [XmlElement]
  public string FolderDescription { get; set; }
  /// <summary>Filter to select the type of files to be monitored.
  /// (Examples: *.shp, *.*, Project00*.zip)</summary>
  [XmlElement]
  public string FolderFilter { get; set; }
  /// <summary>Full path to be monitored
  /// (i.e.: D:\files\projects\shapes\ )</summary>
  [XmlElement]
  public string FolderPath { get; set; }
  /// <summary>If TRUE: the folder and its subfolders will be monitored</summary>
  [XmlElement]
  public bool FolderIncludeSub { get; set; }
  /// <summary>Specifies the command or action to be executed
  /// after an event has raised</summary>
  [XmlElement]
  public string ExecutableFile { get; set; }
  /// <summary>List of arguments to be passed to the executable file</summary>
  [XmlElement]
  public string ExecutableArguments { get; set; }
  /// <summary>Default constructor of the class</summary>       
  public CustomFolderSettings()
  {
  }
}

現在讓我們看看如何轉譯 XML 檔案,使用還原序列化程序這個 C# 類別。請注意不會有一個單一類別的執行個體 CustomFolderSettings;而是會讓 Windows 服務來接聽的許多不同的資料夾位置和檔案的副檔名清單 (清單 < CustomFolderSettings >)。

[圖 2 顯示我可以透過它提供 FileSystemWatcher 運作所需的引數的 XML 設定檔的範例。請務必瞭解目前的 XML 檔案中包含的資訊 ([圖 2) 會摘要的 C# 類別 ([圖 1)。

[圖 2 結構的 XML 設定檔

<?xml version="1.0" encoding="utf-8"?>
<ArrayOfCustomFolderSettings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <CustomFolderSettings FolderID="ExampleKML_files">
    <FolderEnabled>true</FolderEnabled>   
    <FolderDescription>Files in format KML corresponding to the example project
      </FolderDescription>
    <FolderFilter>*.KML</FolderFilter>
    <FolderPath>C:\Temp\testKML\</FolderPath>
    <FolderIncludeSub>false</FolderIncludeSub>
    <ExecutableFile>CMD.EXE</ExecutableFile>
    <!-- The block {0} will be automatically replaced with the
      corresponding file name -->
    <ExecutableArguments>/C echo It works properly for .KML extension-- File {0}
      &gt; c:\temp\it_works_KML.txt</ExecutableArguments>
  </CustomFolderSettings>
  <CustomFolderSettings FolderID="ExampleZIP_files">
    <FolderEnabled>false</FolderEnabled>
    <FolderDescription>Files in format ZIP corresponding to the example project
      </FolderDescription>
    <FolderFilter>*.ZIP</FolderFilter>
    <FolderPath>C:\Temp\testZIP\</FolderPath>
    <FolderIncludeSub>false</FolderIncludeSub>
    <ExecutableFile>CMD.EXE</ExecutableFile>
    <!-- The block {0} will be automatically replaced with the
      corresponding file name -->
    <ExecutableArguments>/C echo It works properly for .ZIP extension -- File {0}
      &gt; c:\temp\it_works_ZIP.txt</ExecutableArguments>
  </CustomFolderSettings>
</ArrayOfCustomFolderSettings>

讓我們仔細看看現在 XML 檔案中所包含的參數。首先,請注意,XML 根項目 < ArrayOfCustomFolderSettings >,可讓多的項目 < CustomFolderSettings > 所需。這是要能夠同時監視數個資料夾位置和檔案的副檔名的索引鍵。

第二,請注意,參數 < FolderEnabled > 第一個資料夾,但第二個為 false,則為 true。這是簡單的方法,而不必刪除從 XML 檔案,即使組態,則表示取消 FileSystemWatchers,執行時,此類別會省略它。

最後,務必了解如何指定何種動作將會觸發時偵測檔案的已建立、 刪除或修改,這是最終的目的,是 FileSystemWatcher 類別。

參數 < ExecutableFile > 包含應用程式,就會啟動,在此範例的 DOS 命令列 (cmd。EXE)。

參數 < ExecutableArguments > 包含將做為引數傳遞至可執行檔的選項。以下是從範例 [圖 2:

>/C echo It works properly for .ZIP extension -- File {0} &gt;
  c:\temp\it_ZIP_works.txt

這會轉譯成在執行時間︰

CMD.EXE /C echo it works properly for .ZIP extension –– File
  d:\tests\file_modified_detected.doc > c:\temp\it_works_ZIP.txt

它會將字串寫入檔案 c:\temp\it_works_ZIP.txt,並將由 FileSystemWatcher 已偵測到的檔案的實際名稱取代 XML 中的值 {0}。如果您熟悉 C# 方法字串。格式,就不需要找出任何問題。

嗯,現在我有一個 XML 組態檔和一個 C# 類別具有相符屬性,因此下一步是要還原序列化 XML 資訊清單的類別 (清單 < CustomFolderSettings >)。[圖 3 示範執行此步驟中索引鍵的方法。

[圖 3 還原序列化的 XML 設定檔

/// <summary>Reads an XML file and populates a list of <CustomFolderSettings> </summary>
private void PopulateListFileSystemWatchers()
{
  // Get the XML file name from the App.config file
  fileNameXML = ConfigurationManager.AppSettings["XMLFileFolderSettings"];
  // Create an instance of XMLSerializer
  XmlSerializer deserializer =
    new XmlSerializer(typeof(List<CustomFolderSettings>));
  TextReader reader = new StreamReader(fileNameXML);
  object obj = deserializer.Deserialize(reader);
  // Close the TextReader object
  reader.Close();
  // Obtain a list of CustomFolderSettings from XML Input data
  listFolders = obj as List<CustomFolderSettings>;
}

一旦執行此方法時,包含所有必要的 FileSystemWatcher 執行個體的清單,將可用,因此下一步是開始 FileSystemWatcher 類別,它會啟動接聽程序。

當然,方法需要知道的位置是 XML 設定檔,而且我可以使用 App.config 檔案來定義 XML 檔案的位置。以下是 App.config 的內容︰

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="XMLFileFolderSettings" value=
      "C:\Work\CSharp_FileSystemW\CustomSettings.xml" />
  </appSettings>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
  </startup>
</configuration>

請務必記得,XML 設定檔中或在 App.config 檔案中的任何變更,需要重新啟動 Windows 服務以套用這些變更的順序。

啟動 FileSystemWatcher 處理程序 (接聽變更)

此時,FileSystemWatcher 的數個 (或至少一個) 執行個體所需的所有設定都會在清單中建立 [圖 3

現在就開始接聽程序。為此,我需要清單執行迴圈,並啟動一個接一個執行個體。中的程式碼 [圖 4 示範如何執行初始化程序,以及如何指派從 XML 檔擷取的所有參數。

[圖 4 FileSystemWatcher 執行個體的初始設定

/// <summary>Start the file system watcher for each of the file
/// specification and folders found on the List<>/// </summary>
private void StartFileSystemWatcher()
{
  // Creates a new instance of the list
  this.listFileSystemWatcher = new List<FileSystemWatcher>();
  // Loop the list to process each of the folder specifications found
  foreach (CustomFolderSettings customFolder in listFolders)
  {
    DirectoryInfo dir = new DirectoryInfo(customFolder.FolderPath);
    // Checks whether the folder is enabled and
    // also the directory is a valid location
    if (customFolder.FolderEnabled && dir.Exists)
    {
      // Creates a new instance of FileSystemWatcher
      FileSystemWatcher fileSWatch = new FileSystemWatcher();
      // Sets the filter
      fileSWatch.Filter = customFolder.FolderFilter;
      // Sets the folder location
      fileSWatch.Path = customFolder.FolderPath;
      // Sets the action to be executed
      StringBuilder actionToExecute = new StringBuilder(
        customFolder.ExecutableFile);
      // List of arguments
      StringBuilder actionArguments = new StringBuilder(
        customFolder.ExecutableArguments);
      // Subscribe to notify filters
      fileSWatch.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName |
        NotifyFilters.DirectoryName;
      // Associate the event that will be triggered when a new file
      // is added to the monitored folder, using a lambda expression                   
      fileSWatch.Created += (senderObj, fileSysArgs) =>
        fileSWatch_Created(senderObj, fileSysArgs,
         actionToExecute.ToString(), actionArguments.ToString());
      // Begin watching
      fileSWatch.EnableRaisingEvents = true;
      // Add the systemWatcher to the list
      listFileSystemWatcher.Add(fileSWatch);
      // Record a log entry into Windows Event Log
      CustomLogEvent(String.Format(
        "Starting to monitor files with extension ({0}) in the folder ({1})",
        fileSWatch.Filter, fileSWatch.Path));
    }
  }
}

在此程式碼,FileSystemWatcher 接聽只建立的事件。不過,其他事件都可用,例如刪除和重新命名的。

我想要特別指向函式會建立 FileSystemWatcher 事件訂閱的線條。在這裡,我使用 lambda 運算式,重要的原因︰ 我有一份 FileSystemWatcher 類別的執行個體,因為我需要將特定的可執行檔,每個執行個體產生關聯。如果我這以不同方式處理 (亦即不使用 lambda 運算式,但直接指派函式),只有最後一個可執行檔將會保留並 FileSystemWatcher 執行個體將會執行相同的動作。

[圖 5 顯示每個單一執行個體 FileSystemWatcher 以個別的準則為基礎的動作會實際執行的函式的程式碼。

[圖 5 執行動作以準則為基礎的每個執行個體

/// <summary>This event is triggered when a file with the specified
/// extension is created on the monitored folder</summary>
/// <param name="sender">Object raising the event</param>
/// <param name="e">List of arguments - FileSystemEventArgs</param>
/// <param name="action_Exec">The action to be executed upon detecting a change in the File system</param>
/// <param name="action_Args">arguments to be passed to the executable (action)</param>
void fileSWatch_Created(object sender, FileSystemEventArgs e,
  string action_Exec, string action_Args)
{
  string fileName = e.FullPath;
  // Adds the file name to the arguments. The filename will be placed in lieu of {0}
  string newStr = string.Format(action_Args, fileName);
  // Executes the command from the DOS window
  ExecuteCommandLineProcess(action_Exec, newStr);
}

而且,最後, [圖 6 示範 ExecuteCommandLineProcess 函式,這是相當標準的方式來執行命令列指令 (DOS 主控台)。

[圖 6 執行命令列指示

/// <summary>Executes a set of instructions through the command window</summary>
/// <param name="executableFile">Name of the executable file or program</param>
/// <param name="argumentList">List of arguments</param>
private void ExecuteCommandLineProcess(string executableFile, string argumentList)
{
  // Use ProcessStartInfo class
  ProcessStartInfo startInfo = new ProcessStartInfo();
  startInfo.CreateNoWindow = true;
  startInfo.UseShellExecute = false;
  startInfo.FileName = executableFile;
  startInfo.WindowStyle = ProcessWindowStyle.Hidden;
  startInfo.Arguments = argumentList;
try
  {
    // Start the process with the info specified
    // Call WaitForExit and then the using-statement will close
    using (Process exeProcess = Process.Start(startInfo))
    {
      exeProcess.WaitForExit();
      // Register a log of the successful operation
      CustomLogEvent(string.Format(
        "Succesful operation --> Executable: {0} --> Arguments: {1}",
        executableFile, argumentList));
    }
  }
  catch (Exception exc)
  {
    // Register a Log of the Exception
  }
}

啟動和停止 Windows 服務內 FileSystemWatcher

一開始所述,為此應用程式被設計來執行以 Windows 服務,因此我需要方法來啟動或 Windows 服務啟動、 停止或重新啟動時自動停止 FileSystemWatcher 執行個體。即使我不打算深入的 Windows 服務定義,值得一提的是 Windows 服務實作的兩個主要的方法︰ OnStart 和 OnStop。一開始,每次啟動 Windows 服務,必須執行兩個動作︰ 填入 FileSystemWatcher 執行個體,從 XML 檔案的清單 ([圖 3),然後啟動執行個體 ([圖 4)。

若要從 Windows 服務啟動程序所需的程式碼如下︰

/// <summary>Event automatically fired when the service is started by Windows</summary>
/// <param name="args">array of arguments</param>
protected override void OnStart(string[] args)
{
  // Initialize the list of FileSystemWatchers based on the XML configuration file
  PopulateListFileSystemWatchers();
  // Start the file system watcher for each of the file specification
  // and folders found on the List<>
  StartFileSystemWatcher();
}

與最後,在方法 [圖 7實作邏輯,以停止 FileSystemWatcher; 它需要停止或重新啟動 Windows 服務。

[圖 7 停止 FileSystemWatcher

/// <summary>Event automatically fired when the service is stopped by Windows</summary>
protected override void OnStop()
{
  if (listFileSystemWatcher != null)
  {
    foreach (FileSystemWatcher fsw in listFileSystemWatcher)
    {
      // Stop listening
      fsw.EnableRaisingEvents = false;
      // Dispose the Object
      fsw.Dispose();
    }
    // Clean the list
    listFileSystemWatcher.Clear();
  }
}

總結

FileSystemWatcher 是功能強大的類別,可讓您監視 (接聽) 變更發生在檔案系統中,例如建立、 刪除和重新命名檔案和資料夾,以及修改它們。此應用程式是以 Windows 服務的身分執行,設計以便方便修改的檔案和資料夾進行監視,包括副檔名。我的方法使用非常方便用於.NET Framework、 序列化和還原序列化,讓您可以從 XML 檔案摘要 FileSystemWatcher 類別,而不需要對原始碼的任何變更。相反地,XML 設定檔中的任何修改之後, 就只需重新啟動 Windows 服務,就是這麼簡單,所做的變更會套用。


Diego Ordonez 是 15 年以上經驗的民間工程師 IT 使用主要 GIS 和 CAD 技術為分析師、 開發人員和架構設計人員。 他是 Microsoft 認證專業程式開發人員在 C#、 ASP.NET、 ADO.NET、 SQL Server 和他真正享有學習及套用.NET Framework 周圍的技術。他與妻子位於 Calgary,alberta 之上,加拿大和兩個可愛的女兒和適用於 Altus Geomatics GIS 團隊會導致 (bit.ly/2aWfi34)。

感謝下列 Microsoft 技術專家來檢閱這份文件︰ James McCaffrey
Dr。James McCaffrey 適用於在美國華盛頓州 Redmond 的 Microsoft Research他曾在數個 Microsoft 產品,包括 Internet Explorer 和 Bing。Dr。可以連線到 McCaffrey jammc@microsoft.com