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 (1 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.

Afficher des suggestions de résultat dans une SearchBox WinRT : bug concernant l’image

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

Aujourd’hui je me suis heurté à un bug bizarre qui m’a fait perdre une heure ou deux, donc je me suis dit que ça méritait d’écrire un billet à ce sujet au cas où quelqu’un d’autre rencontrerait le même problème.

Le contrôle SearchBox a été ajouté dans Windows 8.1 pour permettre des scénarios de recherche directement dans une application Windows Store. L’une de ses fonctionnalités est l’affichage de suggestions basées sur la saisie de l’utilisateur. Il y a trois sortes de suggestions :

  • Les suggestions d’historique sont les requêtes précédemment effectuées par l’utilisateur. C’est géré automatiquement, donc vous n’avez aucun code à écrire pour que ça marche.
  • Les suggestions de recherche permettent de proposer des termes de recherche en fonction de ce que l’utilisateur a déjà saisi ; si l’utilisateur en sélectionne une, le texte actuel de la recherche est remplacé par celui de la suggestion, et valider la requête lancera la recherche avec ce texte.
  • Les suggestions de résultat sont des suggestions pour des résultats exacts. L’utilisateur peut directement choisir un de ces résultats sans lancer une recherche complète.

Pour fournir des suggestions, il faut gérer l’évènement SuggestionsRequested de la SearchBox, et ajouter des suggestions à l’aide des méthodes AppendQuerySuggestion et AppendResultSuggestion. Concentrons-nous sur les suggestions de résultat.

La méthode AppendResultSuggestion prend plusieurs paramètres, dont l’un représente l’image à afficher pour la suggestion. Il est obligatoire (passer null lèvera une exception), et il est de type IRandomAccessStreamReference, c’est-à-dire quelque chose qui peut fournir un flux. Je trouve ça un peu étrange, vu qu’il aurait été plus naturel de passer une ImageSource, mais c’est comme ça… J’ai donc cherché une classe qui implémente l’interface IRandomAccessStreamReference, et le premier candidat évident que j’ai trouvé était la classe StorageFile, qui représente un fichier. J’ai donc écrit le code suivant :

private async void SearchBox_SuggestionsRequested(SearchBox sender, SearchBoxSuggestionsRequestedEventArgs args)
{
    var deferral = args.Request.GetDeferral();
    try
    {
        var imageUri = new Uri("ms-appx:///test.png");
        var imageRef = await StorageFile.GetFileFromApplicationUriAsync(imageUri);
        args.Request.SearchSuggestionCollection.AppendQuerySuggestion("test");
        args.Request.SearchSuggestionCollection.AppendSearchSeparator("Foo Bar");
        args.Request.SearchSuggestionCollection.AppendResultSuggestion("foo", "Details", "foo", imageRef, "Result");
        args.Request.SearchSuggestionCollection.AppendResultSuggestion("bar", "Details", "bar", imageRef, "Result");
        args.Request.SearchSuggestionCollection.AppendResultSuggestion("baz", "Details", "baz", imageRef, "Result");
    }
    finally
    {
        deferral.Complete();
    }
}

Ce code s’exécute sans aucune erreur, et les suggestions sont affichées… mais l’image n’apparait pas !

http://i.stack.imgur.com/BiF0g.png

J’ai passé un long moment à tout revérifier, à faire plein de petits changements pour essayer de trouver l’origine du problème, j’ai même fait ma propre implémentation de IRandomAccessStreamReference… en vain.

J’ai finalement posté mon problème sur Stack Overflow, et quelqu’un m’a gentiment fourni la solution, qui était très simple : au lieu d’utiliser StorageFile, il faut utiliser RandomAccessStreamReference (ça semble assez évident une fois qu’on sait que ça existe). Le code devient donc :

private void SearchBox_SuggestionsRequested(SearchBox sender, SearchBoxSuggestionsRequestedEventArgs args)
{
    var imageUri = new Uri("ms-appx:///test.png");
    var imageRef = RandomAccessStreamReference.CreateFromUri(imageUri);
    args.Request.SearchSuggestionCollection.AppendQuerySuggestion("test");
    args.Request.SearchSuggestionCollection.AppendSearchSeparator("Foo Bar");
    args.Request.SearchSuggestionCollection.AppendResultSuggestion("foo", "Details", "foo", imageRef, "Result");
    args.Request.SearchSuggestionCollection.AppendResultSuggestion("bar", "Details", "bar", imageRef, "Result");
    args.Request.SearchSuggestionCollection.AppendResultSuggestion("baz", "Details", "baz", imageRef, "Result");
}

(Notez que la méthode n’est plus asynchrone, il n’y a donc plus besoin d’utiliser l’objet deferral).

Les suggestions sont maintenant affichées comme je le voulais, avec l’image :

http://i.imgur.com/cjmogKp.png

La leçon à tirer de cette histoire est que, bien que le paramètre image soit de type IRandomAccessStreamReference, il ne semble pas accepter autre chose qu’une instance de la classe RandomAccessStreamReference. Si vous passez n’importe quelle autre implémentation de l’interface, cela échoue silencieusement et l’image n’est pas affichée. C’est clairement un bug : si le type déclaré du paramètre dans la signature de la méthode et une interface, la méthode devrait accepter n’importe quelle implémentation de cette interface ; sinon, la signature devrait déclarer le type concret. J’ai signalé le bug sur Connect, avec un peu de chance ce sera corrigé dans une future version.

En espérant que ce soit utile à quelqu’un !

Posted in WinRT. Tags: , , , , . No Comments »

Une façon simple et sécurisée de stocker un mot de passe à l’aide de Data Protection API

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

Si vous écrivez une application cliente qui a besoin de stocker des identifiants d’utilisateur, ce n’est généralement pas une bonne idée de stocker le mot de passe en clair, pour des raisons évidentes de sécurité. Il faut donc le chiffrer, mais dès qu’on commence à envisager le chiffrement, cela soulève toutes sortes de problèmes… Quel algorithme utiliser ? Quelle clé de chiffrement ? Clairement on va avoir besoin de cette clé pour déchiffrer le mot de passe, il faut donc qu’elle se trouve dans l’exécutable ou dans la configuration. Mais dans ce cas, elle sera être assez facile à trouver…

Eh bien, la bonne nouvelle est qu’en fait vous n’avez pas vraiment besoin de résoudre ce problème, parce que Windows l’a déjà résolu pour vous ! La solution s’appelle Data Protection API, et permet de protéger des données sans avoir à se préoccuper d’une clé de chiffrement. La documentation est un peu longue et ennuyeuse, mais en fait c’est très facile à utiliser depuis .NET, parce que le framework fournit une classe ProtectedData qui s’occupe d’appeler les APIs bas niveau pour vous.

Cette classe a deux méthodes, avec des noms qui se passent d’explication: Protect et Unprotect:

public static byte[] Protect(byte[] userData, byte[] optionalEntropy, DataProtectionScope scope);
public static byte[] Unprotect(byte[] encryptedData, byte[] optionalEntropy, DataProtectionScope scope);

Le paramètre userData correspond aux données binaires en clair, non chiffrées. Le scope est une valeur qui indique si les données doivent être protégées pour l’utilisateur courant (seul cet utilisateur pourra les déchiffrer) ou pour la machine locale (n’importe quel utilisateur sur la même machine pourra les déchiffrer). Et le paramètre optionalEntropy ? Eh bien, je ne suis pas un expert en cryptographie, mais tel que je le comprends, c’est une sorte de “salt”: d’après la documentation, il sert à “augmenter la complexité du chiffrage”. Evidemment, il faudra fournir la même entropie pour déchiffrer les données plus tard. Comme son nom l’indique, ce paramètre est optionnel, il suffit de passer null si on ne veut pas l’utiliser.

Cette API est donc assez simple, mais pas directement utilisable pour notre objectif : l’entrée et la sortie sont des tableaux d’octets, mais on veux chiffrer un mot de passe, qui est une chaine de caractères; de plus, il est généralement plus pratique de stocker une chaine que des données binaires. Pour obtenir un tableau d’octets à partir d’une chaine, c’est assez simple : il suffit d’utiliser un encodage de texte, comme UTF-8. Mais on ne peut pas utiliser cette approche pour obtenir une chaine à partir des données binaires chiffrées, car elles ne correspondront sans doute pas à du texte “imprimable”; au lieu de ça, on peut encoder le résultat en Base64, qui donne une bonne représentation textuelle des données binaires. Donc, en gros voilà ce qu’on va faire :

                         texte en clair
(encodage en UTF-8)   => données binaires en clair
(Protect)             => données binaires chiffrées
(encodage en base64)  => texte chiffré

Et pour le déchiffrement, il suffit d’inverser les étapes:

                            texte chiffré
(decodage depuis base64) => données binaires chiffrées
(Unprotect)              => données binaires en clair
(décodage depuis UTF-8)  => texte en clair

Je n’ai pas parlé de l’entropie dans l’explication ci-dessus ; dans la plupart des cas il sera sans doute plus pratique de la manipuler aussi comme une chaine, donc on pourra là aussi utiliser l’encodage UTF-8 pour obtenir la forme binaire.

Finalement, on peut emballer tout ça sous forme de deux simples méthodes d’extension :

public static class DataProtectionExtensions
{
    public static string Protect(
        this string clearText,
        string optionalEntropy = null,
        DataProtectionScope scope = DataProtectionScope.CurrentUser)
    {
        if (clearText == null)
            throw new ArgumentNullException("clearText");
        byte[] clearBytes = Encoding.UTF8.GetBytes(clearText);
        byte[] entropyBytes = string.IsNullOrEmpty(optionalEntropy)
            ? null
            : Encoding.UTF8.GetBytes(optionalEntropy);
        byte[] encryptedBytes = ProtectedData.Protect(clearBytes, entropyBytes, scope);
        return Convert.ToBase64String(encryptedBytes);
    }
    
    public static string Unprotect(
        this string encryptedText,
        string optionalEntropy = null,
        DataProtectionScope scope = DataProtectionScope.CurrentUser)
    {
        if (encryptedText == null)
            throw new ArgumentNullException("encryptedText");
        byte[] encryptedBytes = Convert.FromBase64String(encryptedText);
        byte[] entropyBytes = string.IsNullOrEmpty(optionalEntropy)
            ? null
            : Encoding.UTF8.GetBytes(optionalEntropy);
        byte[] clearBytes = ProtectedData.Unprotect(encryptedBytes, entropyBytes, scope);
        return Encoding.UTF8.GetString(clearBytes);
    }
}

Exemple de chiffrement :

string encryptedPassword = password.Protect();

Exemple de déchiffrement :

try
{
    string password = encryptedPassword.Unprotect();
}
catch(CryptographicException)
{
    // Causes possible:
    // - l'entropie n'est pas celle qui a été utilisée pour le chiffrage
    // - les données ont été chiffrées par un autre utilisateur (pour scope == CurrentUser)
    // - les données ont été chiffrées sur une autre machine (pour scope == LocalMachine)
    // Dans ce cas, le mot de passe stocké n'est pas utilisable ; il faut demander à l'utilisateur de le saisir à nouveau.
}

Ce que j’aime avec cette technique, c’est que ça marche tout seul : pas besoin de se préoccuper de la façon dont les données sont chiffrées, de l’endroit où est stockée la clé, ou quoi que ce soit, Windows s’occupe de tout.

Le code ci-dessus fonctionne avec le .NET Framework complet, mais la Data Protection API est également disponible :

Détecter les changements d’une propriété de dépendance dans WinRT

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

Aujourd’hui j’aimerais partager une astuce que j’ai utilisée en développant ma première application Windows Store. Je suis complètement nouveau sur cette technologie et c’est mon premier billet à ce sujet, donc j’espère que je ne vais pas trop me ridiculiser…

Il est souvent utile d’être notifié quand la valeur d’une propriété de dépendance change ; beaucoup de contrôles exposent des évènements à cet effet, mais ce n’est pas toujours le cas. Par exemple, récemment j’essayais de détecter les changements de la propriété Content d’un ContentControl. En WPF, j’aurais utilisé la classe DependencyPropertyDescriptor, mais elle n’est pas disponible dans WinRT.

Heureusement, il y a un mécanisme qui existe sur toutes les plateformes XAML, et qui peut résoudre ce problème: le binding. La solution est donc simplement de créer une classe avec un propriété “bidon” qui est liée à la propriété qu’on souhaite observer, et d’appeler un handler quand la valeur de cette propriété bidon change. Pour rendre ça un peu plus propre et masquer l’implémentation réelle, j’ai emballé ça sous forme d’une méthode d’extension qui renvoie un IDisposable:

    public static class DependencyObjectExtensions
    {
        public static IDisposable WatchProperty(this DependencyObject target,
                                                string propertyPath,
                                                DependencyPropertyChangedEventHandler handler)
        {
            return new DependencyPropertyWatcher(target, propertyPath, handler);
        }

        class DependencyPropertyWatcher : DependencyObject, IDisposable
        {
            private DependencyPropertyChangedEventHandler _handler;

            public DependencyPropertyWatcher(DependencyObject target,
                                             string propertyPath,
                                             DependencyPropertyChangedEventHandler handler)
            {
                if (target == null) throw new ArgumentNullException("target");
                if (propertyPath == null) throw new ArgumentNullException("propertyPath");
                if (handler == null) throw new ArgumentNullException("handler");

                _handler = handler;

                var binding = new Binding
                {
                    Source = target,
                    Path = new PropertyPath(propertyPath),
                    Mode = BindingMode.OneWay
                };
                BindingOperations.SetBinding(this, ValueProperty, binding);
            }

            private static readonly DependencyProperty ValueProperty =
                DependencyProperty.Register(
                    "Value",
                    typeof(object),
                    typeof(DependencyPropertyWatcher),
                    new PropertyMetadata(null, ValuePropertyChanged));

            private static void ValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                var watcher = d as DependencyPropertyWatcher;
                if (watcher == null)
                    return;

                watcher.OnValueChanged(e);
            }

            private void OnValueChanged(DependencyPropertyChangedEventArgs e)
            {
                var handler = _handler;
                if (handler != null)
                    handler(this, e);
            }

            public void Dispose()
            {
                _handler = null;
                // There is no ClearBinding method, so set a dummy binding instead
                BindingOperations.SetBinding(this, ValueProperty, new Binding());
            }
        }
    }

On peut l’utiliser comme ceci:

// Abonnement
watcher = myControl.WatchProperty("Content", myControl_ContentChanged);

// Désabonnement
watcher.Dispose();

J’espère que vous trouverez cela utile!

css.php