Quantcast

comments edit

The source repository for this article is on Github.

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 :

public class FormElement
{
    public string Name { get; set; }
    public object Value { get; set; }
}
 
public class FormElement<T> : FormElement
{ 
}
 
public class DateElement : FormElement<DateTime>
{ 
}
 
public class StringElement : FormElement<string>
{ 
}

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

<DataTemplate DataType="{x:Type Elements:DateElement}">
    <DockPanel>
        <TextBlock Text="{Binding Name}" DockPanel.Dock="Left" Width="80"/>
        <DatePicker SelectedDate="{Binding Value}" />
    </DockPanel>
</DataTemplate>
 
<DataTemplate DataType="{x:Type Elements:StringElement}">
    <DockPanel>
        <TextBlock Text="{Binding Name}" DockPanel.Dock="Left" Width="80" />
        <TextBox Text="{Binding Value}" />
    </DockPanel>
</DataTemplate>

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

<ItemsControl ItemsSource="{Binding Parameters}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <StackPanel IsItemsHost="True" />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
</ItemsControl>

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

comments edit

The source repository for this article is on Github.

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" %>

comments edit

The source repository for this article is on Github.

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

The source repository for this article is on Github.

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

The source repository for this article is on Github.

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.