Quantcast

comments edit

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

public class GenericService<T> : IGenericService<T> where T : EntityBase, new()
{
    public T GetEntity(string text)
    {
        var t = new T() { Text = text };
        t.Initialize();
        return t;
    }
}

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

<%@ ServiceHost 
    Language="C#"
    Service="WcfService.GenericService`1[[WcfInterfaces.SpecificEntity, WcfInterfaces]]" 
    CodeBehind="GenericService.cs" %>

Exemple complet sur : GitHub.

comments edit

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.

<Page
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <StackPanel>  
    <StackPanel.Resources>
      <Style TargetType="TextBlock">
        <Setter Property="Background" Value="Red" />
      </Style>
      <Style TargetType="TextBlock" x:Key="Custom1">
        <Setter Property="Foreground" Value="Blue" />
      </Style>
      <Style TargetType="TextBlock" x:Key="Custom2" BasedOn="{StaticResource {x:Type TextBlock}}">
        <Setter Property="Foreground" Value="Blue" />
      </Style>
    </StackPanel.Resources>
    <TextBlock>Defaut</TextBlock>
    <TextBlock Style="{StaticResource Custom1}">Custom 1 : background remis par defaut</TextBlock>
    <TextBlock Style="{StaticResource Custom2}">Custom 2 : background repris de Default</TextBlock>
  </StackPanel>
</Page>

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

<Page
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <StackPanel>  
    <StackPanel.Resources>
      <Style TargetType="TextBlock" x:Key="DefaultTextBlock">
        <Setter Property="Background" Value="Red" />
      </Style>
      <Style TargetType="TextBlock" 
             BasedOn="{StaticResource DefaultTextBlock}">
      </Style>
      <Style TargetType="TextBlock" x:Key="Custom1">
        <Setter Property="Foreground" Value="Blue" />
      </Style>
      <Style TargetType="TextBlock" x:Key="Custom2" 
             BasedOn="{StaticResource DefaultTextBlock}">
        <Setter Property="Foreground" Value="Blue" />
      </Style>
    </StackPanel.Resources>
    <TextBlock>Defaut</TextBlock>
    <TextBlock Style="{StaticResource Custom1}">Custom 1 : background remis par defaut</TextBlock>
    <TextBlock Style="{StaticResource Custom2}">Custom 2 : background repris de Default</TextBlock>
  </StackPanel>
</Page>

comments edit

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>

comments edit

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.

comments edit

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

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