adszone.org
venerdì 20 settembre 2024
"Il modo corretto di pensare il software"
home
consulenza  vb.net  contatti 
  Visual Basic .NET
  Applicazioni Windows Service
Domande

Applicazioni Windows Service

Mettetevi comodi... Un servizio e' un'applicazione, per sistemi windows di tipo NT, che gira in background. Cio' significa che e' in esecuzione indipendentemente dagli utenti che accedono al computer. I servizi possono essere lanciati addirittura in fase di boot, quando l'utente non ha ancora modo di accedere. Essi hanno funzioni speciali e non richiedono un'interfaccia grafica, quindi non interferiscono con l'utente. In realta' e' possibile renderli interattivi, ma con il framework .NET questo e' diventato piu' complicato, forse per scoraggiarne l'approccio.

Queste applicazioni sono, teoricamente, in esecuzione fino a quando non si chiude windows. E' possibile controllarli, cioe', fermarli e farli ripartire, verificarne lo stato. I servizi girano in un contesto di sicurezza proprio, in genere impersonificano un particolare utente che ha i diritti adeguati alla funzione che devono svolgere.

Lo sviluppo di un servizio comporta notevoli differenze rispetto allo sviluppo di una semplice applicazione. Un servizio deve essere installato prima che possa essere eseguito. Non e' possibile eseguirlo immediatamente nell'IDE o farne il debug. In .NET, oltre al servizio, bisogna creare i suoi componenti d'installazione e registrazione. In sostanza bisogna creare il suo progetto di Setup.

In Visual Studio .NET, si puo' implementare un servizio in due modi: creare un progetto Windows Service oppure creare un progetto vuoto. Noi analizzeremo il secondo caso che ci permette di comprendere a fondo il meccanismo che realizza un servizio. Nel primo metodo, lo scheletro del servizio viene gia' fornito dall'ambiente di sviluppo e a noi spetta implementare le funzionalita'.

Attenzione. Poiche' utilizzo la versione in inglese sia di windows che di VS.NET, faro' riferimento alla versione inglese sia per quanto riguarda le etichette dei menu, sia per nomi degli oggetti del sistema ed altro.

Architettura di un Servizio

La struttura di un servizio e' incapsulata nella classe ServiceBase. Qualsiasi servizio, quindi, e' una classe che deriva da ServiceBase. ServiceBase espone i seguenti metodi che noi sovrascriamo per adattarli al nostro scopo. Questi metodi sono:

  • OnStart - Eseguito quando il servizio parte
  • OnPause- Eseguito quando il servizio viene messo in pausa
  • OnStop - Eseguito quando il servizio e' stoppato
  • OnContinue - Eseguito quando si vuole continuare dopo la pausa
  • OnShutDown - Eseguito quando il sistema sta per essere spento
  • OnCustomCommand - Eseguito per notificare un comando Custom
  • OnPowerEvent - Eseguito in risposta ad un evento Power Management

E' compito di un Service Controller chiamare tali metodi per gestire il servizio. Il Service Controller Manager (SCM) di windows e' uno di questi controller. Su windows 2000, ad esso si accede con il tasto destro del mouse su MyComputer e quindi manage. Un altro controller e' implementato direttamente nell'IDE di Visual Studio .NET ed altri ancora si implementano con la classe ServiceController.

Ci sono altre due classi che concorrono allo sviluppo di un service. Esse sono: ServiceProcessInstaller e ServiceInstaller. Queste, in genere, vengono gestite trasparentemente dall'IDE tramite interfaccia grafica.

Come ogni programma, un servizio necessita di un main entry point statico, nel quale instanziare il servizio e chiamare il metodo Run della classe ServiceBase che e' il motore del servizio.

La classe ServiceBase fornisce anche una serie di proprieta' utili per caratterizzare il comportamento del servizio:

  • CanStop - Indica se il servizio e' stoppabile o meno
  • CanShutDown - Indica se il servizio ricevera' la notifica di shutdown
  • CanPauseAndContinue - Indica se il servizio puo' essere messo in pausa
  • CanHandlePowerEvent - Indica se il servizio ricevera la notifica di Power Management
  • AutoLog - Se True, verranno scritti automaticamente messaggi informativi nel Log degli eventi del sistema

Esempio:

Il seguente codice fa', dell'esempio del FileSystemWatcher, presentato in un altro articolo, un servizio di windows. Non avendo a disposizione un interfaccia grafica, ci limitiamo ad accodare gli eventi della directory "desktop" in un file di log chiamato: c:\SampleService.txt. Ricordiamo che gli eventi a cui siamo interessati sono creazione, cancellazione, modifica e rinominazione di file e directory.

1) Innanzitutto, come detto, deriviamo la classe da ServiceBase. Il costruttore inizializza il servizio. ServiceName da' il nome al servizio, lo chiamiamo "SampleService", come la classe. Esso puo' essere stoppato e messo in pausa. Notate che abilitiamo l'AutoLog: il servizio scrivera' automaticamente delle entry nell'Application Event Log di sistema.

Public Class SampleService
    Inherits System.ServiceProcess.ServiceBase

    Private m_watcher As System.IO.FileSystemWatcher

    '
    ' Costruttore del SampleService
    '
    Public Sub New()
        Me.ServiceName = "SampleService"
        Me.CanStop = True
        Me.CanPauseAndContinue = True
        Me.AutoLog = True
    End Sub

    '
    ' Qui verra' inserito il resto del codice...
    '

End Class

2) Ora vediamo come implementare i comandi standard OnStart, OnStop, etc. OnStart e' utilizzata per inizializzare il compito del servizio, nel nostro caso facciamo il setup del FileSystemWatcher. In OnStop, ci interessa disabilitare gli eventi del watcher. OnPause ed OnContinue disabilitano ed abilitano gli eventi del watcher.

    '
    ' Gestori degli eventi del SampleService
    '
    Protected Overrides Sub OnStart(ByVal args() As String)
        watcher = New System.IO.FileSystemWatcher()
        watcher.EnableRaisingEvents = False
        watcher.Path = "C:\Documents and Settings\a\Desktop\"
        watcher.NotifyFilter = IO.NotifyFilters.LastAccess Or _
                               IO.NotifyFilters.LastWrite Or _
                               IO.NotifyFilters.DirectoryName Or _
                               IO.NotifyFilters.FileName
        watcher.Filter = "*.txt"

        AddHandler watcher.Created, AddressOf OnCreated
        AddHandler watcher.Changed, AddressOf OnChanged
        AddHandler watcher.Deleted, AddressOf OnDeleted
        AddHandler watcher.Renamed, AddressOf OnRenamed

        watcher.EnableRaisingEvents = True
    End Sub

    Protected Overrides Sub OnStop()
        watcher.EnableRaisingEvents = False
    End Sub

    Protected Overrides Sub OnPause()
        watcher.EnableRaisingEvents = False
    End Sub

    Protected Overrides Sub OnContinue() 
        watcher.EnableRaisingEvents = True
    End Sub

3) Seguono i gestori degli eventi del FileSystemWatcher, sono gli stessi gia' utilizzati nell'esempio del FileSystemWatcher. In questo caso inviano il messaggio al metodo Logger che lo scrive sul file di log, accodandolo ai precedenti.

    '
    ' Gestori di eventi del FileSystem
    '
    Private Sub OnCreated(ByVal sender As Object, _
            ByVal e As System.IO.FileSystemEventArgs)
        Dim msg As String
        msg = "Created: """ & e.Name & """"
        Logger(msg)
    End Sub

    Private Sub OnChanged(ByVal sender As Object, _
            ByVal e As System.IO.FileSystemEventArgs)
        Dim msg As String
        msg = "Changed: """ & e.Name & """"
        Logger(msg)
    End Sub

    Private Sub OnDeleted(ByVal sender As Object, _
            ByVal e As System.IO.FileSystemEventArgs)
        Dim msg As String
        msg = "Deleted: """ & e.Name & """"
        Logger(msg)
    End Sub

    Private Sub OnRenamed(ByVal sender As Object, _
            ByVal e As System.IO.RenamedEventArgs)
        Dim msg As String
        msg = "Renamed: """ & e.OldName & """ to """ & e.Name & """"
        Logger(msg)
    End Sub

4) Logger gestisce l'archiviazione dei messaggi degli eventi relativi al filesystem. Apre un file di testo c:\SampleService.txt in modalita' append e vi scrive sia la data dell'evento che il tipo di evento.

    '
    ' Gestione del file di log
    '
    Private Sub Logger(ByVal msg As String)
        Dim fs As Stream = New FileStream("c:\SampleService.txt", _
                                 FileMode.Append)
        Dim sw As StreamWriter = New StreamWriter(fs)
        sw.WriteLine(Now)
        sw.WriteLine(msg)
        sw.Close()
        fs.Close()
    End Sub

5) Questo e' il main entry point della nostra applicazione. Facciamo partire il servizio eseguendo il metodo Run della classe ServiceBase e ad esso forniamo una nuova istanza del nostro servizio.

    '
    ' Entry Point dell'applicazione
    '
    Shared Sub main()
        System.ServiceProcess.ServiceBase.Run(New SampleService())
    End Sub

Lo sviluppo del servizio non termina qui. Ora bisogna creare quelle automazioni che siano in grado si installarlo.

Aggiunta degli installer

Fortunatamente l'aggiunta degli installer non comporta necessariamente scrittura di codice da parte nostra. Ci limiteremo a settare qualche proprieta'.

  1. Cliccate sull'interfaccia grafica del servizio che dovrebbe essere vuota, poi selezionate la finestra delle proprieta'. In fondo a tale finestra, nella zona grigia, compare un link Add Installer.
  2. Cliccando tale link due installer vengono aggiunti al progetto, ServiceProcessInstaller1 e ServiceInstaller1.
  3. Selezionate le proprieta' di ServiceInstaller1 ed impostate ServiceName col nome del nostro servizio. Il nome deve essere lo stesso che abbiamo dato nel costruttore: "SampleService". Dovessimo cambiarlo nel costruttore, bisogna aggiornarlo anche qui.
  4. Impostate la proprieta' StartType ad Automatic.
  5. Selezionando le proprieta' di ServiceProcessInstaller1, impostate Account con il valore LocalSystem.

Note:

StartType puo' assusmere i seguenti valori che sono conrollabili successivamente dal SCM.

  • Manual - Il servizio deve essere avviato manualmente, dopo ogni boot
  • Automatic- Il servizio sara' avviato automaticamente dopo il boot
  • Disabled - Il servizio e' disabilitato, non puo' essere avviato

Account puo' assusmere i seguenti valori che sono conrollabili successivamente dal SCM: User, LocalService, LocalSystem, NetworkService. Per maggiori dettagli vedere Specifying the Security Context for Services nella documentazione di Visual Studio .NET.

Creazione del progetto di Setup

Aggiungiamo un progetto di setup:

  1. File menu -> Add Project -> New Project.
  2. Scegliete la cartella Setup and Deployment Projects.
  3. Selezionate un Setup Project, nominandolo SampleServiceSetup.

Inseriamo il servizio SampleService.exe nel setup:

  1. Nel Solution Explorer, clicchiamo col destro su SampleServiceSetup -> Add -> Project Output. Otterremo la dialog box chiamata Add Project Output Group.
  2. SampleService dovrebbe essere gia' selezionato.
  3. Selezionate Primary Output dalla lista.

Aggiungiamo azioni personalizzate al setup:

  1. Nel Solution Explorer, clicchiamo col destro su SampleServiceSetup -> View -> Custom Actions. Otterremo l'editor chiamato Custom Actions.
  2. Nell'editor, cliccate col destro sul nodo Custom Actions, scegliendo Add Custom Action.
  3. Doppio click su Application Folder, selezionare Primary Output from SampleService(Active). Ok. Otteniamo l'effetto di aggiungere il primary output alle seguenti azioni: Install, Commit, Rollback, Uninstal.

Test

Adesso si puo' compilare l'intero lavoro, cliccando col destro, nel Solution Explorer, su SampleServiceSetup e scegliere Build. Dopo ogni modifica apportata ai progetti, si puo' compilare tutto in questo stesso modo.

Per installare il progetto, e' possibile cliccare ancora con il destro su SampleServiceSetup, e scegliere Install. La disinstallazione e' analoga. Nelle cartelle di progetto create da VS.NET e' anche disponibile il file eseguibile per il Setup.

Lanciamo l'SCM oppure il Server Explorer nell'IDE ed individuiamo, fra i nodi, il nostro servizio. Facciamolo partire. Cercate nel log degli eventi per controllare i messagi inviati dal nostro servizio. Giocate con qualche file di testo sul desktop, rinominandolo etc. Poi controllate il contenuto del file c:\SampleService.txt e vedete se il servizio sta loggando gli eventi.

Ultimo aggiornamento 13/01/2004