Introduction

Dans le développement d'applications, il arrive souvent que l'on ait besoin de services globaux à l'application (service de cache, de log, de gestion des paramètres d'initialisation, etc.). Ces services ont souvent un cycle de vie particulier, devant être démarrés avant l'application (afin que celle-ci puisse s'en servir immédiatement) et arrêtés après

Silverlight 3 facilite la création de ce genre de services en introduisant la notion de service d'application.

I. Création du service

Pour illustrer cette nouveauté, nous allons créer un petit service permettant de gérer les paramètres d'initialisation d'une application Silverlight.

Pour rappel, afin de passer des paramètres d'initialisation à une application Silverlight il est possible, dans la page HTML contenant l'application, d'utiliser la balise param avec la valeur initParams pour l'attribut nameet de spécifier une liste de paires clef/valeur séparées par des virgules :

Passage de paramètres à l'application via la page html
Sélectionnez
<param name="initParams" value="fontsize=30" />

I-A. L'interface IApplicationService

L'interface IApplicationService est l'interface que doivent implémenter les services d'application. Elle fournit deux méthodes, StartService et StopService qui permettent respectivement de démarrer et d'arrêter le service.

La méthode StartService est appelée automatiquement avant l'évènement Startup de l'application (méthode Application_Startup du fichier App.xaml.cs) par le runtime Silverlight lors de l'initialisation de l'application. Elle fournit un paramètre de type ApplicationServiceContext qui contient une propriété ApplicationInitParams renvoyant les paramètres d'initialisation (dans le HTML).

Dans notre exemple de service, nous récupérons la liste (un dictionnaire plus précisément) de paramètres d'initialisation via l'argument context de la méthode StartService. Nous exposons cette liste via une propriété InitParams. Il est aussi intéressant de fournir un accesseur typé aux différents paramètres plutôt que de laisser les utilisateurs du service se dépatouiller avec le dictionnaire. C'est pourquoi nous déclarons une propriété FontSize de type double qui renverra la valeur du paramètre « fontsize ». Le paramètre étant récupéré sous forme de chaine de caractères nous devons le convertir en double. Si la conversion échoue, nous renvoyons une valeur par défaut de 20.

 
Sélectionnez
public class AppConfigService : IApplicationService
{
    public Dictionary<string, string> InitParams { get; private set; }

    public Double FontSize
    {
        get
        {
            double fontsize;
            //valeur par defaut 20
            return (InitParams.ContainsKey("fontsize") && double.TryParse(InitParams["fontsize"], out fontsize)) 
				? fontsize : 20;
        }
    }

    //appelé avant l'évènement Application.Startup 
    public void StartService(ApplicationServiceContext context)
    {
        InitParams = context.ApplicationInitParams;
    }

    //appelé après l'évènement Application.Exit 
    public void StopService()
    {        
    }
}


La méthode StopService est appelée après l'évènement Exit de l'application. Elle vous permet d'effectuer des opérations (libération des ressources utilisées par le service par exemple) après l'appel à la méthode Application_Exit de la classe App.xaml.cs afin de quitter proprement le service.

I-B. L'interface IApplicationLifetimeAware

Si vous voulez que votre service puisse réagir plus finement aux différents évènements liés au cycle de vie de l'application, vous pouvez lui faire implémenter l'interface IApplicationLifetimeAware. Celle-ci fournit quatre méthodes : Starting, Started, Exiting et Exited qui permettent d'effectuer des opérations immédiatement avant et après les évènements Application.Startup et Application.Exit.

Le graphique suivant permet de mieux comprendre l'ordre dans lequel sont appelées les différentes méthodes des deux interfaces :

II. Enregistrement du service

En Silverlight 3, la propriété ApplicationLifetimeObjects a été rajoutée à la classe Application. Il s'agit d'une IList<object> contenant les différents services d'application qui ont été enregistrés.

Il existe deux manières d'enregistrer un service : via le code XAML ou via le code C# (ou VB.NET).

La déclaration en XAML se fait dans la section Application.ApplicationLifetimeObjects du fichier App.xaml. N'oubliez pas de déclarer le namespace contenant le type du service.

Enregistrement des services de l'application dans App.xaml
Sélectionnez
<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             x:Class="SilverlightApplicationServicesDemo.App"
             xmlns:local="clr-namespace:SilverlightApplicationServicesDemo.Services">
  <Application.Resources>
  </Application.Resources>
  <Application. ApplicationLifetimeObjects>
    <local:AppConfigService/>
  </Application.ApplicationLifetimeObjects>
</Application>

Attention, dans le cas de la déclaration XAML, la classe du service devra contenir un constructeur sans paramètre !

Si vous préférez passer par le code, vous devrez déclarer votre service dans le constructeur de la classe Application :

Enregistrement des services de l'application dans le constructeur de App.cs
Sélectionnez
public App()
{            
    this.Startup += this.Application_Startup;
    this.Exit += this.Application_Exit;
    this.UnhandledException += this.Application_UnhandledException;

    InitializeComponent();

    //Enregistrement par code - Mettre en commentaire la section Application.Services dans App.xaml
   AppConfigService appConfigService = new AppConfigService();
   Application.Current.ApplicationLifetimeObjects.Add(appConfigService); 
}

L'enregistrement se fait en ajoutant une entrée à la propriété ApplicationLifetimeObjects en indiquant l'instance de notre service.

III. Utilisation du service

Lors de la version bêta de Silverlight 3, les services étaient enregistrés au sein d'un dictionnaire. Pour récupérer un service donné il suffisait alors de passer par la clef associée. Dans la version finale, ce dictionnaire a disparu au profit d'une simple liste. Récupérer un service devient alors un peu plus compliqué.

Une première méthode consisterait à passer par un numéro d'index :

 
Sélectionnez
AppConfigService appConfigService = Application.Current.ApplicationLifetimeObjects[0] as AppConfigService;

Cette méthode n'est cependant pas fiable. Vous ne pouvez jamais être sûr du numéro d'index du service que vous recherchez. Certains services peuvent être ajoutés ou supprimés au cours du cycle de vie de l'application.

Le mieux est donc de ne pas utiliser la liste ApplicationLifetimeObjects pour récupérer un service mais plutôt son propre gestionnaire de services.

Une technique proposée par Microsoft est de créer des singletons pour chaque service. L'accès à l'instance d'un service se fait alors via une propriété static de la classe :

Accès au service via un singleton
Sélectionnez
AppConfigService appConfigService = AppConfigService.Current;

Cependant, qui dit service dit en général interface associée. Il est en effet d'usage de passer par des interfaces lorsque l'on utilise un service plutôt que par son véritable type. Cela permet de modifier l'implémentation du service sans que cela ait d'impact sur le reste du code de l'application.

Voici donc l'interface associée à notre service de gestion des paramètres d'initialisation :

Interface du service de gestion des paramètres d'initialisation 
Sélectionnez
public interface IAppConfigService
{
    Dictionary<string, string> InitParams { get; }
    Double FontSize { get; }
}

Nous ne pouvons pas déclarer de propriétés static au sein d'une interface. Si le code de l'application n'a accès au service que via son interface, nous ne pouvons donc pas utiliser la technique du singleton vue précédemment. Il nous faut donc passer par un objet intermédiaire qui nous fournira une instance du service (via son interface). Pour cela nous allons employer le pattern « Service Locator ».

Ce pattern définit une classe permettant d'associer une instance d'un objet à une interface. Le fonctionnement ressemble à celui d'un dictionnaire dont la clef serait une interface et la valeur l'instance associée. D'ailleurs, dans l'implémentation de cette classe, il s'agit d'un dictionnaire qui est utilisé pour stocker les différentes instances des services.


Voici le code de la classe ServiceLocator :

La classe ServiceLocator
Sélectionnez
public class ServiceLocator
{
    private readonly Dictionary<Type, object> _servicesTable;

    private ServiceLocator()
    {
        _servicesTable = new Dictionary<Type, object>();
    }

    private static ServiceLocator _current;
    public static ServiceLocator Current
    {
        get
        {
            if (_current == null)
            {
                _current = new ServiceLocator();
            }
            return _current;
        }
    }

    public T GetService<T>()
    {
        return (T)_servicesTable[typeof(T)];
    }

    public void AddService<T>(object serviceInstance)
    {
        if (serviceInstance == null) throw new ArgumentNullException("serviceInstance");

        if (!typeof(T).IsInstanceOfType(serviceInstance))
        {
            throw new ArgumentException(
				string.Format("L'objet de service n'est pas de type {0}", typeof(T).Name), "serviceInstance");
        }
        _servicesTable.Add(typeof(T), serviceInstance);
    }

    public void RemoveService<T>()
    {
        _servicesTable.Remove(typeof(T));
    }
}

Son utilisation est très simple. Dans le constructeur de la classe Application il suffit d'instancier notre service AppConfigService puis de l'enregistrer dans le service location via la méthode générique AddService en spécifiant l'interface associée (IAppConfigService).

Enregistrement du service dans le service locator
Sélectionnez
//Enregistrement par code - Mettre en commentaire la section Application.Services dans App.xaml
Services.IAppConfigService appConfigService = new Services.AppConfigService();

//Insertion dans le service locator
Services.ServiceLocator.Current.AddService<Services.IAppConfigService>(appConfigService);

//Enregistrement dans les services d'applications
Application.Current.ApplicationLifetimeObjects.Add(appConfigService);

Lors de l'exécution de l'application Silverlight vous pouvez facilement accéder à un service en utilisant la méthode GetService du service locator et lui spécifiant l'interface implémentée par le service. Le service locator vous renverra l'instance du service associée à l'interface spécifiée.

Récupération du service via le service locator
Sélectionnez
IAppConfigService appConfigService = ServiceLocator.Current.GetService<IAppConfigService>();

if (appConfigService != null)
{
    txtHello.FontSize = appConfigService.FontSize;
}

Vous n'aurez ainsi jamais à connaitre le type exact du service mais seulement son interface.

IV. Liens

Sources

Téléchargez les sources de la solution donnée en exemple.

Remerciements

J'adresse ici tous mes remerciements à l'équipe de rédaction de "developpez.com" pour le temps qu'ils ont bien voulu passer à la correction et à l'amélioration de cet article.

Contact

Si vous constatez une erreur dans le tutorial, dans les sources, dans la programmation ou pour toutes informations, n'hésitez pas à me contacter par le forum.