Partager via



Octobre 2016

Volume 31, numéro 10

Cet article a fait l'objet d'une traduction automatique.

Windows Service : créez un service Windows FileSystemWatcher personnalisable

Par Diego Ordonez

La classe FileSystemWatcher est un outil très puissant qui a été une partie de Microsoft .NET Framework depuis la version 1.1, conformément à sa définition officielle (bit.ly/2b8iOvQ), il « écoute les notifications de modifications de système de fichiers et déclenche des événements lorsqu’un répertoire ou un fichier dans un répertoire est modifié ».

Cette classe est en mesure de détecter les événements du système de fichiers, telles que créer, modifier ou supprimer des fichiers et dossiers. Il est entièrement personnalisable ; et son constructeur accepte les paramètres tels que l’emplacement du dossier et l’extension de fichier pour écouter et un paramètre booléen pour indiquer si le processus d’écoute doit fonctionner de manière récursive dans la structure de dossiers. Toutefois, y compris les paramètres dans votre code source n’est pas une bonne approche car elles ne permettent pas lors de l’application doit inclure des dossiers et des extensions de fichier, qui, en outre, nécessite de codage, de création et de redéploiement. Sauf si vous êtes sûr que votre application sera très peu pour modifier ces paramètres, il est plus judicieux pour implémenter un mécanisme qui peut modifier la configuration sans modifier le code source.

Dans cet article, j’ai Explorer comment écrire une application qui utilise la classe FileSystemWatcher une seule fois, mais ensuite, via XML sérialisation, permet davantage les modifications apportées aux paramètres de l’application, telles que les noms de dossiers, les extensions de fichier et les actions à exécuter après le déclenchement d’un événement. De cette manière, toutes les modifications peuvent être obtenues facilement grâce simplement en mettant à jour un fichier XML et le redémarrage du service Windows.

Par souci de simplicité, je ne vais pas expliquer les détails sur l’exécution de cette application de console c# comme un service Windows, mais de nombreuses ressources sont disponibles en ligne à ce sujet.

La Structure des paramètres de dossier personnalisé

Étant donné que je prévois de désérialiser le fichier de paramètres XML dans une classe c# bien structurée, le premier composant de l’application doit être la définition des paramètres que nécessaires au fonctionnement de FileSystemWatcher. Figure 1 illustre le code qui définit cette classe.

Figure 1 définition de la classe 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()
  {
  }
}

Maintenant nous allons voir comment un fichier XML peut être traduit cette classe c# à l’aide du processus de désérialisation. Veuillez noter qu’il n’y avoir une seule instance de la classe CustomFolderSettings ; au lieu de cela il y aura une liste (< CustomFolderSettings >) ce qui permet au service Windows d’écouter pour de nombreux autres emplacements de dossiers et extensions de fichier.

Figure 2 montre un exemple d’un fichier de paramètres XML à partir de laquelle je peux fournir le FileSystemWatcher avec tous les arguments, elle doit fonctionner. Il est important de comprendre à ce stade que les informations contenues dans le fichier XML (Figure 2) se charge de la classe c# (Figure 1).

Figure 2 Structure de paramètres XML de fichiers

<?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>

Prenons l’examiner plus en détail les paramètres contenus dans le fichier XML maintenant. Tout d’abord, notez que l’élément racine XML < ArrayOfCustomFolderSettings >, ce qui permet d’autant d’éléments < CustomFolderSettings > en fonction des besoins. Il s’agit de la clé de la capacité de surveiller simultanément plusieurs emplacements de dossiers et extensions de fichier.

Ensuite, notez que le paramètre < FolderEnabled > a la valeur true pour le premier dossier, mais false pour le deuxième. Il s’agit d’un moyen simple de désactiver l’un de le FileSystemWatchers sans avoir à le faire à partir du fichier XML, ce qui signifie que même si la configuration est présente, la classe sera omettre lorsqu’il est exécuté.

Enfin, il est important de comprendre comment spécifier l’action qui sera déclenchée lors de la détection d’un fichier qui a été créé, supprimé ou modifié, qui est l’objectif final de la classe FileSystemWatcher.

Le paramètre < Fichier_exécutable > contient l’application est lancée, cette ligne de commande exemple DOS (CMD. (EXE).

Le paramètre < ExecutableArguments > contient les options qui seront transmies à l’exécutable en tant qu’arguments. Voici l’exemple à partir de Figure 2:

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

Elle se traduit en ce qui suit à l’exécution :

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

Il écrit la chaîne dans le fichier c:\temp\it_works_ZIP.txt, et la valeur {0} dans le fichier XML sera remplacé par le nom réel du fichier que FileSystemWatcher a détecté. Si vous êtes familiarisé avec la chaîne de méthode c#. Format, vous n’aurez pas tous les problèmes à ce jour il.

Eh bien, à présent faire en un fichier de configuration XML et c# une classe présentant des attributs, l’étape suivante consiste à désérialiser les informations XML dans une liste de classes (liste < CustomFolderSettings >). Figure 3 présente la méthode qui exécute cette étape importante.

Figure 3 la désérialisation des paramètres XML de fichiers

/// <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>;
}

Une fois que cette méthode s’exécute, une liste contenant toutes les instances FileSystemWatcher nécessaires sera disponible, l’étape suivante consiste à démarrer la classe FileSystemWatcher, qui démarre le processus d’écoute.

Bien entendu, la méthode doit savoir où est le fichier de paramètres XML et utiliser le fichier App.config pour définir l’emplacement du fichier XML. Voici le contenu du fichier 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>

Il est important de se rappeler que toutes les modifications dans le fichier de paramètres XML ou dans le fichier App.config nécessite le redémarrage du service Windows afin que ces modifications soient appliquées.

Démarrage du processus FileSystemWatcher (à l’écoute des modifications)

À ce stade, tous les paramètres requis pour les instances de plusieurs (ou moins) de la FileSystemWatcher sont disponibles dans la liste créée dans Figure 3.

Il est maintenant temps de commencer le processus d’écoute. Pour ce faire, j’ai besoin parcourir la liste et de démarrer les instances une par une. Le code dans Figure 4 montre comment effectuer le processus d’initialisation et affecter tous les paramètres récupérés à partir du fichier XML.

Figure 4 l’initialisation des Instances 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));
    }
  }
}

Dans ce code, le FileSystemWatcher écoute uniquement pour un événement de création. Toutefois, les autres événements sont disponibles, telles que supprimés et renommés.

Je souhaite surtout pointez sur la ligne où une fonction s’abonne à l’événement FileSystemWatcher créé. Ici, j’utilise une expression lambda pour une raison importante : Étant donné que je dispose d’une liste d’instances de la classe FileSystemWatcher, je dois associer un exécutable spécifique pour chaque instance. Si je gère cela différemment (qui est, en n’utilisant ne pas une expression lambda mais directement affecter la fonction), seulement l’exécutable dernière resteront et toutes les instances FileSystemWatcher effectue la même action.

Figure 5 présente le code de la fonction qui effectue l’action en fonction des critères individuels pour chaque instance unique de la FileSystemWatcher.

Figure 5 exécution d’une Action selon les critères pour chaque Instance

/// <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);
}

Et, enfin, Figure 6 illustre la fonction ExecuteCommandLineProcess, qui est un moyen très standard pour exécuter des instructions de ligne de commande (une console DOS).

Figure 6 l’exécution des Instructions de ligne de commande

/// <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
  }
}

Démarrage et arrêt FileSystemWatcher dans un Service Windows

Comme indiqué initialement, cette application est conçue pour être exécuté comme un service Windows, donc j’ai besoin d’un moyen pour démarrer ou arrêter les instances FileSystemWatcher automatiquement lorsque le service Windows démarre, arrête ou redémarre. Même si je ne vais pas à faire des recherches dans la définition du Service Windows, il est intéressant de mentionner les deux méthodes principales de l’implémentation du service Windows : OnStart et OnStop. Au départ, chaque fois que le service Windows démarre, il doit effectuer deux actions : Remplir la liste des instances FileSystemWatcher à partir du fichier XML (Figure 3), puis démarrez les instances (Figure 4).

Voici le code requis pour démarrer le processus à partir du service 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();
}

Et, enfin, la méthode Figure 7implémente la logique pour arrêter le FileSystemWatcher ; il nécessite l’arrêt ou le redémarrage du service Windows.

Figure 7 l’arrêt du 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();
  }
}

Pour résumer

FileSystemWatcher est une classe puissante qui vous permet de surveiller les modifications (écouter) qui se produisent dans le système de fichiers, telles que la création, suppression, changement de nom et les fichiers des dossiers, ainsi que leur modification. Cette application, qui est conçue pour s’exécuter comme un service Windows, a été conçue pour permettre la modification facile des fichiers et dossiers à analyser, y compris les extensions de fichier. L’approche que j’ai suivi utilise un concept très pratique disponible dans le .NET Framework, la sérialisation et la désérialisation, rendant ainsi possible d’introduire la classe FileSystemWatcher à partir d’un fichier XML sans aucune modification au code source. Au lieu de cela, après toute modification dans le fichier de paramètres XML, il vous suffit de redémarrer le service Windows et voilà, les modifications sont appliquées.


Diego Ordonez est ingénieur civil avec plus de 15 ans d’expérience en INFORMATIQUE fonctionne principalement avec GIS et CAO technologies comme architecte et développeur, analyste.  Il est un Microsoft Certified Professional Developer dans c#, ASP.NET, ADO.NET, SQL Server et il aime vraiment d’apprentissage et en appliquant des technologies dans le .NET Framework. Il habite à Calgary, Alberta, Canada, avec sa femme et entraîner des deux filles jolies et fonctionne pour géomatique Altus comme une équipe GIS (bit.ly/2aWfi34).

Merci à l'expert technique Microsoft suivant d'avoir relu cet article : James McCaffrey
Dr. James McCaffrey travaille pour Microsoft Research à Redmond, Wash. Il a travaillé sur plusieurs produits Microsoft, y compris Internet Explorer et Bing. Dr. James McCaffrey peut être atteint à jammc@microsoft.com.