Bienvenue à Blogs CodeS-SourceS Identification | Inscription | Aide

Frédéric Hamel

Assert.Success();

[Office 365 Beta] [Exchange Online] Scenario pour petite entreprise

J’ai pu profiter de la Beta publique d’Office 365 pour tester un petit scénario de création d’entreprise.

Scénario

Une jeune startup “ma-startup.com” est composée de trois employés : Pierre le directeur, Paul le commercial, et Jacques le développeur. La jeune startup veut mettre à disposition un site web public avec son propre nom de domaine et proposer un email à chaque employé. Elle veut aussi pouvoir avoir des alias, par exemple info@ma-startup.com, sales@ma-startup.com, support@ma-startup.com.

Le domain

Une fois la beta de office 365 activée, un compte administrateur par défaut est automatiquement crée. Il permet d’ajouter des utilisateurs et d’administrer les différentes parties d’office 365.

Attention: ne pas supprimer cet utilisateur!

La première chose à ne pas faire est de supprimer le compte d’administration par défaut. Il m’a été confirmé par Microsoft sur le forum d’offiice 365 qu’il est préférable de créer un ou plusieurs comptes administrateurs supplémentaires, et qu’il faut éviter de supprimer le compte créé par le système. Une fois un autre compte administrateur créé, vous pourrez retirer la license au compte admin par défaut, ce qui évitera de payer une licence pour ce compte.

Pour mettre en place votre domaine c’est assez facile (ou pas…). Office 365 utilise un mecanisme de “Full domain relegation” : cela veut dire que votre domaine sera géré par office 365 et qu’il sera automatiquement configuré pour que exchange, linq, sharepoint et les autres services de office 365 fonctionnent. C’est une très bonne nouvelle si vous êtes dans un scénario simple comme celui ici, et c’est une beaucoup moins bonne nouvelle si vous avez déjà beaucoup de configurations pour votre domaine comme des redirections permanantes, des champs CNAME, etc.

Pour mettre en place la “Full domain relegation” il suffit d’aller sur le panneau d’admin –> Management –> Domains puis Add domains. Ensuite, un wizard vous prend en main pour vérifier que vous êtes le propriétaire du domaine (il faudra pour ça ajouter un champ CNAME à votre configuration actuelle). Ensuite, attendre que office 365 vérifie le domaine, puis procéder au changement des serveurs DNS. L’opération m’a pris 30min en tout et pour tout, et j’étais agréablement surpris de voir que office 365 me proposais des indications spécifiques pour Godaddy mon registraire. Je n’ai donc eu aucun mal pour mettre cela en place.

Attention : office 365 ne supporte pas les redirections permanantes. En gros, si votre site public est hébergé sur Windows Azure par exemple, vous ne pourrez pas configurer le domain ma-startup.com pour qu’il pointe vers votre site Windows Azure. Vous pourrez seulement rajouter un champ WWW qui pointe sur le site Windows Azure. Cela me semble être vraiment une limitation importante : j’espère qu’ils le corrigeront pour la release.

Attention : si vous souhaitez ne pas faire une “Full domain relagation”, je conseille vivement d’expliquer ce que vous souhaitez faire sur le forum d’office 365 pour avoir de l’aide, car si vous faite des manipulations fausses, votre domaine peut présenter des problèmes pour plusieurs heures.

URL du site public Sharepoint

Par défaut, office 365 utilise une adresse type ma-startup.onmicrosoft.com. Une fois votre propre domaine ajouté, vous pourrez changer l’adresse du site sharepoint public pour être ma-startup.com. Ceci se fait dans la même partie de gestion du domaine.

Création des utilisateurs et administrateurs

Une fois le domaine en place, il est possible de créer des utilisateurs sur ce domaine. C’est le moment de créer un autre admin sur le domaine et de retirer la license sur l’admin par défaut qui ne sera plus utilisé par la suite.

Dans notre scénario, on crée trois utilisateurs : Pierre, Paul et Jacques et on indique que Jacques est administrateur.

Attention : si vous souhaitez réutiliser l’admin par défaut mais sur votre nom de domaine, c’est possible, mais il faut créer un autre admin, et changer le domaine depuis ce compte.

Configuration des alias pour Exchange online

La première idée qui vient à l’esprit est d’ajouter des alias : Pierre@ma-startup.com va gérer info@ma-startup.com, Paul@ma-startup.com va gérer sales@ma-startup.com, et Jacques@ma-startup.com va gérer support@ma-startup.com. Cela fonctionne très bien pour recevoir les emails, par contre ça ne marche pas du tout pour envoyer ! Il y a un bien un champ “From” quand on compose un email, mais il est automatiquement remis sur le compte principal quand on clique sur ”Send”. L’alias ne permet que d’accepter des emails entrants.

Cette solution ne marche donc pas dans notre scénario, car Jacques ne pourra par exemple pas répondre en tant que support@ma-startup.com.

La deuxième idée qui m’est venue est de créer des comptes supplémentaires… mais le problème, c’est que de créer un compte supplémentaire coûte une license de plus. Ce n’est donc pas non plus une solution acceptable, car si l’on a déjà payé pour trois utilisateurs réels, ajouter trois utilisateurs virtuels reviendrait à doubler le coût de office 365! J’ai essayé de créer les comptes et de retirer les licences pour voir s’il était possible de trouver un hack pour envoyer, mais ça ne fonctionne pas.

La dernière idée qui m’a été suggérée sur le forum est d’utiliser des “listes de diffusions”. Cette technique en effet fonctionne. Il faut allez dans Admin –> Outlook –> Distribution List. Là, vous pourrez ajouter une nouvelle liste de distribution avec l’alias souhaité, et ajouter à la liste les utilisateurs pour qui l’on souhaite voir les emails arriver. Vous pouvez configurer la liste comme étant un “security group”, ce qui permet de gérer aussi qui est ajouté et enlevé des listes.

Une fois la liste créée et configurée, on n’a pas pour autant terminé. Une fois la liste sauvée, il faut la réouvrir pour éditer ses propriétés et un nouvel onglet apparaît : “Delivery Management”. Il faut choisir inside and outside of my organization pour que les gens de l’extérieur puissent écrire à la liste de distribution.

Attention : une fois la liste sauvée, il faut re-éditer pour avoir les options de “delivery management”.

Attention : il faut bien supprimer les alias avant de créer une liste du même nom.

Tout content d’avoir une solution qui semble marcher, je la teste, et j’arrive à recevoir un email. Je tente d’y répondre en faisant apparaitre le champ “From”, je choisis le group (dont je suis le owner d’ailleurs) avec lequel je veux répondre et je clique send! Ca ne marche toujours pas, message d’erreur qui indique que je n’ai pas les droits pour envoyer un message avec cet utilisateur.

Configuration de Exchange online pour autoriser l’envoi

Après avoir bien cherché dans le site d’administration et après avoir posté sur le forum, j’ai pu trouver une séquence de commandes powershell qui permettent d’envoyer des emails depuis une liste de distribution. Les voila :

  1. $LiveCred = Get-Credential
  2. $Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://ps.outlook.com/powershell/ -Credential $LiveCred -Authentication Basic –AllowRedirection
  3. Import-PSSession $Session
  4. Add-RecipientPermission "support@my-startup.com" -AccessRights SendAs -Trustee "jacques@my-startup.com”
  5. Répéter 4 autant que nécessaire
  6. Remove-PSSession $Session

1 2 et 3 permettent de se connecter à Exchange Online et il vous sera demandé votre email et password d’un compte admin pour continuer.
4 permet d’ajouter les permissions pour pouvoir “Send As”

Attention : pour que ces commandes aboutissent, il faut que votre ExecutionPolicy soit en RemoteSigned.

Vous pouvez le vérifier avec Get-ExecutionPolicy et le changer avec Set-ExecutionPolicy RemoteSigned. Il faut aussi que la console powershell soit exécutée en tant qu’administrateur.

Les liens qui m’ont servi :

Conclusion

J’ai pu atteindre l’objectif de mettre en ligne la petite startup et configurer son courrier et son site public via office 365. Je retiendrais aussi que la “Full domain relegation” présente des limites, et qu’il est parfois nécessaire d’avoir recours à des commandes powershell pour faire des tâches administratives qui semblent pourtant standard.

Dans l’ensemble, j’ai eu plutôt une bonne impression de Office 365 : ça marche bien, c’est assez rapide, et le forum est suffisament actif pour avoir de l’aide. J’espère qu’ils modifieront un peu la gestion de domaines et feront quelques améliorations dans l’administration.

Windows 7 – Language pack pour les éditions Home et Pro

La version finale de Windows 7 risque de réserver une petite surprise aux utilisateurs de la version Home et Pro.

En effet, la possibilité de changer la langue d’affichage est une fonctionnalité réservée aux éditions Ultimate et Enterprise.

Heureusement pour les autres, il y a quand même une solution, mais ça demande quelques lignes de commandes!

  1. Récupérez le language pack : fr-FR/lp.cab sur le Language Pack DVD, ou téléchargez-le depuis MSDN
  2. Ouvrez une cmd en mode admin et rentrez les commandes suivantes (remplacer le chemin et le code de langue si besoin):
  3. dism /online /add-package /packagepath:C:\fr-fr\lp.cab
  4. bcdedit /set {current} locale fr-FR
  5. bcdboot %WinDir% /l fr-FR
  6. Ouvrez RegEdit et supprimez le dossier suivant:
  7. HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\MUI\UILanguages\en-US
  8. Redémarrer :)

Voilà, j’espère que ça sera utile :)

Enterprise Library (entlib 4.1) et Unity 1.2 sont sortis

Entlib et Unity: quoi de neuf?

Une nouvelle version d'Enterprise Library et de Unity est disponible sur Codeplex:

C'est surtout une grosse mise à jour pour Unity qui ajoute le support pour les génériques et les tableaux, et voit ses performances augmenter.

Cette version offre aussi de nouvelle possibilités d'interception et s'intègre avec le bloc d'injection de politiques d'Entreprise Library.

En ce qui concerne Entlib 4.1, on voit surtout l'intégration de la nouvelle version de Unity et de nombreux bug fix.

Mise à jour dans vos programmes

J'ai fait la mise à jour dans mes applications aujourd'hui et ça c'est bien passé.

En ce qui concerne Unity, l'interface IUnityContainer a changé et rajoute des paramètres optionnels presque sur chaque fonction. Cela ne posera de problème que si vous avez dérivé de IUnityContainer. Pour l'utilisation du container dans le code existant, c'est transparent (pas de modification à faire).

Pour Entlib et pour les blocs que j'utilise dans mon application, je n'ai eu besoin de changer que le numéro de version dans le fichier de config (Version = 4.0.0.0 -> Version = 4.1.0.0 ). Le public token et le nom des dll n'ont pas changé.

 

Voilà, j'espère que ça vous aidera à faire votre mise à jour sans soucis.

 

Happy Programming!

[Unit Test] Tests unitaires et développement par le test facilités avec Rhino Mock 3.5

Introduction

Rhino Mock 3.5 a été amélioré pour tirer parti au maximum de la nouvelle syntaxe offerte par C# 3 et .NET 3.5. On peut maintenant utiliser des expressions lambda et la syntaxe est vraiment très agréable et efficace.

Je pense qu'à cette occasion, il est important d'écrire un post pour militer en faveur des tests unitaires et du développement par le test. Souvent, j'entends dire que le développement par le test est bien, mais qu'en pratique il n'est pas facile à mettre en place. En fait, c'est surtout une question d'habitude puisque c'est une façon de programmer différente ; de bons outils et réflexes d'architecture sont par ailleurs nécessaires pour se simplifier la tâche.

Rappels sur les Mocks et leur utilité

Pour ceux qui ne savent pas encore ce qu'est un "Mock", je vous recommande vivement l'article de Martin Fowler Mocks Aren't Stubs.

Pour résumer, un mock est un objet qui imite un autre objet à des fins de test. Cela permet de développer, par exemple, le contrôleur et le modèle d'un pattern MVC avant d'avoir la vue, car on va "Mocker" (singer) la vue et tester spécifiquement le contrôleur sans avoir besoin de lancer l'application à chaque fois. Dans l'outil financier que mon entreprise développe actuellement, les accès aux données et métadonnées ont été singés avec Rhino Mock pour éviter d'utiliser le système financier en COM Interop. Voilà donc de nombreuses bonnes raisons de s'intéresser à ce type de framework.

Syntaxe de Rhino Mock 3.5

Regardons maintenant de plus près à quoi cela ressemble.

J'ai pris comme exemple un appareil photo (Camera) qui utilise des objectifs (Lens). On veut développer l'appareil photo avant d'avoir développé un objectif. Pour les besoins du post, j'ai créé une interface qui représente les objectifs, et j'y ai mis la plupart des cas que l'on peut rencontrer, à savoir un événement, une propriété, une fonction qui retourne une valeur, et une fonction qui retourne des paramètres via out ou ref.

Ceux qui aiment bien la photo auront reconnu que Aperture est l'ouverture de l'objectif.

public interface ILens : INotifyPropertyChanged
{
    void ApertureRange(
        out double minAperture, 
        out double maxAperture);
    double CurrentAperture { get; set; }
    double FocusDistance{ get; set; }
    bool IsAutoFocus { get; }
    bool HasFeature(LensFeatures feature);
}
J'ai aussi utilisé l'énumération suivante :
public enum LensFeatures 
    {
        Stabilisator,
        Motorisation
    }

Maintenant que tout est en place, regardons comment singer l'interface :

Création du proxy :

//We generate a proxy for the ILens interface.
var lens = MockRepository.GenerateStub<ILens>();

Singer une fonction :

//We return different values according to the parameters.
lens.Stub( 
    myLens => myLens.HasFeature(LensFeatures.Stabilisator))
    .Return(false);

lens.Stub(
    myLens => myLens.HasFeature(LensFeatures.Motorisation))
    .Return(true);

Singer une fonction avec paramètres out ou ref :

//We can also return out and ref parameters.
lens.Stub(
    myLens =>
    {
        double myMinAperture, myMaxAperture;
        myLens.ApertureRange(
            out myMinAperture, 
            out myMaxAperture);
    }).OutRef(2.0D, 22D);

Singer une propriété :

//We can always return the same property value.
lens.Stub(
    myLens => myLens.IsAutoFocus)
    .Return(true).Repeat.Any();

Utiliser une propriété :

//The stub also provides storage for properties
lens.CurrentAperture = 10;

Singer un événement :

//Event can also be raised
lens.Raise(
    myLens => myLens.PropertyChanged += lens_PropertyChanged,
    lens, new PropertyChangedEventArgs("CurrentAperture"));

Test du proxy :

Le code de test suivant permet de voir les valeurs qui sont retournées par le proxy.

Assert.AreEqual(10, lens.CurrentAperture);

double minAperture, maxAperture; 
lens.ApertureRange(out minAperture, out maxAperture);
Assert.AreEqual(2.0, minAperture);
Assert.AreEqual(22, maxAperture);

Assert.IsFalse(lens.HasFeature(LensFeatures.Stabilisator));
Assert.IsTrue(lens.HasFeature(LensFeatures.Motorisation));

Assert.IsTrue(lens.IsAutoFocus);

Un peu plus d'explications :

La fonction MockRepository.GenerateStub<ILens>(); permet de créer un proxy à partir d'une interface. Cela fonctionne également avec une classe. Ainsi, si vous n'avez pas designé votre système avec une interface, vous pouvez quand même générer un proxy. Cela peut aussi être utile si vous n'êtes pas le créateur de la classe.

Dans la partie sur les fonctions, j'ai mis en évidence que l'on pouvait singer la même fonction avec des paramètres différents : c'est extrêmement utile. Dans notre exemple, on voit que notre objectif a une motorisation mais n'a pas de stabilisateur.

Dans la partie sur les propriétés, on voit apparaître .Repeat.Any(); cela permet de dire que l'on veut que la propriété retourne toujours la même valeur. Par défaut, le proxy ne la retourne qu'une seule fois. Cela peut poser des problèmes : par exemple, si on la regarde au débugger, cela va consommer la valeur et la suivante sera null. Il faut donc être vigilant à ce propos.

On voit aussi dans "Utiliser une propriété" que le proxy offre le support de la propriété sans avoir besoin d'ajouter du code. On peut donc affecter une valeur et l'utiliser ensuite.

L'événement est invoqué grâce à Raise. On n'est cependant pas obligé de mettre le handler de l'événement dans le Raise : il est possible de le mettre ailleurs et faire dans le Raise += null qui marche aussi.

De manière générale, la nouvelle syntaxe permet de décrire ce que l'on va faire grâce à une lambda et quand cette dernière se produit, le proxy retourne la valeur indiquée.

On peut également remettre à zéro le proxy grâce à BackToRecord();

Liens utiles :

Le site de l'auteur de Rhino Mock http://ayende.com/ : vous trouverez la page de download de la version 3.5 qui n'est actuellement qu'une beta, mais déjà stable. La page directe vers le projet est http://ayende.com/projects/rhino-mocks.aspx.

Voilà aussi une entrée utile sur son blog, qui décrit la syntaxe : Arrange Act Assert. Il y a aussi la page des tests de Rhino Mock qui permet de vraiment voir presque toute la syntaxe et son utilisation :  Rhino.Mocks.Tests.

Pour ceux qui voudraient voir d'autres frameworks, il y a aussi :

  • NMock
  • NMock2
  • Typemock .NET (utilise l'API de profiling : il permet d'intercepter pratiquement n'importe quel appel, même un HttpContext par exemple ;))
  • Easymock .Net 
  • Moq (gros buzz autour de ce framework, syntaxe complètement adaptée à .NET 3.5)

Conclusion :

Je trouve ce type de framework vraiment puissant et utile.

J'ai comparé un peu les frameworks avant de partir sur Rhino Mock. On peut noter que Typemock permet de faire des interceptions très puissantes grâce à l'API de profiling ; Moq a une syntaxe écrite spécialement pour tirer parti de .NET 3.5. Quant à Rhino Mock, il offre aussi une syntaxe .NET 3.5, il a plus de fonctionnalités et d'historique que Moq. Par rapport à TypeMock, il a l'avantage d'être open source. Je dirais donc que mon choix se résume à ces trois candidats :).

Dans mes projets, pour le moment, je n'utilise que les fonctionnalités de Rhino Mock que j'ai décrites dans l'article, et je trouve déjà cela très productif. Rhino Mock permet également d'exprimer des attentes (Expectations), et, à la fin du test, de voir si toutes ces attentes ont été réalisées.

Voilà, j'espère que ce post vous sera utile, et je ne saurais que trop vous encourager à tester, tester, tester :)

 

Happy programming !

[WPF] SplashScreen pour WPF en .NET 3.5 sp1

Le démarrage des applications .NET et notamment WPF surtout à froid peut parfois être un problème car il peut prendre un certain temps. De nombreuses applications utilisent aussi le démarrage de l'application pour faire une partie des initialisations du programme, ce qui n'améliore bien évidement pas le temps de démarrage. L'idée classique est donc d'utiliser un SplashScreen qui va s'afficher très vite et qui va afficher une image qui aidera l'utilisateur à patienter.

Faire cela n'est généralement pas si simple, mais heureusement le SP1 de .NET 3.5 nous apporte une nouvelle classe SplashScreen.

Mise en place via VS 2008 SP1

Pour en profiter rapidement, il suffit de créer un .PNG avec l'image que l'on veut voir afficher au démarrage et de l'inclure dans le projet de votre application WPF. Ensuite, dans la fenêtre de propriété, il suffit de mettre la Build Action à Splash Screen.

Et c'est tout! Lancer l'application, et on voit l'image s'afficher.

La 1ère impression est que ça marche bien et que le splashscreen s'ouvre et se ferme automatiquement sans rien faire. Mais on voit aussi que la fenêtre de l'application passe devant quand elle s'ouvre et que le splashscreen repasse devant pour se fermer. Pas terrible donc...

 

Mise en place à la main

La méthode via VS 2008 SP1 est très simple et fonctionne imédiatement, mais le résultat n'est pas complètement optimal. Heureusement, on peut aussi le faire à la main.

Pour cela il faut toujours ajouter le .PNG au projet, mais cette fois il faut le mettre en tant que Resource. Et il faut un peu modifier le App.xaml et le App.xaml.cs.

Dans le App.xaml il faut enlever l'attribut StartUpUri car on va afficher nous-mêmes la fenêtre principale.

Dans le App.xaml.cs on rajoute le code suivant:

public App()
{
    var splashScreen = new SplashScreen("SplashScreen.png");
    splashScreen.Show(false);

InitializeComponent();

var mainWindow = new Window1(); mainWindow.Show(); splashScreen.Close(TimeSpan.Zero); }

On crée l'objet SplashScreen avec le nom du .PNG et on fait Show. Le paramètre false signifie que l'on va fermer le splashscreen nous-mêmes. Si on le met à true, il va se fermer de lui-même à la fin du constructeur de App.

Ensuite, on crée la fenêtre principale et on la montre.

Et finalement on ferme le splashscreen avec Close et un timeSpan à zéro, ce qui permet d'avoir une fermeture immédiate et d'éviter ainsi qu'il repasse devant la fenêtre principale. Si on veut par contre un effet et voir le splashscreen disparaître petit à petit, on peut mettre TimeSpan.FromSeconds(1) qui va faire durer l'effet pendant une seconde.

 

Conclusion

Voilà une classe qui était attendue et qui est arrivée avec le SP1 de .NET 3.5 et VS 2008. On regrettera que la classe ne permette d'afficher que des PNG, pas d'image animée ni rien d'autre, et qu'il y ait un petit effet disgracieux quand on l'utilise directement via la BuildAction. J'ai essayé cette classe avec compositeWPF (Prism) et cela permet d'avoir le splashscreen pendant le chargement et l'initialisation des modules, ce qui n'est pas si mal. De plus, elle est quand même vraiment facile à mettre en oeuvre. On trouvera d'autres détails concernant cette classe sur le post what's new in WPF 3.5 sp1: Splash Screen to improve perceived startup perf.

Happy programming!

[Back to basics] Encapsulation, bien designer ses classes dans le monde réel

Écrire du code de qualité dans un langage ne sous-entend pas seulement maîtriser sa syntaxe, mais aussi comprendre la philosophie et les méthodologies qui sont derrière. C# est un langage orienté objet, et en tant que tel, les principes et concepts de la programmation orientée objet s'appliquent à lui.

Le design d'objets est parfois un challenge redoutable. L'exercice peut se révéler périlleux, surtout si les bases ne sont pas parfaitement maîtrisées. Je me propose ici de discuter d'encapsulation sur des cas concrets, pour que ce concept soient utilisé au mieux dans nos programmes.

Introduction et définition

L'encapsulation est un concept qui vise à protéger l'intégrité des données d'un objet et à en masquer les détails d'implémentation. En effet, un objet va avoir une ou plusieurs responsabilités : c'est à lui de s'assurer que la charge qui lui a été confiée se passe bien et de faire en sorte que ses clients ne soient pas impactés par la manière dont il le fait.

Prenons par exemple un objet responsable d'un compte bancaire. L'objet devra exposer, par exemple, les méthodes de débit, de crédit et d'obtention du solde du compte. L'objet doit s'assurer qu'aucune autre opération que celles prévues ne soit faite sur le compte, et que si pour des raisons x ou y l'objet doit changer la manière dont est géré le compte, cela soit transparent pour les utilisateurs du compte.

Exemple de la classe Point

Démarrons par un cas simple. Je m'inspire ici d'un excellent exemple de Simon Robinson.

public class Point
{
    public double X;
    public double Y;
}

Ici la classe Point expose directement ses données : aucune encapsulation n'est réalisée.

Pour améliorer cela, on va classiquement rendre les membres privés et les exposer via des propriétés.

public class Point
{
    private double x;
    public double X{
        get { return x; }
        set { x = value; }
    }

    private double y;
    public double Y
    {
        get { return y; }
        set { y = value; }
    }
}

Grâce à ces propriétés, les données de la classe Point sont protégées, et on a mis un niveau d'indirection entre elles et le client. Ce qui permettra par exemple si on le souhaite d'ajouter du code dans le set ou le get en fonction des besoins de l'application (par exemple du log, des validations, la notification du changement des propriétés, etc.).

Regardons maintenant quelques cas d'utilisation de cette classe.

var point = new Point();

//Translate the point
point.X = point.X + 10;
point.Y = point.Y + 10;

//Move the point
point.X = 5;
point.Y = 5;

Pour faire une translation du point, je prends la valeur de chaque axe et je leur ajoute les "offset" désirés. Pour déplacer un point, je lui affecte simplement les nouvelles valeurs.

Le problème ici vient du fait que l'objet ne masque pas vraiment son implémentation interne. En effet, le client récupère les données à l'exérieur de l'objet et les remet dedans une fois le traitement fini. L'objet n'a aucun moyen de savoir si la translation a été faite selon les règles attendues ou pas. Par rapport à la définition posée au début, on voit que les détails de l'implémentation ne sont pas vraiment cachés au client malgré les propriétés, et que l'intégrité des données n'est pas du tout gérée. D'ailleurs, si l'on regarde la syntaxe .NET 3.5 de la classe Point, il est troublant de voir qu'on a l'impression d'être sur l'implémentation originale.

public class Point
{
    public double X { get; set; }
    public double Y { get; set; }
}

Voila par exemple comment la classe Point pourrait être mieux encapsulée :

public class Point
{
    public double X { get; private set; }
    public double Y { get; private set; }

    public Point(double x, double y)
    {
        X = x;
        Y = y;
    }

    public void Translate(Point point) 
    {
        X += point.X;
        Y += point.Y;
    }

    public void Move(Point point)
    {
        X = point.X;
        Y = point.Y;
    }
}

Grâce au mot clé "private", on rend inaccessible la modification des data de l'extérieur. L'initialisation de l'objet est faite via le constructeur et la modification se fait via les méthodes. L'objet Point offre ainsi de meilleures garanties d'intégrité de ses données et ses détails d'implémentation sont mieux masqués.

Oui mais...

Une fois arrivé là, on se demande pourquoi toutes les classes Point ne sont pas implémentées comme cela ! En réalité, un point peut être considéré quasiment comme un type de base, une donnée en elle-même. De plus, il est difficile voire impossible d'imaginer à l'avance tous les traitements que l'on souhaite faire sur un point. Pour ajouter à la difficulté, les points sont des petits objets qui risquent d'être instanciés de très nombreuses fois et manipulés de manière intensive : pour des raison d'optimisation, l'objet Point est souvent implémenté sous forme de structure et ses membres sont directement accessibles pour manipulation.

Généralement, ce n'est pas le cas pour un objet business : l'objet Compte par exemple doit être correctement encapsulé si la banque ne veut pas avoir de sérieux problèmes.

Exemple des collections

Prenons l'exemple suivant : une banque possède un ensemble de comptes. Elle permet de consulter les comptes et de d'ouvrir un compte si tout est valide pour ce compte.

public class Bank
{
    public List<Account> Accounts { get; private set; }

    public Bank(){
        Accounts = new List<Account>();
    }

    public void OpenAccount(Account account) 
    {
        if (Validate(account))
        {
            Accounts.Add(account);
        }
    }

    private bool Validate(Account account) { 
        //Do something
        return true;
    }
}

public class Account
{
}

Ici, il est donc possible de voir les comptes via la propriété Accounts et d'ouvrir un compte grâce à OpenAccount (qui va en plus s'assurer que tout est ok avant de l'ajouter à la liste des comptes de la banque).

Le design de cette classe est catastrophique, car il existe une backdoor qui permet d'ajouter un compte sans que le code de validation ne soit appelé.

//Breaking business rules
bank.Accounts.Add(new Account());
//Instead of
bank.OpenAccount(new Account());

Il faut toujours se méfier quand on expose directement une collection, et se poser la question de si c'est vraiment ce que l'on veut ou pas.

Une solution classique à ce problème est d'exposer un énumérateur qui va offrir un accès en lecture seule à la collection.

public class Bank
{
    private List<Account> accounts = new List<Account>();
    public IEnumerable<Account> Accounts
    {
        get
        {
            return accounts.AsEnumerable();
        }
    }

    public void OpenAccount(Account account) 
    {
        if (Validate(account))
        {
            accounts.Add(account);
        }
    }

    public bool Validate(Account account) { 
        //Do something
        return true;
    }
}

Ici, le fait d'exposer un énumerateur va permettre de donner de la visibilité sur la collection, sans pour autant compromettre son encapsulation. De plus, grâce aux méthodes d'extension de .NET 3.5, exposer un énumerateur est vraiment simple. Si l'on n'a pas d'accès aux méthodes d'extension, on peut toujours utiliser le mot clé "yield".

Exemple ObservableCollection

En WPF par exemple, il n'est pas rare d'avoir à utiliser une ObservableCollection pour que les contrôles synchronisés dessus puissent se mettre à jour automatiquement si des objets sont enlevés ou rajoutés à la collection. En général, on est confrontés au même problème que pour les collections dans le cas précédent. Sauf qu'en plus, l'ObservableCollection ne peut pas vraiment être exposée au framework WPF via un enumerator, car on perd alors tout le côté observable de la collection. Heureusement, le framework .NET propose une solution :

public class Bank
{
    private ObservableCollection<Account> accounts 
        = new ObservableCollection<Account>();

    public ReadOnlyObservableCollection<Account> Accounts 
    {
        get {
            return new ReadOnlyObservableCollection<Account>(accounts);
        }
    }
}

La collection est ainsi protégée et les fonctionalités "Observable" sont préservées.

Remarque : si l'on a besoin de meilleures performances, il n'est pas nécessaire de recréer une nouvelle ReadOnlyObservableCollection à chaque fois.

Conclusion

L'encapsulation est un principe fondamental, et il faut parfois se creuser la tête pour le mettre en oeuvre dans certaines situations. Cependant, cela vaut généralement le coup de se poser ce genre de questions, surtout pour les objets qui vont être réutilisés ou utilisés par d'autres. J'espère que ce post vous sera utile dans vos développements.

Happy programming!

[WPF] Réglons son compte à INotifyPropertyChanged (dérivation, réflexion, AOP, EntLib, Spring.NET, extension)

Introduction

INotifyPropertyChanged est une interface classique pour notifier à un client qu'une propriété a changé. C'est un scénario typique en WPF quand on utilise du Binding. Je vais vous présenter plusieurs techniques pour l'implémenter et discuter des problèmes que l'on peut rencontrer.

Voilà le sommaire de ce post :

  • Introduction
  • Version élémentaire
  • Version avec une classe de base
  • Version avec une classe de base par Josh Smith
  • Bilan de mi-parcours
  • Version sans création de PropertyEventArgs à chaque fois
  • AOP interception et injection
  • Entreprise Library, Policy Injection Application block
  • Spring.NET and AOP
  • La solution .NET 3.5 via extension methods
  • Optimisation possible
  • Conclusion

Et voilà la classe qui va nous suivre tout au long de ce post :

public class Person : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private string _name;
    public string Name { 
        get { return _name; }
        set { _name = value; }
    }
}

La classe Person dérive de INotifyPropertyChanged et l'implémente grâce à l'événement PropertyChanged.

Version élémentaire

On rajoute la fonction suivante qui permet de déclencher l'événement :

private void RaisePropertyChanged(string propertyName) 
{
    if (PropertyChanged == null) return;
    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}

Et on modifie le set de la propriété, comme cela, pour déclencher l'évènement :

set 
{ 
    _name = value;
    RaisePropertyChanged("Name");
}

C'est la méthode la plus simple et c'est celle que je me vois implémenter le plus souvent.

Discutons un peu cette méthode :

Les Pour :

  • Simple
  • Efficace

Les Contre :

  • Copier/coller de la fonction RaisePropertyChanged pour chaque classe
  • Utiliser la string "Name" pose des problèmes de maintenance car une faute de frappe est vite arrivée et il est souvent difficile de débugger ce genre d'erreur.
  • Création d'un objet PropertyChangedEventArgs à chaque set de la propriété : Josh Smith mentionne dans un de ses posts que cette création de petits objets peut risquer de fragmenter la mémoire managée.
  • C'est relativement fastidieux si l'on a beaucoup de propriétés et/ou beaucoup d'objets.

Version avec une classe de base

Le problème du copier/coller de la fonction RaisePropertyChanged peut être résolu en utilisant une classe de base :

public class NotifyPropertyChangedBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private void RaisePropertyChanged(string propertyName)
    {
        if (PropertyChanged == null) return;
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

Les Pour :

  • Les mêmes que pour la version précédente
  • Il n'y a plus de copier/coller de la fonction ni de l'événement

Les Contre :

  • A part le copier/coller les problèmes sont les mêmes
  • Dériver d'une classe de base n'est pas toujours possible à cause de l'héritage simple en C# ou VB.NET

Version avec une classe de base par Josh Smith

Josh Smith a écrit une classe de base aussi pour ce problème ici

Les principaux avantages de sa classe sont que le nom de la propriété est vérifié via réflexion et que les événements sont mis en cache.

Les Pour :

  • Vérification du nom de la propriété par réflexion
  • Mise en cache des PropertyChangedEventArgs
  • La vérification n'est faite qu'en mode débug, donc pas de perte de performance en mode release

Les Contre :

  • L'implémentation est lente, beaucoup plus que de recréer l'objet à chaque fois
  • Encore une fois cela oblige à dériver d'un objet
  • Il est toujours fastidieux d'écrire le RaisePropertyChanged pour chaque setter

Bilan de mi-parcours

On voit que malgré trois propositions d'implémentation, il reste toujours un certain nombre de problèmes plus ou moins gênants. Pour le moment, nous n'avons abordé que des solutions classiques, c'est-à-dire implémentation directe ou via dérivation. Nous avons aussi commencé à voir que via réflexion, on peut valider le nom de la propriété. Par la suite, je vais vous présenter une version optimisée pour la vitesse et d'autres solutions moins classiques.

Version sans création du PropertyChangedEventArgs à chaque fois

Dans la version de Josh, la création de l'évènement est remplacé par un lookup dans un dictionnaire pour récupérer la même instance de l'argument à chaque fois. C'est de là que vient la lenteur de la méthode. On peut, simplement, arriver à une méthode rapide et qui évite la fragmentation :

public class Person : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private void RaisePropertyChanged(PropertyChangedEventArgs eventArgs)
    {
        if (PropertyChanged == null) return;
        PropertyChanged(this, eventArgs);
    }

    private static readonly PropertyChangedEventArgs _nameChangedEventArgs
        = new PropertyChangedEventArgs("Name");

    private string _name;
    public string Name { 
        get { return _name; }
        set 
        {
            _name = value;
            RaisePropertyChanged(_nameChangedEventArgs);
        }
    }
}
 

Ici, la ruse est simple : on crée un singleton de l'argument et on le passe directement à la méthode RaisePropertyChanged. Grâce aux mots-clés static et readonly, le runtime .NET assure la création unique de l'objet.

Les Pour :

  • Simple
  • Très efficace
  • Pas de fragmentation de la mémoire

Les Contre :

  • Toujours les mêmes problèmes que pour la version élémentaire (à part la fragmentation)

AOP interception et injection

L'AOP (Aspect Oriented Programming) est un paradigme de programmation qui prône la séparation des considérations techniques et des descriptions métier dans une application. Ici, on est tout à fait dans ce cas. Je dois dire qu'en ce moment je suis très intéressé par ce paradigme de programmation.

Il existe beaucoup de frameworks permettant de faire de l'AOP en .NET et encore beaucoup plus dans le monde Java. Je vais en citer deux:

Je vais présenter une méthode sur Entreprise Library et son module de Policy Injection, et une pour Spring.NET.

Entreprise Library, Policy Injection Application Block

Le Policy Injection Application Block de Entreprise Library permet de construire ou d'envelopper un objet avec un proxy, qui va pouvoir intercepter les appels de méthodes et de propriétés et réaliser des actions avant ou après. Les injections de bases fournies par Entlib et Spring sont par exemple le log et la gestion d'exception, ou encore la mise en cache. Pour les deux frameworks, il faudra écrire le petit bout de code qui fait la notification.

De plus, l'objet aura une contrainte suplémentaire : il devra implémenter une interface ou dériver de la classe MarshalByRefObject. Tout ceci est très bien expliqué dans la documentation d'Entlib. Concentrons-nous donc sur l'implémentation de INotifyPropertyChanged.

Pour commencer, on va créer un handler qui vas permettre de gérer l'interception de la propriété :

public class NotifyPropertyChangedHandler : ICallHandler
{
    public IMethodReturn Invoke(IMethodInvocation input,
                                 GetNextHandlerDelegate getNext)
    {
        //Before the call

        IMethodReturn result = getNext()(input, getNext);

        //After the call
    }

    public int Order{
        get{ return 0; }
        set{ throw new NotImplementedException(); }
    }
}

Pour créer le handler, il suffit de dériver de ICallHandler qui se trouve dans Microsoft.Practices.EnterpriseLibrary.PolicyInjection. L'interface comprend la méthode Invoke et la propriété Order. La méthode Invoke, comme son nom l'indique, va représenter l'appel de la méthode ; la propriété Order va être utile dans le cas où il y a plusieurs handlers pour la même méthode.

IMethodReturn result = getNext()(input, getNext);

Cette ligne représente l'appel de la propriété ou de la fonction. Tout ceci est détaillé dans la documentation d'Entlib. Ce qu'il faut comprendre, c'est que le code que l'on met avant s'éxécutera avant, et celui qu'on met après... ben après ! Pour nous, c'est la partie après qui nous intéresse.

Dans la variable input on trouve les deux propriétés qui vont nous servir :

  • input.MethodBase.Name
  • input.Target

Le premier point nous permet d'avoir le nom de la propriété et le deuxième l'objet que l'on est en train d'intercepter.

Maintenant, reste à savoir quoi mettre après l'appel pour déclencher l'événement. On pense tout de suite à la réflexion, mais après avoir cherché un peu, ce n'est pas si facile qu'il y parait. Si on récupère l'événement via GetEvent, on obtient un EventInfo qui n'a pas l'air de permettre de lancer l'événement. En continuant mes recherches, j'ai trouvé une solution via le field qui permet de récupérer le PropertyChangedEventHandler. Voilà le code commenté :

public class NotifyPropertyChangedHandler : ICallHandler
{
    public IMethodReturn Invoke(IMethodInvocation input,
                                 GetNextHandlerDelegate getNext)
    {
        //Before the call

        IMethodReturn result = getNext()(input, getNext);

        //Check if it's a get or a set
        if (!input.MethodBase.Name.StartsWith("set_")) return result;

        //Get the field that contains the event handler
        var propertyChangedField =
            input.Target.GetType().GetField("PropertyChanged",
            BindingFlags.Instance | BindingFlags.NonPublic);

        var eventHandler = propertyChangedField.GetValue(input.Target)
            as PropertyChangedEventHandler;

        if (eventHandler == null) return result;

        //Get the name of the property by removing set_
        var propertyName = input.MethodBase.Name.Remove(0, 4);
        //Job done invoke the event
        eventHandler.Invoke(input.Target, 
            new PropertyChangedEventArgs(propertyName));

        return result;
    }

    public int Order{
        get{ return 0; }
        set{ throw new NotImplementedException(); }
    }
}

Pour se simplifier la vie, l'idéal est de rajouter un attribut qui permet de marquer les propriétés que l'on veut intercepter :

[AttributeUsage(AttributeTargets.Property)]
public class NotifyPropertyChangedAttribute : HandlerAttribute
{
    public override ICallHandler CreateHandler()
    {
        return new NotifyPropertyChangedHandler();
    }
}

Voilà, il ne reste plus qu'à marquer la classe Person et à utiliser l'injecteur pour créer le proxy :

public class Person : IPerson, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private string _name;
    [NotifyPropertyChanged]
    public string Name { 
        get { return _name; }
        set 
        {
            _name = value;
        }
    }
}

On notera aussi que j'ai fait une interface IPerson car pour créer le proxy, il faut soit une interface, soit un MarshalByRefObject.

Voilà finalement le code qui créera le wraper et qui déclenchera le handler pour les propriétés :

var person = new Person();
person.PropertyChanged += 
    (sender, e) => Console.WriteLine(e.PropertyName);

var wrapedPerson = PolicyInjection.Wrap<IPerson>(person);
wrapedPerson.Name = "Frédéric Hamel";

Je pense que c'est une méthode très intéressante à cause des concepts qu'elle met en jeu, car cela peut permettre de résoudre de nombreux problèmes plus complexes.

Donc pour résumer :

Les Pour :

  • Pas de copier/coller
  • Réutilisation du code flexible
  • Récupération du nom de la propriété automatique

Les Contre :

  • Complexe la 1ère fois qu'on le fait
  • Introduit une dépendance dans le projet au framework d'injection
  • L'objet est wrapé dans un proxy
  • Oblige à avoir une interface ou à dériver de MarshalByRefObject

Spring.NET and AOP

Avec Spring.NET, il est possible d'aller plus loin qu'avec Entreprise Library. En effet, avec Spring.NET, on peut carrément mixer l'objet cible avec l'objet qui contient les mécanismes à insérer. Donc, avec Spring, il suffit de créer une classe qui va implémenter INotifyPropertyChanged, et de la mixer avec l'objet business.

Regardons le code qui permet de créer le proxy et voyons ensuite les classes à créer pour que cela fonctionne.

var factory = new ProxyFactory(new Person());

factory.AddIntroduction(advisor);
factory.AddAdvice(advisor.AfterAdvice);

factory.ProxyTargetType = true;

var person = (Person)factory.GetProxy();

On commence par créer une factory et on lui passe l'objet dont on souhaite avoir le proxy. Ensuite, on ajoute à la factory une introduction. Une introduction est la classe que l'on va mixer avec l'objet business. Une fois les deux objets mixés, l'objet business implémentera INotifyPropertyChanged : il ne reste donc plus qu'à écrire l'interception des setters pour pouvoir invoquer l'événement.

Pour cela, on ajoute une advice. Celle que j'ai choisie est l'AfterReturningAdvice. En effet, cette advice est particulièrement bien adaptée à notre situation car elle s'exécute après que la méthode ait retourné sa valeur. Pour finir, on met la propriété ProxyTargetType à true pour que l'on puisse caster le proxy dans le type d'origine et on appelle GetProxy qui va nous fabriquer tout cela.

Voyons maintenant l'objet à introduire :

public class NotifyPropertyChangedMixin : 
    INotifyPropertyChanged, 
    ITargetAware, 
    IAfterReturningAdvice
{
    public event PropertyChangedEventHandler PropertyChanged;

    private void RaisePropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            //Here you can use TargetProxy or the target
            //argument of the AfterReturning method
            PropertyChanged(TargetProxy, 
                new PropertyChangedEventArgs(propertyName));
        }
    }

    //The proxy
    public IAopProxy TargetProxy
    {
        private get;
        set;
    }

    //This method will be called after the method returned
    public void AfterReturning(
        object returnValue, 
        MethodInfo method, 
        object[] args, 
        object target)
    {
        //We check that we have a setter
        if (method.Name.StartsWith("set_") 
            && (method.GetParameters().Length == 1)) 
        {
            RaisePropertyChanged(method.Name.Remove(0, 4));
        }
    }

}

La classe dérive de INotifyPropertyChanged et l'implémente avec la technique élémentaire que l'on a vu au début du post. La classe implémente aussi ITargetAware qui est une interface fournie par le framework Spring et qui permet de savoir dans quel proxy l'on se trouve. C'est une interface optionnelle, mais ici je l'ai implémentée pour pouvoir récupérer le proxy.

Le private get a été rajouté par mes soins car il n'est pas nécessaire non plus pour l'interface ITargetAware.

La dernière interface IAfterReturningAdvice permet d'avoir la méthode AfterReturning qui, comme son nom l'indique, va nous permettre d'éxécuter du code après le retour de la méthode cible.

La méthode AfterReturning est ultra simple : elle vérifie juste que l'on a une propriété et déclenche l'événement.

J'aime bien la manière dont les interfaces sont nommées dans Spring. En effet, c'est très imagé : l'advisor (le professeur) va donner des advices (des conseils) à la factory, et ces advices vont s'appliquer au moment de l'exécution. Voila le advisor que j'ai réalisé :

public class NotifyPropertyChangedAdvisor 
    : DefaultIntroductionAdvisor
{
    public NotifyPropertyChangedAdvisor()
        :base(new NotifyPropertyChangedMixin()) {}

    public IAfterReturningAdvice AfterAdvice
    {
        get {
            return (IAfterReturningAdvice)base.Advice;
        }
    }
}

On voit qu'une implémentation par défaut est donnée par Spring et on lui passe juste la classe à mélanger. J'ai aussi rajouté une propriété pour récupérer le conseil.

Normalement, les classes à mélanger et les advices sont des objets différents. Ici, j'ai choisi de tout mettre dans NotifyPropertyChangedMixin, car je trouvais que c'était mieux du point de vue de l'encapsulation. En effet, comme l'advice est dans la même classe, elle peut déclencher l'événement sans avoir recours à la réflexion qui viendrait quand même court-circuiter l'encapsulation de l'objet.

Il est par ailleurs à noter que pour utiliser var person = (Person)factory.GetProxy();

il faut que les propriétés de la classe personne soient marqués virtual, car sinon le proxy ne peut pas les intercepter. On peut aussi utiliser une interface et remplacer la ligne par :

var person = (IPerson)factory.GetProxy();

Dans ce cas, les propriétés n'ont pas besoin d'être marquées virtual. Sur le forum de Spring, Mark Pollack (le co-lead de Spring) m'a expliqué que dans la prochaine version de Spring, il n'y aura plus besoin de marquer les propriétés virtual.

Les Pour :

  • Sont les mêmes que pour Entreprise Library et son Policy Injection Block
  • Le framework spring étant plus puissant, il permet aussi d'injecter l'événement, l'interface et les fonctions

Les Contre :

  • Ici encore on retrouve les mêmes problèmes. L'AOP est un paradigme de programmation auquel il faut s'habituer, et il faut faire l'effort d'apprendre les frameworks. De plus, on crée une dépendance sur notre projet.

La solution .NET 3.5 via Extension methods

Pour finir en beauté, voilà une solution élégante pour l'implémentation de INotifyPropertyChanged. En effet, on peut attacher une méthode d'extension à une interface, ce qui est conceptuellement génial car cela veut dire que l'on peut ajouter un comportement à l'interface quand l'extension est ajoutée ! C'est presque de l'héritage multiple du pauvre et limité, mais ça donne des idées.

public static class NotifyPropertyChangedExtensions
{
    public static void RaisePropertyChanged(
        this INotifyPropertyChanged reference) 
    {
        FieldInfo propertyChangedField = 
            reference.GetType().GetField("PropertyChanged", 
            BindingFlags.Instance | BindingFlags.NonPublic);

        var eventHandler = propertyChangedField.GetValue(reference)
            as PropertyChangedEventHandler;

        if (eventHandler == null) return;

        var previewStackFrame = new StackFrame(1);
        var method = previewStackFrame.GetMethod();
        var propertyName = method.Name.Remove(0, 4);

        eventHandler.Invoke(reference, 
            new PropertyChangedEventArgs(propertyName));
    }
}

Donc comme indiqué, je définis l'extension sur INotifyPropertyChanged : this INotifyPropertyChanged reference

Ensuite, via réflexion, je récupère l'événement comme on l'a vu dans d'autres méthodes. Et pour récupérer le nom de la méthode, j'ai recours à une petite ruse. En effet, grâce à la class StackFrame, je peux remonter la pile des appels et récupérer la méthode appelante. Les setters sont toujours transformés en "set_propertyName", donc il suffit d'enlever le set_ et on a le nom de la propriété.

public string Name { 
    get { return _name; }
    set 
    {
        _name = value;
        this.RaisePropertyChanged();
    }
}

Comme on a affaire à une méthode d'extension, on est obligé de mettre this pour que le compilateur et l'intellisense puissent trouver la méthode.

Optimisation possible

Une optimisation que l'on voit souvent est de ne déclencher l'événement que si la valeur de la propriété a effectivement changé.

if (_name != value)
{
    _name = value;
    this.RaisePropertyChanged();
}

Concrètement, ce genre d'optimisation ne va apporter un plus que si les traitements réalisés en réaction à l'événement sont importants.

Conclusion

Nous avons vu dans ce long post de nombreuses manières d'implémenter INotifyPropertyChanged.

Ce qui est important ici est surtout de voir les différentes approches qui ont été utilisées et qui peuvent être transposées dans d'autres cas. Certaines méthodes visent la maintenabilité, d'autres l'efficacité, d'autres abordent le problème de manière plus générale. Je dirais qu'il n'y a donc pas vraiment de bonne ou de mauvaise méthode : il n'y a que des méthodes différentes qui s'appliquent en fonction des besoins et des contraintes.

La technique .NET 3.5 avec l'extension est sûrement suffisante dans bien des cas. Si on a besoin de plus de performance, alors celle qui met les arguments en singleton static (Version sans création du PropertyChangedEventArgs à chaque fois) est peut être la plus efficace. Si le projet a déjà des dépendances sur Spring ou sur Entreprise Library, il peut être bon de connaître les possibilités et de savoir les utiliser si besoin.

Happy programming !

[WPF] Data Binding Quick Reference

Vous trouverez ici les principales syntaxes permettant de réaliser un Data Binding en WPF. Le but ici n'est pas d'expliquer en détail son mécanisme mais plutôt de proposer une référence où vous pourrez trouver la syntaxe qui correspondra à votre besoin.

Sommaire :

  1. Affecter la source d'un binding
  2. Affiner la sélection avec la propriété Path
  3. Mode du Binding
  4. Utiliser des Converters
  5. Quelques astuces pour le Binding
  6. Utiliser le MultiBinding
  7. Rendre ses objets .NET Binding-Friendly

Affecter la source d'un binding

  • Binding sur le DataContext
    <ListBox ItemsSource="{Binding}" />
  • Binding sur un autre élément

    <Slider Name="_mySlider"/>
    <TextBlock Text="{Binding ElementName=_mySlider,Path=Value}"/>
  • Binding sur soi-même
    <TextBlock Name="_selfName" 
     Text="{Binding Name, RelativeSource={RelativeSource Self}}" />
  • Binding vers un parent
    <Canvas Name="_canvas">
        <TextBlock Text="{Binding Path=Name, 
                                  RelativeSource=
     {RelativeSource FindAncestor, AncestorType={x:Type Canvas}}}" />
    </Canvas>
  • Binding vers le template parent
    <ControlTemplate x:Key="_template">
        <TextBlock Background="{TemplateBinding Background}"/>
    </ControlTemplate>
  • Binding vers le data item précédent
     {Binding RelativeSource={RelativeSource PreviousData}}
  • Binding d'une propriété statique
    <TextBlock Text="{x:Static local:Window1.AStaticProperty}"/>
  • Binding d'une ressource statique
    <TextBlock Background="{StaticResource _blackBrush}"/>
  • Binding d'une ressource dynamique
    <TextBlock Background="{DynamicResource _blackBrush}"/>

Affiner la sélection avec la propriété Path

  • Deuxième constructeur de la classe Binding
    <!--Use the constructor : public Binding(string path)-->
    <TextBlock Text="{Binding Name}"/>
  • Propriété Path

    <TextBlock Text="{Binding Path=Name}"/>
  • Syntaxe longue
    <TextBlock>
        <TextBlock.Text>
            <Binding>
                <Binding.Path>Name</Binding.Path>
            </Binding>
        </TextBlock.Text>
    </TextBlock>
  • Binding vers une DependencyProperty attachée
    <TextBlock Canvas.Left="50" 
               Text="{Binding Path=(Canvas.Left), 
    RelativeSource={RelativeSource Self}}" />
  • Binding vers l'item courant synchronisé
<TextBlock Text="{Binding Path=/}" />
<TextBlock Text="{Binding Path=Photos/}" />
<TextBlock Text="{Binding Path=/DateTime}" />
<TextBlock Text="{Binding Path=Photos/DateTime}" />

Mode du Binding

  • Bidirectionnel
    <TextBlock Text="{Binding Path=Name, Mode=TwoWay}"/>
  • De la source vers la target
    <TextBlock Text="{Binding Path=Name, Mode=OneWay}"/>
  • Une fois de la source vers la target
    <TextBlock Text="{Binding Path=Name, Mode=OneTime}"/>
  • De la target vers la source
    <TextBlock Text="{Binding Path=Name, Mode=OneWayToSource}"/>

Utiliser des Converters

  • Affecter un Converter
    <TextBlock Text="{Binding Name, 
        Converter={StaticResource myDoNothingConverter}}"/>
  • Passer des paramètres à un Converter
    <TextBlock Text="{Binding Name, 
        Converter={StaticResource myDoNothingConverter},
        ConverterParameter='Hello'}"/>
  • Écrire un Converter
    En Xaml:
    xmlns
    :local="clr-namespace:Wpf.BindingLibrary.QuickReference"
    <local:DoNothingConverter x:Key="myDoNothingConverter"/>
    En Code:
    namespace
    Wpf.BindingLibrary.QuickReference { public class DoNothingConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { return Binding.DoNothing; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } } }
  • Écrire un Converter multi-values
    public class MultiValueConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, 
            object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    
        public object[] ConvertBack(object value, Type[] targetTypes,
            object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
  • Utiliser le string formater converter, disponible bientôt dans le SP1 de .NET 3.5
     Lire le post de Lester à ce sujet

Quelques astuces pour le binding

  • Récupérer un Binding
    BindingOperations.
    GetBindingExpression(dependencyObject, TextBlock.TextProperty); BindingOperations.
    GetBinding(dependencyObject, TextBlock.TextProperty);
  • Mettre à jour un Binding
    var bindingExpression =
    BindingOperations.
    GetBindingExpression(dependencyObject, TextBlock.TextProperty); binding.UpdateSource(); binding.UpdateTarget();
  • Effacer un Binding
    BindingOperations.
    ClearBinding(dependencyObject,TextBlock.TextProperty); BindingOperations.ClearAllBindings(dependencyObject);
  • Ne rien faire pendant un Binding
    return Binding.DoNothing;

Utiliser le MultiBinding

  • Syntaxe
    <TextBlock>
        <TextBlock.Text>
            <MultiBinding 
    Converter="{StaticResource myMultiValueConverter}"> <Binding Path="Name"/> <Binding Path="Adress"/> </MultiBinding> </TextBlock.Text> </TextBlock>

Rendre ses objets .NET Binding-friendly

  • INotifyPropertyChanged
    public class Person : INotifyPropertyChanged
    {
        [field:NonSerialized]
        public event PropertyChangedEventHandler PropertyChanged;
    
        private void RaisePropertyChanged(string propertyName) 
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this,
                new PropertyChangedEventArgs(propertyName));
            }
        }
    
        private string _name;
        public string Name { get { return _name; }
            set {
                _name = value;
                RaisePropertyChanged("Name");
            }
        }
    }
  • ObservableCollection<T>
    Vous pouvez utiliser le deuxième constructeur avec votre liste existante
    public ObservableCollection(List<T> list);
[Hello World] 1er post sur mon 1er blog

Bonjour,

Voilà c'est fait : le blog est ouvert et le premier post posté :)
Je remercie Cyril et Thomas pour m'avoir aidé à ouvrir le blog.

J'aimerais grâce à ce blog faire partager mes réflexions sur .NET et sur WPF en particulier. J'espère que ça aidera la communauté et que la communauté aimera lire ce blog!

En ce qui me concerne, pour me présenter un peu, je m' appelle Frédéric Hamel, je suis ingénieur, et je travaille en Suisse à Genève pour une société financière et d'édition de logiciel. Les projets sont en .NET 3.5 et font intervenir WPF, WCF et WF donc je pense que ça va aider à alimenter ce blog avec des contribution à la pointe !

Bienvenue et bonne lecture :)



Les 10 derniers blogs postés

- Office 365: Script PowerShell pour auditer l’usage des Office Groups de votre tenant par Blog Technique de Romelard Fabrice le 04-26-2019, 11:02

- Office 365: Script PowerShell pour auditer l’usage de Microsoft Teams de votre tenant par Blog Technique de Romelard Fabrice le 04-26-2019, 10:39

- Office 365: Script PowerShell pour auditer l’usage de OneDrive for Business de votre tenant par Blog Technique de Romelard Fabrice le 04-25-2019, 15:13

- Office 365: Script PowerShell pour auditer l’usage de SharePoint Online de votre tenant par Blog Technique de Romelard Fabrice le 02-27-2019, 13:39

- Office 365: Script PowerShell pour auditer l’usage d’Exchange Online de votre tenant par Blog Technique de Romelard Fabrice le 02-25-2019, 15:07

- Office 365: Script PowerShell pour auditer le contenu de son Office 365 Stream Portal par Blog Technique de Romelard Fabrice le 02-21-2019, 17:56

- Office 365: Script PowerShell pour auditer le contenu de son Office 365 Video Portal par Blog Technique de Romelard Fabrice le 02-18-2019, 18:56

- Office 365: Script PowerShell pour extraire les Audit Log basés sur des filtres fournis par Blog Technique de Romelard Fabrice le 01-28-2019, 16:13

- SharePoint Online: Script PowerShell pour désactiver l’Option IRM des sites SPO non autorisés par Blog Technique de Romelard Fabrice le 12-14-2018, 13:01

- SharePoint Online: Script PowerShell pour supprimer une colonne dans tous les sites d’une collection par Blog Technique de Romelard Fabrice le 11-27-2018, 18:01