cogimator.net

Une ligne à la fois...

Implémenter simplement un formulaire dynamique avec WPF

En s'appuyant sur les DataTemplates et les capacités de binding de WPF, on peut très facilement mettre en place des formulaires dynamiques. La première étape est de définir les classes qui serviront à définir les éléments de notre formulaire :

Ensuite, on va définir les DataTemplates qui vont permettre leur affichage :

Et pour finir, il reste juste a ajouter les éléments dans un conteneur :

Tout le reste est géré par le WPF, qui, au travers des DataTemplates et du Binding affichera les contrôles nécessaires pour remplir nos éléments.

Le code source complèt est disponible sur : https://github.com/mathieubrun/Cogimator.Samples

Implémentation d'un service WCF générique

Parfois on peut être améné a déclarer un service de manière générique :

Toutefois, le service ainsi déclaré ne pourra pas être utilisé dans IIS sans modifier la déclaration dans le fichier .svc :

Exemple complet sur : https://github.com/mathieubrun/Cogimator.Samples

Hériter un style WPF par défaut

Parfois il peut arriver d’avoir besoin de baser un style sur un autre déclaré sans x:Key (s'applicant donc a tous les éléments correspondant à l'attribut TargetType. Ici; la clé est de déclarer l'attribut BasedOn avec non pas une ressource, mais le type de l'élément sur lequel appliquer le style.

Cela a le mérite d’être plus concis et plus explicite que le pattern suivant :

Comment utiliser protobuf-net avec WCF

Protobuf-net est l'implémentation .NET de protocol buffers mis au point par Google, qui l'utilise comme protocole de communication pour ses échanges de données. L'objectif est d'obtenir une sérialisation binaire de faible taille (largement moindre qu'une sérialisation XML par exemple), et peu coûteuse a sérialiser et désérialiser, autant coté serveur que client.

L'intérêt de protobuf-net est le gain en performances offert, grâce a une sérialisation plus efficace que le DataContractSerializer utilisé par défaut dans WCF.En utilisant des entités Linq-to-Sql générées à partir de la base Northwind :

  • Sérialisation 3x plus rapide
  • Désérialisation 4x plus rapide
  • Taille 3.5x moindre

Pour l'implémenter rapidement dans un projet existant (afin de valider un gain en performances substantiel), 3 étapes principales a suivre :

Placer un attribut sur les classes devant être sérialisées :

[DataContract]
[ProtoBuf.ProtoContract(ImplicitFields = ProtoBuf.ImplicitFields.AllFields)]
public class CompositeType
{
	public CompositeType()
	{
		this.Children = new List<CompositeTypeChild>
		{
			new CompositeTypeChild { IntValue = 1234, StringValue = "1234" },
			new CompositeTypeChild { IntValue = 5678, StringValue = "5678" }
		};
	}

	[DataMember]
	public int IntValue { get; set; }

	[DataMember]
	public string StringValue { get; set; }
	
	[DataMember]
	public List<CompositeTypeChild> Children { get; set; }
}

[DataContract]
[ProtoBuf.ProtoContract(ImplicitFields = ProtoBuf.ImplicitFields.AllFields)]
public class CompositeTypeChild
{
	[DataMember]
	public int IntValue { get; set; }

	[DataMember]
	public string StringValue { get; set; }
}

Modifier la configuration du serveur, en rajoutant un endpointBehavior

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.serviceModel>
    <services>
      <service name="WcfService.Service" behaviorConfiguration="WcfService.ServiceBehavior">
        <endpoint address="" binding="basicHttpBinding" contract="WcfService.IService" behaviorConfiguration="protoEndpointBehavior" />
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="WcfService.ServiceBehavior">
          <serviceMetadata httpGetEnabled="true"/>
          <serviceDebug includeExceptionDetailInFaults="true"/>
        </behavior>
      </serviceBehaviors>
      <endpointBehaviors>
        <behavior name="protoEndpointBehavior">
          <protobuf/>
        </behavior>
      </endpointBehaviors>
    </behaviors>
    <extensions>
      <behaviorExtensions>
        <add name="protobuf" type="ProtoBuf.ServiceModel.ProtoBehaviorExtension, protobuf-net"/>
      </behaviorExtensions>
    </extensions>
  </system.serviceModel>
</configuration>

Modifier la configuration du client

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <client>
      <endpoint address="http://localhost:1085/Service.svc" binding="basicHttpBinding" contract="ServiceReference.IService"
          behaviorConfiguration="protoEndpointBehavior"
          name="BasicHttpBinding_IService" />
    </client>
    <behaviors>
      <endpointBehaviors>
        <behavior name="protoEndpointBehavior">
          <protobuf/>
        </behavior>
      </endpointBehaviors>
    </behaviors>
    <extensions>
      <behaviorExtensions>
        <add name="protobuf" type="ProtoBuf.ServiceModel.ProtoBehaviorExtension, protobuf-net"/>
      </behaviorExtensions>
    </extensions>
  </system.serviceModel>
</configuration>

Et le résultat dans Fiddler :

Avant :

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Body>
    <GetDataUsingDataContractResponse xmlns="http://tempuri.org/">
      <GetDataUsingDataContractResult xmlns:a="http://schemas.datacontract.org/2004/07/WcfService" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
        <a:Children>
          <a:CompositeTypeChild>
            <a:IntValue>1234</a:IntValue>
            <a:StringValue>1234</a:StringValue>
          </a:CompositeTypeChild>
          <a:CompositeTypeChild>
            <a:IntValue>5678</a:IntValue>
            <a:StringValue>5678</a:StringValue>
          </a:CompositeTypeChild>
        </a:Children>
        <a:IntValue>1234</a:IntValue>
        <a:StringValue>1234</a:StringValue>
      </GetDataUsingDataContractResult>
    </GetDataUsingDataContractResponse>
  </s:Body>
</s:Envelope>

Apres :

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Body>
    <GetDataUsingDataContractResponse xmlns="http://tempuri.org/">
      <proto>CgkI0gkSBDEyMzQKCQiuLBIENTY3OAoJCNIJEgQxMjM0CgkIriwSBDU2NzgQ0gkaBDEyMzQ=</proto>
    </GetDataUsingDataContractResponse>
  </s:Body>
</s:Envelope>

.

 

 

JQuery.AutoComplete et ASP.NET MVC : Partie 5

Pour terminer cette série, voici un extrait de code permettant de créer simplement une zone de texte avec autocompletion.

En premier, le code de la méthode d’extension :

using System;
using System.Linq.Expressions;
using System.Web.Mvc;
using System.Web.Mvc.Html;

namespace Cogimator.Demos.Mvc
{
    /// <summary>
    /// Classes helper pour autocompletion
    /// </summary>
    public static class AutoCompleteExtensions
    {
        /// <summary>
        /// Genere un editeur avec autocompletion
        /// </summary>
        /// <typeparam name="TModel">Type de Model</typeparam>
        /// <typeparam name="TDisplayValue">Type de la propriété utilisée 
        /// pour affichage</typeparam>
        /// <param name="html">helper</param>
        /// <param name="displayExpression">Expression pour indiquer la 
        /// propriété à utiliser</param>
        /// <param name="action">Action du Controler courant renvoyant du 
        /// JSON</param>
        /// <returns>Chaine HTML</returns>
        public static string AutoCompleteFor<TModel, TDisplayValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TDisplayValue>> displayExpression, string action)
        {
            var res = html.EditorFor(displayExpression, null).ToHtmlString();

            var displayName = ModelMetadata.FromLambdaExpression(displayExpression, html.ViewData).PropertyName;

            res += string.Format(
                @"<script type=""text/javascript"">
                    $(document).ready(function() {{
                        $(""#{0}"")
                            .autocomplete({{
                                source: function (request, response) {{
                                    $.post(""{1}"",
                                        {{
                                            term: request.term
                                        }}, 
                                        function (result) {{
                                            response(result)
                                        }})
                                }},
                                minLength: 1,
                                select: function(event, ui) {{
                                    $('#{0}').val(ui.item.label);
                                    return false;
                                }}
                            }});
                    }});
                </script>",
                html.ViewData.TemplateInfo.GetFullHtmlFieldId(displayName),
                UrlHelper.GenerateUrl(null, action, null, null, html.RouteCollection, html.ViewContext.RequestContext, true));

            return res;
        }
    }
}

Un modèle simple :

namespace Cogimator.Demos.FrontEnd.Mvc.Models
{
    public class SimpleModel
    {
        public string Label { get; set; }
    }
}

Dans la partie configuration/pages/namespace des fichiers web.config situé dans le dossier racine et dans le dossier Views de votre application MVC, n’oubliez pas d’indiquer le namespace de vos méthodes d’extension et de vos modeles :

<add namespace="Cogimator.Demos.Mvc" />
<add namespace="Cogimator.Demos.FrontEnd.Mvc" />

Et la vue :

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<SimpleModel>" %>
<asp:Content ContentPlaceHolderID="plhCenter" runat="server">
    <%using (var f = Html.BeginForm()) { %>
    <h2>Extension</h2>
    <p>Champ : <%=Html.AutoCompleteFor( x=> x.Label, "AutoComplete") %></p>
    <%} %> 
</asp:Content>

Ceci termine cette série sur JQuery.UI.AutoComplete et ASP.NET MVC.

JQuery.AutoComplete et ASP.NET MVC : Partie 4

Pour clore cette série, nous allons présenter les résultats avec des catégories. Comme précédemment, commençons par l’action de notre Controller :

public JsonResult AutoCompleteCategorized(string search){
    var data = GenerateData()
        .Where(x => x.Libelle.Contains(search))
        .Select(x => new
        {
            value = x.Id,
            label = x.Libelle,
            category = x.Category
        });
     return Json(data);
}

Suivie de la vue, dans laquelle on pourra noté que lors de l’appel de $().autocomplete(), le résultat est placé dans une variable, afin de pouvoir personnaliser plus facilement les fonctions de rendu.

<%using (var f = Html.BeginForm()) { %>
<p>Categorized</p>
<%=Html.TextBox("Libelle_Categorized")%>
<%=Html.TextBox("Id_Categorized")%>
<%} %>
<script type="text/javascript">
$(document).ready(function () {
    var ac_categorized = $('#Libelle_Categorized').autocomplete({
        minLength: 1,
        source: function (request, response) {
            $.post('<%=Url.Action("AutoCompleteCategorized") %>',
                {
                    search: request.term
                },
                function (result) {
                    response(result)
                })
        },
        select: function (event, ui) {
            $('#Libelle_Categorized').val(ui.item.label);
            $('#Id_Categorized').val(ui.item.value);
            return false;
        }
    });
    ac_categorized.data("autocomplete")._renderItem = function (ul, item) {
        return $("<li></li>")
        .data("item.autocomplete", item)
        .append("<a>" + item.label + "</a>")
        .appendTo(ul);
    };
    ac_categorized.data("autocomplete")._renderMenu = function (ul, items) {
        var self = this;
        var currentCategory = "";
        $.each(items, function (index, item) {
            if (item.category != currentCategory) {
                ul.append("<li class='ui-autocomplete-category'>"
                     + item.category + "</li>");
                currentCategory = item.category;
            }
            self._renderItem(ul, item);
        });
    };
});
</script>

Et le résultat :

AutoComplete_5

Pour terminer cette série, nous verrons comment créer une méthode d’extension pour capitaliser les cas simples (libellé et identifiant).

JQuery.AutoComplete et ASP.NET MVC : Partie 3

Cette fois ci, nous allons personnaliser les objets retournés par notre Controller :

public JsonResult AutoCompleteCustomJson(string search, string prefixWith)
{
    var data = GenerateData()
        .Where(x => x.Libelle.Contains(search))
        .Select(x => new
        {
            Id = x.Id,
            Libelle = x.Libelle,
            Prefix = prefixWith
        });

    return Json(data);
}

Pour que ces modifications soient prises en compte dans notre vue, il faudra modifier la fonction dans “source”, et remplacer la fonction “_renderItem”.

La fonction “$.map” permet de transformer la liste d’objets JSON reçu dans une autre liste, dans le cas où l’on souhaiterait renommer encore une fois les propriétés des objets de notre liste.
La fonction “_renderItem” permet de contrôler le rendu de la liste d’auto complétion.

<%using (var f = Html.BeginForm()) { %>
    <%=Html.TextBox("Libelle_CustomJson")%>
    <%=Html.TextBox("PrefixWithJson")%>
    <%=Html.TextBox("Id_CustomJson")%>
<%} %>

<script type="text/javascript">
    $(document).ready(function () {
        $('#Libelle_CustomJson').autocomplete({
            minLength: 1,
            source: function (request, response) {
                $.post('<%=Url.Action("AutoCompleteCustomJson") %>',
                    {
                        search: request.term,
                        prefixWith: $("#PrefixWithJson").val()
                    }, 
                    function (result) {
                        response(result)
                    })
            },
            select: function (event, ui) {
                $('#Libelle_CustomJson').val(ui.item.Libelle);
                $('#Id_CustomJson').val(ui.item.Id);
                return false;
            }
        }).data("autocomplete")._renderItem = function (ul, item) {
            return $("<li></li>")
            .data("item.autocomplete", item)
            .append("<a>" + item.Prefix 
                    + "<br>" + item.Libelle + "</a>")
            .appendTo(ul);
        };
     });
</script>

Le résultat :

AutoComplete_4

JQuery.AutoComplete et ASP.NET MVC : Partie 2

Dans le billet précédent, nous avons vu comment réaliser une auto complétion sur une TextBox, avec ASP.NET MVC 2.

Nous allons maintenant voir comment personnaliser cet exemple, pour envoyer un deuxième paramètre en plus de la valeur saisie.

Comme dans la partie 1, l’action du Controller, avec deux paramètres. De plus, le paramètre JsonRequestBehavior a été retiré.

public JsonResult AutoCompleteCustom(string search, string prefixWith)
{
    var data = GenerateData()
        .Where(x => x.Libelle.Contains(search))
        .Select(x => new
        {
            value = x.Id,
            label = prefixWith + " " + x.Libelle
        });
 
    return Json(data);
}

Dans la vue, la source de données passée devient plus complexe : il s’agit d’une requête avec $.post, qui nous permet de passer en HTTP POST (d’où le retrait de JsonRequestBehavior.AllowGet), mais surtout de nommer les paramètres passés, via un objet anonyme. Ici les paramètres reprennent les noms des paramètres de notre action : “search�� et “prefixWith”.

Enfin, les données renvoyées par le Controller sont passés a jquery.ui.autocomplete par la fonction “response”.

<%using (var f = Html.BeginForm()) { %>
    <%=Html.TextBox("Libelle_Custom")%>
    <%=Html.TextBox("PrefixWith")%>
    <%=Html.TextBox("Id_Custom")%>
<%} %>

<script type="text/javascript">
    $(document).ready(function () {
        $('#Libelle_Custom').autocomplete({
            minLength: 1,
            source: function (request, response) {
                $.post('<%=Url.Action("AutoCompleteCustom") %>', 
                    {
                        search: request.term,
                        prefixWith: $("#PrefixWith").val()
                    },
                    function (result) {
                        response(result);
                    })
            },
            select: function (event, ui) {
                $('#Libelle_Custom').val(ui.item.label);
                $('#Id_Custom').val(ui.item.value);
                return false;
            }
        });
    });
</script>

Et le résultat :

AutoComplete_3

JQuery.AutoComplete et ASP.NET MVC : Partie 1

Pour implémenter facilement une autocompletion sur une TextBox dans vos applications ASP.NET MVC, vous aurez besoin de :

  • JQuery
  • JQuery.UI (contient le plugin autocomplete)
  • ASP.NET MVC 2

Premiere étape, créer une action dans le controller. Par défaut, jquery.ui.autocomplete envoie la valeur saisie dans une textbox dans le paramètre “term”. Toujours par défaut, les objets JSON renvoyés doivent posséder une propriété value et une propriété label.

public IEnumerable<Data> GenerateData()
{
    return new List<Data>() 
    {
        new Data() { Id = 11, Libelle = "Foo 1", Category = "Foos" },
        new Data() { Id = 12, Libelle = "Foo 2", Category = "Foos" },
        new Data() { Id = 13, Libelle = "Foo 3", Category = "Foos" },
        new Data() { Id = 21, Libelle = "Bar 1", Category = "Bars" },
        new Data() { Id = 22, Libelle = "Bar 2", Category = "Bars" },
        new Data() { Id = 23, Libelle = "Bar 3", Category = "Bars" },
        new Data() { Id = 31, Libelle = "Baz 1", Category = "Bazs" },
        new Data() { Id = 32, Libelle = "Baz 2", Category = "Bazs" },
        new Data() { Id = 33, Libelle = "Baz 3", Category = "Bazs" }
    };
}

public JsonResult AutoComplete(string term)
{
    var data = GenerateData()
        .Where(x => x.Libelle.Contains(term))
        .Select(x => new
        {
            value = x.Id,
            label = x.Libelle
        });
 
    return Json(data, JsonRequestBehavior.AllowGet);
}

Ici, le jeu de donnée est crée à la volée, filtré, et renvoyé sous forme JSON. Le paramètre  JsonRequestBehavior.AllowGet permet de renvoyer les resultats JSON suite à une requête HTTP GET.

Dans la vue :

<%using (var f = Html.BeginForm()) { %>
    <%=Html.TextBox("Libelle_Simple")%>
    <%=Html.TextBox("Id_Simple")%>
<%} %>

<script type="text/javascript">
    $(document).ready(function () {
        $('#Libelle_Simple').autocomplete({
            minLength: 1,
            source: "<%=Url.Action("AutoComplete") %>",
            select: function (event, ui) {
                $('#Libelle_Simple').val(ui.item.label);
                $('#Id_Simple').val(ui.item.value);
                return false;
            }
        });
    });
</script>

Dans le code JavaScript :

- minlength représente la longueur minimale devant être saisie pour déclencher une rêquete d’autocompletion
- source sera l’url de l’action de notre controller renvoyant le JSON
- select est la fonction exécutée lors du clic sur un élément. Ici, on va placer les valeurs des propriétés label et value dans leurs TextBox respectives.

Il ne nous reste plus qu’à tester :

AutoComplete_1

Lorsque que l’on sélectionne un élément, la valeur de sa propriété value est renseigné dans la deuxième TextBox

AutoComplete_2

Dans les prochains billets, nous verrons comment capitaliser cela dans une méthode d’extension de HtmlHelper, et comment personnaliser les paramètres envoyés par JQuery.

Création d’images zoomables avec DeepZoom.

DeepZoom est une technologie Microsoft permettant de visionner des images de très grande taille. Pour le web, cela permet d’éviter le téléchargement d’une image complète avant de pouvoir la visualiser.
Plus d’informations sur DeepZoom ici : http://msdn.microsoft.com/fr-fr/library/cc645077%28v=VS.95%29.aspx

Afin de pouvoir convertir vos images, vous devrez disposer de DeepZoom Composer : http://www.microsoft.com/downloads/details.aspx?familyid=457b17b7-52bf-4bda-87a3-fa8a4673f8bf&displaylang=en
Une fois l’installation effectuée, vous pourrez référencer DeepZoomTools.dll.

Une fois la dll DeepZoomTools référencée, vous pouvez convertir vos images avec la classe ImageCreator :

var ic = new ImageCreator();
ic.Create("input.jpg", "output");

La conversion d’une image est relativement gourmande en CPU et en entrée/sortie disque, du fait des nombreuses images crées.

Néanmoins, l’objet ImageCreator propose quelques propriétés permettant de modifier la taille des fichiers en sortie :

TileSize

Par défaut 256.  Une valeur de 512 produira des “tuiles” plus grandes et permettra de réduire les temps de conversion. Le nombre de fichiers JPG produits sera également réduit.

ConversionImageQuality

Par défaut 0.8, définit la compression JPG des images de sortie. Une valeur plus faible (0.7 par exemple) produira des fichiers plus petits, au prix d’une perte de qualité.

MaxLevel

Pour une image de 3072*2304, l’outil générera 12 niveaux (soit 12 sous dossiers avec les “tuiles”). Cette valeur définit donc le niveau de zoom maximum. Une valeur plus faible accélérera grandement la vitesse de conversion. En effet, plus le niveau de zoom est élevé, plus le nombre de fichiers JPG produit sera grand.

Pourquoi modifier ces paramètres ?

Dans le cas de conversion à la volée, ou de conversion en masse, il peut devenir intéressant d’optimiser les temps de conversion. De la même manière, si les images converties sont beaucoup consultées, il est important de tenir compte de la taille et du nombre des fichiers de sortie.

Le programme ci joint met en évidence l’influence de chaque paramètre sur le temps de conversion, la taille de l’image convertie, ainsi que le nombre de fichiers générés.

Liens utiles