Sommaire
Certains de vos packages peuvent avoir besoin d’enregistrer/persister des données et vous devez savoir qu’à chaque mise à jour d’un package par sa sentinelle, l’ensemble du package est supprimé avant d’être redéployé.
Il existe deux possibilités pour persister les données :
- Utiliser un stockage “hors Constellation”
- Utiliser les StateObjects
Stocker en dehors de Constellation
Comme vous le savez, un package n’est ni plus ni moins qu’une application.
Vous pouvez donc utiliser n’importe quel système de stockage :
- File System
- Base de donnée en tout genre
- Web service
- Etc …
Libre à vous de stoker vos données où bon vous sembles mais attention si vous souhaitez diffuser votre package, vous ajoutez des prérequis pour l’installation de votre package.
Note sur le File System
Vous pouvez bien sûr stocker des données dans des fichiers. Par défaut le “CurrentDirectory” du package est son répertoire de déploiement. Prenez garde toutefois car à chaque déploiement du package par la sentinelle ce répertoire est vidé.
Vous devez donc utiliser un répertoire “externe” qu’on définira par un setting.
SerializationHelper
Notez que la librairie Constellation met à disposition la classe “SerializationHelper” dans le namespace “Constellation.Utils” permettant de sérialiser et dé-sérialiser facilement des objets en JSON ou en XML.
1 2 |
Constellation.Utils.SerializationHelper.SerializeToFile<MyCustomObject>(new MyCustomObject() { Number = 42, String = "Demo" }, PackageHost.GetSettingValue("MyData.json")); MyCustomObject myData = Constellation.Utils.SerializationHelper.DeserializeFromFile<MyCustomObject>(PackageHost.GetSettingValue("MyData.json")); |
Vous disposez des méthodes :
- SerializeToFile et DeserializeFromFile pour sérialiser/dé-sérialiser vers/depuis un fichier
- SerializeToString et DeserializeFromString pour sérialiser/dé-sérialiser vers/depuis un string
Par défaut c’est le format JSON qui est utilisé mais vous pouvez définir le DataContractSerialiser (format XML) dans les paramètres de la méthode.
Utiliser les StateObjects comme stockage
Le principe est d’utiliser les StateObjects comme stockage, très utile pour sauvegarder/restaurer l’état d’un package.
Chaque package peut publier des StateObjects que ce soit de simple valeur ou de véritable objet complexe. A chaque (re)démarrage de votre package, tous les StateObjects du package sont purgés de la Constellation mais cependant vous avez la possibilité de les récupérer dans votre code avant cette purge.
Pour cela vous devez ajouter dans le manifeste du package (PackageInfo.xml) l’attribut “RequestLastStateObjectsOnStart” à true.
Ensuite dans votre code, vous devez le plus tôt possible dans le “OnStart” vous abonnez à l’événement “LastStateObjectsReceived“.
Cet événement sera levé lorsque votre package recevra ses anciens StateObjects du serveur avant d’être purgés. L’argument passé dans l’événement contient une liste des StateObjects avant purge.
1 2 3 4 |
PackageHost.LastStateObjectsReceived += (s, e) => { PackageHost.WriteInfo("Reception de mes anciens StateObjects au nombre de {0}", e.StateObjects.Count); }; |
Vous pouvez “bêtement” re-pusher l’ensemble des StateObjects sans aucune modification :
1 2 3 4 5 6 7 8 |
PackageHost.LastStateObjectsReceived += (s, e) => { foreach (StateObject so in e.StateObjects) { // Re-Pusher “bêtement” les StateObjects PackageHost.PushStateObject(so.Name, so.DynamicValue, so.Type, so.Metadatas, so.Lifetime); } }; |
Grace à ce mécanisme, vos packages peuvent conserver des StateObject sur “leur état”.
Par exemple, imaginez un package “Brain” qui pilote l’allumage automatique des éclairages d’un jardin.
Ce package déclare un StateObjectLink vers un StateObject qui indique la luminosité extérieure. Si la valeur franchie un certain seuil, le package décide d’allumer ou éteindre les éclairages en envoyant un message pour déclencher un MessageCallback du package pilotant les éclairages.
Imaginez maintenant que suite à l’allumage automatique des éclairages par votre package vous décidez de les éteindre manuellement. Puis, quelques instants plus tard, pour diverses raisons vous devez redémarrer (ou mettre à jour) votre package “Brain”. Vos éclairages risquent de se rallumer car votre “Brain” n’a pas mémorisé qu’il a déjà réalisé cette action !
Pour ce genre de problématique, il est intéressant de stocker ces variables dans une classe d’état et de publier cette classe dans un StateObject qu’on pourra nommer “State”. Lorsque notre package redémarre, on demandera à Constellation de nous renvoyer les derniers StateObjects.
Cela nous permettra de récupérer le dernier état de notre package avant son redémarrage.
Par exemple, vous pouvez définir une classe d’état de la façon suivante :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
public class CurrentState { public DateTime OuvertureVolet { get; set; } public DateTime FemertureVolet { get; set; } public DateTime OuvertureLumiereJardin { get; set; } // etc.... public const string STATEOBJECT_NAME = "CurrentState"; private static CurrentState current; public static CurrentState Current { get { return current; } } public static void Load(StateObject stateObject = null) { if (stateObject != null) { PackageHost.WriteInfo("Restoring state from {0}", stateObject.LastUpdate); current = stateObject.GetValue<CurrentState>(); } else { PackageHost.WriteInfo("Creating new state"); current = new CurrentState(); } current.Save(); } public void Save() { PackageHost.WriteInfo("Saving current state"); PackageHost.PushStateObject(STATEOBJECT_NAME, current); } } |
Sans oublier d’ajouter un handler pour la restauration de l’état précèdent :
1 2 3 4 5 6 7 8 |
PackageHost.LastStateObjectsReceived += (s, e) => { if (e.StateObjects != null) { PackageHost.WriteInfo("Received old StateObjects (Count:{0})", e.StateObjects.Count); CurrentState.Load(e.StateObjects.FirstOrDefault(so => so.Name == CurrentState.STATEOBJECT_NAME)); } }; |
De cette façon vous pouvez à tout moment accéder à votre variable via votre classe statique “State.Current” et dès que vous changez une valeur, invoquez la méthode Save(), pour re-pusher votre “State” comme StateObject de votre package :
1 2 3 4 5 6 |
if (DateTime.Now.Date != CurrentState.Current.FemertureVoletSalon.Date && ** Luminosite < seuil ****) { // Fermer le volet ici !! CurrentState.Current.FemertureVoletSalon = DateTime.Now; CurrentState.Current.Save(); } |
Hello,
Quelle est la différence entre State et CurrentState dans les différents codes ? Es-ce une erreur d’inattention à la rédaction ? Sans avoir tout le code, c’est difficile de détecter d’où peut venir cette classe CurrentState
Bon courage !
Bonjour Rom,
Oui en effet c’est une erreur de ma part, State et CurrentState font référence à la même classe
Je viens de corriger l’exemple !
Merci du feedback