La beta 1 de Visual Studio 2010 et du Framework .Net 4.0 est disponible depuis quelques jours (normalement, si vous lisez ce blog, vous êtes déjà au courrant !), et une des nouveautés majeures de cette nouvelle version (si ce n’est LA nouveauté) concerne le Multi-Threading. En effet, .Net 4.0 intègre un certain nombre d’améliorations au niveau du ThreadPool qui ont permis de créer tout un jeu d’APIs dédiées à l’exécution de tâches en parrallèle et à leur synchronisation.

Ces APIs se distinguent en 3 catégories :

  • Task & TaskScheduler : Il s’agit de la possibilité de créer des tâches avec une granularité très fine, de définir des dépendances (telle tâche ne peut démarrer avant la fin de l’exécution de telle autre), et de les exécuter. Le TaskScheduler se chargera alors de distribuer ces tâches entre les différents Threads du ThreadPool pour exploiter au mieux les ressources de la machine. On peut aussi utiliser les tâches pour effectuer des traitements asynchrones afin de ne pas bloquer un Thread de rendu en WPF ou Windows Forms par exemple.
  • Parallel loops : Il s’agit d’un ensemble de méthodes statiques remplacant les boucles traditionelles, permettant de traiter chaque itération d’une boucle for / foreach dans des tâches séparées afin de parallèliser le traitement, et potentiellement d’aggréger les résultats des traitements de chaque tour de boucle. Parallel.For, Parallel.Foreach, Parallel.Do etc. reposent sur les classes Task et TaskScheduler.
  • Parallel Linq : Il s’agit d’une extension à Linq to Objects permettant de paralleliser le traitement d’une requête. Si vous aimé tout comme moi le style de développement fonctionnel apporté par C# 3.0 et que vous mettez des lambdas, et des appels à Enumerable.Where, Enumerable.Select, etc. partout dans votre code, vous adorerez PLinq :).

L’exemple qui vient avec cet article exploite essentiellement les parallel loops. Le but de l’application est d’effectuer des traitements sur une série d’images (ajustement de contraste). L’algorythme employé n’est pas le plus optimisé qui soit, le but étant de mettre en valeur la simplicité des APIs plutôt que de concurrencer Photoshop. L’exemple a aussi l’avantage de montrer quelques astuces spécifiques au développement multi-thread avec WPF.

Focus 1 : chargement des images

La première utilisation que je fais de PTL, est assez classique, et équivaut à l’utilisation d’un BackgroundWorker. Il s’agit simplement de charger les images dans un thread séparé pour éviter de bloquer le thread UI de WPF :

if (ofd.ShowDialog() ?? false)
{
    _images.Clear();
    Task.Factory.StartNew(() =>
        {
            foreach (var file in ofd.FileNames)
            {
                try
                {
                    var source = new BitmapImage(new Uri(file));
                    source.Freeze();
                    var tuple = new SourceAndTarget { Source = source };
                    Dispatcher.BeginInvoke((Action)(() =>
                        {
                            _images.Add(tuple);
                            tuple.Target = new WriteableBitmap(source);
                        }));
                }
                catch
                {
                    MessageBox.Show("Image " + file + " failed");
                }
            }
        }).ContinueWith((t) =>
            Dispatcher.BeginInvoke((Action)(() => LoadingImages = false)));
}

Task.Factory.StartNew permet de créer une tâche à partir d’un délégué (ici une lambda) et de la scheduler. Notez la ligne source.Freeze(), elle est très importantes. En effet, avec WPF, tout DependencyObject ne peut par défaut être manipulé que dans le thread où il a été créé. Hors, comme on est dans un thread différent du Thread UI, la BitmapImage que l’on vient de créer ne peut pas, par défaut être accessible par le Thread UI.

Il existe dans WPF une classe dérivant de DependencyObject appelée Freezable permettant de “figer” l’objet et le rendre immutable. Une fois que l’objet est figé, il n’y a plus de risques liés à la concurrence d’accès (car tout accès en écriture aux propriétés est désactivé), l’objet devient accessible depuis n’importe quel thread. Beaucoup d’objets (notament les images, les brushes, les Paths etc) de WPF héritent de cette classe Freezable. Une fois notre BitmapImage crée, nous appelons donc sa méthode Freeze() pour la rendre accessible au thread UI de WPF.

Le cas de la WriteableBitmap est plus complexe, car elle demande d’être accessible par le thread UI tout en restant modifiable. Grace au Dispatcher, nous basculons donc dans le Thread UI et créons la WriteableBitmap depuis celui-ci.

Focus 2 : traitement en parallèle avec Parallel.For

Le traitement d’image se prête assez facilement à la parallèlisation. En effet, il suffit de découper l’image source, calculer les différentes portions dans des threads séparés et recomposer l’image destination. La classe ImageProcessor fournie dans l’exemple illustre cela parfaitement : que l’on passe en parallèle ou que l’on reste en traitement séquenciel, la grosse majorité du code reste identique. Seulement dans un cas, on parallèlise la boucle de traitement :

public void ProcessSequential(WriteableBitmap target, ITransformer transformation)
{
    Contract.Requires(target != null);
    Contract.Requires(transformation != null);
    int count = Math.Min(Environment.ProcessorCount * 2, _image.PixelWidth);
    int chunkWidth = _image.PixelWidth / count;

    for (int i = 0; i < count; i++)
    {
        ProcessChunck(target, transformation, count, chunkWidth, i);
    }
}
public void ProcessParallel(WriteableBitmap target, ITransformer transformation)
{
    Contract.Requires(target != null);
    Contract.Requires(transformation != null);
    int count = Math.Min(Environment.ProcessorCount * 2, _image.PixelWidth);
    int chunkWidth = _image.PixelWidth / count;

    Parallel.For(0, count, (i) =>
    {
        ProcessChunck(target, transformation, count, chunkWidth, i);
    });
    
}

Le nombre de “morceaux” d’image générés est lié au nombre de processeurs. Le *2 est simplement là pour augmenter la granularité car on ne sait pas si l’algorythme de transformation prendra autant de temps pour chaque morceau d’image. Le contenu de la méthode ProcessChunk n’est quand à lui pas très compliqué. Il s’agit simplement de récupérer les données de l’image source correspondant au morceau à traiter, de le traiter, et de l’injecter dans la WritableBitmap. On aura d’ailleurs besoin de se replacer dans le Thread UI pour effectuer cette dernière opération (la seule à ne pas être parallèlisée) :

private void ProcessChunck(WriteableBitmap target, ITransformer transformation, int count, int chunkWidth, int i)
{
    var width = chunkWidth;
    if (i == count - 1)
        width += _image.PixelWidth % chunkWidth;

    var rect = new Int32Rect(i * chunkWidth, 0, width, _image.PixelHeight);
    int stride = width * 4;
    byte[] data = new byte[stride * _image.PixelHeight];
    _image.CopyPixels(rect, data, stride, 0);
    var transformed = transformation.Transform(new ImageChunk(rect, data));
    target.Dispatcher.BeginInvoke((Action)(() =>
    {
        target.WritePixels(transformed.Rect, transformed.Data, stride, 0);
    }));
}

Conclusion

Parallel Task Library apporte une réelle facilité de syntaxe pour le développement d’applications faisant appel au parallèlisme (que ce soit pour effectuer un traitement asynchrone ou pour exploiter la puissance d’un processeur multi-coeur). En plus de cela, Visual Studio 2010 apporte une collection d’outils permettant de faciliter le Debugging d’applications multi-thread, ainsi qu’un outil de profiling permettant de détecter les problèmes rencontrés dans ces applications (contentions, attente sur une ressource partagées…).

Ce qu’il ne faut par contre pas oublier, c’est que quelque soit la technologie employée (PTL, OpenMP, etc…), il ne s’agit que d’outils simplifiant les aspects technique de la création d’applications Multi-Thread. En effet, PTL ne dispense pas le développeur de “penser” ses algorythmes dans un style parallèle (découpage en tâches, détection des tâches non-dépendantes, utilisation d’objets immutables, éviter les partages d’états…). Il faudra donc de toute façon se méfier des Race conditions, des deadlocks, etc. Le style de programmation fonctionnel apporté par C# 3.0 se prète par contre très bien à la parallèlisation, car il pousse à l’utilisation d’objets immutables et donc évite le problème le plus courrant lors du développement parallèle : le partage d’état.

Sources de la solution : ici.

Mots clés Technorati : ,,

I think this topic is quite important and quite unanswered even on English-speaking blogs, so I decided to publish this entry in english… sorry French frogs!

So, a couple of weeks ago, Nikhil Kothari has written a very interesting blog post about encapsulating a RIA Services DomainContext into a ViewModel. The only feature that were not covered by the blog post was server-side paging and sorting without exposing the DomainContext and without using a DomainDataSource.

DomainDataSource is a control that encapsulate a DomainContext to provide an ICollectionView and IPagedCollectionView implementation which will be used by the RIA controls (DataGrid, DataPager…). This implementation does trigger a roundtrip to the server when the user ask for a sort or paging operation (using DataGrid headers, or DataPager control).

In a comment of his blog, Nikhil exposed a very interesting idea to make server-side paging / sorting work with a ViewModel : Implement a control similar to the DomainDataSource, but encapsulating a custom ViewModel. I have been thinking about this for a couple of days, and I implemented something that does that using reflection.

Here is the usage :

<SStuff_Ria:PagingObjectProvider x:Name="pagingObjectProvider" 
                                 DataSource="{Binding Mode=OneWay}" 
                                 GoToPageMethodName="LoadProductsPage" 
                                 ItemsSourcePropertyName="Products" 
                                 PageChangingPropertyName="PageChanging" 
                                 PageIndexPropertyName="PageIndex" 
                                 TotalItemCountPropertyName="TotalItemCount" />

(of course, the DataContext is an instance of my ViewModel, which exposes the specified methods and properties, and implements INotifyPropertyChanged).

After that, we can reference this DataSource as we did with the DomainDataSource:

<data:DataGrid Grid.Row="1"
             
IsReadOnly="True"
             
ItemsSource="{Binding ElementName=pagingObjectProvider,Path=Data}"
             
AutoGenerateColumns="False">
    <
data:DataGrid.Columns>
        …
    </data:DataGrid.Columns>
</
data:DataGrid>

<dataControls:DataPager Grid.Row="2" 
                        Source="{Binding ElementName=pagingObjectProvider, Path=Data}"/>

One really cool side effect, is that the PagingObjectProvider does not depends on RIA Services at all and could be used with custom WCF Services or with ADO.Net Data Service. So we will be able to use it as soon as SL3 is out (no need to wait for a RIA Services RTW).

Here are the sources (with 2 samples : One with RIA Services and one with custom WCF Service) : SStuff.RIA.zip


 

 

 

 

 

Microsoft a récemment publié la Beta 1 de Silverlight 3 ainsi qu’une CTP des .Net RIA Services. Je commencerai d’ailleurs par rappeler que si Silverlight 3 RTW est prévu pour l’été prochain, les RIA Services n’ont pour l’instant aucune date de sortie, et sont sujet à des Breaking Changes TRES conséquents (les plans sont d’en faire une sorte d’extension à ADO.Net Data Services).

 

Toujours est-il que le modèle est très intéressant et que dès aujourd’hui, nous pouvons commencer à travailler sur l’intégration du DomainContext ainsi que des nouveautés propre à Silverlight 3 avec les Design Pattern existants actuellement pour la création d’application LOB (Line of business) avec Silverlight. Aujourd’hui je vais donc présenter quelques  “early”-astuces avec Silverlight 3, RIA Services, Unity Application bloc et le pattern MVVM.

 

Introduction

 

L’application présentée est très simple : Il s’agit simplement d’une grille de données affichant une liste de personnes avec leur manager avec pour chaque ligne un bouton “Foo”, qui lorsqu’il est cliqué fait apparaitre une MessageBox contenant le nom de la personne :

image

Les données sont exposées par le serveur via RIA Services. Pour des raisons de simplicité, il n’y a pas de base de donnée, seulement quelques instances en mémoire.

 

Afin de pouvoir utiliser le pattern MVVM, il m’a aussi fallu implémenter l’interface ICommand ainsi que créer un modèle d’adapters capable d’associer une commande à un contrôle. Je ne détaillerai pas ce code, car il n’est pas du tout spécifique à Silverlight 3, et si vous travaillez en mode MVVM avec Silverlight, vous avez déjà très certainement ce qu’il vous faut pour associer des commandes à des boutons de manière déclarative.

 

 

Rappel sur M-V-VM

 

M-V-VM représente un modèle à 3 responsabilités :

  • Le modèle : Il s’agit de la business logique de l’application cliente, qui doit être indépendante de la technologie d’affichage. En général dans une application Silverlight, il s’agit de business entities échangées avec le serveur + logique de validation côté client + business logique côté client + proxy WCF. Dans notre cas, vu le peu de complexité il s’agit essentiellement du DomainContext RIA Services
  • La vue : En Silverlight il s’agira d’un UserControl, ou d’une page (depuis Silverlight 3), avec peu (dans l’idéal pas) de code behind.
  • Le ViewModel : littéralement le “modèle de la vue”. Il s’agit d’une classe encapsulant le modèle de manière à le présenter à la vue de la manière la plus “bindable” possible. Le ViewModel expose dans certain cas directement des entités du modèle, dans d’autres il les encapsule pour les rendre plus “Silverlight-aware” (par exemple en présentant les propriétés booléennes sous forme de Visibility, en fournissant les ItemsSource des combobox / listbox etc, et en présentant les comportements sous forme de Commandes sur lesquelles la vue peut se “binder”).

 

Un des enjeux majeurs est de faire en sorte d’avoir des vues en pur XAML, et des ViewModels complètement indépendants des vues qui leur seront associées, afin d’avoir un XAML entièrement modifiable par un designer sans que le comportement ne soit affecté.

 

Un autre enjeu est la testabilité des applications, et l’isolation des différents composants. Dans notre exemple, toutes les interdépendances seront résolues par injection de dépendance, à l’aide de Unity Application bloc

 

Introduction à l’injection de dépendance avec Unity

 

Le but de l’injection de dépendance est de réduire le couplage entre les différents composants d’une application. Ce pattern repose sur un composant central, le conteneur, qui permet de configurer la façon dont les différents composants seront résolus. Voici quelques exemples d’enregistrements de dépendances avec Unity:

 

            IUnityContainer container = new UnityContainer();
            
            // Map l'interface IMainViewModel à l'implémentation MainViewModel.
            // Chaque fois que l'on demandera un IMainViewModel à Unity, il créera une instance
            // de MainViewModel :
            container.RegisterType<IMainViewModel, MainViewModel>();
            
            // La même chose en mode "Singleton" (une seule instance sera créée
            // par le conteneur, qui sera renvoyé à chaque fois :
            container.RegisterType<IMainViewModel, MainViewModel>(new ContainerControlledLifetimeManager());

            // La même chose avec "une instance par thread" :
            container.RegisterType<IMainViewModel, MainViewModel>(new PerThreadLifetimeManager());

            // Spécifie comment notre DomainContext doit être construit
            // (ici en spécifiant un DomainClient de test plutot que le client Rest/HTTP):
            container.RegisterType<PeopleContext, PeopleContext>(new InjectionConstructor(new TestDomainClient()));
 

Ensuite pour résoudre une dépendance on a juste à faire :

 

var viewModel = container.Resolve<IMainViewModel>();
 

Le fonctionnement basique de Unity est vraiment très simple. Il y’a pas mal d’autres fonctionnalités comme l’injection de propriétés, l’interception d’appels de méthode (DynamicProxy…) mais elles ne seront pas présentées dans cet article

 

Configuration de l’application

 

Dans cette application, nous n’avons qu’un seul ViewModel : MainViewModel. Ce ViewModel implémente IMainViewModel :

 

namespace SilverlightSample.ViewModels
{
    /// <summary>
    /// ViewModel exposing data and commands
    /// </summary>
    public interface IMainViewModel
    {
        /// <summary>
        /// The RIA Services exposed Data
        /// </summary>
        PeopleContext DomainContext { get; }

        /// <summary>
        /// A command that needs a Person instance as a parameter
        /// </summary>
        ICommand FooCommand { get; }
    }
}
 

L’implémentation de IMainViewModel expose le DomainContext RIA afin de fournir à la vue la possibilité de définir des DomainDataSources permettant aux contrôles DataGrid et DataPager de fonctionner correctement. On expose aussi une commande à laquelle la vue pourra se binder (voir les boutons Foo dans le screenshot en début de post). Le DomainContext sera résolu par Unity, tandis qu’une RelayCommand sera créée pour la propriété FooCommand :

 

public class MainViewModel : IMainViewModel, IInitialize
    {
        private PeopleContext _domainContext;
        private RelayCommand _fooCommand;
        IUnityContainer _unityContainer;
        public MainViewModel(IUnityContainer unityContainer)
        {
            _unityContainer = unityContainer;

            _domainContext = unityContainer.Resolve<PeopleContext>();
          

            _fooCommand = new RelayCommand(
                obj => DoFoo(obj as Person),
                obj => obj is Person);
        }

}
 

note : MainViewModel implémente aussi IInitialize. Cette interface est déclarée dans ma boîte à outils MVVM et est utilisée par le contrôle ViewModelHelper décrit un peu plus loin.

 

Au niveau du App.xaml, nous allons donc configurer notre conteneur de dépendances pour IMainViewModel et PeopleContext :

 

private void Application_Startup(object sender, StartupEventArgs e)
{
    IUnityContainer rootApplicationContainer = new UnityContainer();
    rootApplicationContainer.RegisterType<IMainViewModel, MainViewModel>(new ContainerControlledLifetimeManager());
    rootApplicationContainer.RegisterType<PeopleContext, PeopleContext>(new InjectionConstructor()); 
//on spécifie explicitement
// l’utilisation du constructeur par défaut Resources.Add("RootContainer", rootApplicationContainer); this.RootVisual = new MainPage(); }
 

note : vous pouvez constater que l’on rajoute le conteneur aux ressources de l’Application afin de pouvoir y accéder de partout (y compris à partir du Xaml en utilisant une {StaticResource}).

 

Accéder au conteneur Unity depuis la vue

 

Afin d’accéder au ViewModel via Unity à partir de ma vue, j’ai créé un contrôle ViewModelHelper. Ce contrôle n’a pas de template d’affichage et permet simple d’accéder à un ViewModel enregistré dans un conteneur Unity (un peu à la manière du DomainDataSource qui permet d’accèder aux entités d’un DomainContext).

 

ViewModelHelper a les propriétés suivantes :

  • UnityContainer : le conteneur Unity utilisé pour résoudre le ViewModel
  • ViewModelType : le type du ViewModel (par exemple : “SilverlightSample.ViewModels.IMainViewModel, SilverlightSample, Version=1.0.0.0”)
  • ViewModelName (optionnel) : utilisé si l’enregistrement de la dépendance est nommé (paramètre optionnel de UnityContainer.RegisterType() et UnityContainer.Resolve())
  • InitParam (optionnel) : paramètre à passer à la méthode Initialize si le ViewModel implémente IInitialize.
  • ViewModel (lecture seule) : le ViewModel résolu

 

Dans notre exemple, la vue principale de l’application possède donc un ViewModelHelper :

 

<sstuff:ViewModelHelper 
    UnityContainer="{StaticResource RootContainer}"
    ViewModelType="SilverlightSample.ViewModels.IMainViewModel,
                   SilverlightSample, Version=1.0.0.0"
    x:Name="vmh" />
 

Comme Silverlight 3 introduit le Binding inter-contrôles, on peut alors se Binder au ViewModel ainsi exposé :

 

<ria:DomainDataSource x:Name="dds"
                              AutoLoad="True"                           
                              LoadMethodName="LoadAllPeople"
                              DomainContext="{Binding ViewModel.DomainContext, ElementName=vmh}"
                              />
 

Comme la vue n’est pas liée à une implémentation particulière de IMainViewModel, on peut tout à fait imaginer créer des tests unitaires vérifiant par exemple la validité des bindings, en proposant un “mock” du ViewModel en lieu et place de l’implémentation normale (c’est d’ailleurs le cas dans la solution liée au post).

 

note : Pourquoi ViewModelHelper est un contrôle ? Tout simplement pour pouvoir Binder son InitParam ou son ViewModelName à une propriété du DataContext (ce qui est très utile dans des scénarii de vues Master / Details où chaque vue détail est associé à un ViewModel).

 

Accéder au ViewModelHelper à partir d’une ligne du DataGrid

 

Une des limites du Binding inter-éléments de Silverlight, est qu’il ne remonte pas au NameScope parent si il ne trouve pas l’UIElement dans le scope courrant. Hors, dans mon cas, la colonne de commande est réalisée grâce à une DataGridTemplatedColumn, basé sur un template qui génèrera donc pour chaque cellule un NameScope différent. Pour palier à ce problème, j’ai écrit un deuxième contrôle non-graphique utilisé simplement pour explorer l’arbre visuel : le VisualTreeDataSource.

 

Ce contrôle expose 2 propriétés :

  • Query : n’importe quel objet implémentant IVisualTreeQuery (cette interface est vraiment très simple à implémenter, regardez les sources pour vous en convaincre)
  • Match (lecture seule) : contrôle retourné par la requète. Cette propriété est maintenue à jour à chaque évènement LayoutUpdated

 

Voici donc le template utilisé pour la colonne de commandes :

 

<DataTemplate>
    <Grid>
        <!-- Element Binding do not work accross NameScopes.
        RelativeSource markup extension is lacking the FindAncestor mode-->
        <!-- So, the VisualTreeDataSource is used to find an element using VisualTree browsing-->
        <sstuff:VisualTreeDataSource x:Name="vds">
            <sstuff:VisualTreeDataSource.Query>
                <sstuff:ComposedVisualQuery>
                    <!-- first get the MainPage control-->
                    <sstuff:FindAncestor AncestorType="SilverlightSample.MainPage,
                                                       SilverlightSample,Version=1.0.0.0" />
                    <!-- then find the control named "vmh"-->
                    <sstuff:FindName TargetName="vmh" />
                </sstuff:ComposedVisualQuery>
            </sstuff:VisualTreeDataSource.Query>
        </sstuff:VisualTreeDataSource>
        <!-- Now we can access the ViewModelHelper via the Match DependencyProperty 
        of the VisualTreeDataSource and bind to exposed command-->
        <Button sstuff:Commanding.Command="{Binding Match.ViewModel.FooCommand, ElementName=vds}"
                sstuff:Commanding.CommandParameter="{Binding}" 
                Content="Foo"/>
    </Grid>
</DataTemplate>
 

Et voilà, nous avons une vue qui accède à son ViewModel via Unity et qui n’a aucun CodeBehind.

 

Conclusion

 

Voici les différents points que j’ai abordé dans ce post :

  • Intégrer les RIA Services au pattern MVVM (dans ma wish-list, j’aimerais bien pouvoir me passer du DomainDataSource dans les vues tout en gardant la possibilité de tri / pagination côté server et donc ne plus être obligé d’exposé le DomainContext complet, qui n’est pas très simple à mocker)
  • Utiliser les nouvelles fonctionnalités de Binding de Silverlight 3 pour créer des vues “CodeBehind-less”
  • Réduire le couplage entre la vue et le ViewModel, et entre le ViewModel et le modèle afin d’améliorer la testabilité des composants de façon unitaire (Injection de dépendance avec Unity)
  • Accéder au conteneur Unity en pur Xaml pour la résolution des ViewModels

 

Ce que je n’ai pas abordé dans le post mais que vous pouvez trouver dans la solution téléchargeable : l’écriture des tests unitaires de la vue (par mocking du ViewModel) et du ViewModel (par mocking du DomainClient utilisé par le PeopleContext).

 

 

 


Le Silverlight Control Toolkit comprend un contrôle fort utile pour les applications RIA : l’AutoCompleteBox. Malheureusement, sa propriété SelectedItem est en lecture seule, et ne permet donc pas de faire du Binding dessus. Il existe 2 solutions à ce problème :

1. Télécharger les sources associées au ChangeSet 10371 ou supérieur sur le site CodePlex et recompiler le toolkit

2. Passer par une attached property (seule solution garantissant qu’il n’y ait pas de Breaking Change):

public class AutoCompleteHelper : DependencyObject
    {
        public static bool GetBound(DependencyObject obj)
        {
            return (bool)obj.GetValue(BoundProperty);
        }

        public static void SetBound(DependencyObject obj, bool value)
        {
            obj.SetValue(BoundProperty, value);
        }

        // Using a DependencyProperty as the backing store for Bound.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty BoundProperty =
            DependencyProperty.RegisterAttached("Bound", typeof(bool), typeof(AutoCompleteHelper), new PropertyMetadata(false,
                (sender, args) =>
                {

                    var box = sender as AutoCompleteBox;
                    if ((bool)args.NewValue)
                        box.SelectionChanged += box_SelectionChanged;
                    else
                        box.SelectionChanged -= box_SelectionChanged;
                }));

        static void box_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            var box = sender as AutoCompleteBox;
            SetSelectedItem(box, box.SelectedItem);
        }

        public static object GetSelectedItem(AutoCompleteBox obj)
        {
            return (object)obj.GetValue(SelectedItemProperty);
        }

        public static void SetSelectedItem(AutoCompleteBox obj, object value)
        {
            obj.SetValue(SelectedItemProperty, value);
        }

        // Using a DependencyProperty as the backing store for SelectedItem.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty SelectedItemProperty =
            DependencyProperty.RegisterAttached("SelectedItem", typeof(object), typeof(AutoCompleteHelper), new PropertyMetadata(null,
                (sender, args) =>
                {
                    var box = sender as AutoCompleteBox;
                    var txt = string.Empty;
                    if (args.NewValue != null)
                        txt = args.NewValue.ToString();
                    box.Text = txt;
                    SetBound(box, true);
                }));

    }

 

Ensuite, dans le xaml il suffira de faire:

<tk:AutoCompleteBox  ItemsSource="{Binding ItemsSource}"
                                    c:AutoCompleteHelper.SelectedItem="{Binding TheSelectedItem, Mode=TwoWay}"
                                    c:AutoCompleteHelper.Bound="true"  />

Enjoy !


… et on est super content, la preuve en video, apres 1H30 de dev (dont 1H15 de recherche d’image / musique ^^):

Si vous avez des idées débiles (ou pas) autour de Surface, mais que vous n’avez pas la table / le SDK pour les réaliser, vous pouvez me contacter pour que l’on travaille dessus et je me ferai un plaisir de refaire des petites videos dans le genre…

 

/love Surface

Mots clés Technorati : ,,,

Silverlight 2 introduit le contrôle “PasswordBox” qui permet d’entrer un mot de passe sans qu’il n’apparaisse en clair à l’écran. Le problème de ce contrôle est qu’il ne supporte pas le DataBinding sur sa propriété “Password”. En effet, si on lui affecte un Binding, une exception est levée (avec par ailleurs un message d’erreur pour le moins obscure).

Afin d’ajouter des possibilités de Binding à ce contrôle, on aurait pu en hériter et modifier son comportement. Mais cette approche est très intrusive et oblige l’utilisateur à utiliser ce nouveau contrôle. Une approche plus élégante est d’utiliser une AttachedProperty comme Pierre Lagarde nous l’a montré mercredi dernier lors de la session “Développement de contrôles avec Silverlight 2” qu’il co-animait avec Thierry Bouquain et moi, avec son exemple d’image downloader. En effet, une AttachedProperty étant une DependencyProperty spéciale, elle supporte le DataBinding, et nous pouvons donc écrire une propriété “proxy” permettant de faire le lien vers la propriété Password d’une PropertyBox.

Voici le code utilisé (fournit dans SLExtension) :

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace SLExtensions
{
    /// <summary>
    /// Provide attached property to enable DataBinding on PasswordBox
    /// </summary>
    public class PasswordHelper : DependencyObject
    {

        /// <summary>
        /// Indicates if a PasswordBox is DataBound
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public static bool GetIsBound(PasswordBox obj)
        {
            return (bool)obj.GetValue(IsBoundProperty);
        }

        private static void SetIsBound(PasswordBox obj, bool value)
        {
            obj.SetValue(IsBoundProperty, value);
        }

        public static readonly DependencyProperty IsBoundProperty =
            DependencyProperty.RegisterAttached("IsBound", typeof(bool), 
            typeof(PasswordHelper), 
            new PropertyMetadata(false));


        /// <summary>
        /// Text of the PasswordBox (bindable vision of Password property)
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public static string GetText(PasswordBox obj)
        {
            return (string)obj.GetValue(TextProperty);
        }

        public static void SetText(PasswordBox obj, string value)
        {
            obj.SetValue(TextProperty, value);
        }

        public static readonly DependencyProperty TextProperty =
            DependencyProperty.RegisterAttached("Text", typeof(string),
            typeof(PasswordHelper), 
            new PropertyMetadata(string.Empty,
                (sender, args) =>
                {
                    var pwdBox = sender as PasswordBox;
                    if (pwdBox == null)
                        return;
                    var nval = (args.NewValue as string) ?? string.Empty;
                    if(pwdBox.Password != nval)
                        pwdBox.Password = nval;
                    if (!GetIsBound(pwdBox))
                    {
                        pwdBox.PasswordChanged += delegate
                        {
                            SetText(pwdBox, pwdBox.Password);
                        };
                        SetIsBound(pwdBox, true);
                    }
                }));
    }
}

Et à l’utilisation, il suffit maintenant de faire :

<PasswordBox sle:PasswordHelper.Text="{Binding Text,Mode=TwoWay}" />

Enjoy !

J'étais il y a quelques jours en mission de conseil sur WCF, et je suis tombé sur une erreur assez typique dans des solutions basées sur une plateforme de Services exposée sur Internet. Il s'agit du degré de confiance que l'on a tendance à avoir envers le client. Voici par exemple un morceau de code assez typique :

Contrats :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.Runtime.Serialization;

namespace ServiceContracts
{
    [DataContract]
    public class Consumer
    {
        [DataMember]
        public Guid ID { get; set; }
        [DataMember]
        public string Name { get; set; }
        [DataMember]
        public string Email { get; set; }
    }


    [ServiceContract]
    public interface IMyConsumersService
    {
        [OperationContract]
        IEnumerable<Consumer> GetConsumersPage( int pageIndex, int pageSize, out int pageCount);
    }
}

Implémentation :

public class MyConsumersService : IMyConsumersService
    {
        #region IMyConsumersService Members

        public IEnumerable<Consumer> GetConsumersPage(int pageIndex, int pageSize, out int pageCount)
        {
            // Security Checks
            var userID = SecurityHelper.GetCurrentUserID();
            SecurityHelper.AssertPermission(userID, Permission.BrowseConsumers);

            IConsumerProvider dataProvider = ServiceProvider.GetService<IConsumerProvider>();
            return dataProvider.GetConsumersOfUser(userID, pageIndex, pageSize, out pageCount);
        }

        #endregion
    }

Dans cet exemple, on peut déjà voir des pratiques tout à fait bonnes : Injection de dépendance pour ne pas référencer la couche DAL directement (idéal pour assurer la testabilité du service en "Mockant" la DAL), ainsi qu'une gestion centralisée des permissions des utilisateurs, afin de ne pas polluer outre mesure le code des services, qui doit se concentrer sur le métier.

 

Il y a cependant quelque chose qui ne va pas quand on sait que ce service est sensé être accessible depuis Internet, et voici donc la question que j'ai posée à mon client : "Comment pouvez vous être sûr que le client ne va pas vous demander 1500000 lignes et ainsi consommer beaucoup plus de ressources que ce que vous ne voudriez ?".

La réponse fut : "L'application cliente ne propose que 3 options : 5, 20, et 100 lignes par page".

 

C'est exactement le genre de justification complètement invalide dans un scénario tel que celui là : De même que l'on ne peut faire confiance en l'utilisateur qui appelle le service (ainsi on effectue des assertions pour vérifier qu'il possède les droits nécessaire), nous ne pouvons pas faire confiance en l'application cliente, car rien n'empêche aux utilisateurs du service de développer leurs propres applications consommant ce service, adaptées à leurs propres besoins métiers. Ainsi, nous devons donc faire une validation des paramètres d'entrée afin d'imposer des limites.

La manière la plus simple de prime abord (et dans ce cas précis, c'est certainement la meilleure solution, vu le peu de paramètres à valider), est de faire une validation manuelle dans le code du service lui-même :

public IEnumerable<Consumer> GetConsumersPage(int pageIndex, int pageSize, out int pageCount)
        {
            // Security Checks
            var userID = SecurityHelper.GetCurrentUserID();
            SecurityHelper.AssertPermission(userID, Permission.BrowseConsumers);

            // Technical validation :
            ValidationResume resume = new ValidationResume();
            if (pageIndex < 0)
                resume.Entries.Add(new ValidationEntry { Name = "pageIndex", 
ValidationMessage = "pageIndex should be >= 0" }); if (pageSize > 200) resume.Entries.Add(new ValidationEntry { Name = "pageSize",
ValidationMessage = "pageSize cannot be > 200" }); if (resume.Entries.Count > 0) throw new FaultException<ValidationResume>(resume); IConsumerProvider dataProvider = ServiceProvider.GetService<IConsumerProvider>(); return dataProvider.GetConsumersOfUser(userID, pageIndex, pageSize, out pageCount); }

Le problème de cette technique c'est qu'elle est très intrusive vis à vis du code métier. Sur un projet d'une certaine taille, cela peut devenir compliqué à maintenir, et représenter un volume de code très conséquent qu'il peut être très tentant de factoriser.

 

WCF nous permet de faire ce genre de choses, en créant un attribut implémentant IOperationBehavior qui nous permettra d'injecter notre code dans la stack WCF côté serveur ou côté client, et nous permettant de faire notre validation avant même l'appel de la méthode. Pour ceci, nous devrons aussi implémenter l'interface IParameterInspector fournie par WCF. Notre implémentation regardera des Attributs que nous placerons sur les paramètres de nos OperationContracts afin de les valider à chaque appel.

Interface IValidator et attributs applicables sur des paramètres de méthode :

public interface IValueValidator
    {
        bool Validate(object value, out string validationMessage);
    }
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter)]
    public class MaxValueAttribute : Attribute, IValueValidator
    {
        private double _compareValue;

        public MaxValueAttribute(double maxValue)
        {
            _compareValue = maxValue;
        }

        #region IValueValidator Members

        public bool Validate(object value, out string validationMessage)
        {
            var toCompare = Convert.ToDouble(value);
            if (toCompare > _compareValue)
            {
                validationMessage = "Maximum value is " + _compareValue.ToString();
                return false;
            }
            validationMessage = null;
            return true;
        }

        #endregion
    }

Classe implémentant IParameterInspector et permettant l'exécution des différents validateurs :

public class OperationInputValidator : IParameterInspector
    {
        List<ValidatorEntry> _validators;
        private MethodInfo _operationContractInfo;
        public OperationInputValidator(MethodInfo operationContractInfo)
        {
            _operationContractInfo = operationContractInfo;
            _validators = ValidatorEntry.GetValidators(operationContractInfo).ToList();
        }
        private class ValidatorEntry
        {
            public string ParameterName { get; set; }
            public int ParameterIndex { get; set; }
            public IValueValidator Validator { get; set; }

            public static IEnumerable<ValidatorEntry> GetValidators(MethodInfo operationContractInfo)
            {
                foreach (var param in operationContractInfo.GetParameters())
                {
                    foreach (var validator in param.GetCustomAttributes(false).OfType<IValueValidator>())
                    {
                        yield return new ValidatorEntry
                        {
                            ParameterIndex = param.Position,
                            ParameterName = param.Name,
                            Validator = validator
                        };
                    }
                }
            }
        }


        #region IParameterInspector Members

        public void AfterCall(string operationName, object[] outputs, object returnValue, object correlationState)
        {
        }

        public object BeforeCall(string operationName, object[] inputs)
        {
            ValidationResume resume = new ValidationResume();
            foreach (var validator in _validators)
            {
                string validationMessage;
                if (!validator.Validator.Validate(inputs[validator.ParameterIndex], out validationMessage))
                {
                    resume.Entries.Add(new ValidationEntry
                    {
                        Name = validator.ParameterName,
                        ValidationMessage = validationMessage
                    });
                }
            }
            if (resume.Entries.Count > 0)
                throw new FaultException<ValidationResume>(resume,resume.ToString());
            return null;
        }

        #endregion
    }

Attribut branchant le ParameterInspector sur un OperationContract WCF :

 

[AttributeUsage(AttributeTargets.Method, Inherited = false)]
    public sealed class RequiresInputValidationAttribute : Attribute, IOperationBehavior
    {

        #region IOperationBehavior Members

        public void AddBindingParameters(OperationDescription operationDescription, 
System.ServiceModel.Channels.BindingParameterCollection bindingParameters) { } public void ApplyClientBehavior(OperationDescription operationDescription,
System.ServiceModel.Dispatcher.ClientOperation clientOperation) { } public void ApplyDispatchBehavior(OperationDescription operationDescription,
System.ServiceModel.Dispatcher.DispatchOperation dispatchOperation) { dispatchOperation.ParameterInspectors.Add(new OperationInputValidator(operationDescription.SyncMethod)); } public void Validate(OperationDescription operationDescription) { } #endregion }

Et maintenant, nous pouvons modifier notre contrat de service afin de valider automatiquement les paramètres grâce à des attributs :

[ServiceContract]
    public interface IMyConsumersService
    {
        [OperationContract]
        [RequiresInputValidation]
        [FaultContract(typeof(ValidationResume))]
        IEnumerable<Consumer> GetConsumersPage
            ([MinValue(0)] int pageIndex, [MinValue(1), MaxValue(100)] int pageSize, out int pageCount);
    }

 

La mise en place de cette technique est assez complexe, mais se fait très vite oubliée à l'utilisation. En effet, tout se fait de manière déclarative et très concise : si l'on ne considère que la modification apportée au contrat de service, cela a effectivement un aspect très simple, et à la portée de n'importe quel développeur. Sur un gros projet invoquant des développeurs d'un niveau inégal, ceci reste donc tout à fait exploitable.

Un autre aspect de ce modèle est son extensibilité. En effet, pour rajouter une règle de validation, il suffit d'écrire une classe dérivant de Attribute et implémentant IValueValidator. Voici par exemple une autre règle interdisant une valeur nulle :

[AttributeUsage(AttributeTargets.Parameter)]
    public class RequiredAttribute : Attribute, IValueValidator
    {
        #region IValueValidator Members

        public bool Validate(object value, out string validationMessage)
        {
            if (value == null)
            {
                validationMessage = "Value must not be null";
                return false;
            }
            var asString = value as string;
            if (asString != null && asString.Length == 0)
            {
                validationMessage = "Value must not be empty";
                return false;
            }

            validationMessage = null;
            return true;
        }

        #endregion
    }

Nous pouvons alors placer un attribut [Required] sur un paramètre d'opération pour forcer une valeur non nulle / non vide.

Bien sûr, rien ne nous empêche de créer des validateurs pour des types complexes, et s'en servir pour valider des entités métier...

 

Voilà donc une solution élégante pour la validation des paramètres d'entrée d'un service WCF.

Les sources de l'exemple sont disponibles ici.

Mots clés Technorati : ,,,

Les Techdays 2009 vont avoir lieu du mardi 10 février au jeudi 12 février prochain à Paris au palais des congrès de Paris. Ce sera l'occasion pour moi de vous faire partager mon expérience sur Silverlight au sein de 2 sessions co-animées avec Pierre Lagarde (MS France) et Thierry Bouquain (Ucaya), tous les deux contributeurs du projet Silverlight Extensions (Développement avancé avec Silverlight 2, et Développement de contrôles en Silverlight 2) , ainsi que de m'essayer à quelques exercices de style avec Mitsuru Furuta dans une session pompeusement appelée Programmation dynamique.

Bref, quelques journées de préparation en perspective (et quelques mojitos aussi vu les gus avec qui je co-anime tout ca !).

Mots clés Technorati : ,,

Voilà un année qui commence bien, je viens de recevoir ma nomination de MVP "Client App" dev ! Merci à Microsoft et plus particulièrement à Mitsu et aux developper evangelysts de MS France DPE pour m'avoir appuyé :-).

A bientôt, Simon


Nativement, Silverlight ne prend en compte qu'un nombre limité d'évènements souris :

  • MouseMove / Enter/ Leave
  • MouseLeftButtonDown / Up

Or, dans beaucoup d'applications métiers, certains écrans font apparaître des scrollbars (vue complexe, ListBox, ComboBox, DataGrid...). A la vue de ces scrollbars, l'utilisateur lambda a très souvent le réflexe d'utiliser sa souris pour faire défiler les éléments de haut en bas. Hors Silverlight ne supporte pas ces évènements et les Controles ScrollViewer, DataGrid etc. ne supportent donc pas nativement la molette.

Si vous avez déjà joué avec le Scrolling "programmatique", vous avez peut-être remarqué que beaucoup de contrôles faisant apparaître une ScrollBar n'utilisent pas de ScrollViewer, mais ont leur propre logique de Scrolling (bien souvent non exposée publiquement - c'est le cas notamment du DataGrid) pour diverses raisons (Virtualisation des éléments...) et il ne semble donc pas possible d'avoir une solution "miracle" qui marche dans tous les cas.

L'astuce présentée ici s'appuie sur les fonctionnalités d'automatisation d'interface (voir namespace System.Windows.UIAutomation dans la doc MSDN). En effet, l'API d'automatisation de Microsoft propose un moyen d'exposer des fonctionnalités de Scrolling aux lecteurs / contrôleurs d'écrans et framework de test. Chaque contrôle ayant une logique de scrolling peut donc l'exposer via son AutomationPeer : c'est ce que font tous les contrôles de ce type de Silverlight core, Silverlight Toolkit, ainsi que ceux des auteurs de CustomControls consciencieux. Voici par exemple un extrait de code d'une SuggestBox supportant l'automation :

public class SuggestBox : Control
    {
        private class SuggestBoxAutomationPeer : FrameworkElementAutomationPeer
        {
            SuggestBox _automated;
            public SuggestBoxAutomationPeer(SuggestBox automated)
                : base(automated)
            {
                _automated = automated;
            }

            public override object GetPattern(PatternInterface patternInterface)
            {
                if (patternInterface == PatternInterface.Scroll)
                {
                    if (_automated.IsPopupOpen && _automated._childListBox != null)
                        return FrameworkElementAutomationPeer.CreatePeerForElement(_automated._childListBox).GetPattern(patternInterface);
                }
                return base.GetPattern(patternInterface);
            }
        }
        protected override System.Windows.Automation.Peers.AutomationPeer OnCreateAutomationPeer()
        {
            return new SuggestBoxAutomationPeer(this);
        }
    }
 

Pour exploiter celà, la première chose à faire, est d'implémenter notre scrolling programmatique a un endroit centralisé, qui sera appelable par JavaScript. Dans cet exemple, nous prendrons l'objet Application lui-même. Nous aurons besoin simplement d'une méthode marquée [ScriptableMember] et d'enregistrer l'objet application comme un ScriptableObject:

IEnumerable<UIElement> SelfAndAncestors(UIElement source)
        {
            var current = source;
            do
            {
                yield return current;
                current = VisualTreeHelper.GetParent(current) as UIElement;
            }
            while (current != null);
        }

        [ScriptableMember]
        public void OnMouseWheel(double delta)
        {          

            IEnumerable<UIElement> candidates;
            
            candidates = VisualTreeHelper.FindElementsInHostCoordinates(_currentMousePosition, RootVisual);
            // Si un element a le focus, on gere les candidats 
            // en placant en premier l'élément ayant le focus ainsi que ses parents
            var focused = FocusManager.GetFocusedElement() as UIElement;
            if (focused != null)
                candidates = SelfAndAncestors(focused)
                    .Concat(candidates);
            var scrollProvider = (from elem in candidates
                                  let automationPeer = FrameworkElementAutomationPeer.CreatePeerForElement(elem)
                                  where automationPeer != null
                                  let scroller = automationPeer.GetPattern(PatternInterface.Scroll) as IScrollProvider
                                  where scroller != null
                                  select scroller).FirstOrDefault();

            if (scrollProvider != null && scrollProvider.VerticallyScrollable)
            {

                if (delta > 0)
                    scrollProvider.Scroll(System.Windows.Automation.ScrollAmount.NoAmount,
                        System.Windows.Automation.ScrollAmount.SmallDecrement);
                else
                    scrollProvider.Scroll(System.Windows.Automation.ScrollAmount.NoAmount,
                        System.Windows.Automation.ScrollAmount.SmallIncrement);
            }
            
        }
Le paramêtre delta aura pour valeur le delta de l'évènement JavaScript MouseWheel intercepté. 

La partie la plus importante de la méthode est la requête Linq qui parcoure la liste des candidats (liste d'UIElement produient par VisualTreeHelper.FindElementsInHostCoordinates et par le FocusManager. Ce que l'on recherche, c'est le premier UIElement ayant un automationPeer supportant le scrolling. Une foit qu'on l'a trouvé, on manipule simplement le scrollProvider pour lui indiquer ce que l'on souhaite faire. Cette méthode fonctionne aussi bien avec les ScrollViewer, DataGrid, ListBox, SuggestBox etc, bien que leur logique de scrolling soit différente (et souvent inaccessible publiquement).

Il ne reste plus qu'à brancher ce code sur un évènement JavaScript.

D'abord, nous enregistrons l'objet application dans le dictionnaire d'objet Scriptable dans la méthode Application_Startup():

HtmlPage.RegisterScriptableObject("SilverlightApp", this);

Ensuite dans la page HTML hôte de notre application, nous effectuons les tâches suivantes :

  • Modification du tag object:
<div id="silverlightControlHost">
        <object data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="100%" height="100%">
            ...
<param name="onload" value="onSilverlightLoad" /> <a href="http://go.microsoft.com/fwlink/?LinkID=124807" style="text-decoration: none;"> <img src="http://go.microsoft.com/fwlink/?LinkId=108181" alt="Get Microsoft Silverlight" style="border-style: none"/> </a> </object> <iframe style='visibility:hidden;height:0;width:0;border:0px'></iframe> </div>

Ce qu'il faut rajouter, c'est le tag <param name="onload" />.

  • Implémenter la fonction onSilverlightLoad:
var slApp;
        /** This is high-level function.
        * It must react to delta being more/less than zero.
        */
        function handle(delta) {
            slApp.OnMouseWheel(delta);
        }

        /** Event handler for mouse wheel event.
        */
        function wheel(event) {
            var delta = 0;
            if (!event) /* For IE. */
                event = window.event;
            if (event.wheelDelta) { /* IE/Opera. */
                delta = event.wheelDelta / 120;
                /** In Opera 9, delta differs in sign as compared to IE.
                */
                if (window.opera)
                    delta = -delta;
            } else if (event.detail) { /** Mozilla case. */
                /** In Mozilla, sign of delta is different than in IE.
                * Also, delta is multiple of 3.
                */
                delta = -event.detail / 3;
            }
            /** If delta is nonzero, handle it.
            * Basically, delta is now positive if wheel was scrolled up,
            * and negative, if wheel was scrolled down.
            */
            if (delta)
                handle(delta);
            /** Prevent default actions caused by mouse wheel.
            * That might be ugly, but we handle scrolls somehow
            * anyway, so don't bother here..
            */
            if (event.preventDefault)
                event.preventDefault();
            event.returnValue = false;
        }



        function onSilverlightLoad(sender, args) {

            var slCtl = sender.getHost();
            slApp = slCtl.Content.SilverlightApp;
            /** Initialization code. 
            * If you use your own event management code, change it as required.
            */
            if (window.addEventListener)
            /** DOMMouseScroll is for mozilla. */
                window.addEventListener('DOMMouseScroll', wheel, false);
            /** IE/Opera. */
            window.onmousewheel = document.onmousewheel = wheel;

        }
Et voilà, that's done :).