Network Monitor : cas pratique combinant Visio 2010, WPF et WCF

Florian Casabianca

Cet article présente un cas pratique d'utilisation de Visio 2010 et sa nouvelle fonctionnalité d'export en XAML pour développer un outil de monitoring réseau en WPF et WCF. ♪

- Titre : Network Monitor : cas pratique combinant Visio 2010, WPF et WCF

- Auteur : Florian Casabianca

- Parution : 23 janvier 2010

- Mise à jour : 23 janvier 2010

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2010 Florian Casabianca. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts. Droits de diffusion permanents accordés à Developpez LLC.

Sommaire

- Introduction

- I. Création et export du schéma Visio

- II. Création du service Web

- II-A. Contrat du service

- II-B. Implémentation du service

- III. Création du client WPF

- III-A. Un peu de code dans VS 2010

- III-B. Un peu de design dans Expression Blend

- Conclusion

- Sources

- Liens

- Remerciements

- Contact

Introduction

« Qu'ils sont beaux mes diagrammes Visio, si seulement je pouvais les exporter en XAML et ainsi les réutiliser dans mon application WPF ou Silverlight ». Pour répondre à ce besoin, il existait jusqu'à présent un add-in pour Visio 2007 disponible sur Codeplex à cette adresse : http://www.codeplex.com/VisioExportToXAML. Visio 2010 permet désormais d'exporter ses schémas en XAML, plus précisément au format Silverlight. Nous allons voir comment récupérer le fichier XAML généré et le réutiliser dans Expression Blend 3 au sein d'une application WPF.

I - Création et export du schéma Visio

Dans Visio 2010 nous créons tout d'abord un nouveau schéma à partir du modèle de diagramme réseau.

Après quelques drag and drop et un peu d'habillage, notre schéma ressemble à ceci :

Pour chaque élément du réseau, nous avons associé une pastille (de couleur verte sur l'image) qui servira à refléter son état (en ligne ou hors ligne). Le but est qu'au sein d'une l'application WPF la couleur de ces pastilles change en temps réel en fonction des informations renvoyées par un service Web.

La fonctionnalité d'export en XAML de Visio 2010 n'est pas triviale à trouver. Pour la découvrir, il faut utiliser le menu « Enregistrer sous » et sélectionner le type « Page Web ». Un bouton « Publier » apparait vous permettant d'accéder aux options de publication.

Enfin, dans l'onglet « Avancé » de la fenêtre d'options, il faut sélectionner le format XAML dans la liste déroulante. Ce format est normalement déjà sélectionné par défaut.

La visualisation du résultat dans un navigateur :

Une fois l'export effectué, vous trouverez dans le dossier contenant les fichiers associés à la page Web, un fichier XAML. Il s'agit du fichier que nous allons exploiter.

Vous trouverez aussi un (ou plusieurs) fichier d'extension « .odttf ». Il s'agit d'un fichier contenant la fonte de caractères utilisée dans le diagramme Visio (dans notre cas, la fonte Calibri) et référencé dans le fichier XAML. Malheureusement, ce type de fichier n'est pas des plus exploitables au sein de Blend ou de Visual Studio. Nous verrons par la suite comment nous en débarrasser.

II - Création du service Web

Pour le développement du service Web nous utiliserons Visual Studio 2010 et le Framework 4.0. Toutes les opérations effectuées ici sont cependant reproductibles avec Visual Studio 2008 et le Framework 3.5.

Nous utiliserons le template WCF Service Application pour créer le projet hébergeant le Web service.

II-A - Contrat du service

Définissons tout d'abord le contrat de notre service. L'attribut CallbackContract permet d'associer un contrat de rappel pour des communications bidirectionnelles. Ce contrat de rappel est lui aussi un contrat de service; nous en parlerons plus en détail juste après. L'attribut SessionMode indique que les sessions doivent être prises en compte.

Contrat du service

[ServiceContract(Namespace = "http://developpez.com/MonitorService/2010/01",
Name = "MonitorService",
CallbackContract = typeof(IMonitorCallback),
SessionMode = SessionMode.Required)]
public interface IMonitorService
{
[OperationContract(IsOneWay = true, IsInitiating = true, IsTerminating = false)]
void Subscribe();

[OperationContract(IsOneWay = true, IsInitiating = false, IsTerminating = true)]
void Unsubscribe();
}

Le service est composé de deux méthodes Subscribe et Unsubscribe. L'attribut Initiating positionné à true sur la méthode Subscribe indique qu'un appel à cette méthode créera une session. Positionné à false sur la méthode Unsubscribe, il indique que la méthode ne pourra être appelée que si une session existe déjà (dans le cas contraire, une exception sera déclenchée). Ainsi, le client aura l'obligation d'appeler la fonction Subscribe en premier. L'attribut IsTerminating à true pour la méthode Unsubscribe, signifie que celle-ci terminera la session en cours.

Le contrat de rappel est un contrat de service que devra implémenter le client afin de pouvoir être rappelé par le serveur.

Contrat de rappel

public interface IMonitorCallback
{
[OperationContract(IsOneWay = true)]
void ReceiveNetworkInfo(NetworkElementInfo networkElementInfo);
}

Dans notre cas, le serveur rappellera le client afin de lui envoyer les données concernant un élément du réseau sous la forme d'un objet de type NetworkElementInfo.

La classe NetworkElementInfo est déclarée en tant que DataContract. Elle possède trois propriétés contenant le statut (sous forme d'énumération), le nom et le code d'un élément réseau.

Contrats de données

[DataContract]
public class NetworkElementInfo
{
[DataMember]
public NetworkStatus Status {get;set;}

[DataMember]
public string Name {get;set;}

[DataMember]
public string Code { get; set; }
}

[DataContract]
public enum NetworkStatus
{
[EnumMember]
Online,
[EnumMember]
Offline
}

II-B - Implémentation du service

Dans l'implémentation du service, nous déclarons un tableau de NetworkElementInfo qui servira de référentiel de données.

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession,    ConcurrencyMode = ConcurrencyMode.Single)]
public class MonitorService : IMonitorService
{
IMonitorCallback _client;
Timer _timer;
bool _closing;

NetworkElementInfo[] Machines = {
new NetworkElementInfo { Code = "FW", Name = "FIREWALL", Status = NetworkStatus.Online },
new NetworkElementInfo { Code = "RO", Name = "ROUTER", Status = NetworkStatus.Online },
new NetworkElementInfo { Code = "FS", Name = "FILES_SERVER", Status = NetworkStatus.Online },
new NetworkElementInfo { Code = "WS", Name = "WEB_SERVER", Status = NetworkStatus.Online },
new NetworkElementInfo { Code = "DS", Name = "DATA_SERVER", Status = NetworkStatus.Online },
new NetworkElementInfo { Code = "SW", Name = "SWITCH", Status = NetworkStatus.Online }
};
//reste du code
}

Lorsque le client appelle la méthode Subscibe nous conservons une référence vers le canal de rappel et initialisons un timer qui appellera la méthode CallClient toutes les cinq secondes.

Méthode Subscribe du service

public void Subscribe()
{
// récupération du channel de rappel client
_client = OperationContext.Current.GetCallbackChannel<IMonitorCallback>();

OperationContext.Current.Channel.Closing += new EventHandler(Channel_Closing);

//on simule le fait que le service va rappeler le client toutes les 5 secondes
_timer = new Timer(new TimerCallback(CallClient), null, 5000, 5000);

Thread.Sleep(11000);
}

La méthode CallClient choisit au hasard un élément du tableau de NetworkElementInfo et inverse son statut. On rappelle ensuite le client en lui envoyant les nouvelles informations concernant cet élément réseau.

Rappelle du client

private void CallClient(object o)
{
Random rElement = new Random();
NetworkElementInfo info = Machines[rElement.Next(5)];
info.Status = info.Status == NetworkStatus.Online ? NetworkStatus.Offline : NetworkStatus.Online;

try
{
//rappel du client
_client.ReceiveNetworkInfo(info);
}
catch
{
//TODO: log, arrêt du service, etc.
}

La méthode Unsubscribe ne fait que supprimer le timer en cours. Nous nous abonnons aussi à l'événement Closing du canal de communication pour aussi supprimer le timer au cas où le client se serait déconnecté (coupure réseau par exemple) sans appeler la méthode Unsubscribe avant.

public void Unsubscribe()
{
_closing = true;
_timer.Dispose();
}

//le client ferme le canal
private void Channel_Closing(object sender, EventArgs e)
{
//si on n'est pas passé par Unsubscribe avant
if (!_closing)
Unsubscribe();
}

III - Création du client WPF

Le client sera une application WPF 4.0 développée avec Visual Studio 2010 et Blend 3 Preview for .Net 4. Toutes les manipulations sont cependant reproductibles sous Visual Studio 2008 et le Framework 3.5.

III-A - Un peu de code dans VS 2010

Une fois le projet WPF créé, nous ajoutons une référence à notre Web service :

Nous utiliserons le pattern MVVM pour structurer l'application. La vue (le diagramme réseau) sera « bindée » à la classe NetworkMonitorViewModel :

public class NetworkMonitorViewModel : ViewModelBase, MonitorServiceCallback
{
public NetworkMonitorViewModel()
{
InitializeData();
MonitorNetwork();
}

private void InitializeData()
{
Logs = new ObservableCollection<string>();

FirewallInfos = new NetworkElementInfo { Code = "FW", Name = "FIREWALL", Status = NetworkStatus.Online };
RouterInfos = new NetworkElementInfo { Code = "RO", Name = "ROUTER", Status = NetworkStatus.Online };
FileServerInfos = new NetworkElementInfo { Code = "FS", Name = "FILES_SERVER", Status = NetworkStatus.Online };
WebServerInfos = new NetworkElementInfo { Code = "WS", Name = "WEB_SERVER", Status = NetworkStatus.Online };
DataServerInfos = new NetworkElementInfo { Code = "DS", Name = "DATA_SERVER", Status = NetworkStatus.Online };
SwitchInfos = new NetworkElementInfo { Code = "SW", Name = "SWITCH", Status = NetworkStatus.Online };
}

private void MonitorNetwork()
{
MonitorServiceClient client = new MonitorServiceClient(new InstanceContext(this));
client.Subscribe();
}

public ObservableCollection<String> Logs { get; private set; }

private NetworkElementInfo _firewallInfos;
public NetworkElementInfo FirewallInfos
{
get { return _firewallInfos; }
private set { _firewallInfos = value; OnPropertyChanged("FirewallInfos"); }
}

//reste du code
}

La classe ViewModelBase permet de gérer l'implémentation de l'interface INotifyPropertyChanged.

Pour chaque élément réseau, nous aurons une référence vers un objet NetworkElementInfo le représentant (dans l'extrait de code ci-dessus il n'y a que l'élément firewall qui est visible). La collection Logs servira à afficher à l'écran les logs des messages reçus du service.

Il y a deux éléments importants à noter ici. Le premier est que la classe implémente l'interface MonitorServiceCallback (il s'agit bien d'une interface, même si elle n'est pas préfixée par « I ») qui a été créée par Visual Studio lorsque l'on a référencé le Web service. Il s'agit en fait de l'interface IMonitorCallback créée dans notre Web service et que le client doit implémenter afin d'être rappelé par le service. Le deuxième élément à noter est la construction de l'objet MonitorServiceClient (le proxy vers le Web service). Nous lui passons en paramètre une référence vers un objet implémentant l'interface de rappel (c'est-à-dire le ViewModel lui-même).

L'interface MonitorServiceCallback nous oblige à implémenter la méthode ReceiveNetworkInfo :

Implémentation de l'interface MonitorServiceCallback

public void ReceiveNetworkInfo(NetworkElementInfo networkElementInfo)
{
if (networkElementInfo != null)
{
switch (networkElementInfo.Code)
{
case "FW":
FirewallInfos = networkElementInfo;
break;
case "RO":
RouterInfos = networkElementInfo;
break;
case "FS":
FileServerInfos = networkElementInfo;
break;
case "WS":
WebServerInfos = networkElementInfo;
break;
case "DS":
DataServerInfos = networkElementInfo;
break;
case "SW":
SwitchInfos = networkElementInfo;
break;
}
Logs.Add(String.Format("{0} - Name: {1} - Status: {2}",
DateTime.Now.ToLongTimeString(), networkElementInfo.Name, networkElementInfo.Status));
}
else
{
Logs.Add("Erreur");
}
}

Le code est assez simple. Nous mettons simplement à jour les informations concernant l'élément réseau que le Web service vient de nous envoyer. Nous ajoutons aussi un élément à la collection de logs avec l'heure de réception du message, le nom et le statut de l'élément.

Afin de représenter graphiquement (couleur verte ou rouge) les statuts en ligne/hors ligne, nous aurons besoin d'un convertisseur entre les types NetworkStatus et SolidColorBrush :

Convertisseur Statut vers Couleur

public class StatusToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null)
{
NetworkStatus status = (NetworkStatus)value;

switch (status)
{
case NetworkStatus.Online:
return new SolidColorBrush(Colors.Green);
case NetworkStatus.Offline:
return new SolidColorBrush(Colors.Red);
}
}

return new SolidColorBrush(Colors.White);
}

public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}

III-B - Un peu de design dans Expression Blend

Il est maintenant temps d'utiliser l'export XAML venant de Visio 2010. Pour cela, il suffit de copier/coller le XAML de l'export dans le code XAML de la fenêtre principale de l'application WPF. On repasse ensuite en mode design afin d'admirer le résultat :

Aïe ! On a comme qui dirait un léger problème. Si l'on regarde le message, on s'aperçoit que cela vient du fichier « .odttf » contenant la fonte de caractères utilisée dans le diagramme Visio.

Si l'on regarde de plus près le XAML, on s'aperçoit que les éléments Glyphs référencent ce fichier dans leur propriété FontUri :

<Glyphs RenderTransform="105.6, 0, 0, 96, 21.377739, 765.354331" FontRenderingEmSize="0.13889"
FontUri="48230029-18BE-6784-E14A-6C3DD62CAE72.odttf" Indices="87">
</Glyphs>

Nous allons tout d'abord déclarer une source de données qui nous renverra le chemin complet vers la fonte Calibri se trouvant sur le système d'exploitation courant :

<ObjectDataProvider x:Key="Font_Calibri" ObjectType="{x:Type s:Environment}" MethodName="ExpandEnvironmentVariables">
<ObjectDataProvider.MethodParameters>
<s:String>%WINDIR%\Fonts\Calibri.ttf</s:String>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>

Puis, remplaçons la chaine 48230029-18BE-6784-E14A-6C3DD62CAE72.odttf par {Binding Source={StaticResource Font_Calibri}} dans tout le fichier :

Finalement, la propriété FontUri ressemblera à ceci :

FontUri="{Binding Source={StaticResource Font_Calibri}}"

La propriété n'est plus liée au fichier « .odttf », mais à la font du système.

Si l'on repasse maintenant en mode design, nous obtenons quelque chose de nettement plus intéressant :

Le diagramme a été placé au sein d'un Viewbox pour répondre au redimensionnement de la fenêtre et un ItemsControl a été ajouté afin d'afficher les logs définis dans le ViewModel.

Nous pouvons maintenant passer à la partie « binding » pour donner vie à notre interface. Pour cela il suffit de sélectionner les différentes pastilles associées à chaque élément du réseau et dans le panneau de propriétés de lier la couleur de remplissage à une source de données (via le petit carré blanc à droite de la propriété).

Dans la fenêtre de création d'une liaison de données, nous allons ajouter une nouvelle source de données de type CLR Object et choisir notre ViewModel(NetworkMonitorViewModel ).

Dans le panneau de droite, nous choisissons la propriété à laquelle nous voulons lier la couleur de fond (dans notre cas, la propriété Status) et sélectionnons le convertisseur StatusToColorConverter que nous avions créé précédemment.

Cela aura notamment pour effet de créer deux nouvelles ressources dans la fenêtre ; une pour le ViewModel qui servira de DataContext à la fenêtre et une pour le convertisseur :

<Window.Resources>

<NetworkMonitorClient_ViewModels:NetworkMonitorViewModel x:Key="NetworkMonitorViewModelDataSource" d:IsDataSource="True"/>

<local:StatusToColorConverter x:Key="statusToColorConverter"/>
<ObjectDataProvider x:Key="Font_Calibri" ObjectType="{x:Type s:Environment}" MethodName="ExpandEnvironmentVariables">
<ObjectDataProvider.MethodParameters>
<s:String>%WINDIR%\Fonts\Calibri.ttf</s:String>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</Window.Resources>

Il ne reste maintenant plus qu'à lancer l'application (et le Web service) et admirer le résultat :

Les logs sont affichés dans la liste de gauche et les différents éléments réseau deviennent rouges ou verts en fonction de leur état.

Conclusion

Au travers de cet article, nous avons vu comment exporter un diagramme en XAML sous Visio 2010 et le réutiliser dans Visual Studio et Blend afin d'en faire une application connectée à un Web service.

La nouvelle fonctionnalité d'export en XAML de Visio 2010 ouvre des perspectives très intéressantes et permet de nouveaux usages pour les diagrammes Visio.

Sources

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

Liens

Visio 2010

Le blog du groupe utilisateur Visio en France

Visual Studio 2010

Expression Blend

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 tutoriel, dans les sources, dans la programmation ou pour toutes informations, n'hésitez pas à me contacter via le forum.