JQuery.AutoComplete et ASP.NET MVC : Partie 2

by mathieu 31. July 2010 22:47

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 = new[] {
        new { value = 1, label = prefixWith + " Foo" },
        new { value = 2, label = prefixWith + " Bar" },
        new { value = 3, label = prefixWith + " Baz" }
    };

    return Json(data.Where(x => x.label.Contains(search)));
}

Dans la vue, la source de données passée devient plus complexe : il s’agit d’une requête avec $.ajax, 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 “data”. 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) {
                $.ajax({
                    url: '<%=Url.Action("AutoCompleteCustom")%>',
                    type: "POST",
                    dataType: "json",
                    data: {
                        search: request.term,
                        prefixWith: $("#PrefixWith").val()
                    },
                    success: function (data) {
                        response(data)
                    }
                })
            },
            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

Tags: ,

Code

JQuery.AutoComplete et ASP.NET MVC : Partie 1

by mathieu 28. July 2010 21:57

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 JsonResult AutoComplete(string term)
{
    var data = new[] {
        new { value = 1, label = "Foo" },
        new { value = 2, label = "Bar" },
        new { value = 3, label = "Baz" }
    };

    return Json(data.Where(x => x.label.Contains(term)), 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.

Tags: ,

Code

FluentNHibernate 1.1, Castle.DynamicProxy2 2.2.0 et Castle.Core 1.2.0

by mathieu 23. July 2010 20:32

Pour ceux qui rencontrent des problèmes à l’exécution (Castle.DynamicProxy2 2.1.0 attendu, et 2.2.0 trouvé), deux solutions s’offrent à vous :

- télécharger la dernière version du code source de FluentNHibernate, et la compiler en utilisant les références aux bonnes versions de Castle.Core, et Castle.DynamicProxy2

- ajouter les lignes suivantes dans votre web.config/app.config, dans la section “configuration” :

  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="Castle.DynamicProxy2" 
publicKeyToken="407dd0808d44fbdc" culture="neutral" />
        <bindingRedirect oldVersion="2.1.0.0" newVersion="2.2.0.0" />
      </dependentAssembly>
    </assemblyBinding>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="Castle.Core" 
publicKeyToken="407dd0808d44fbdc" culture="neutral" />
        <bindingRedirect oldVersion="1.1.0.0" newVersion="1.2.0.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>

Tags: , ,

Divers

VS 2010 Productivity Power Tools

by mathieu 21. July 2010 21:39

Disponible depuis quelques semaines, et mise à jour récemment, cette suite de “power tools” pour Visual Studio 2010 apporte son lot d’agréments :

Solution Navigator

Remplace avantageusement l’explorateur de solution pour parcourir les projets/fichiers d’une solution. En effet, ce nouvel onglet permet :

- de déplier les fichiers source, afin d’en afficher la liste des membres.
solution_explorer

- d’afficher uniquement les fichiers ouverts, ou les fichiers non sauvegardés.
solution_explorer_2

- les onglets prennent une couleur par projet, et il est possible de les épingler
solution_explorer_3

Power commands

Mon rêve, enfin exaucé : formater le document automatiquement, et trier/supprimer les using. Ceux qui utilisent régulièrement StyleCop comprendront tout l’intérêt de la chose.
solution_explorer_4

En conclusion, les fans de raccourcis claviers, et d’optimisation de leur productivité sous Visual Studio seront ravis. Le meilleur : c’est gratuit. Ceux qui n’ont pas de licence Reflector apprécieront !

Tags: ,

Outils

Création d’images zoomables avec DeepZoom.

by mathieu 14. July 2010 20:05

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

Tags: ,

Code

NHibernate entity generator

by Mathieu 26. August 2008 06:51

Avec un peu de retard, j'ai également publié un petit générateur de code pour NHibernate. A partir d'un fichier de mapping, celui-ci génère un peu tout ce qu'on veut à partir de templates modifiables.

Le moteur de template utilisé est SharpTemplate (http://www.codeplex.com/SharpTemplate).

Le projet se télécharge ici : http://www.codeplex.com/EntityGenerator.

Tags: ,

EntityGenerator

SimpleValidation.NET pour ASP.NET MVC

by Mathieu 26. August 2008 06:47

Toujours en beta, voir même proof of concept, j'ai publié une nouvelle version de SimpleValidation.NET, supportant ASP.NET MVC (Preview 4). La validation côté client se fait avec jquery.validation (http://docs.jquery.com/Plugins/Validation).

Pour le moment, seul les validators "required" et "email" sont implémentés coté client. Les autres viendront par la suite !

Le tout se télécharge ici : http://www.codeplex.com/SimpleValidation

Simple à utiliser, tout ce que vous avez à faire :

Créez votre entité

public class User
{
    private string name;
    private int age;
    private string email;
    private string password;
    private decimal size;

    [Required("Name is required")]
    public string Name
    {
        get { return this.name; }
        set { this.name = value; }
    }

    [Required("Age is required")]
    [Range(0, 99, "Age must be in range {0} {1}")]
    [ValidateType("Age format is not valid")]
    public int Age
    {
        get { return this.age; }
        set { this.age = value; }
    }

    [ValidateType("Size format is not valid")]
    public decimal Size
    {
        get { return this.size; }
        set { this.size = value; }
    }

    [Email("Email is invalid")]
    [Confirm("Please confirm your email")]
    [Required("Email is required")]
    public string Email
    {
        get { return this.email; }
        set { this.email = value; }
    }

    [Confirm("Please confirm your password")]
    [Required("Password is required")]
    public string Password
    {
        get { return this.password; }
        set { this.password = value; }
    }
}

Plutot explicite :)

Créez une vue

<%=Html.CreateValidators<Samples.Entities.User>("signupForm")%>

<%using( Html.Form("User", "New", FormMethod.Post, new Dictionary<string, object>() { { "id", "signupForm"} } ) ) { %>
    <div class="inputform">
        <fieldset>
            <legend>
                User
            </legend>
            <label>Name:</label>
            <div class="droite">
                <%= Html.TextBox("Name", ViewData["Name"] as string) %>
                <%= Html.ValidationErrors("Name") %>
            </div>
            
            <label>Password:</label>
            <div class="droite">
                <%= Html.TextBox("Password")%>
                <%= Html.ValidationErrors("Password")%>
            </div>
            
            <label>Confirm Password:</label>
            <div class="droite">
                <%= Html.TextBox("PasswordConfirm")%>
                <%= Html.ValidationErrors("PasswordConfirm")%>
            </div>

            <label>Age:</label>
            <div class="droite">
                <%= Html.TextBox("Age", ViewData["Age"] as string)%>
                <%= Html.ValidationErrors("Age")%>
            </div>
            
            <label>Size:</label>
            <div class="droite">
                <%= Html.TextBox("Size", ViewData["Size"] as string)%>
                <%= Html.ValidationErrors("Size")%>
            </div>

            <label>Email:</label>
            <div class="droite">
                <%= Html.TextBox("Email", ViewData["Email"] as string)%>
                <%= Html.ValidationErrors("Email")%>
            </div>

            <label>Confirm Email:</label>
            <div class="droite">
                <%= Html.TextBox("EmailConfirm", ViewData["EmailConfirm"] as string)%>
                <%= Html.ValidationErrors("EmailConfirm")%>
            </div>
            
            <div class="droite boutons">
                <input type="submit" value="Register" />
            </div>
        </fieldset>

    </div>
<%} %>

 

N'oubliez pas de referencer les js de jquery (http://jquery.com/) et jquery.validation (http://docs.jquery.com/Plugins/Validation) dans votre vue.

Créez votre controller

public class UserController : Controller
{
    public ActionResult Index()
    {
        return View("New");
    }

    public ActionResult New()
    {
        User u = new User();

        if (!MvcValidationHelper.UpdateFrom(u, Request.Form, ViewData))
        {
            return View();
        }

        return View("UserCreated", u);
    }
}

 

Et voila!

Tags: ,

SimpleValidation

Integration continue : Partie 5, Tests unitaires

by Mathieu 3. June 2008 09:21

Tout d'abord, un test unitaire, quesako ? Un test unitaire est un bout de code qui va tester un bout de code de votre application. Unitairement. La partie "unitairement" signifie qu'on va tester une "unité" de notre code, par exemple, une classe et ses méthodes. Si on commence à tester plusieurs classes ensemble, on parlera plutôt de tests d'intégration. Même si dans la pratique, l'outil (NUnit) restera le même.

Prenons une méthode "simple" :

public class StringUtil 
{ 
    public static string GetLength( string input ) 
    { 
        return input.Length; 
    }
}

Son test unitaire ressemblera à ceci (on ajoutera une référence à nunit.framework.dll, situé dans le répertoire d'installation de NUnit):

[TestFixture] 
public class StringUtilTest 
{ 
    [Test] 
    public void GetLenghTest() 
    { 
        Assert.That( StringUtil.GetLength( "ab" ) == 2 ); 
        Assert.That( StringUtil.GetLength( "abc" ) == 3 ); 
        Assert.That( StringUtil.GetLength( "abcd" ) == 4 ); 
    } 
} 

Jusqu'ici tout va bien, on vient de vérifier que le fonctionnement interne de notre méthode est correct, et nous avons 100% de couverture de code. Toutefois, 100% de couverture de code ne signifient pas 0% de bugs. En effet, les tests unitaires ne peuvent pas tester du code inexistant, comme un contrôle d'erreurs par exemple... Nous parlerons dans ce cas de couverture d'états.

Justement, un utilisateur nous soumet un bug : "lorsque je passe null à la méthode, elle plante avec un NullReferenceException !". Ici, deux solutions, soit nous décidons de renvoyer 0 si on passe null à notre fonction GetLength, soit on remonte une exception de type "ArgumentNullException". Partons sur la 2ème solution. Nous pourrions immédiatement modifier le code de notre méthode, mais tout d'abord, modifions notre test unitaire :

[code:c#]

[TestFixture] 
public class StringUtilTest 
{ 
    [Test] 
    public void GetLenghtTest() 
    { 
        Assert.That( StringUtil.GetLength( "ab" ) == 2 ); 
        Assert.That( StringUtil.GetLength( "abc" ) == 3 ); 
        Assert.That( StringUtil.GetLength( "abcd" ) == 4 ); 
    } 

    [Test] 
    [ExpectedException(typeof(ArgumentNullException))] 
    public void GetLenghtArgNulTest 
    { 
        StringUtil.GetLength( null ); 
    } 
} 

Au lancement du jeu de test, GetLenghtArgNulTest devra logiquement échouer : on attend un ArgumentNullException, on reçoit un NullReferenceException. C'est évidemment voulu : nous utilisons les tests unitaires pour reproduire un "bug" avant de corriger celui-ci. Cela permet la non régression : une fois un bug soumis, un test est écrit, et si ce test échoue après la correction du bug, on a régressé. Nous n'augmentons pas encore notre couverture de code : le bug n'est pas corrigé. Par contre, nous améliorons notre couvertures d'états.

Corrigeons maintenant notre code :

public class StringUtil 
{ 
    public static string GetLength( string input ) 
    { 
        if( input == null ) throw new ArgumentNullException( "Merci de ne pas passer de null!"); 
        return input.Length; 
    } 
} 

Tout est maintenant au vert!

Pourquoi faire tout cela, écrire plein de lignes de code en plus, maintenir des tests unitaires ? Pour la fiabilité, et la maintenance future de l'application : une modification dans une partie de l'application non couverte pas les tests unitaires est une modification "sans filet". Il n'est pas possible, avant de tester manuellement, de savoir si la modification a introduit un bug. Si je modifie le fonctionnement interne de GetLength, j'aurai les tests unitaires qui m'indiqueront que, dans notre test, dans le cas des paramètres passés en entrée, les valeurs de retour sont bonnes.

On peut également utiliser NUnit pour "tester" une application avant même d'avoir une interface graphique. Le test d'une couche d'accès au données s'en trouve simplifié.

Enfin, un code qui est écrit à l'avance pour être testable est souvent modularisé et architecturé plus clairement : séparation des rôles, utilisation de couches et d'interfaces.

Même si ce billet peut paraître simple, voire simpliste dans son approche des tests unitaires, j'espère qu'il vous aura donné envie d'essayer.

Tags:

Integration Continue

Integration continue : Partie 4, Microsoft Source Analysis Tool for C#

by Mathieu 25. May 2008 22:13

Initialement, la partie 4 devait parler de tests unitaires, mais la sortie récente de cet outil mérite qu'on fasse un petit détour. Concrètement, MSAT va effectuer une analyse statique de votre code. Il va donc vous indiquer les parties de votre code qui ne se conforment pas à un standard de codage. Il est possible de le configurer selon ses besoins (par exemple, la règle qui dit que les tabulations c'est mal à l'air de faire couler beaucoup d'encre...). Donc attention, le but d'un tel outil n'est pas de définir une norme de codage pour tous les développeurs C# du monde (rien que ça), mais plutôt de maintenir une cohérence au sein des équipes travaillant sur un même projet.

Installation de l'outil

Microsoft Source Analysis Tool for C# peut être téléchargé ici : http://code.msdn.microsoft.com/sourceanalysis/Release/ProjectReleases.aspx?ReleaseId=1047. L'installation est très simple, il faut juste s'assurer que la partie MSBuild est bien installée, sinon, pas d'intégration dans notre processus de build.

Intégration dans vos projets

Une fois l'outil installé, une nouvelle entrée apparait dans le menu "Outils" de Visual Studio : "Run Source Analysis". Elle vous permet d'exécuter l'analyse du code directement dans Visual Studio, et affichera les résultats dans l'onglet "Source Analysis". Un double clc sur un message vous emmene sur la ligne concerné. Rien d'inhabituel donc.

Il est également possible d'intégrer l'analyse de votre code source directement dans le procéssus de build, et là, celà devient vraiment intéressant : lorsqu'un développeur lancera une compilation, toutes les erreurs liées à l'analyse du code source apparaitront en tant que warning dans l'onglet "Liste d'erreurs".

Pour permettre cela, deux solutions :

Si MSAT est installé sur tous les postes de développeur

Ouvrez chaque fichier .csproj pour lequel vous souhaitez analyser le code, et modifiez le de la façon suivante :

<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> 
    [...] 
    <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> 
    <Import Project="$(ProgramFiles)\MSBuild\Microsoft\SourceAnalysis\v4.2\Microsoft.SourceAnalysis.targets" /> 
    [...] 
</Project> 

Si MSAT n'est pas installé sur tous les postes de développeur

Pour faire suite au billet sur l'arborescence projet, vous pouvez maintenant rajouter un autre répertoire à la racine de la solution : "External" (par exemple... vous pouvez aussi l'appeler Tools, ou Trucs, peu importe!). Créez un répertoire "SourceAnalysis", et copiez y le contenu du répertoire "C:\Program Files\Microsoft Source Analysis Tool for C#". Maintenant, ouvrez chaque fichier .csproj pour lequel vous souhaitez analyser le code, et modifiez le de la façon suivante :

<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> 
    [...] 
    <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> 
    <Import Project="..\..\External\SourceAnalysis\Microsoft.SourceAnalysis.targets" /> 
    [...] 
</Project> 

Ensuite, intégrez ce répertoire dans le contrôle de code source. Les développeurs bénéficieront de l'analyse de code source dès qu'ils mettront leur copie locale à jour. Lors du premier rechargement du projet, vous obtiendrez un message d'avertissement :

avertissement_securite  

Choisissez de charger normalement le projet.

Et voilà, l'analyse statique intégrée dans Visual Studio, et dans le processus de compilation (et dans CruiseControl, de fait!).

Tags:

Integration Continue

Integration continue : Partie 3, Arborescence Projet

by Mathieu 22. May 2008 05:55

Arborescence projetAvant d'aller plus loin, je vais rapidement décrire l'arborescence projet que je vais utiliser pour les billets à venir (cf image):

  • Apps : applications Winforms, Console, WebForms, tout ce qui est exécutable
  • Libs : toutes les bibliothèques de classes, ou tout ce qui n'est pas exécutable
  • References : toutes les librairies externes référencéés dans votre solution (NHibernate, Castle Project, etc)(non présent dans la solution visual studio, uniquement dans l'arborescence "physique")
  • Scripts : scripts de compilation, de création de base de données
  • Tests : tests unitaires

Important : si vous créez des dossiers de solution dans Visual Studio, attention lors de la création d'un nouveau projet : par défaut, celui-ci est placé à la racine de votre dossier de solution. Il en est de même pour les éléments de solution (par ex : Demo.config et Demo.nunit), il est préférable de les créer physiquement dans le dossier voulu, et de les ajouter à la solution dans un deuxième temps (via "Ajouter un élément existant").

Cette arborescence n'est évidemment pas figée dans ses termes, il s'agit juste de "classer" un peu notre solution. Il est également possible de créer des sous dossiers, comme par exemple "Console" ou "WebApps" dans "Apps".

Concernant les librairies externes, j'ai pris le parti de les lier à chaque solution : cela permet d'éviter les problèmes lors d'une mise à niveau de librairie. Si tous vos projets référencent la meme librairie (NHibernate par exemple), qu'une mise à jour avec un changement d'API de leur part est effectuée, tous vos projets sont impactés. En liant les librairies par projet, vous pouvez effectuer les mises à jour dans chaque projet, si le besoin s'en fait ressentir.

Tags:

Integration Continue