Chaque package, virtuel ou non, peut publier des StateObjects dans votre Constellation. Découvrons dans cet article comment intégrer les valeurs de ces StateObjets dans votre code C#.

La base : le Request & Subscribe de StateObjects

Le hub Constellation comporte deux méthodes :

  • RequestStateObjects : permettant d’interroger « à un instant T »  des StateObjects de votre Constellation
  • SubscribeStateObjects : permettant de s’abonner aux mises à jours des StateObjects de votre Constellation

Ces deux méthodes acceptent jusqu’à 4 paramètres, tous optionnels :

Vous devez définir le ou StateObjects que vous souhaitez récupérer (Request) ou suivre (Subscribe) en appliquant des filtres. Le wildcard “*” permet de tout sélectionner, c’est à dire de ne pas filtrer.

Par exemple, pour récupérer tous les StateObjects du package “MonPackage” (et peut importe le nombre d’instance du package) :

Si vous souhaitez tous les StateObjects du package “MonPackage” qui tourne sur une sentinelle en particulier, nommée ci-dessous « MA-SENTINELLE » :

Pour cibler un StateObject en participer sur une instance d’un package :

Bien entendu, vous pouvez définir la combinaison que vous souhaitez !

Par exemple, récupérons tous les StateObjects de type “HWMonitor.HardwareList” dans notre Constellation :

Comme on sait que ce type de StateObject ne peut être publié que par le package “HWMonitor”, on pourrait écrire :

Dans les deux cas (Request ou Subscribe) les StateObjects sont reçus un par un par l’événement .NET : “PackageHost.StateObjectUpdated”.

Par exemple, déployons le package”HWMonitor” et analysons le StateObject “Hardware” via le StateObject Explorer :

Detail d'un StateObject dans la Console

On retrouve un StateObject de type “HWMonitor.HardwareList” qui contient une liste du hardware de la machine.

Dans notre package nous allons récupérer tous les StateObjects de type “HWMonitor.HardwareList” (donc autant de StateObjects que nous avons d’instance de ce package dans notre Constellation).

On pourrait écrire :

En testant notre package dans Constellation :

Console log

Notez qu’ici nous avons utilisé la méthode « RequestStateObjects » c’est à dire que nous récupérons tous les StateObjects du package HWMonitor (quelque soit la sentinelle) de type « HWMonitor.HardwareList » au moement de l’invocation de la méthode.

Allons un peu plus long en affichant en temps réel la consommation du CPU. Cette valeur est publiée dans le StateObject “/intelcpu/0/load/0” par le package HWMonitor.

Pour suivre en temps réel la consommation du CPU des machines (sentinelles) sur lesquelles le package HWMonitor est déployé, on commence par s’abonner à ce StateObject :

Puis dans l’événement “StateObjectUpdated” il faudra différencier le traitement en fonction du StateObject reçu.

Le code final :

Console log

Notez qu’ici nous avons utilisé la méthode « SubscribeStateObjects » c’est à dire que nous nous abonnons à tous les StateObjects nommés “/intelcpu/0/load/0” du package HWMonitor (quelque soit la sentinelle). C’est un abonnement, donc à chaque modification des StateObjects respectant notre filtre, nous recevrons dans notre code les nouvelles valeurs des StateObjects.

Cependant, si les StateObjects changent peu fréquemment, nous n’obtenons rien immédiatement. C’est pourquoi il est parfois nécessaire de faire un Request suivi d’un Subscribe pour obtenir la version actuelle du/des StateObject(s) et s’abonner aux mises à jour futures.

Les StateObjectLink

Pour simplifier l’exploitation des StateObjects dans votre code, l’API Constellation introduit la notion de “StateObjectLink”.

Avec l’API.NET, il vous suffit simplement de déclarer une propriété .NET dans votre code que vous allez décorer avec l’attribut [StateObjectLink].

Votre propriété .NET peut être privée ou publique, d’instance ou statique. De plus le type de votre propriété .NET peut être :

  • Un type de base
  • Un type complexe
  • Un dynamic
  • Un StateObject
  • Un StateObjectNotifier
  • Un StateObjectCollectionNotifier

Par défaut seules les propriétés .NET de l’instance de votre package (IPackage) sont enregistrées. Si dans votre package vous instanciez des classes contenants des StateObjectsLink vous devez appeler la méthode “PackageHost.RegisterStateObjectLinks” en passant l’instance de votre classe pour l’enregistrement de ses StateObjectLinks (autrement les propriétés resteront nulles).

Lier la valeur d’un StateObject à une propriété NET

Prenons l’exemple du StateObject “MyNumber” que vous avons publié dans cet article par la ligne :

Pour qu’un autre package puisse l’inclure dans son code, on peut simplement écrire :

Ici la propriété est de type “int” (car la valeur du StateObject est un int) et est liée à la valeur de ce StateObject.

Dès que le StateObject est mis à jour (PushStateObject) par le package qui la produit, votre propriété est également mise à jour de sorte que vous ayez toujours la dernière valeur du StateObject dans votre propriété.

Bien entendu la valeur du StateObject doit être compatible avec le type de la propriété liée (même type ou cast implicite possible), sinon la valeur du StateObject lié ne sera jamais affecté à votre propriété.

Vous pouvez également utiliser des types complexes. Par exemple prenons l’exemple déjà cité ci-dessus :

Pour créer le StateObjectLink depuis un autre package, on pourra écrire :

La propriété “MyObject” sera bien du type “MyCustomObject” et sera lié au StateObject nommé “MyObject”.

Cela sous entend que votre package ait la même définition du type “MyCustomObject” : soit en recopiant la classe ou soit en partageant une assembly commune. Notez toutefois que, comme Constellation connait la description des types utilisés par les StateObjects ou MessagesCallbacks, il est possible de générer le code (lire ici).

Maintenant si le StateObject est un type anonyme ou que vous n’avez pas la définition du type dans votre code, vous pouvez utiliser le type “dynamic”.

On pourrait alors écrire :

Pour terminer reprenons notre StateObject “Hardware” publié par le package HWMonitor :

Et à tout moment dans votre code pour pourrez itérer sur cette liste :

On pourrait reprendre également l’exemple du CPU mais nous aurions un problème : comment savoir quand le StateObject à été mis à jour pour afficher la nouvelle valeur ? C’est que nous verrons avec le StateObjectNotifier ci-dessous.

L’unicité des liens

Dans les exemples ci-dessus, nous lions des propriétés .NET aux StateObjects de votre Constellation en utilisant seulement leurs noms (Name). Or comme vous le savez il peut y avoir, dans votre Constellation, plusieurs packages sur plusieurs sentinelles qui publient des StateObjects avec le même nom !

De ce fait, je peux par exemple écrire :

Ici, je crée un lien entre cette propriété .NET et les StateObjects nommés “MyObject” et publiés par le package “MonPackage”. Mais je ne sais pas combien d’instance du package “MonPackage” je trouverai dans ma Constellation (je pourrais déployer ce package sur toutes mes sentinelles).

L’unicité ne peut se faire qu’avec le triplet : Nom de la sentinelle + Nom du package + Nom du StateObject.

Dans ce dernier cas j’ai la garantie de lier cette propriété .NET à un et un seul StateObject : celui nommé “MyObject” publié par le package “MonPackage” et déployé sur la sentinelle “MON-PC”.

Dans le cas où votre lien correspond à plusieurs StateObjects, chaque mise à jours de l’un d’entre eux est affectée à la propriété.

Ainsi si vous avez un package nommé “MonPackage” qui tourne sur deux sentinelles (disons “PC1” et “PC2”), votre propriété “MyObject” définie ci-dessous sera tantôt liée à la valeur du StateObject publié par le package sur PC1 tantôt sur l’instance de PC2.

Soyez donc vigilant aux liens que vous créez, ou sinon utilisez les StateObjectCollectionNotifier comme nous le verrons dans la suite de cet article.

Lier le StateObject entier à une propriété .NET

Jusqu’à présent nous avons lier des propriétés .NET avec les valeurs de StateObjects. Une fois la liaison établie, vous pouvez utiliser ces propriétés pour accéder à la valeur des StateObjects mais non au StateObject lui même.

Le StateObject contient à la fois la valeur du StateObject mais également les propriétés du StateObject comme son nom, le couple sentinelle/package qui l’a publié, sa date de publication, sa durée de vie (lifetime), son type ou encore des métadonnées (dictionnaire de string/object).

Pour cela, il suffit simplement de créer une propriété de type “StateObject”.

Par exemple pour le StateObject “Hardware” du package “HWMonitor” de la sentinelle “MON-PC” (pour n’avoir qu’une seule valeur), on peut écrire :

Vous aurez ensuite accès aux différentes propriétés de votre StateObject :

Un StateObject

La valeur du StateObject peut être obtenue par la propriété “Value” ou “DynamicValue”.

La propriété “DynamicValue” retourne la “Value” en tant qu’objet dynamique. Dans le cas d’un objet complexe, la “Value” sera de type JObject ou JArray.

Il est donc conseillé de travailler directement avec la “DynamicValue” pour résoudre dynamiquement la valeur de votre StateObject.

Vous disposez également d’une méthode GetValue<T> (ou son équivalent TryGetValue) qui se chargera de convertir votre valeur de StateObject en T .

StateObjectNotifier : être notifié des mises à jour des StateObjects liés

Jusqu’à présent nous lions des propriétés .NET avec la valeur d’un StateObject ou le StateObject lui même.

Dans les deux cas, vous pouvez accéder à tout moment à la dernière version de votre StateObject (ou sa valeur) publié dans votre Constellation car un StateObjectLink réalise implicitement un RequestStateObjects à l’initialisation de la propriété puis s’abonne aux StateObjects (SubscribeStateObject) pour mettre à jour en temps réel votre propriété dès que le ou les StateObjects liés sont mis à jour.

Seulement vous ne savez pas “quand” vos StateObject liés sont mis à jour, autrement dit quand est-ce que vos propriétés .NET sont mises à jour.

Pour cela il existe les StateObjectNotifiers. Le principe est simple, vous devez simplement créer une propriété liés de type StateObjectNotifier.

Reprenons le StateObject du CPU publié par le package HWMonitor sur “MON-PC” :

La classe StateObjectNotifier un container de StateObject. Elle comporte une propriété “Value” dans laquelle est contenu le StateObject.

On peut donc afficher la consommation à instant T :

Pour détailler :

  • this (l’instance courante)
  • .CPU (le StateObjectNofitier lié au StateObject de notre CPU)
  • .Value (l’objet StateObject en question)
  • .DynamicValue (la valeur du StateObject sous forme dynamique)
  • .Value (la propriété de l’objet publié par le package HWMonitor)

Notez que le StateObjectNotifier comporte une propriété “DynamicValue” qui retourne la “DynamicValue” du StateObject (en clair : StateObjectNotifier.DynamicValue = StateObjectNotifier.Value.DynamicValue).

On peut donc simplifier  le code par :

Lorsque le (ou les) StateObjets liés sont mis à jour, l’instance du StateObjectNotifier reste inchangée, c’est seulement sa propriété Value qui est mis à jour. De ce fait il est possible de s’abonner à des événements sur un StateObjectNotifier.

A ce sujet, le StateObjectNotifier implémente l’interface bien connue en .NET et notamment dans le monde WPF : “INotifyPropertyChanged”.

De ce fait, un StateObjectNotifier comporte un événement “PropertyChanged” qui vous informera lorsque le StateObject change.

Très pratique notamment pour rafraichir une interface graphique WPF (binding WPF).

De plus le StateObjectNotifier comporte un 2ème événement qui se déclenche lui aussi à la mise à jour du StateObject : le “ValueChanged”.

La différence avec le PropertyChanged réside dans l’EventArgs passé lorsque l’événement se déclenche :

ValueChanged d'un StateObjectNotifier

Le “StateObjectChangedEventArgs” fourni trois propriétés :

  • NewState : la nouvelle version du StateObject
  • OldState : l’ancienne version du StateObject
  • IsNew : un booléen qui indique si c’est un “nouveau” StateObject (c’est à dire que OldState est null)

On peut donc comparer l’évolution du StateObject

StateObjectLink et Notifier personnalisés

Notez que vous pouvez créer vos propres attributs “StateObjectLink” personnalisés en héritant de la classe “StateObjectLinkAttribute”.

De la même façon vous pouvez également créer vos propres StateObjectNotifier en créant simplement une classe qui hérite de “StateObjectNotifier”.

StateObjectCollectionNotifier : collection de StateObjectNotifier

Comme nous l’avons vu plus haut, si un StateObjectLink peut lier plusieurs StateObjects dans la même propriété .NET. Chaque mise à jour d’un des StateObjects “remplace” le précèdent !

Pour pouvoir lier plusieurs StateObjects dans une seule propriété .NET, nous pouvons utiliser les StateObjectCollectionNotifiers. La classe StateObjectCollectionNotifier est une ObservableCollection de StateObjectNotifier.

Prenons cet exemple :

Nous utilisons le constructeur du StateObjectLink où le 1er argument est le nom du package et le 2ème le nom du StateObject.

Pour faciliter la compréhension, on peut également utiliser les paramètres nommés :

Nous créons donc un lien vers le StateObjects “/intelcpu/0/load/0” du package HWMonitor (celui qui correspond à la consommation du CPU).

On aura donc un StateObject par sentinelle où ce package est déployé.

Par exemple, dans ma Constellation on trouve 9 StateObjects qui correspond, un par machine (sentinelle) :

StateObject Explorer

Comme il s’agit d’une collection de StateObjectNotifier, vous pouvez itérer pour chaque StateObject afin d’afficher par exemple le CPU de chaque sentinelle où le package “HWMonitor” est déployé :

Console log

Tout comme le StateObjectNotifier, la classe StateObjectCollectionNotifier comporte aussi l’évènement ValueChanged :

Générer du code

Le principe est le même que la génération de code pour les MessageCallbacks.

Ouvrez le générateur de code (clic-droit sur votre projet dans Visual Studio > Constellation > Generate Code) et sélectionnez votre Constellation puis cliquez sur “Discover”. Sélectionnez ensuite les packages que vous souhaitez ajouter dans votre code et cliquez sur “Generate”.

Générateur de code

Comme pour les MessageCallbacks, vous devez ajouter le namespace correspondant aux “StateObjects” du package que vous souhaitez manipuler.

Par exemple pour inclure les StateObjects du package “MonPremierPackage” :

Using du code généré par package

Ensuite créez votre propriété .NET liée à un StateObject mais en utilisant le StateObjectLink généré par package.

Exemple, pour les StateObjects du package “MonPremierPackage”, vous avez la classe “MonPremierPackageStateObjectLink”.

Inutile de définir le “PackageName” sur ce StateObjectLink par il est nativement fixé par cet attribut personnalisé.

Pour le nom du StateObject, vous pouvez utiliser l’énumération MonPremierPackageStateObjectNames générée par le générateur de code :

StateObjectLink généré par package

Par exemple, pour lier notre propriété “MonObject” au StateObject “MyObject” publié par le “MonPremierPackage” (revoir l’exemple ici), on peut écrire :

Ensuite, sur chaque StateObject ou StateObjectNotifier, vous trouverez des méthodes d’extension permettant de convertir la valeur du StateObject dans le type généré dans votre code à partir de la description du package :

Méthode d'extension générées sur les StateObject & StateObjectLink

Par exemple, pour convertir votre StateObjectNotifier en “MyCustomObject”, le type du StateObject défini dans le package “MonPremierPackage” :

Chaque type de StateObject décrit dans la Constellation est reproduit par le générateur de code dans votre projet.

Vous retrouvez alors toutes les propriétés du StateObject proprement typées et documentées :

Classes générées

N’hésitez pas lire cet article très complet sur la génération de code C# dans Constellation.

Consommer des StateObjects
Editer la page sur GitHub

Démarrez la discussion sur le forum Constellation