[WinRT] Faire un Sleep en C++

Lorsque l’on programme des opérations asynchrones on veux souvent tester la réaction de l’interface graphique. Par exemple est-ce qu’un indicateur visuel de chargement s’affiche bien si l’opération est longue etc… En C# on a souvent eu la surprise de ne pas retrouver notre bon vieux Thread.Sleep(milliseconds) mais on a fini par s’habituer à son remplaçant await Task.Delay(milliseconds). Oui mais quand on développe un composant en C++ on fait comment pour faire un Sleep ?
Voici la réponse :

Windows::Foundation::IAsyncOperation<int>^ Class1::MyAsyncMethod()
{
	return create_async([this]() {
		// CECI EST LE SLEEP
		WaitForSingleObjectEx(GetCurrentThread(), milliseconds, FALSE);
		return 42;
	});
}

Si vous êtes intéressés par la documentation de la méthode WaitForSingleObjectEx voici où la trouver :
http://msdn.microsoft.com/en-us/library/ms687036(v=VS.85).aspx

Tips – Traitements parallèles avec async/await

J’ai lu récemment quelques articles sur le parallelisme avec async await et j’ai décidé de faire un petit test pour vérifier pour voir ce qui serai le plus rapide à l’execution.

Le test se déroule de la manière suivante. J’appelle la même méthode asynchrone 5 fois et chacun de ces appels va mettre un temps prédeterminé à se finir. Le choix des durées n’est pas totalement fortuit. Je souhaitait que le premier appel soit le plus long pour montrer un problème décrit plus tard.

Voici la méthode en question :

private int[] _waitDurations = new[] { 5000, 2500, 1250, 625, 312 };

private async Task<string> DoWorkAsync(int i)
{
    await Task.Delay(_waitDurations[i]);
    return _waitDurations[i].ToString();
}

Je vais donc faire une boucle allant de 0 à 5 (non compris), appeler la méthode et récupérer son résultat pour le mettre dans une liste.La méthode 1 a un fonctionnement assez classique. On lance toutes les tâches en parallèle et on await le résultat ensuite dans un boucle.

private async Task Test1()
{
	var sw = new Stopwatch();
	sw.Start();

	var tasks = new Task<string>[5];
	for (int i = 0; i < 5; i++)
	{
		tasks[i] = DoWorkAsync(i);
	}

	foreach (Task<string> task in tasks)
	{
		string item = await task;
		myList.Items.Add(item);
	}

	sw.Stop();
	myTimeList.Items.Add(String.Format("{0} ms", sw.ElapsedMilliseconds.ToString()));
}

Vu qu’ici la tâche 0 est celle qui met le plus longtemps à s’executer on aura l’impression de recevoir tout les résultat en même temps alors qu’en fait les tâches de 1 à 4 se seront terminées il y a longtemps. C’est dû ici au fait qu’on await la tâche zéro avant de passer aux autres. Sur ma machine le temps d’execution total était 5050 ms environ.

 

La méthode 2 est une légère amélioration de la 1. Ici on va attendre toutes les tâches en même temps au lieu de le faire une par une.

private async Task Test2()
{
	var sw = new Stopwatch();
	sw.Start();

	var tasks = new Task<string>[5];
	for (int i = 0; i < 5; i++)
	{
		tasks[i] = DoWorkAsync(i);
	}

	await Task.WhenAll(tasks);

	foreach (Task<string> task in tasks)
	{
		string item = task.Result;
		myList.Items.Add(item);
	}

	sw.Stop();
	myTimeList.Items.Add(String.Format("{0} ms", sw.ElapsedMilliseconds.ToString()));
}

La particularité est qu’on ne repasse pas sur le thread initial (ici le Dispatcher) entre chaque récupération de résultat mais on ne le fait qu’une fois lorsque tout les résultats seront arrivés. Au final on fait beaucoup moins de changements de contextes avec une impression identique pour l’utilisateur dû au fait que le premier appel est le plus long dans le premier cas. Sur ma machine le gain est de l’ordre de 15 ms environ par rapport à la méthode précedente.

La méthode 3 est surtout valable lorsque l’ordre d’arrivé des résultats importe peu. Ici on va envoyer le résultat dès qu’on le reçoit.

private async Task Test3()
{
	var sw = new Stopwatch();
	sw.Start();

	var tasks = new Task<string>[5];
	for (int i = 0; i < 5; i++)
	{
		var task = DoWorkAsync(i);
		task.ContinueWith(t => myList.Items.Add(t.Result), TaskScheduler.FromCurrentSynchronizationContext());
		tasks[i] = task;
	}

	await Task.WhenAll(tasks);

	sw.Stop();
	myTimeList.Items.Add(String.Format("{0} ms", sw.ElapsedMilliseconds.ToString()));
}

L’astuce consiste à repasser en mode Task classique et de faire un ContinueWith sur le thread initial de l’ajout dans la liste. Avec cette méthode je gagne 50ms. Bien entendu ici le temps d’ajout dans la liste n’est pas compté par la Stopwatch car il s’execute dans le ContinueWith donc le gain effectif est probablement un peu plus faible mais tout de même. En ce qui concerne l’utilisateur par contre lui ça l’arrange car les éléments arrivent à dès qu’ils sont disponibles.

 

Il y a encore surement bien des cas où d’autres façons de faires vos appels seront plus efficaces, le tout étant de bien analyser les besoins pour choisir la bonne méthode.

A vos claviers maintenant !

WinRT – XAML – Suspending et Timers

Dans WinRT on sait que le système peut passer une application en mode Suspending. Ce que l’on ne sait pas en revanche c’est au bout de combien de temps le système va décider à le faire. Lorsque l’utilisateur fait un Alt+Tab pour changer d’application active le système peut mettre une dizaine de secondes à passer une application metro en Suspending. Dans la plupart des cas ce n’est pas quelquechose dont on a à se préoccuper cependant dans le cas d’un jeu par exemple, ou un compteur de temps existe, il est très important de savoir exactement lorsque l’utilisateur fait un Alt+Tab pour mettre en pause ce compteur. Aussi je vous propose ici une solution simple que j’ai mis en place dans mon application.

Dans le fichier App.xaml.cs de l’application, à la fin de l’évènement OnLauching (le temps que la Window soit créée), abonnez-vous à l’évènement VisibilityChanged de la fenêtre courante :

Window.Current.VisibilityChanged += WindowVisibilityChanged;

Dans le gestionnaire de cet évènement ajoutez un code similaire à celui-ci :

private async void WindowVisibilityChanged(object sender, VisibilityChangedEventArgs e)
{
    if (e.Visible == false)
    {
        await SaveCurrentGameIfNeededAsync();
    }
    else
    {
        await RestoreGameIfNeededAsync();   
    }
}

Les méthodes SaveCurrentGameIfNeededAsync et RestoreGameIfNeededAsync sont aussi utilisées dans les gestionnaires des évènements OnSuspending et OnResuming pour bien faire et déterminent si la sauvegarde ou la restauration de l’état du jeu à déjà été faite ou non.

Et voilà c’est tout, ainsi plus de timers qui tournent dans le vide.

Asynchronisme – WinRT – Passer d’un pattern asynchrone avec callback à async/await

Lorsque l’on développe sous WinRT en C# on utilise assez vite async/await. Mais comment faire dans le cas où l’on développe une application multiplateforme dont une des plateformes ne dispose pas de ce pattern par exemple Windows Phone 7 ?
Dans le cas de mon application, j’utilise un pattern assez simple où chaque repository dispose d’une méthode Load qui est asynchrone. La fin du chargement étant notifié grâce à une callback passée en paramètre qui contient soit le résultat de l’action, soit l’exception lancée en cas d’erreur. Si vous avez lu mes précédents articles vous saurez que mon utilisation de ce pattern n’est pas récente.
Voici ce que celà donne en vrai :

public void Load(Action<GameStatistics, Exception> callback)
{
...
}

Rendre celà compatible avec async/await n’est pas très compliqué et toute la magie réside dans la classe TaskCompletionSource.

Voici comment envelopper l’appel précédent :

public async Task<GameStatistics> LoadAsync()
{
    TaskCompletionSource<GameStatistics> source = new TaskCompletionSource<GameStatistics>();

    this.Load((result, error) =>
        {
            if (error != null)
                source.SetException(error);
            else
                source.SetResult(result);

        });

    return await source.Task;
}

Grâce à cette petite astuce on peux creer toute une série de wrapper autour des méthodes que l’on souhaite utiliser avec async/await et déclarée avec d’autres pattern asynchrones.

 

XAML – Comment connaître la fin d’une transition entre deux VisualStates depuis un view model

Je développe pour WinRT depuis un petit moment en XAML et C#. J’ai récemment fait face à un problème où je devais attendre qu’une animation soit terminée avant de commencer un traitement dans un view model. Le problème était que cette animation est contenue dans une transition entre VisualState dans un de mes Custom Controls.

Mon Custom Control représente un pion de reversi ayant donc deux faces, noire et blanche. Quand sa propriété IsBlack change celà lance une transition entre deux VisualStates. Comme mon application utilise le pattern MVVM j’ai besoin de trouver un moyen de propager l’information qu’une animation a commencée et s’est arretée depuis le custom control au view model.

J’ai tout d’abord ajouté une propriété une propriété a mon contrôle Token :

public bool IsColourChanging
{
    get { return (bool) GetValue(IsColourChangingProperty); }
    set { SetValue(IsColourChangingProperty, value); }
}

public static readonly DependencyProperty IsColourChangingProperty =
    DependencyProperty.Register("IsColourChanging", typeof (bool), typeof(Token), new PropertyMetadata(false));

Ensuite dans la méthode OnApplyTemplate j’appelle la méthode qui est utilisée pour s’abonner aux évènement CurrentStateChanging et CurrentStateChanged du VisualStateGroup contenant les états visuels représentant chaque couleur :

private void HandleColourChanging()
{
    var child = VisualTreeHelper.GetChild(this, 0) as FrameworkElement;

    Debug.Assert(child != null, "Token's template must be defined !");

    var groups = VisualStateManager.GetVisualStateGroups(child);

    Debug.Assert(groups != null, "Token's VisualStateGroups should be defined!");

    // The Cast operator is not necessary in Windows 8 Metro Style Apps
    var colourGroup = groups.Cast<VisualStateGroup>().FirstOrDefault(g => g.Name == ColourStatesGroupName);

    Debug.Assert(colourGroup != null, "Token's VisualState Transition should be defined!");

    colourGroup.CurrentStateChanging += ColourGroupCurrentStateChanging;
    colourGroup.CurrentStateChanged += ColourGroupCurrentStateChanged;
}

Dans les méthodes ColourGroupCurrentStateChang[ing/ed] je n’ai plus qu’à définir la valeur de la propriété IsColourChanging :

private void ColourGroupCurrentStateChanging(object sender, VisualStateChangedEventArgs e)
{
    IsColourChanging = true;
}

private void ColourGroupCurrentStateChanged(object sender, VisualStateChangedEventArgs e)
{
    IsColourChanging = false;
}

 

A cette étape, à chaque fois qu’une transition entre deux couleurs est effectuée la propriété IsColourChanging est mise à True.

Donc maintenant comment propager cette information à mon view model ? En utilisant le binding biensûr et voici comment celà peut-être effectué si on part du principe que l’ItemSource de l’ItemsControl est définie à une collection de TokenViewModels :

<ItemsControl>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <local:Token IsBlack="{Binding IsBlack}"
                            IsColourChanging="{Binding IsAnimating, Mode=TwoWay}"
                            Width="100"
                            Height="100" />
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

Vous pouvez voir ici que la propriété IsColourChanging du contrôle est bindée à la propriété IsAnimating du view model en mode bi-directionnel. Maintenant c’est au view model de réagir aux changements de la propriété IsAnimating.

Je considère ce trick comme étant important car les applications WinRT se doivent d’être fluides et rapide et donc utilisent beaucoup d’animations. Mais comme fluide et rapide n’est pas synonyme d’inmaintenable c’est un moyen comme un autre de séparer la logique entre la vue et le viewmodel.

Vous pouvez voir un exemple de code en WPF sur mon skydrive.

En espérant que ça aide.

Executer une action suite à des appels WCF parallèles

Récemment on m’as posé la question suivante :

“Comment je peux faire pour exécuter une action après que tout les appels WCF asynchrones se soient exécutés en Silverlight ?”

Ca tombe bien, j’avais une solution sous le coude que j’avais mis en place lors d’un précédent projet. Dans ce projet je devais charger une série de tables de références telles que Pays, Villes etc… Tout ces chargements s’effectuaient de manières asynchrones et étaient lancés en parallèles. Durant le temps de chargement, j’affichais un message indiquant que l’application était en cours de chargement et lorsque toutes les tables de références étaient chargées je faisait disparaitre ce message et rendais active l’application.

L’idée est simple, je vais créer une classe qui va se charger de lancer tout les appels WCF en parallèle. Cette classe prend en paramètre une callback qui sera invoquée lorsque tout les appels auront été terminés.

Pour savoir quand tout les retours ont eu lieu on va faire ça à l’ancienne. A chaque retour d’appels WCF je vais incrémenter un compteur. Lorsque ce compteur aura pour valeur le nombre total d’appels WCF que j’ai lancé j’appellerai la callback passée en paramètre. Simple non ?

Maintenant que le concept est posé passons à l’implémentation

Mon implémentation originale était en Silverlight et pas très générique. J’ai donc retravaillé un peu la chose et choisi de faire un exemple multi-plateforme Windows 8 Metro/Silverlight/WPF. Chaque plateforme ayant ses spécificités je vais d’abord les présenter grâce à un petit tableau.

 

Windows 8 Metro Silverlight WPF
Basé sur des Task Oui Non Oui
Basé sur des évènements Oui Oui Oui

 
Vous remarquez que j’ai classé chaque plateforme dans deux catégories, Task ou Event. Le truc à savoir c’est qu’en fonction de la plateforme que vous ciblez, Visual Studio va générer les proxy WCF de manières différentes. A noter que pour WPF on a le choix de la méthode :

Afin de pouvoir réutiliser ultérieurement un peu de code j’ai créé un Portable Library contenant une classe de base pour les opérations d’appels asynchrones (le code est très commenté et se passe donc de plus de commentaires) :

using System;
using System.Collections.Generic;

namespace WcfUtils
{
    /// <summary>
    /// Cette classe de base permet de définir les méthodes et membres de base pour gérer
    /// des appels WCF asyncrhrones devant se synchroniser à la fin.
    /// </summary>
    public abstract class ParallelCallBase<TResult> where TResult : ParallelCallResultBase
    {
        /// <summary>
        /// Nombre de retours d'appels asynchrones.
        /// </summary>
        private int _count;

        /// <summary>
        /// Nombre total d'appels asynchrones.
        /// </summary>
        private readonly int _totalCount;

        /// <summary>
        /// Méthode à appeller lorsque tout les retours auront été reçus.
        /// </summary>
        private Action<TResult> _callback;

        /// <summary>
        /// Contexte de synchronisation.
        /// </summary>
        private System.Threading.SynchronizationContext _context;

        /// <summary>
        /// Objet servant à vérouiller les accès concurrentiels.
        /// </summary>
        protected readonly object SyncRoot = new object();

        /// <summary>
        /// Résultat des appels parallèles.
        /// </summary>
        protected TResult Result { get; set; }

        /// <summary>
        /// Constructeur de l'objet
        /// </summary>
        /// <param name="totalCount">Nombre total de retours à attendre.</param>
        protected ParallelCallBase(int totalCount)
        {
            _totalCount = totalCount;
        }

        /// <summary>
        /// Méthode à invoquer à chaque retour d'appel WCF.
        /// </summary>
        /// <param name="error">Erreur éventuelle survenue.</param>
        protected virtual void OnCallCompleted(Exception error)
        {
            // On lock l'objet de façon à pouvour incrémenter _count et modifier la collection Result.Errors
            // au cas où les deux webservice se terminerai en même temps.
            lock (SyncRoot)
            {
                _count++;

                // Si on a une erreur alors on l'ajoute à la liste des erreurs.
                if (error != null)
                    Result.Errors.Add(error);

                // Si tout les appels ont été effectués on appelle la callback.
                if (_count == _totalCount)
                    MarshallToContext(() => _callback(Result));
            }
        }

        /// <summary>
        /// Execute la méthode <paramref name="a"/> sur le contexte de synchronisation enregistré dans la méthode Run.
        /// </summary>
        /// <param name="a">Action à exécuter.</param>
        protected virtual void MarshallToContext(Action a)
        {
            if (_context != null)
                _context.Post(_ => a(), null);
            else
                a();
        }

        /// <summary>
        /// Execute les appels asynchrones.
        /// </summary>
        /// <param name="context">Contexte de synchronisation.</param>
        /// <param name="callback">Méthode à appeller lorsque tout les retours auront eu lieu.</param>
        public virtual void Run(System.Threading.SynchronizationContext context, Action<TResult> callback)
        {
            if (callback == null)
                return;

            _context = context;
            _callback = callback;
        }
    }

    /// <summary>
    /// Classe de base des résultats d'appels de service WCF en parallèle.
    /// </summary>
    public abstract class ParallelCallResultBase
    {
        private readonly List<Exception> _errors = new List<Exception>();

        /// <summary>
        /// Liste des erreurs survenues.
        /// </summary>
        public ICollection<Exception> Errors { get { return _errors; } }
    }
}

On a donc déjà l’infrastructure nécessaire pour ces appels parallèle. Reste maintenant à rajouter notre propre logique. Dans mon exemple, j’appelle deux services qui me renvoie une liste de pays et une liste de ville. Voyons comment on se sert de la classe précédente pour ce cas là :

 

using System;
using System.Collections.Generic;

#if NETFX_CORE
using MetroApplication.CityServiceReference;
using MetroApplication.CountryServiceReference;
using System.Threading.Tasks;
using System.Collections.ObjectModel;
using Windows.UI.Xaml;
#elif SILVERLIGHT
using SilverlightApplication.CityServiceReference;
using SilverlightApplication.CountryServiceReference;
#elif WPF_EVENT_BASED
using System.Collections.ObjectModel;
using WpfApplicationEventBasedServiceProxy.CityServiceReference;
using WpfApplicationEventBasedServiceProxy.CountryServiceReference;
#elif WPF_TASK_BASED
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using WpfApplicationTaskBasedServiceProxy.CityServiceReference;
using WpfApplicationTaskBasedServiceProxy.CountryServiceReference;
#endif

namespace WcfUtils
{
    /// <summary>
    /// Classe chargeant une liste de pays et de ville de manière asynchrone et parallèle.
    /// </summary>
    public class ReferenceLoader : ParallelCallBase<ReferenceLoaderResult>
    {
        /// <summary>
        /// Méthode appellée lorsque le retour de l'appel à GetAllCities survient.
        /// </summary>
        public Action<Exception, IEnumerable<string>> OnGetAllCitiesCompletedTrigger { get; set; }

        /// <summary>
        /// Méthode appellée lorsque le retour de l'appel à GetAllCountries survient.
        /// </summary>
        public Action<Exception, IEnumerable<string>> OnGetAllCountriesCompletedTrigger { get; set; }

        /// <summary>
        /// Constructeur du loader.
        /// </summary>
        public ReferenceLoader()
            // Nombre de retours d'appels WCF à attendre
            : base(2)
        {
        }

        #region OnCompleted
        private void OnGetAllCitiesCompleted(Exception error, IEnumerable<string> result)
        {
            if (OnGetAllCitiesCompletedTrigger != null)
                MarshallToContext(() => OnGetAllCitiesCompletedTrigger(error, result));
            if (error == null)
                ((ReferenceLoaderResult)Result).Cities = result;
            OnCallCompleted(error);
        }

        private void OnGetAllCountriesCompleted(Exception error, IEnumerable<string> result)
        {
            if (OnGetAllCountriesCompletedTrigger != null)
                MarshallToContext(() => OnGetAllCountriesCompletedTrigger(error, result));
            if (error == null)
                ((ReferenceLoaderResult)Result).Countries = result;
            OnCallCompleted(error);
        }

#if NETFX_CORE || WPF_TASK_BASED
        private void OnGetAllCitiesCompleted(Task<ObservableCollection<string>> antecedent)
        {
            OnGetAllCitiesCompleted(antecedent.Exception, antecedent.Result);
        }

        private void OnGetAllCountriesCompleted(Task<ObservableCollection<string>> antecedent)
        {
            OnGetAllCountriesCompleted(antecedent.Exception, antecedent.Result);
        }
#elif SILVERLIGHT || WPF_EVENT_BASED
        private void OnGetAllCitiesCompleted(object s, GetAllCitiesCompletedEventArgs e)
        {
            OnGetAllCitiesCompleted(e.Error, e.Result);
        }

        private void OnGetAllCountriesCompleted(object s, GetAllCountriesCompletedEventArgs e)
        {
            OnGetAllCountriesCompleted(e.Error, e.Result);
        }
#endif
        #endregion

        /// <summary>
        /// Execute les appels asynchrones.
        /// </summary>
        /// <param name="context">Contexte de synchronisation.</param>
        /// <param name="callback">Méthode à appeller lorsque tout les retours auront eu lieu.</param>
        public override void Run(System.Threading.SynchronizationContext context, Action<ReferenceLoaderResult> callback)
        {
            base.Run(context, callback);
            Result = new ReferenceLoaderResult();

            // On instancie tout les webservices.
            var cityServiceClient = new CityServiceClient();
            var countryServiceClient = new CountryServiceClient();

#if NETFX_CORE || WPF_TASK_BASED
            var task1 = cityServiceClient.GetAllCitiesAsync();
            var task2 = countryServiceClient.GetAllCountriesAsync();

            // On ajoute des continuation à appeler lorsque nos tasks auront terminées leurs executions.
            task1.ContinueWith(t => OnGetAllCitiesCompleted(t.Exception, t.Result));
            task2.ContinueWith(t => OnGetAllCountriesCompleted(t.Exception, t.Result));
#elif SILVERLIGHT || WPF_EVENT_BASED
            // On s'abonne aux évènement Completed de chacune des méthodes que l'on veux appeller.
            cityServiceClient.GetAllCitiesCompleted += OnGetAllCitiesCompleted;
            countryServiceClient.GetAllCountriesCompleted += OnGetAllCountriesCompleted;
#endif

            // On appelle tout les webservice en parallèle
            cityServiceClient.GetAllCitiesAsync();
            countryServiceClient.GetAllCountriesAsync();
        }
    }

    /// <summary>
    /// Classe représentant englobant les résultats de tout les webservices
    /// et de leurs erreurs possibles.
    /// </summary>
    public class ReferenceLoaderResult : ParallelCallResultBase
    {
        /// <summary>
        /// Liste des pays.
        /// </summary>
        public IEnumerable<string> Countries { get; set; }

        /// <summary>
        /// Liste des villes.
        /// </summary>
        public IEnumerable<string> Cities { get; set; }
    }
}

Vous remarquerez quelques directives de compilations :
- NETFX_CORE, directive standard pour la compilation d’applications Metro
- SILVERLIGHT, directive standard pour la compilation d’applications Silverlight
- WPF_EVENT_BASED, directive personnalisée pour le projet WPF utilisant les proxy wcf basés sur des évènements.
- WPF_TASK_BASED, directive personnalisée pour le projet WPF utilisant les proxy wcf basés sur des Task.

Celà nous permet d’avoir une majorité du code indépendant du type de proxy wcf.

Maintenant au niveau client graphique voici un exemple d’utilisation avec une application Metro :

<Grid Background="{StaticResource ApplicationPageBackgroundBrush}">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>
    <ListBox x:Name="lstCountries" />
    <ListBox x:Name="lstCities" Grid.Column="1" />
    <ProgressRing x:Name="progressRing" Grid.ColumnSpan="2" />
</Grid>

 

/// <summary>
/// Invoked when this page is about to be displayed in a Frame.
/// </summary>
/// <param name="e">Event data that describes how this page was reached.  The Parameter
/// property is typically used to configure the page.</param>
protected override void OnNavigatedTo(NavigationEventArgs e)
{
    progressRing.IsActive = true;

    // On instancie le loader et on appelle la méthode Run en lui passant en paramètre la méthode à
    // appeller lorsque tout les appels de webservices auront été effectués.
    var loader = new ReferenceLoader();

    // Pensez à traiter les éventuelles erreurs !!!
    loader.OnGetAllCitiesCompletedTrigger = (error, result) => lstCities.ItemsSource = result;
    loader.OnGetAllCountriesCompletedTrigger = (error, result) => lstCountries.ItemsSource = result;
    loader.Run(System.Threading.SynchronizationContext.Current, OnLoadCompleted);
}

private void OnLoadCompleted(ReferenceLoaderResult result)
{
    // Pensez à traiter les éventuelles erreurs !!!
    if (result.Errors.Count > 0)
    {
        var sb = new StringBuilder();
        sb.AppendLine("Errors :");
        foreach (var err in result.Errors)
            sb.AppendLine(err.ToString());

        MessageDialog d = new MessageDialog(sb.ToString(), "Errors");
        d.ShowAsync();
    }

    // On peux aussi traiter les retours ici
    //lstCities.ItemsSource = result.Cities;
    //lstCountries.ItemsSource = result.Countries;

    progressRing.IsActive = false;
}

Voici une manière simple, réutilisable et surtout adaptable pour répondre à une problématique qui se rencontre de plus en plus souvent. Car il n’y a pas qu’async et await dans la vie, il y a aussi le parallèlisme :-)

 

Comme d’habitude une solution de sample (Visual Studio 11, WPF 4.5, Silverlight 4, Windows 8 Metro Style) est disponible sur mon skydrive.

WinRT – CacheMode et VisualStatesTransition

Je suis récemment tombé sur un bug en portant mon jeu de reversi fonctionnant sur Wp7, Silverlight et WPF vers WinRT. En effet les animations qui permettaient aux pions de se retourner ne se lançaient pas sur WinRT. Ces animations sont en fait des transitions entre deux états visuels dont le code était strictement identique sur toutes les plateformes. Après quelques jours de galères et de recherche j’ai fini par comprendre que le problème résidait d’un cache que j’avais mis sur le contrôle Board contenant mes Tokens. Ce contrôle Board n’est qu’un contrôle personnalisé héritant de ItemsControl. Dans le template de ma board j’avais placé un CacheMode à BitmapCache.

A des fins d’exemple j’ai reproduit le comportement dans un petit projet contenant une application WPF et une application WinRT.

Le contrôle Token est le suivant. Lorsque sa propriété IsBlack change, une animation est lancée :

#if NETFX_CORE
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Shapes;
#else
using System.Windows;
using System.Windows.Controls;
using System.Windows.Shapes;
#endif

namespace CacheBug
{
    [TemplatePart(Name = PartWhite, Type = typeof(Ellipse))]
    [TemplatePart(Name = PartBlack, Type = typeof(Ellipse))]
    public sealed class TokenControl : Control
    {
        private const string PartWhite = "PART_White";
        private const string PartBlack = "PART_Black";

        private Ellipse _black = null;
        private Ellipse _white = null;

        public TokenControl()
        {
            this.DefaultStyleKey = typeof(TokenControl);
        }

        public bool IsBlack
        {
            get { return (bool)GetValue(IsBlackProperty); }
            set { SetValue(IsBlackProperty, value); }
        }

        public static readonly DependencyProperty IsBlackProperty =
            DependencyProperty.Register("IsBlack", typeof(bool), typeof(TokenControl), new PropertyMetadata(true, new PropertyChangedCallback((s, a) =>
                {
                    ((TokenControl)s).SetColor();
                })));

#if NETFX_CORE
        protected override void OnApplyTemplate()
#else
        public override void OnApplyTemplate()
#endif
        {
            base.OnApplyTemplate();

            _white = (Ellipse)GetTemplateChild(PartWhite);
            _black = (Ellipse)GetTemplateChild(PartBlack);

            SetColor();
        }

        private void SetColor()
        {
            if (_black == null || _white == null)
                return;

            if (IsBlack)
                VisualStateManager.GoToState(this, "BlackState", true);
            else
                VisualStateManager.GoToState(this, "WhiteState", true);
        }
    }
}

Le template du contôle est le suivant :

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:CacheBug">

    <Style TargetType="local:TokenControl">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:TokenControl">
                    <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="VisualStateGroup">
                                <VisualStateGroup.Transitions>
                                    <VisualTransition From="BlackState" To="WhiteState">
                                        <Storyboard Duration="0:0:0.5">
                                            <DoubleAnimationUsingKeyFrames BeginTime="0:0:0" Duration="0:0:1" Storyboard.TargetName="PART_White" Storyboard.TargetProperty="(UIElement.RenderTransform).ScaleX">
                                                <EasingDoubleKeyFrame KeyTime="0:0:0.0" Value="0" />
                                                <EasingDoubleKeyFrame KeyTime="0:0:0.25" Value="0">
                                                    <EasingDoubleKeyFrame.EasingFunction>
                                                        <CubicEase EasingMode="EaseIn"/>
                                                    </EasingDoubleKeyFrame.EasingFunction>
                                                </EasingDoubleKeyFrame>
                                                <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="1">
                                                    <EasingDoubleKeyFrame.EasingFunction>
                                                        <CubicEase EasingMode="EaseOut"/>
                                                    </EasingDoubleKeyFrame.EasingFunction>
                                                </EasingDoubleKeyFrame>
                                            </DoubleAnimationUsingKeyFrames>
                                            <DoubleAnimationUsingKeyFrames BeginTime="0:0:0" Storyboard.TargetName="PART_Black" Storyboard.TargetProperty="(UIElement.RenderTransform).ScaleX">
                                                <EasingDoubleKeyFrame KeyTime="0:0:0" Value="1">
                                                    <EasingDoubleKeyFrame.EasingFunction>
                                                        <CubicEase EasingMode="EaseIn"/>
                                                    </EasingDoubleKeyFrame.EasingFunction>
                                                </EasingDoubleKeyFrame>
                                                <EasingDoubleKeyFrame KeyTime="0:0:0.25" Value="0">
                                                    <EasingDoubleKeyFrame.EasingFunction>
                                                        <CubicEase EasingMode="EaseIn"/>
                                                    </EasingDoubleKeyFrame.EasingFunction>
                                                </EasingDoubleKeyFrame>
                                            </DoubleAnimationUsingKeyFrames>
                                        </Storyboard>
                                    </VisualTransition>
                                    <VisualTransition From="WhiteState" To="BlackState">
                                        <Storyboard Duration="0:0:0.5">
                                            <DoubleAnimationUsingKeyFrames BeginTime="0:0:0" Duration="0:0:1" Storyboard.TargetName="PART_White" Storyboard.TargetProperty="(UIElement.RenderTransform).ScaleX">
                                                <EasingDoubleKeyFrame KeyTime="0:0:0" Value="1">
                                                    <EasingDoubleKeyFrame.EasingFunction>
                                                        <CubicEase EasingMode="EaseIn"/>
                                                    </EasingDoubleKeyFrame.EasingFunction>
                                                </EasingDoubleKeyFrame>
                                                <EasingDoubleKeyFrame KeyTime="0:0:0.25" Value="0">
                                                    <EasingDoubleKeyFrame.EasingFunction>
                                                        <CubicEase EasingMode="EaseIn"/>
                                                    </EasingDoubleKeyFrame.EasingFunction>
                                                </EasingDoubleKeyFrame>
                                            </DoubleAnimationUsingKeyFrames>
                                            <DoubleAnimationUsingKeyFrames BeginTime="0:0:0" Storyboard.TargetName="PART_Black" Storyboard.TargetProperty="(UIElement.RenderTransform).ScaleX">
                                                <EasingDoubleKeyFrame KeyTime="0:0:0.25" Value="0" />
                                                <EasingDoubleKeyFrame KeyTime="0:0:0.25" Value="0">
                                                    <EasingDoubleKeyFrame.EasingFunction>
                                                        <CubicEase EasingMode="EaseIn"/>
                                                    </EasingDoubleKeyFrame.EasingFunction>
                                                </EasingDoubleKeyFrame>
                                                <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="1">
                                                    <EasingDoubleKeyFrame.EasingFunction>
                                                        <CubicEase EasingMode="EaseOut"/>
                                                    </EasingDoubleKeyFrame.EasingFunction>
                                                </EasingDoubleKeyFrame>
                                            </DoubleAnimationUsingKeyFrames>
                                        </Storyboard>
                                    </VisualTransition>
                                </VisualStateGroup.Transitions>
                                <VisualState x:Name="BlackState">
                                    <Storyboard>
                                        <DoubleAnimationUsingKeyFrames BeginTime="0:0:0" Storyboard.TargetName="PART_Black" Storyboard.TargetProperty="(UIElement.RenderTransform).ScaleX">
                                            <EasingDoubleKeyFrame KeyTime="0:0:0" Value="1" />
                                        </DoubleAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames BeginTime="0:0:0" Storyboard.TargetName="PART_White" Storyboard.TargetProperty="(UIElement.RenderTransform).ScaleX">
                                            <EasingDoubleKeyFrame KeyTime="0:0:0" Value="0" />
                                        </DoubleAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="WhiteState">
                                    <Storyboard>
                                        <DoubleAnimationUsingKeyFrames BeginTime="0:0:0" Storyboard.TargetName="PART_Black" Storyboard.TargetProperty="(UIElement.RenderTransform).ScaleX">
                                            <EasingDoubleKeyFrame KeyTime="0:0:0" Value="0" />
                                        </DoubleAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames BeginTime="0:0:0" Storyboard.TargetName="PART_White" Storyboard.TargetProperty="(UIElement.RenderTransform).ScaleX">
                                            <EasingDoubleKeyFrame KeyTime="0:0:0" Value="1" />
                                        </DoubleAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                        <Ellipse x:Name="PART_Black" RenderTransformOrigin="0.5,0.5" Fill="Black" CacheMode="BitmapCache">
                            <Ellipse.RenderTransform>
                                <ScaleTransform />
                            </Ellipse.RenderTransform>
                        </Ellipse>
                        <Ellipse x:Name="PART_White" RenderTransformOrigin="0.5,0.5" Fill="#FFFAFAFA" CacheMode="BitmapCache">
                            <Ellipse.RenderTransform>
                                <ScaleTransform />
                            </Ellipse.RenderTransform>
                        </Ellipse>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

La page de test est la suivante (vous remarquerez la ligne en commentaire pour qui provoque le bug si décommentée) :

<Page
    x:Class="CacheBug.BlankPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:CacheBug"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">
    <Page.Resources>
        <Style x:Key="ItemsControlStyle" TargetType="ItemsControl">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="ItemsControl">
                        <Border Background="{TemplateBinding Background}"
                                BorderBrush="{TemplateBinding BorderBrush}"
                                BorderThickness="{TemplateBinding BorderThickness}"
                                Padding="{TemplateBinding Padding}">
                            <!-- Uncomment below to make tokens stop animating -->
                            <!--<Border.CacheMode>
                                <BitmapCache />
                            </Border.CacheMode>-->
                            <ItemsPresenter />
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Page.Resources>

    <Grid Background="{StaticResource ApplicationPageBackgroundBrush}">
        <ItemsControl x:Name="lstBox" Style="{StaticResource ItemsControlStyle}">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <local:TokenControl Width="100" Height="100" IsBlack="{Binding IsBlack}" />
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </Grid>
</Page>

Et son codebehind se charge d’initialiser une collection de pions et de changer la valeur de la propriété IsBlack avec un timer :

using System;
using System.Collections.Generic;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;

namespace CacheBug
{
    public sealed partial class BlankPage : Page
    {
        private readonly DispatcherTimer _timer = new DispatcherTimer();
        private readonly Random _rnd = new Random();
        private readonly List<TokenModel> list = new List<TokenModel>();

        public BlankPage()
        {
            this.InitializeComponent();

            _timer.Tick += TimerTick;
            _timer.Interval = TimeSpan.FromSeconds(0.5d);
        }

        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            list.Add(new TokenModel { IsBlack = true });
            list.Add(new TokenModel { IsBlack = false });
            list.Add(new TokenModel { IsBlack = true });
            list.Add(new TokenModel { IsBlack = false });
            list.Add(new TokenModel { IsBlack = true });
            list.Add(new TokenModel { IsBlack = false });
            list.Add(new TokenModel { IsBlack = true });

            lstBox.ItemsSource = list;

            _timer.Start();
        }

        private void TimerTick(object sender, object e)
        {
            int index = _rnd.Next(0, list.Count - 1);
            var token = list[index];
            token.IsBlack ^= true;
        }
    }
}

 

Ce bug est spécifique à WinRT car le même code fonctionne correctement en WPF, Silverlight ou même WP7. J’ai donc désactivé le CacheMode sur ce contrôle pour WinRT.

 

Bug ou feature je ne sais pas trop mais en tout cas c’est posté sur Connect.

 

Et comme souvent, vous pouvez télécharger le code de sample sur mon Skydrive.

 

Edit :

J’ai eu une réponse du pourquoi celà ne marchait pas correctement sous WinRT avec le CacheMode. C’est dû au fait que certains types d’animations (celles impliquant un recalcul du layout ou celles que le moteur estime comme étant lourde) ne sont plus lancées automatiquement. Pour ce faire il faut rajouter la propriété EnableDependentAnimation à True sur chacune des animations. Après tout fonctionne comme prévu.

 

 

Préparez vos RaisePropertyChanged pour une mise en production

Lorsque l’on développe des applications XAML on se met assez rapidement à utiliser l’interface INotifyPropertyChanged. Chaque développeur a en général une méthode levant l’évènement PropertyChanged. Dans le cadre de cet article elle sera RaisePropertyChanged.
L’implémentation classique du RaisePropertyChanged est la suivante :

protected virtual void RaisePropertyChanged(string propertyName)
{
    PropertyChangedEventHandler handler = PropertyChanged;

    if (handler != null)
        handler(this, new PropertyChangedEventArgs(propertyName);
}

L’utilisation de cette méthode se fait donc de cette façon :

private bool _isBusy;
public bool IsBusy
{
    get { return _isBusy; }
    set
    {
        if (value != _isBusy)
        {
            _isBusy = value;
            RaisePropertyChanged("IsBusy");
        }
    }
}

 

Il arrive aussi très régulièrement que l’on adopte une syntaxe basée sur des expressions lambdas  :

private bool _isBusy;
public bool IsBusy
{
    get { return _isBusy; }
    set
    {
        if (value != _isBusy)
        {
            _isBusy = value;
            RaisePropertyChanged(() => IsBusy);
        }
    }
}

L’utilisation de cette syntaxe est très pratique en développement et permet de refactoriser le code sans craindre d’oublier de renommer les noms des propriétés. Elle a cependant un coup non négligeable en performance et en mémoire. Effectivement elle génère des arbres d’expressions qui doivent être analysés pour extraire le nom de la propriété et le tout à l’exécution.

Mon avis sur la question est donc d’utiliser la méthode avec des expressions lambas lorsque le projet débute et qu’il y a encore beaucoup de refactorisation. Ensuite je préfère utiliser la première méthode qui est beaucoup moins coûteuse à l’execution.

La question est donc de savoir comment changer toutes ces expressions lambdas en chaînes de caractères ? Pour un projet simple avec 3 classes c’est rapide à faire à la main, mais sur un projet avec des centaines de classes, faire tout cela à la main est juste inenvisageable.

C’est ici que rentre en scène Visual Studio car lui il sais le faire ! Faut juste savoir lui dire comment.
Si vous allez dans le menu “Edition” puis “Rechercher et remplacer” vous trouverez une action “Remplacer dans les fichiers” (ou alors faites Ctrl+Shift+H c’est plus rapide) vous aurez la possibilité de remplacer du texte en masse. Se cache dans les options de recherche une option très pratique nommée “Expression régulière”. C’est elle qui va nous permettre de remplacer en masse par le bon texte. Cochez donc la case “Expression régulière” et appliquez ensuite la méthode pour votre version de Visual Studio :

Pour Visual Studio 2010 :
Dans le champ de recherche entrez : RaisePropertyChanged\(\(\) +=\> +{:i}\)
Dans le champ de remplacement entrez : RaisePropertyChanged(“\1″)

Si vous utilisez Visual Studio 2011 Beta il faut utiliser d’autres expressions :
Dans le champ de recherche entrez : RaisePropertyChanged\(\(\) +=\> +(\b(_\w+|[\w-[0-9_]]\w*)\b)\);
Dans le champ de remplacement entrez : RaisePropertyChanged(“$1″);

 

Plus aucune excuse pour générer des arbres d’expressions inutilement à chaque mise à jour de propriétés maintenant !

Windows 8 – Metro – WCF multi-endpoint pour C# et JS.

Ajourd’hui j’ai voulu m’amuser à regarder comment fonctionnait l’accès aux services WCF depuis JS et C# dans une application Metro.

Pour cela j’ai voulu n’utiliser qu’un seul et même service consommé par ces deux plateformes via divers protocoles.

Rapidement j’ai constaté la nécessité d’adopter le protocole HTTP avec Javascript. Plusieurs options s’offrent alors, exposer les données en JSON ou en XML. Pour des raisons de pure discrimination envers un format aussi verbeux que le XML, j’ai choisi JSON.

En ce qui concerne le C#, ceux ayant lu quelques-un de mes précédents articles se sont surement rendu compte de mon point faible envers les protocoles de bas niveau et tout particulièrement le netTcpBinding. Pour moi il est le juste milieu entre un protocole typé et léger sur le réseau. Il a des inconvénients tout particulièrement son non-support de la sécurité, mais pour des données non sensibles c’est la Rolls (ou Porsche pour certains expatriés français dans la bay qui se reconnaitront) des protocoles WCF. Pour la route j’ai aussi voulu exposer les données dans un protocole un peu plus haut niveau qui est le NetHttpBinding. Il offre l’avantage d’encoder les données en binaire ce qui offre une volumétrie plus faible que du XML mais malgré tout assez importante (voir détails à la fin).

Donc pour résumer on a :

  • NetHttpBinding (C#)
  • NetTcpBinding (C#)
  • WebHttpBinding (JS)

 

Maintenant un peu de code !

J’ai généré une solution de base avec un client C#/XAML de base, un client JS/HTML de base et un service WCF aussi de base (original non ?). J’ai pris soin d’héberger ce dernier dans mon IIS local et non dans IIS Express pour le netTcpBinding.

Voici à quoi ressemble le service WCF :

[ServiceContract]
public interface IService1
{
    [OperationContract]
    string GetData(int value);

    [OperationContract]
    CompositeType GetDataUsingDataContract(CompositeType composite);

    [OperationContract]
    [WebGet(UriTemplate = "/GetDataJSON/{value}", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
    string GetDataJSON(string value);

    [OperationContract]
    [WebInvoke(Method="POST", UriTemplate = "/GetDataUsingDataContractJSON", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json, BodyStyle=WebMessageBodyStyle.Bare)]
    CompositeType GetDataUsingDataContractJSON(CompositeType value);
}

[DataContract]
public class CompositeType
{
    bool boolValue = true;
    string stringValue = "Hello ";

    [DataMember]
    public bool BoolValue
    {
        get { return boolValue; }
        set { boolValue = value; }
    }

    [DataMember]
    public string StringValue
    {
        get { return stringValue; }
        set { stringValue = value; }
    }
}

Et son implémentation :

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class Service1 : IService1
{
    public string GetData(int value)
    {
        System.Threading.Thread.Sleep(2000);
        return string.Format("You entered: {0}", value);
    }

    public string GetDataJSON(string value)
    {
        return GetData(Convert.ToInt32(value));
    }

    public CompositeType GetDataUsingDataContract(CompositeType composite)
    {
        System.Threading.Thread.Sleep(2000);
        if (composite == null)
        {
            throw new ArgumentNullException("composite");
        }
        if (composite.BoolValue)
        {
            composite.StringValue += "Suffix";
        }
        return composite;
    }

    public CompositeType GetDataUsingDataContractJSON(CompositeType composite)
    {
        return GetDataUsingDataContract(composite);
    }
}

 

Le seul truc un peu original a été d’ajouter les attributs WebGet et WebInvoke pour rendre les méthodes concernées utilisable par Javascript en REST.

Maintenant le fichier de configuration. Il ressemble beaucoup à celui que j’avais fait pour la série sur le netTcpBinding et le httpPollingDuplex en Silverlight :

<system.serviceModel>
  <behaviors>
    <serviceBehaviors>
      <behavior>
        <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
        <serviceDebug includeExceptionDetailInFaults="false"/>
      </behavior>
    </serviceBehaviors>
    <endpointBehaviors>
      <behavior name="restbehavior">
        <webHttp />
      </behavior>
    </endpointBehaviors>
  </behaviors>
  <bindings>
    <netTcpBinding>
      <binding name="netTcpBindingConfig">
        <security mode="None" />
      </binding>
    </netTcpBinding>
  </bindings>
  <services>
    <service name="WcfService1.Service1">
      <endpoint address="" contract="WcfService1.IService1" binding="netHttpBinding" name="HttpBinding" />
      <endpoint address="rest" contract="WcfService1.IService1" binding="webHttpBinding" behaviorConfiguration="restbehavior" name="rest"/>
      <endpoint address="netTcp" contract="WcfService1.IService1" binding="netTcpBinding" bindingConfiguration="netTcpBindingConfig" name="netTcpBinding" />
      <endpoint address="mex" binding="mexHttpBinding" name="mex" contract="IMetadataExchange" />
      <host>
        <baseAddresses>
          <add baseAddress="net.tcp://xiaoba:808/WcfService1/Service1.svc" />
          <add baseAddress="http://xiaoba/WcfService1/Service1.svc" />
        </baseAddresses>
      </host>
    </service>
  </services>
  <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="false" />
</system.serviceModel>

Maintenant qu’en est-t-il des clients ?
Je n’ai pas été bien loin dans l’investigation, le but étant juste de le faire marcher des deux côtés.
Dans chaque client j’appelle d’abord la méthode GetData puis la méthode GetDataUsingDataContract et met le résultat dans un champs texte à l’écran.

Commençons par la star du moment le dénommé Javascript :

(function () {
    "use strict";

    var app = WinJS.Application;

    app.onactivated = function (eventObject) {
        if (eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.launch) {
            if (eventObject.detail.previousExecutionState !== Windows.ApplicationModel.Activation.ApplicationExecutionState.terminated) {
                GetData();
            }
            WinJS.UI.processAll();
        }
    };

    function GetData() {
        var value = 1;

        var ResultSpan = document.getElementById('SpanResult');

        var baseUrl = "http://xiaoba/WcfService1/Service1.svc/rest/GetDataJSON/";
        var urlPost = "http://xiaoba/WcfService1/Service1.svc/rest/GetDataUsingDataContractJSON";

        var url = baseUrl + value;

       WinJS.xhr({url:url}).then(function (r) {
            var result = JSON.parse(r.responseText);
            ResultSpan.innerHTML = result;
        });

        var sData = JSON.stringify({
            BoolValue: true,
            StringValue: "Ô rage ! Ô désespoir ! Ô C# ennemi !"
        });

        WinJS.xhr({
            url: urlPost,
            type: "POST",
            headers: { "Content-Type": "application/json; charset=utf-8" },
            data: sData
        }).then(function (r) {
            var result = JSON.parse(r.responseText);
            ResultSpan.innerHTML = result.StringValue;
        });
    }

    app.oncheckpoint = function (eventObject) {
    };

    app.start();
})();

L’appel au service WCF se fait ici en JSON en utilisant les objets WinJS et JSON. Attention aux deux petits fourbes ici : j’ai nommé JSON.stringify et les headers de l’objet xhr pour le post. Si vous oubliez d’encoder votre objet Javascript en une chaîne de caractère JSON avant de l’affecter au champ data de l’objet xhr, bah ça marche pas. De même pour les headers, il faut les préciser. C’est d’autant plus étrange lorsqu’on a l’habitude de JQuery mais c’est comme ça.

Je ne reviens pas sur le concept de promise utilisé ici, de très bon articles existent sur le sujet.

 

Et maintenant comment ça marche en C# ?

public sealed partial class BlankPage : Page
{
    public BlankPage()
    {
        this.InitializeComponent();
    }

    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
        GetData();
    }

    private async void GetData()
    {
        var client = new Service1Client(Service1Client.EndpointConfiguration.HttpBinding);
        //var client = new Service1Client(Service1Client.EndpointConfiguration.netTcpBinding);

        string result = await client.GetDataAsync(1);

        resultTextBlock.Text = result;

        CompositeType c = await client.GetDataUsingDataContractAsync(new CompositeType { BoolValue = true, StringValue = "Ô rage ! Ô désespoir ! Ô Javascript ennemi !" });

        resultTextBlock.Text = c.StringValue;

        await client.CloseAsync();
    }
}

Ici j’ai déclaré la méthode GetData en async pour pouvoir profiter du pattern async await. Le fonctionnement ici n’est pas exactement identique à la version JS car ici j’attend le retour du premier appel avant de lancer le second, ça ne change fondamentalement pas les choses. On voit dans le code ci-dessus une ligne commentée. Elle permet de changer de protocole de communication pour le netTcpBinding.

 

 

 

Qu’est-ce que j’ai appris de ce petit exercice.

  1. Il n’est pas facile de trouver les erreurs que l’on fait lorsqu’on configure mal WCF pour le JSON. Au final j’ai réussi à débuguer lorsque j’ai installé Fiddler et que j’ai forgé mes requêtes HTTP à la main.
  2. C’est quand même beaucoup plus simple de consommer des services WCF en C#
  3. On fait très vite des erreurs en Javascript sans s’en rendre compte. Genre une majuscule à la place d’une minuscule dans une variable. Aucune erreur de “compilation” ne nous prévient de ça.
  4. En jouant un peu avec Fiddler2 on se rend compte que JSON est beaucoup plus concis que le NetHttpBinding. Il faut jouer un peu des coudes avec des extensions IIS pour faire baisser la taille des données renvoyées par ce dernier.
  5. Les différentes valeurs de EndPointConfiguration sont générées par le générateur de proxy de Visual Studio 11 en fonction des endpoints disponibles sur le service et compatibles avec C#/XAML.
  6. Le changement de binding wcf côté client en C# ne se passe pas du tout comme en Silverlight.
  7. Enfin rien à voir mais c’est juste l’enfer de faire un VerticalAlignement=”Center” en HTML…

 

A vot’ bon cœur m’sieurs dames et bonne soirée.

Comme d’habitude le code source se trouve dans les nuages.

 

Asynchronisme – Simuler de la latence

Un des problèmes récurrents lorsque l’on développe une application contenant des opérations asynchrones est de tester cet asynchronisme. On souhaite vérifier le comportement qu’aurait l’application si telle ou telle opération mettait 10 secondes ou 1 minute à s’exécuter.

Prenons un exemple simple
On a une méthode MyAsyncMethod qui notifie la fin de son exécution avec un évènement MyAsyncMethodCompleted. On souhaite simuler une latence de 5 secondes sur cette méthode.

Voici le code permettant de faire cela :

public void MyAsyncMethod()
{
    Timer timer = null;

    var context = SynchronizationContext.Current;

    TimerCallback c = state =>
    {
        if (timer != null)
            timer.Dispose();

        context.Post(o => MyAsyncMethodCompleted(this, null));
    };
    timer = new Timer(c, null, 5000, int.MaxValue);
}

L’idée est que l’on va créer un timer qui attendra le temps voulu avant d’invoquer un callback.
Ce callback se chargera de disposer le timer et d’invoquer l’évènement Completed.
Le problème du timer est qu’il va invoquer son callback dans un autre thread que celui qui a initialisé l’appel.
Afin de rebasculer sur le bon thread on a sauvegardé le contexte de synchronisation dans la variable context. Ensuite, on a envoyé l’appel à l’évènement completed au contexte de synchronisation grâce à sa méthode Post. C’est un peu l’équivalent à Dispatcher.BeginInvoke pour Silverlight ou WPF mais là ça marche pour tout les threads.

Donc pour finir, pour la méthode apellante tout est transparent et on a simulé une latence.

Simple et pratique :-)