ZebraPuma.System.ServiceProcess
Gestion Avancée des Services Windows avec Support Plugins
📋 Table des Matières
- Vue d'ensemble
- Architecture
- Interfaces et Classes
- ServiceManager
- ServiceControllerForm
- Configuration
- Utilisation
- Exemples
- API Référence
- Déploiement Windows
- Bonnes Pratiques
Vue d'ensemble
ZebraPuma.System.ServiceProcess est une bibliothèque avancée pour créer et gérer des services Windows extensibles en .NET. Elle combine le framework de services Windows natif avec le système de plugins ZebraPuma pour créer des services modulaires et maintenables.
Caractéristiques Principales
- ✅ Services Windows étendus avec interface
IServiceExtended - ✅ Classe de base
ServiceBaseExtendedthread-safe - ✅ ServiceManager pour gérer plusieurs services simultanément
- ✅ Interface WinForms de contrôle (
ServiceControllerForm) - ✅ Intégration complète avec ZebraPuma.Plugins
- ✅ Support multi-frameworks avec adaptation automatique
- ✅ Mode console pour développement et debugging
- ✅ Mode service pour production Windows
- ✅ Rechargement dynamique des services
Cibles Supportées
- .NET Framework 4.8 (Windows)
- .NET 10.0-windows
Dépendances
- ZebraPuma.Plugins (projet référencé)
- Newtonsoft.Json 13.0.4
- NLog 6.0.7
- System.ServiceProcess (pour .NET Framework 4.8)
- System.ServiceProcess.ServiceController 9.0.0 (pour .NET 10.0)
Architecture
Diagramme de Classes
┌─────────────────────────────────────────────────────────┐
│ Windows Service Host │
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ ServiceManager │ │
│ │ • Services: List<IServiceExtended> │ │
│ │ • StartService(name) │ │
│ │ • StopService(name) │ │
│ │ • StartAll() / StopAll() │ │
│ │ • ReloadFromPlugins() │ │
│ │ • Events: ServiceStateChanged, ServicesReloaded │ │
│ └────────────────────────────────────────────────────┘ │
│ │ │
│ ┌───────────────┴────────────┐ │
│ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ IServiceExt. │ │ServiceFactory│ │
│ │ + IPlugin │ │ LoadServices │ │
│ └──────────────┘ └──────────────┘ │
│ │ │
└──────────┼───────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────┐
│ ServiceBaseExtended │
│ : ServiceBase, IServiceExtended │
│ │
│ • OnStart(args) → OnStartCore(args) │
│ • OnStop() → OnStopCore() │
│ • StartService() / StopService() │
│ • IsRunning (thread-safe) │
│ • Lock-based synchronization │
└────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────┐
│ Votre Service Concret │
│ │
│ protected override OnStartCore() │
│ protected override OnStopCore() │
└────────────────────────────────────────┘
Flux d'Exécution
Mode Service Windows
1. Windows SCM lance le service
↓
2. OnStart() appelé automatiquement
↓
3. ServiceBaseExtended.OnStart() → StartInternal()
↓
4. Lock acquisition
↓
5. OnStartCore() de votre service
↓
6. IsRunning = true
↓
7. Service en cours d'exécution
↓
8. Windows SCM demande l'arrêt
↓
9. OnStop() → StopInternal()
↓
10. OnStopCore() de votre service
↓
11. IsRunning = false
Mode Console (Debug)
1. Application console démarre
↓
2. ServiceManager.CreateFromPlugins()
↓
3. Chargement des services via PluginLoader
↓
4. ServiceControllerForm.Show() (optionnel)
↓
5. manager.StartAll() ou StartService(name)
↓
6. Services s'exécutent
↓
7. manager.StopAll() avant exit
Interfaces et Classes
IServiceExtended
Interface étendant IPlugin avec les capacités de service Windows.
public interface IServiceExtended : IPlugin
{
/// <summary>
/// Démarre le service Windows.
/// </summary>
void StartService();
/// <summary>
/// Arrête le service Windows.
/// </summary>
void StopService();
/// <summary>
/// Indique si le service est actuellement en cours d'exécution.
/// </summary>
bool IsRunning { get; }
}
ServiceBaseExtended
Classe de base abstraite pour créer des services Windows.
public abstract class ServiceBaseExtended : ServiceBase, IServiceExtended
{
// Propriétés héritées de IPlugin
public abstract string Name { get; }
public virtual string Version { get; }
public virtual string Description { get; }
// Propriété de IServiceExtended
public bool IsRunning { get; }
// Méthodes publiques
public void StartService();
public void StopService();
public virtual void Initialize(IPluginContext context);
// Méthodes abstraites à implémenter
protected abstract void OnStartCore(string[] args);
protected abstract void OnStopCore();
// Pattern Dispose
protected virtual void DisposeManagedResources();
protected virtual void DisposeUnmanagedResources();
}
Caractéristiques :
- Thread-safe : Utilise un lock pour synchroniser Start/Stop
- Gestion d'état :
IsRunningvolatile pour lecture thread-safe - Sealed overrides :
OnStart()etOnStop()sont sealed, vous devez overrideOnStartCore()etOnStopCore() - Exception safety : Si
OnStartCore()lève une exception,IsRunningrestefalse
ServiceManager
Gestionnaire centralisé pour administrer plusieurs services.
public class ServiceManager : IDisposable
{
// Constructeurs
public ServiceManager(IServiceExtended service);
public ServiceManager(List<IServiceExtended> services);
// Factory methods
public static ServiceManager CreateFromPlugins(PluginLoadingOptions options = null);
// Propriétés
public IList<IServiceExtended> Services { get; }
// Méthodes de contrôle
public void StartService(string name);
public void StopService(string name);
public void RestartService(string name);
public void StartAll();
public void StopAll();
public void ReloadFromPlugins(PluginLoadingOptions options = null);
// Événements
public event EventHandler<ServiceStateChangedEventArgs> ServiceStateChanged;
public event EventHandler ServicesReloaded;
}
ServiceFactory
Factory pour créer des services depuis une configuration JSON.
public class ServiceFactory
{
public List<IServiceExtended> LoadServices(string configPath);
}
ServiceDefinition
Classe de configuration pour définir un service.
public class ServiceDefinition
{
public string Name { get; set; }
public string Type { get; set; }
public string Description { get; set; }
}
ServiceManager
Le ServiceManager est le composant central pour gérer plusieurs services.
Création
Depuis des plugins :
var manager = ServiceManager.CreateFromPlugins(new PluginLoadingOptions
{
PluginsDirectoryName = "Services",
ConfigFileName = "services.json"
});
Avec une liste de services :
var services = new List<IServiceExtended>
{
new MonService1(),
new MonService2()
};
var manager = new ServiceManager(services);
Contrôle des Services
// Démarrer un service spécifique
manager.StartService("MonService1");
// Arrêter un service
manager.StopService("MonService1");
// Redémarrer
manager.RestartService("MonService1");
// Démarrer tous les services
manager.StartAll();
// Arrêter tous les services
manager.StopAll();
Rechargement Dynamique
// Recharger les services depuis les plugins
manager.ReloadFromPlugins();
// Événement déclenché après rechargement
manager.ServicesReloaded += (sender, args) =>
{
Console.WriteLine("Services rechargés");
};
Événements
manager.ServiceStateChanged += (sender, args) =>
{
Console.WriteLine($"Service {args.ServiceName}: {(args.IsRunning ? "Démarré" : "Arrêté")}");
};
ServiceControllerForm
Interface graphique WinForms pour contrôler les services.
Utilisation
var manager = ServiceManager.CreateFromPlugins();
var form = new ServiceControllerForm(manager);
form.ShowDialog();
Fonctionnalités
- ✅ Liste tous les services avec leur état
- ✅ Boutons Start/Stop/Restart par service
- ✅ Bouton Start All / Stop All
- ✅ Bouton Reload pour recharger les plugins
- ✅ Refresh automatique de l'interface
- ✅ Gestion d'erreurs avec MessageBox
Configuration
services.json
Structure pour ServiceFactory :
[
{
"Name": "MonService1",
"Type": "MonApp.Services.MonService1, MonApp.Services",
"Description": "Premier service"
},
{
"Name": "MonService2",
"Type": "MonApp.Services.MonService2, MonApp.Services",
"Description": "Deuxième service"
}
]
plugins.json
Pour utiliser avec PluginLoader :
{
"AutoDiscover": true,
"Plugins": [
{
"Folder": "MonService",
"Assembly": "MonService.dll",
"Type": "MonApp.Services.MonService"
}
]
}
NLog.config
Logging structuré pour les services :
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<targets>
<target name="file" xsi:type="File"
fileName="${basedir}/logs/${shortdate}.log"
layout="${longdate} ${uppercase:${level}} ${logger} ${message} ${exception:format=tostring}" />
<target name="eventlog" xsi:type="EventLog"
source="MonService"
log="Application"
layout="${message} ${exception:format=tostring}" />
</targets>
<rules>
<logger name="*" minlevel="Info" writeTo="file" />
<logger name="*" minlevel="Error" writeTo="eventlog" />
</rules>
</nlog>
Utilisation
Scénario 1 : Service Simple
Étape 1 : Créer le Service
using System;
using System.Threading;
using NLog;
using ZebraPuma.Plugins;
using ZebraPuma.System.ServiceProcess;
namespace MonApp.Services
{
public class HeartbeatService : ServiceBaseExtended
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private Timer _timer;
public override string Name => "HeartbeatService";
public override string Description => "Service envoyant un heartbeat toutes les 10 secondes";
protected override void OnStartCore(string[] args)
{
Logger.Info("Démarrage de {Service}", Name);
_timer = new Timer(OnTick, null, TimeSpan.Zero, TimeSpan.FromSeconds(10));
}
protected override void OnStopCore()
{
Logger.Info("Arrêt de {Service}", Name);
_timer?.Change(Timeout.Infinite, Timeout.Infinite);
_timer?.Dispose();
_timer = null;
}
private void OnTick(object state)
{
Logger.Debug("Heartbeat à {Time}", DateTime.Now);
}
protected override void DisposeManagedResources()
{
_timer?.Dispose();
}
}
}
Étape 2 : Mode Console (Debug)
using System;
using ZebraPuma.System.ServiceProcess;
namespace MonApp
{
class Program
{
static void Main(string[] args)
{
if (Environment.UserInteractive)
{
// Mode console pour développement
var service = new HeartbeatService();
service.StartService();
Console.WriteLine("Service démarré. Appuyez sur Enter pour arrêter...");
Console.ReadLine();
service.StopService();
service.Dispose();
}
else
{
// Mode service Windows
var servicesToRun = new ServiceBase[]
{
new HeartbeatService()
};
ServiceBase.Run(servicesToRun);
}
}
}
}
Étape 3 : Installation Windows
# En tant qu'administrateur
# Créer le service
sc create HeartbeatService binPath="C:\MonApp\MonApp.exe" DisplayName="Heartbeat Service"
# Configurer le démarrage automatique
sc config HeartbeatService start=auto
# Démarrer le service
sc start HeartbeatService
# Vérifier l'état
sc query HeartbeatService
Scénario 2 : Multi-Services avec ServiceManager
Programme Principal
using System;
using System.ServiceProcess;
using ZebraPuma.Plugins;
using ZebraPuma.System.ServiceProcess;
namespace MonApp
{
class Program
{
static void Main(string[] args)
{
if (Environment.UserInteractive)
{
RunInConsoleMode();
}
else
{
RunAsWindowsService();
}
}
static void RunInConsoleMode()
{
var manager = ServiceManager.CreateFromPlugins(new PluginLoadingOptions
{
PluginsDirectoryName = "Services",
ConfigFileName = "plugins.json"
});
// Afficher l'interface de contrôle
var form = new ServiceControllerForm(manager);
form.ShowDialog();
// Cleanup
manager.StopAll();
manager.Dispose();
}
static void RunAsWindowsService()
{
// Charger les services
var services = PluginLoader.LoadPlugins<IServiceExtended>(new PluginLoadingOptions
{
PluginsDirectoryName = "Services"
});
// Convertir en ServiceBase[]
var serviceBases = services
.OfType<ServiceBase>()
.ToArray();
if (serviceBases.Any())
{
ServiceBase.Run(serviceBases);
}
}
}
}
Scénario 3 : Service avec Configuration
Configuration (heartbeatservice.json)
{
"IntervalSeconds": 30,
"LogToFile": true,
"LogToEventLog": false,
"Endpoints": [
"https://api1.example.com/ping",
"https://api2.example.com/ping"
]
}
Classe de Configuration
public class HeartbeatServiceConfig
{
public int IntervalSeconds { get; set; } = 10;
public bool LogToFile { get; set; } = true;
public bool LogToEventLog { get; set; } = false;
public List<string> Endpoints { get; set; } = new List<string>();
}
Service avec Configuration
using ZebraPuma.Plugins;
using ZebraPuma.System.ServiceProcess;
public class HeartbeatService : ServiceBaseExtended
{
private HeartbeatServiceConfig _config;
private Timer _timer;
public override string Name => "HeartbeatService";
public override void Initialize(IPluginContext context)
{
base.Initialize(context);
// Charger la configuration
_config = PluginConfigLoader.LoadAssemblyConfig<HeartbeatServiceConfig>();
Logger.Info("Configuration chargée: Intervalle={Seconds}s, Endpoints={Count}",
_config.IntervalSeconds, _config.Endpoints.Count);
}
protected override void OnStartCore(string[] args)
{
var interval = TimeSpan.FromSeconds(_config.IntervalSeconds);
_timer = new Timer(OnTick, null, TimeSpan.Zero, interval);
}
private void OnTick(object state)
{
foreach (var endpoint in _config.Endpoints)
{
try
{
// Ping l'endpoint
using (var client = new HttpClient())
{
var response = client.GetAsync(endpoint).Result;
Logger.Debug("Ping {Endpoint}: {Status}", endpoint, response.StatusCode);
}
}
catch (Exception ex)
{
Logger.Error(ex, "Erreur ping {Endpoint}", endpoint);
}
}
}
protected override void OnStopCore()
{
_timer?.Dispose();
}
}
Exemples
Exemple 1 : Service de Traitement de Queue
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using ZebraPuma.System.ServiceProcess;
public class QueueProcessorService : ServiceBaseExtended
{
private readonly ConcurrentQueue<string> _queue = new ConcurrentQueue<string>();
private CancellationTokenSource _cts;
private Task _processingTask;
public override string Name => "QueueProcessor";
protected override void OnStartCore(string[] args)
{
_cts = new CancellationTokenSource();
_processingTask = Task.Run(() => ProcessQueue(_cts.Token));
Logger.Info("Queue processor démarré");
}
protected override void OnStopCore()
{
_cts?.Cancel();
_processingTask?.Wait(TimeSpan.FromSeconds(30));
Logger.Info("Queue processor arrêté");
}
private async Task ProcessQueue(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
if (_queue.TryDequeue(out string item))
{
await ProcessItem(item);
}
else
{
await Task.Delay(100, ct);
}
}
}
private async Task ProcessItem(string item)
{
Logger.Debug("Traitement de {Item}", item);
await Task.Delay(1000); // Simule le traitement
}
public void Enqueue(string item)
{
_queue.Enqueue(item);
}
protected override void DisposeManagedResources()
{
_cts?.Dispose();
_processingTask?.Dispose();
}
}
Exemple 2 : Service de Monitoring avec Alertes
using System;
using System.Diagnostics;
using System.Threading;
using ZebraPuma.System.ServiceProcess;
public class SystemMonitorService : ServiceBaseExtended
{
private Timer _timer;
private PerformanceCounter _cpuCounter;
private PerformanceCounter _ramCounter;
public override string Name => "SystemMonitor";
public override string Description => "Surveillance du CPU et de la RAM";
protected override void OnStartCore(string[] args)
{
_cpuCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total");
_ramCounter = new PerformanceCounter("Memory", "Available MBytes");
_timer = new Timer(CheckSystem, null, TimeSpan.Zero, TimeSpan.FromMinutes(1));
Logger.Info("Monitoring système démarré");
}
private void CheckSystem(object state)
{
var cpu = _cpuCounter.NextValue();
var ram = _ramCounter.NextValue();
Logger.Info("CPU: {Cpu:F2}%, RAM disponible: {Ram:F0} MB", cpu, ram);
// Alertes
if (cpu > 90)
{
Logger.Warn("⚠️ CPU élevé: {Cpu:F2}%", cpu);
SendAlert($"CPU critique: {cpu:F2}%");
}
if (ram < 500)
{
Logger.Warn("⚠️ RAM faible: {Ram:F0} MB", ram);
SendAlert($"RAM critique: {ram:F0} MB");
}
}
private void SendAlert(string message)
{
// Envoyer email, SMS, etc.
Logger.Error("ALERTE: {Message}", message);
}
protected override void OnStopCore()
{
_timer?.Dispose();
_cpuCounter?.Dispose();
_ramCounter?.Dispose();
Logger.Info("Monitoring système arrêté");
}
}
API Référence
Namespace: ZebraPuma.System.ServiceProcess
Classes
| Classe | Description |
|---|---|
ServiceBaseExtended |
Classe de base pour services Windows |
ServiceManager |
Gestionnaire multi-services |
ServiceFactory |
Factory pour créer des services |
ServiceControllerForm |
Interface WinForms de contrôle |
ServiceDefinition |
Définition de configuration de service |
Interfaces
| Interface | Description |
|---|---|
IServiceExtended |
Contrat pour services Windows étendus |
Events
| Event | Args | Description |
|---|---|---|
ServiceStateChanged |
ServiceStateChangedEventArgs |
Service démarré/arrêté |
ServicesReloaded |
EventArgs |
Services rechargés |
Déploiement Windows
Installation d'un Service
# Méthode 1 : sc.exe (recommandé)
sc create "MonService" `
binPath="C:\MonApp\MonService.exe" `
DisplayName="Mon Service ZebraPuma" `
start=auto `
obj="LocalSystem"
# Méthode 2 : PowerShell
New-Service -Name "MonService" `
-BinaryPathName "C:\MonApp\MonService.exe" `
-DisplayName "Mon Service ZebraPuma" `
-StartupType Automatic
# Méthode 3 : InstallUtil (legacy)
InstallUtil.exe C:\MonApp\MonService.exe
Configuration du Service
# Changer le compte de démarrage
sc config MonService obj="DOMAIN\ServiceAccount" password="P@ssw0rd"
# Définir la description
sc description MonService "Service de traitement ZebraPuma"
# Configurer la récupération sur échec
sc failure MonService reset=86400 actions=restart/5000/restart/10000/restart/30000
Contrôle du Service
# Démarrer
Start-Service MonService
# ou
sc start MonService
# Arrêter
Stop-Service MonService
# ou
sc stop MonService
# Redémarrer
Restart-Service MonService
# Vérifier l'état
Get-Service MonService
# ou
sc query MonService
Désinstallation
# Arrêter d'abord
Stop-Service MonService
# Supprimer
sc delete MonService
# ou
Remove-Service MonService
Bonnes Pratiques
1. Thread Safety
✅ Bon - Utiliser des CancellationToken
private CancellationTokenSource _cts;
protected override void OnStartCore(string[] args)
{
_cts = new CancellationTokenSource();
Task.Run(() => DoWork(_cts.Token), _cts.Token);
}
protected override void OnStopCore()
{
_cts?.Cancel();
}
2. Timeout sur OnStopCore
✅ Bon - Toujours implémenter un timeout
protected override void OnStopCore()
{
_cts?.Cancel();
if (!_task.Wait(TimeSpan.FromSeconds(30)))
{
Logger.Warn("Timeout lors de l'arrêt");
}
}
3. Logging Structuré
✅ Bon
Logger.Info("Service {Name} démarré avec {Count} workers", Name, workerCount);
Logger.Error(ex, "Erreur lors du traitement de {Item}", item);
4. Gestion d'Erreurs
✅ Bon
protected override void OnStartCore(string[] args)
{
try
{
// Initialisation critique
InitializeDatabase();
}
catch (Exception ex)
{
Logger.Fatal(ex, "Échec d'initialisation critique");
throw; // Laisser remonter pour que IsRunning reste false
}
}
5. Ressources
✅ Bon
protected override void DisposeManagedResources()
{
_timer?.Dispose();
_connection?.Close();
_connection?.Dispose();
}