StringTemplate: une autre approche de l’interpolation de chaines

Très mauvaisMauvaisMoyenBonExcellent (Pas encore de note) 
Loading...Loading...

Avec la version 6 de C# qui approche, il y a beaucoup de discussions sur CodePlex et ailleurs à propos de l’interpolation de chaines. Pas très étonnant, puisqu’il s’agit d’une des fonctionnalités majeures de cette version… Au cas où vous auriez vécu dans une grotte ces derniers mois et n’en auriez pas entendu parler, l’interpolation de chaines est un moyen d’insérer des expressions C# à l’intérieur d’une chaine de caractère, de façon à ce qu’elles soient évaluées lors de l’exécution et remplacées par leurs valeurs. En gros, vous écrivez quelque chose comme ça :

string text = $"{p.Name} est né le {p.DateOfBirth:D}";

Et le compilateur le transforme en ceci :

string text = String.Format("{0} est né le {1:D}", p.Name, p.DateOfBirth);

Note: la syntaxe présentée ci-dessus correspond aux dernières notes de conception sur cette fonctionnalité. Elle peut encore changer d’ici à la sortie finale, et la preview actuelle de VS2015 utilise encore une syntaxe différente : “\{p.Name} est né le \{p.DateOfBirth:D}”.

J’adore cette fonctionnalité. Elle va être extrêmement pratique pour des choses comme le logging, la génération d’URL ou de requêtes, etc. Je l’utiliserai certainement beaucoup, surtout maintenant que Microsoft a écouté les retours de la communauté et a inclus un moyen de personnaliser la façon dont les expressions dans la chaine sont évaluées (regardez la partie à propos de IFormattable dans les notes de conception).

Cependant, quelque chose me chiffonne : puisque les chaines interpolées sont interprétées par le compilateur, elles doivent être codées en dur ; on ne peut pas les extraire dans des ressources pour la localisation. Cela signifie que cette fonctionnalité n’est pas utilisable pour la localisation, et qu’on est obligés de continuer à utiliser des marqueurs numériques dans les chaines localisées.

Mais est-ce vraiment inévitable ?

Depuis quelques années, j’utilise un moteur d’interpolation de chaines que j’ai créé, qui s’utilise de la même façon que String.Format, mais avec des marqueurs nommés plutôt que des numéros. Il prend une chaine de format, et un objet avec des propriétés qui correspondent aux noms des marqueurs :

string text = StringTemplate.Format("{Name} est né le {DateOfBirth:D}", new { p.Name, p.DateOfBirth });

Bien sûr, si vous avez déjà un objet avec les propriétés que vous voulez inclure dans la chaine, vous pouvez simplement passer cet objet directement :

string text = StringTemplate.Format("{Name} est né le {DateOfBirth:D}", p);

Le résultat est exactement ce à quoi on pourrait s’attendre : les marqueurs sont remplacés par les valeurs des propriétés correspondantes.

En quoi est-ce mieux que String.Format ?

  • C’est beaucoup plus lisible; un marqueur nommé indique immédiatement quelle valeur ira à cet emplacement
  • On est moins susceptible de se tromper : pas besoin de faire attention à l’ordre des valeurs à formater
  • Quand on extrait les chaines de format dans des ressources pour la localisation, le traducteur voit un nom dans le marqueur, pas un numéro. Cela donne plus de contexte à la chaine, et permet de comprendre plus facilement à quoi la chaine finale va ressembler.

Notez que vous pouvez utiliser les mêmes spécificateurs de format que dans String.Format. La classe StringTemplate analyse la chaine de format et la transforme en une chaine compatible avec String.Format, extrait les valeurs des propriétés dans un tableau, et appelle String.Format.

Bien sûr, analyser la chaine et extraire les valeurs des propriétés par réflexion à chaque fois serait très inefficace, il y a donc des optimisations :

  • chaque chaine de format distincte est analysée une seule fois, et le résultat de l’analyse est mis en cache pour être réutilisé plus tard.
  • pour chaque propriété utilisée dans une chaine de format, un delegate accesseur est généré et mis en cache, pour éviter de faire appel à la réflexion à chaque fois.

Cela signifie que la première fois que vous utilisez une chaine de format données, il y a un coût lié à l’analyse et à la génération des delegates, mais les utilisations ultérieures de la même chaine de format sont beaucoup plus rapides.

La classe StringTemplate fait partie d’une librairie nommée NString, qui contient également des méthodes d’extension pour faciliter la manipulation de chaines. La librairie est une PCL qui peut être utilisée avec toutes les variantes de .NET à l’exception de Silverlight 5. Un paquet NuGet est disponible ici.

Passage de paramètres par référence à une méthode asynchrone

Très mauvaisMauvaisMoyenBonExcellent (1 votes) 
Loading...Loading...

L’asynchronisme dans C# est une fonctionnalité géniale, et je l’ai beaucoup utilisé depuis son apparition. Mais il y a quelques limitations agaçantes; par exemple, on ne peut pas passer des paramètres par référence (ref ou out) à une méthode asynchrone. Il y a de bonnes raisons pour cela; la plus évidente est que si vous passez par référence une variable locale, elle est stockée sur la pile, or la pile ne va pas rester disponible pendant toute l’exécution de la méthode asynchone (seulement jusqu’au premier await), donc l’emplacement de la variable n’existera plus.

Cependant, cette limitation est assez facile à contourner : il suffit de créer une classe Ref<T> pour encapsuler la valeur, et de passer une instance de cette classe par valeur à la méthode asynchrone:

async void btnFilesStats_Click(object sender, EventArgs e)
{
    var count = new Ref<int>();
    var size = new Ref<ulong>();
    await GetFileStats(tbPath.Text, count, size);
    txtFileStats.Text = string.Format("{0} files ({1} bytes)", count, size);
}

async Task GetFileStats(string path, Ref<int> totalCount, Ref<ulong> totalSize)
{
    var folder = await StorageFolder.GetFolderFromPathAsync(path);
    foreach (var f in await folder.GetFilesAsync())
    {
        totalCount.Value += 1;
        var props = await f.GetBasicPropertiesAsync();
        totalSize.Value += props.Size;
    }
    foreach (var f in await folder.GetFoldersAsync())
    {
        await GetFilesCountAndSize(f, totalCount, totalSize);
    }
}

La class Ref<T> ressemble à ceci:

public class Ref<T>
{
    public Ref() { }
    public Ref(T value) { Value = value; }
    public T Value { get; set; }
    public override string ToString()
    {
        T value = Value;
        return value == null ? "" : value.ToString();
    }
    public static implicit operator T(Ref<T> r) { return r.Value; }
    public static implicit operator Ref<T>(T value) { return new Ref<T>(value); }
}

Comme vous pouvez le voir, il n’y a rien de très compliqué. Cette approche peut également être utilisée pour les blocs itérateurs (yield return), qui n’autorisent pas non plus les paramètres ref ou out. Elle a aussi un avantage par rapport aux paramètres ref et out standards: elle permet de rendre le paramètre optionel, par exemple si on n’est pas intéressé par le résultat (évidemment il faut que la méthode appelée gère ce cas de façon appropriée).

Un moyen facile de tester unitairement la validation des arguments null

Très mauvaisMauvaisMoyenBonExcellent (Pas encore de note) 
Loading...Loading...

Quand on teste unitairement une méthode, une des choses à tester est la validation des arguments : par exemple, vérifier que la méthode lève bien une ArgumentNullException quand un argument null est passé pour un paramètre qui ne doit pas être null. Ecrire ce genre de test est très facile, mais c’est une tâche fastidieuse et répétitive, surtout pour une méthode qui a beaucoup de paramètres. J’ai donc écrit une méthode qui automatise en partie cette tâche : elle essaie de passer null pour chacun des arguments spécifiés, et vérifie que la méthode lève bien une ArgumentNullException. Voici un exemple qui teste une méthode d’extension FullOuterJoin :

[Test]
public void FullOuterJoin_Throws_If_Argument_Null()
{
    var left = Enumerable.Empty<int>();
    var right = Enumerable.Empty<int>();
    TestHelper.AssertThrowsWhenArgumentNull(
        () => left.FullOuterJoin(right, x => x, y => y, (k, x, y) => 0, 0, 0, null),
        "left", "right", "leftKeySelector", "rightKeySelector", "resultSelector");
}

Le premier paramètre est une expression lambda qui représente la façon d’appeler la méthode testée. Dans cette lambda, tous les arguments passés à la méthode doivent être valides. Les paramètres suivants sont les noms des paramètres qui ne doivent pas être null. Pour chacun des noms spécifiés, AssertThrowsWhenArgumentNull va remplacer l’argument correspondant par null dans l’expression lambda, compiler et invoquer l’expression lambda, et vérifier que la méthode lève bien une ArgumentNullException.

Grâce à cette méthode, au lieu d’écrire un test pour chacun des arguments à valider, il suffit d’un seul test.

Voici le code de la méthode TestHelper.AssertThrowsWhenArgumentNull (vous pouvez aussi le trouver sur Gist):

using System;
using System.Linq;
using System.Linq.Expressions;
using NUnit.Framework;

namespace MyLibrary.Tests
{
    static class TestHelper
    {
        public static void AssertThrowsWhenArgumentNull(Expression<TestDelegate> expr, params string[] paramNames)
        {
            var realCall = expr.Body as MethodCallExpression;
            if (realCall == null)
                throw new ArgumentException("Expression body is not a method call", "expr");

            var realArgs = realCall.Arguments;
            var paramIndexes = realCall.Method.GetParameters()
                .Select((p, i) => new { p, i })
                .ToDictionary(x => x.p.Name, x => x.i);
            var paramTypes = realCall.Method.GetParameters()
                .ToDictionary(p => p.Name, p => p.ParameterType);
            
            

            foreach (var paramName in paramNames)
            {
                var args = realArgs.ToArray();
                args[paramIndexes[paramName]] = Expression.Constant(null, paramTypes[paramName]);
                var call = Expression.Call(realCall.Method, args);
                var lambda = Expression.Lambda<TestDelegate>(call);
                var action = lambda.Compile();
                var ex = Assert.Throws<ArgumentNullException>(action, "Expected ArgumentNullException for parameter '{0}', but none was thrown.", paramName);
                Assert.AreEqual(paramName, ex.ParamName);
            }
        }

    }
}

Notez que cette méthode a été écrite pour NUnit, mais vous pouvez facilement l’adapter à d’autres frameworks de test unitaire.

J’ai utilisé cette méthode dans ma librairie Linq.Extras, qui fournit de nombreuses méthodes d’extension supplémentaires pour travailler avec des séquences et collections (elle inclut par exemple la méthode FullOuterJoin mentionnée plus haut).

Intégration avec Visual Studio Online + Git dans Team Explorer

Très mauvaisMauvaisMoyenBonExcellent (Pas encore de note) 
Loading...Loading...

J’ai commencé récemment à utiliser Visual Studio Online pour des projets personnels, et je dois dire que c’est une très bonne plateforme, même si ce serait bien de pouvoir héberger des projets publics et non pas seulement privés. J’apprécie particulièrement l’intégration dans le Team Explorer de Visual Studio pour gérer les tâches et les builds.

Cependant j’ai remarqué un petit bug quand on utilise Git pour le contrôle de version : le remote pour VS Online doit s’appeler origin, sinon Team Explorer ne détecte pas qu’il s’agit d’un projet VS Online, et n’affiche pas les pages “Builds” et “Work Items”.

Quand le remote VSO s'appelle "origin"Quand le remote VSO s'appelle "vso"

C’est clairement un bug (quoique mineur), car le nom origin est juste une convention, et un remote Git peut s’appeler n’importe comment ; je l’ai signalé sur Connect. Si vous rencontrez ce problème, vous pouvez le contourner en renommant le remote en origin :

git remote rename vso origin

[WPF] Déclarer des raccourcis clavier globaux en XAML avec NHotkey

Très mauvaisMauvaisMoyenBonExcellent (3 votes) 
Loading...Loading...

Un besoin fréquent pour les applications de bureau est de gérer des raccourcis claviers globaux, pour pouvoir réagir aux raccourcis même quand l’application n’a pas le focus. Malheureusement, il n’y aucune fonctionnalité intégrée dans le .NET Framework pour gérer ça.

Bien sûr, le problème n’est pas nouveau, et il y a un certain nombre de librairies open-source qui se proposent d’y remédier (par exemple VirtualInput). La plupart d’entre elles sont basées sur des hooks système globaux, ce qui leur permet d’intercepter toutes les frappes de touche, même celles qui ne vous intéressent pas. J’ai déjà utilisé certaines de ces librairies, mais je n’en suis pas vraiment satisfait :

  • elles sont souvent liées à un framework IHM spécifique (généralement Windows Forms), ce qui les rend peu pratiques à utiliser avec un autre framework (comme WPF)
  • je n’aime pas trop l’approche consistant à intercepter toutes les frappes de touche. Cela a généralement pour conséquence d’écrire un grosse méthode avec un paquet de if/else if pour décider quoi faire en fonction de la combinaison de touches qui a été pressée

Une meilleure option, à mon sens, est d’écouter uniquement les touches qui vous intéressent, et de spécifier de façon déclarative l’action à effectuer pour chacune d’entre elles. L’approche utilisée en WPF pour les KeyBindings est assez élégante :

<Window.InputBindings>
    <KeyBinding Gesture="Ctrl+Alt+Add" Command="{Binding IncrementCommand}" />
    <KeyBinding Gesture="Ctrl+Alt+Subtract" Command="{Binding DecrementCommand}" />
</Window.InputBindings>

Mais bien sûr, les KeyBindings ne fonctionnent pas de façon globale, ils nécessitent que votre application ait le focus… Et si on pouvait changer ça ?

NHotkey est une librairie très simple pour gérer les raccourcis clavier, qui permet entre autres d’avoir des KeyBindings globaux. Il suffit de mettre à true une propriété attachée sur le KeyBinding :

<Window.InputBindings>
    <KeyBinding Gesture="Ctrl+Alt+Add" Command="{Binding IncrementCommand}"
                HotkeyManager.RegisterGlobalHotkey="True" />
    <KeyBinding Gesture="Ctrl+Alt+Subtract" Command="{Binding DecrementCommand}"
                HotkeyManager.RegisterGlobalHotkey="True" />
</Window.InputBindings>

Et c’est tout ; les commandes définies sur les KeyBindings seront maintenant invoquées même si votre application n’a pas le focus !

Vous pouvez aussi utiliser NHotkey depuis le code :

HotkeyManager.Current.AddOrReplace("Increment", Key.Add, ModifierKeys.Control | ModifierKeys.Alt, OnIncrement);
HotkeyManager.Current.AddOrReplace("Decrement", Key.Subtract, ModifierKeys.Control | ModifierKeys.Alt, OnDecrement);

La librairie tire partie de la fonction RegisterHotkey. Parce qu’elle supporte également Windows Forms, elle est constituée de 3 parties distinctes, afin de ne pas avoir à référencer l’assembly Windows Forms depuis une appli WPF, ou vice versa :

  • La librarie “core”, qui gère l’enregistrement des hotkeys proprement dit, indépendamment d’un framework IHM spécifique. Cette librairie n’est pas directement utilisable, mais elle est utilisée par les deux autres.
  • L’API spécifique à WinForms, qui utilise l’énumération Keys de System.Windows.Forms.
  • L’API spécifique à WPF, qui utilise les énumérations Key et ModifierKeys de System.Windows.Input, et supporte les KeyBindings globaux en XAML.

Si vous installez la librairie à l’aide de Nuget, ajoutez l’un ou l’autre des packages NHotkey.Wpf ou NHotkey.WindowsForms ; le package “core” sera automatiquement ajouté.

Gérer les problèmes de timeout lors de l’upload de gros fichiers avec HttpWebRequest

Très mauvaisMauvaisMoyenBonExcellent (Pas encore de note) 
Loading...Loading...

Si vous avez déjà eu à uploader de gros volumes de données en HTTP, vous avez probablement rencontré des problèmes de timeout. La valeur par défault de Timeout pour HttpWebRequest est de 100 secondes, ce qui signifie que s’il s’écoule plus que cette durée entre le moment où vous envoyez les en-têtes de la requête et celui où vous recevez les en-têtes de la réponse, votre requête échouera. Bien sûr, si vous uploadez un gros fichier, vous devez augmenter ce timeout… mais à quelle valeur ?

Si vous connaissez la bande passante disponible, vous pourriez calculer une approximation du temps nécessaire à l’upload, mais ce n’est pas très fiable, parce que si le réseau est encombré, cela prendra plus longtemps, et votre requête échouera alors qu’elle aurait pu réussir si elle avait eu plus de temps. Alors, faut-il définir le timeout à une valeur très grande, comme plusieurs heures, voire Timeout.Infinite ? Probablement pas. La principale raison est que même si le transfert proprement dit peut durer des heures, certaines phases de l’échange ne devraient pas durer si longtemps. Décomposons les phases d’un upload HTTP :

timeout1

Obtenir le flux de la requête ou récupérer la réponse (parties oranges) n’est pas supposé prendre très longtemps, donc il est clair qu’on a besoin ici d’un timeout plutôt court (la valeur par défaut de 100 secondes semble raisonnable). Par contre, envoyer le corps de la requête (partie bleue) peut prendre beaucoup plus longtemps, et il n’y a pas de moyen fiable de déterminer combien de temps ça devrait prendre; tant qu’on arrive à envoyer des données et que le serveur les reçoit, il n’y a aucune raison de ne pas continuer, même si ça prend des heures. Donc en fait, on ne veut pas du tout de timeout dans ce cas ! Malheureusement, le comportement de la propriété Timeout est de tout prendre en compte de l’appel à GetRequestStream jusqu’au retour de GetResponse

A mon avis, c’est un défaut de conception de la classe HttpWebRequest, et il me gêne depuis très longtemps. Donc j’ai fini par trouver une solution, qui se base sur le fait que les versions asynchrones de GetRequestStream et GetResponse n’ont pas de mécanisme de timeout. Voilà ce que dit la documentation :

La propriété Timeout n’a aucun effet sur les requêtes asynchrones lancées à l’aide des méthodes BeginGetResponse ou BeginGetRequestStream.

Dans le cas de requêtes asynchrones, l’application cliente implémente son propre mécanisme de délai d’expiration. Consultez l’exemple de la méthode BeginGetResponse.

Une solution pourrait donc être d’utiliser ces méthodes directement (ou les nouvelles versions basées sur des Task: GetRequestStreamAsync et GetResponseAsync) ; mais bien souvent, il y déjà une base de code existante qui utilise les méthodes synchrones, et changer le code pour le rendre complètement asynchrone n’est généralement pas trivial. L’approche la plus simple est de créer des wrapper synchrones autour de BeginGetRequestStream et BeginGetResponse, avec un moyen de spécifier un timeout pour ces opérations :

    public static class WebRequestExtensions
    {
        public static Stream GetRequestStreamWithTimeout(
            this WebRequest request,
            int? millisecondsTimeout = null)
        {
            return AsyncToSyncWithTimeout(
                request.BeginGetRequestStream,
                request.EndGetRequestStream,
                millisecondsTimeout ?? request.Timeout);
        }

        public static WebResponse GetResponseWithTimeout(
            this HttpWebRequest request,
            int? millisecondsTimeout = null)
        {
            return AsyncToSyncWithTimeout(
                request.BeginGetResponse,
                request.EndGetResponse,
                millisecondsTimeout ?? request.Timeout);
        }

        private static T AsyncToSyncWithTimeout<T>(
            Func<AsyncCallback, object, IAsyncResult> begin,
            Func<IAsyncResult, T> end,
            int millisecondsTimeout)
        {
            var iar = begin(null, null);
            if (!iar.AsyncWaitHandle.WaitOne(millisecondsTimeout))
            {
                var ex = new TimeoutException();
                throw new WebException(ex.Message, ex, WebExceptionStatus.Timeout, null);
            }
            return end(iar);
        }
    }

(notez que j’ai utilisé les méthodes Begin/End plutôt que les méthodes Async, afin de garder la compatibilité avec des versions plus anciennes de  .NET)

Ces méthodes d’extension peuvent être utilisées à la place de GetRequestStream et GetResponse ; chacune d’elle déclenchera une exception de timeout si elle dure trop longtemps, mais une fois que vous avez le flux de la requête, vous avez tout le temps que vous voulez pour uploader les données. Notez que le flux lui-même a ses propres timeouts de lecture et d’écriture (5 minutes par défaut), donc si 5 minutes s’écoulent sans que le moindre octet soit uploadé, la méthode Write déclenchera une exception. Voilà le nouveau scénario d’upload en utilisant ces nouvelles méthodes :

timeout2

Comme vous pouvez le voir, la seule différence est que le timeout ne s’applique plus au transfert du corps de la requête, mais seulement à l’obtention du flux de la requête et de la réponse. Voilà un exemple complet qui correspond au scénario ci-dessus :

long UploadFile(string path, string url, string contentType)
{
    // Build request
    var request = (HttpWebRequest)WebRequest.Create(url);
    request.Method = WebRequestMethods.Http.Post;
    request.AllowWriteStreamBuffering = false;
    request.ContentType = contentType;
    string fileName = Path.GetFileName(path);
    request.Headers["Content-Disposition"] = string.Format("attachment; filename=\"{0}\"", fileName);
    
    try
    {
        // Open source file
        using (var fileStream = File.OpenRead(path))
        {
            // Set content length based on source file length
            request.ContentLength = fileStream.Length;
            
            // Get the request stream with the default timeout
            using (var requestStream = request.GetRequestStreamWithTimeout())
            {
                // Upload the file with no timeout
                fileStream.CopyTo(requestStream);
            }
        }
        
        // Get response with the default timeout, and parse the response body
        using (var response = request.GetResponseWithTimeout())
        using (var responseStream = response.GetResponseStream())
        using (var reader = new StreamReader(responseStream))
        {
            string json = reader.ReadToEnd();
            var j = JObject.Parse(json);
            return j.Value<long>("Id");
        }
    }
    catch (WebException ex)
    {
        if (ex.Status == WebExceptionStatus.Timeout)
        {
            LogError(ex, "Timeout while uploading '{0}'", fileName);
        }
        else
        {
            LogError(ex, "Error while uploading '{0}'", fileName);
        }
        throw;
    }
}

J’espère que cela vous sera utile !

Envoyer des données avec HttpClient selon un modèle “push”

Très mauvaisMauvaisMoyenBonExcellent (Pas encore de note) 
Loading...Loading...

Si vous avez déjà utilisé la classe HttpWebRequest pour envoyer des données, vous savez qu’elle utilise un modèle “push”. Ce que j’entends par là, c’est que vous appelez la méthode GetRequestStream, qui ouvre la connexion si nécessaire, envoie les en-têtes, et renvoie un flux sur lequel vous pouvez écrire directement.

.NET 4.5 a introduit la classe HttpClient comme nouveau moyen de communiquer en HTTP. Elle repose en fait sur HttpWebRequest en interne, mais offre une API plus pratique et complètement asynchrone. HttpClient utilise une approche différente en ce qui concerne l’upload de données : au lieu d’écrire directement sur le flux de la requête, il faut affecter à la propriété Content du HttpRequestMessage une instance d’une classe dérivée de HttpContent. On peut également passer le contenu directement aux méthodes PostAsync ou PutAsync.

Le framework .NET fournit quelques implémentations standard de HttpContent, voici quelques unes des plus communément utilisées :

  • ByteArrayContent: représente du contenu binaire brut en mémoire
  • StringContent: représente du texte avec un encodage spécifique (c’est une spécialisation de ByteArrayContent)
  • StreamContent: représente des données binaires brutes sous forme d’un flux

Par exemple, voilà comment on peut uploader le contenu d’un fichier :

async Task UploadFileAsync(Uri uri, string filename)
{
    using (var stream = File.OpenRead(filename))
    {
        var client = new HttpClient();
        var response = await client.PostAsync(uri, new StreamContent(stream));
        response.EnsureSuccessStatusCode();
    }
}

Comme vous l’aurez peut-être remarqué, ce code n’écrit jamais explicitement sur le flux de la requête : le contenu est automatiquement lu depuis le flux source et copié vers le flux de la requête.

Ce modèle “pull” convient à la plupart des usages, mais il a un inconvénient : il nécessite que les données à envoyer existent déjà sous une forme qui peut être directement envoyée au serveur. Ce n’est pas toujours souhaitable, parce que parfois on veut générer “à la volée” le contenu de la requête. Par exemple, si on veut envoyer un objet sérialisé en JSON, avec l’approche “pull”, il faut d’abord le sérialiser en mémoire dans une String ou un MemoryStream, puis affecter cela au contenu de la requête :

async Task UploadJsonObjectAsync<T>(Uri uri, T data)
{
    var client = new HttpClient();
    string json = JsonConvert.SerializeObject(data);
    var response = await client.PostAsync(uri, new StringContent(json));
    response.EnsureSuccessStatusCode();
}

Ce n’est pas vraiment un problème pour des petits objets, mais ce n’est clairement pas optimal pour des graphes d’objets plus importants…

Alors, comment pourrait-on inverser ce modèle pull en un modèle push ? Eh bien c’est en fait assez simple : il suffit de créer une classe qui hérite de HttpContent, et de redéfinir sa méthode SerializeToStreamAsync pour écrire directement sur le flux de la requête. En fait, j’avais l’intention de bloguer sur ma propre implémentation, mais j’ai fait quelques recherches, et il s’avère que Microsoft a déjà fait le travail : la librairie Web API 2 Client fournit une classe PushStreamContent qui fait exactement ça. En gros, il faut juste lui passer un délégué qui définit quoi faire avec le flux de la requête. Voilà comment ça fonctionne :

async Task UploadJsonObjectAsync<T>(Uri uri, T data)
{
    var client = new HttpClient();
    var content = new PushStreamContent((stream, httpContent, transportContext) =>
    {
        var serializer = new JsonSerializer();
        using (var writer = new StreamWriter(stream))
        {
            serializer.Serialize(writer, data);
        }
    });
    var response = await client.PostAsync(uri, content);
    response.EnsureSuccessStatusCode();
}

Notez que la classe PushStreamContent fournit aussi une surcharge du constructeur qui accepte un délégué asynchrone, si vous voulez écrire sur le flux de façon asynchrone.

En fait, pour ce cas d’utilisation spécifique, la librairie Web API 2 Client propose une approche moins alambiquée : la classe ObjectContent. Il faut juste lui passer l’objet à envoyer ainsi qu’un MediaTypeFormatter, et elle se charge de sérialiser l’objet sur le flux de la requête :

async Task UploadJsonObjectAsync<T>(Uri uri, T data)
{
    var client = new HttpClient();
    var content = new ObjectContent<T>(data, new JsonMediaTypeFormatter());
    var response = await client.PostAsync(uri, content);
    response.EnsureSuccessStatusCode();
}

Par défaut, la classe JsonMediaTypeFormatter utilise Json.NET comme sérialiseur JSON, mais il y a une option pour utiliser DataContractJsonSerializer à la place.

Notez que si vous voulez lire un objet depuis le contenu de la réponse, c’est encore plus facile : utilisez simplement la méthode d’extension ReadAsAsync<T> (également dans la librairie Web API 2 Client). Comme vous pouvez le voir, il est donc extrêmement facile de consommer des APIs REST à l’aide de HttpClient.

[WinRT] Sélectionner un élément de liste lors d’un appui long

Très mauvaisMauvaisMoyenBonExcellent (1 votes) 
Loading...Loading...

Comme vous le savez probablement, la méthode standard pour sélectionner ou déselectionner un élément dans un contrôle de liste WinRT est de le faire glisser légèrement vers le haut ou vers le bas. Même si j’aime bien ce geste, il n’est pas très intuitif pour les utilisateurs qui ne sont pas habitués à Modern UI. Et ça devient encore plus déroutant, car ma déclaration précédente n’est pas tout à fait exacte : en fait, il faut faire glisser l’élément perpendiculairement à la direction de défilement de la liste. Dans une GridView, qui défile horizontalement (par défaut), c’est vers le haut ou vers le bas ; mais pour une ListView, qui défile verticalement, il faut faire glisser l’élément vers la gauche ou vers la droite. Si une application utilise les deux types de liste, ça devient vraiment très déroutant pour l’utilisateur.

Bien sûr, dans le style par défaut, il y a une indication visuelle (une discrète animation “glissement vers le bas” avec une coche grise) quand l’utilisateur appuie longuement sur un élément, mais ce n’est pas toujours suffisant pour que tout le monde comprenne. Beaucoup de gens (par exemple les utilisateurs d’Android) on l’habitude de sélectionner les éléments par un appui long (appelé “Hold” dans la terminologie Modern UI). Donc, pour rendre votre application facilement utilisable par le plus grand nombre d’utilisateurs, il peut être intéressant de permettre la sélection par un appui long.

Un moyen simple de le faire est de créer une propriété attachée qui, quand on la met à true, s’abonne à l’évènement Holding de l’élément, et change la valeur de la propriété IsSelected quand l’évènement se produit. Voilà une implémentation possible :

using Windows.UI.Input;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Input;

namespace TestSelectOnHold
{
    public static class SelectorItemEx
    {
        public static bool GetToggleSelectedOnHold(SelectorItem item)
        {
            return (bool)item.GetValue(ToggleSelectedOnHoldProperty);
        }

        public static void SetToggleSelectedOnHold(SelectorItem item, bool value)
        {
            item.SetValue(ToggleSelectedOnHoldProperty, value);
        }

        public static readonly DependencyProperty ToggleSelectedOnHoldProperty =
            DependencyProperty.RegisterAttached(
              "ToggleSelectedOnHold",
              typeof(bool),
              typeof(SelectorItemEx),
              new PropertyMetadata(
                false,
                ToggleSelectedOnHoldChanged));

        private static void ToggleSelectedOnHoldChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            var item = o as SelectorItem;
            if (item == null)
                return;

            var oldValue = (bool)e.OldValue;
            var newValue = (bool)e.NewValue;

            if (oldValue && !newValue)
            {
                item.Holding -= Item_Holding;
            }
            else if (newValue && !oldValue)
            {
                item.Holding += Item_Holding;
            }
        }

        private static void Item_Holding(object sender, HoldingRoutedEventArgs e)
        {
            var item = sender as SelectorItem;
            if (item == null)
                return;

            if (e.HoldingState == HoldingState.Started)
                item.IsSelected = !item.IsSelected;
        }
    }
}

Vous pouvez ensuite définir cette propriété dans l’ItemContainerStyle du contrôle de liste :

<GridView.ItemContainerStyle>
    <Style TargetType="GridViewItem">
        ...
        <Setter Property="local:SelectorItemEx.ToggleSelectedOnHold" Value="False" />
    </Style>
</GridView.ItemContainerStyle>

Et c’est tout : l’utilisateur peut maintenant sélectionner les éléments par un appui long. Le geste standard fonctionne toujours, bien sûr, donc les utilisateurs qui le connaissent peuvent toujours l’utiliser.

Notez que cette fonctionnalité aurait aussi pu être implémentée comme un Behavior à part entière. Il y a deux raisons pour lesquelles je n’ai pas choisi cette approche :

  • Les Behaviors ne sont pas supportés nativement dans WinRT (bien qu’on puisse les ajouter avec un package Nuget)
  • Les Behaviors ne fonctionnent pas très bien avec les styles, parce que Interaction.Behaviors est une collection, et on ne peut pas ajouter des éléments à une collection dans un style. Une solution possible pour contourner le problème serait de créer une propriété attachée IsEnabled, qui ajouterait le behavior à la collection quand on la met à true, mais on se retrouverait finalement avec une solution quasiment identique à celle décrite plus haut, en plus complexe…

Exécuter un outil personnalisé automatiquement quand un fichier est modifié

Très mauvaisMauvaisMoyenBonExcellent (Pas encore de note) 
Loading...Loading...

Aussi loin que je me souvienne, il y a toujours eu dans Visual Studio quelque chose appelé “outils personnalisés” (custom tools), également connus sous le nom de single-file generators. Quand vous appliquez un tel outil à un fichier de votre projet, il génère quelque chose (généralement du code, mais pas forcément) en fonction du contenu du fichier. Par exemple, l’outil personnalisé par défaut pour les fichiers de ressource s’appelle ResXFileCodeGenerator, et génère une classe qui permet d’accéder facilement aux ressources définies dans le fichier resx.

image

Quand vous enregistrez un fichier qui a un outil personnalisé associé, Visual Studio réexécute automatiquement l’outil personnalisé pour regénérer sa sortie. Vous pouvez aussi le faire manuellement, en utilisant la commande “Exécuter l’outil personnalisé” depuis le menu contextuel du fichier dans l’explorateur de solution.

Habituellement, les outils personnalisés ne se basent que sur un fichier d’entrée pour générer leur sortie, mais parfois les choses sont un peu plus complexes. Par exemple, prenons les templates T4 : ils ont un outil personnalisé associé (TextTemplatingFileGenerator), donc cet outil est exécuté quand le template est sauvegardé, mais bien souvent, le template lui-même utilise d’autres fichiers d’entrée pour générer sa sortie. Donc l’outil personnalisé doit être exécuté non seulement quand le template est modifié, mais également quand les fichiers dont il dépend sont modifiés. Puisqu’il n’y a pas de moyen d’indiquer à Visual Studio l’existence de cette dépendance, il faut exécuter l’outil personnalisé manuellement, ce qui est assez agaçant…

Comme j’étais dans cette situation, et que j’en avais assez d’aller exécuter manuellement l’outil personnalisé sur mes templates T4, j’ai finalement créé une extension Visual Studio pour le faire automatiquement : AutoRunCustomTool. Le nom manque un peu d’imagination, mais au moins il est descriptif…

Cet outil est conçu pour être très simple et discret : il fait son travail en silence, sans vous gêner, et vous oubliez très vite qu’il est là. Il ajoute une nouvelle propriété à chaque élément du projet : “Run custom tool on”. Cette propriété est une collection de noms de fichiers pour lesquels l’outil personnalisé doit être exécuté à chaque fois que cet élément de projet est enregistré. Par exemple, si vous avez un template T4 (Template.tt) qui génère un fichier (Output.txt) en fonction du contenu d’un autre fichier (Input.txt), il suffit d’ajouter “Template.tt” à la propriété “Run custom tool on” de Input.txt. A chaque fois que vous enregistrerez Input.txt, l’outil personnalisé sera exécuté automatiquement sur Template.tt, ce qui regénèrera le contenu de Output.txt. Vous trouverez un exemple concret sur la page de l’outil dans la galerie Visual Studio.

J’ai créé AutoRunCustomTool il y a 6 mois environ, mais la version initiale n’était pas très bien dégrossie, donc je n’ai pas communiqué à son sujet. J’ai publié la deuxième version il y a quelques jours, et je pense qu’il est maintenant prêt à être utilisé par tout le monde. Si vous êtes intéressé par le code, vous pouvez le trouver sur GitHub, qui est aussi l’endroit où vous pouvez signaler les problèmes éventuels.

Helper fortement typé pour les notifications toast

Très mauvaisMauvaisMoyenBonExcellent (Pas encore de note) 
Loading...Loading...

Windows 8 fournit une API pour afficher des notifications toast. Malheureusement, elle est très peu pratique à utiliser : pour définir le contenu d’une notification, il faut utiliser un modèle prédéfini qui est fourni sous la forme d’un XmlDocument, et fixer la valeur de chaque champ dans le XML. Il n’y a rien dans l’API pour indiquer quels champs sont définis dans le modèle et à quoi ils correspondent, il faut consulter le catalogue de modèles de toast dans la documentation. Il serait beaucoup plus pratique d’avoir une API fortement typée…

J’ai donc créé un simple wrapper autour de l’API des toasts. On peut l’utiliser comme ceci :

var content = new ToastContent.ImageAndText02
{
    Image = "ms-appx:///Images/dotnet.png",
    Title = "Hello world!",
    Text = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
};
var notifier = ToastNotificationManager.CreateToastNotifier();
notifier.Show(content.CreateNotification());

Notez que j’ai gardé les noms d’origine du catalogue de modèles, parce que des noms suffisamment descriptifs auraient été trop longs. J’ai inclus des commentaires XML de documentation pour faciliter le choix du modèle.

Si vous voulez plus de flexibilité qu’un modèle fortement typé ne peut en offrir, mais que vous ne voulez pas manipuler le XML du modèle, vous pouvez utiliser la classe ToastContent directement :

var content = new ToastContent(ToastTemplateType.ToastImageAndText02);
content.SetImage(1, "ms-appx:///Images/dotnet.png");
content.SetText(1, "Hello world!");
content.SetText(2, "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.");
var notifier = ToastNotificationManager.CreateToastNotifier();
notifier.Show(content.CreateNotification());

Le code est disponible sur Github, avec une application de démo. Un package NuGet est également disponible.

Un point intéressant est la façon dont j’ai créé les classes de modèle : j’aurais pu le faire à la main, mais cela aurait été assez fastidieux. J’ai donc extrait les modèles de toast dans un fichier XML, je l’ai enrichi de quelques informations supplémentaires (noms des propriétés, description pour les commentaires de documentation), et j’ai créé un template T4 pour générer automatiquement les classes à partir du fichier XML.

css.php