vendredi 7 septembre 2012

"Model Binding" avec ASP.NET 4.5, Visual Studio 2012

.NET Framework 4.5 et Visual Studio 2012 sont maintenant disponibles depuis le 15 août 2012. Il est noter que cette version remplace totalement la version 4.0 même si les "breaking changes" sont minimisés. Ce billet va exposer brièvement la nouvelle fonctionnalité de "Model Binding" ajoutée aux WebForms.

La première étape sera d'ajouter à un nouveau projet web, la base SQL Server Compact C:\Program Files\Microsoft SQL Server Compact Edition\v4.0\Samples\Northwind.sdf, puis de créer un nouveau modèle "ADO.NET Entity Data Model". On peut noter en passant que ce modèle "database first" génère du code basé sur l'API DbContext (et non plus ObjectContext).

Ce modèle permet donc d'accéder à la base. Bien que cela soit un peu surperflu dans ce cas précis, nous allons maintenant créer un "ViewModel", chargé d'adapter spécifiquement le modèle permettant d'accéder à la base aux besoins particuliers de la vue que nous allons créer. Cette vue va être constitué d'une grille et d'un formulaire permettant d'éditer la ligne sélectionnée. Notre "ViewModel" va donc exposer la liste de toutes les catégories, la possibilité de sélectionner une catégorie en particulier, de la mettre à jour et de récupérer les erreurs éventuelles de mise à jour ce qui nous donne le code ci-dessous :

using System.Collections.Generic;
using System.Linq;
namespace WebApplication1
{
    public class CategoriesViewModel
    {
        private NorthwindEntities model = new NorthwindEntities();
        public string Error;
        public IQueryable Select()
        {
            return model.Categories;
        }
        public bool Update(Category category)
        {
            model.Entry(category).State = System.Data.EntityState.Modified;
            Error = "";
            try
            {
                model.SaveChanges();
            }
            catch (System.Data.Entity.Validation.DbEntityValidationException ex)
            {
                foreach (var v in ex.EntityValidationErrors)
                {
                    if (!v.IsValid)
                        foreach (var o in v.ValidationErrors)
                        {
                            Error += o.ErrorMessage;
                        }
                }
                return false;
            }
            return true;
        }
        public Category Select(object[] id)
        {
            return model.Categories.Find(id);
        }
    }
}

Notre vue va être constituée par :

<asp:GridView runat="server"
    ID="gridView1" 
    ItemType="WebApplication1.Category"
    AllowSorting="True"
    AutoGenerateSelectButton="true"
    DataKeyNames="Category_ID"
    SelectMethod="gridView1_SelectMethod" 
/>
<asp:FormView  runat="server" 
    ID="formView1" 
    ItemType="WebApplication1.Category"
    DataKeyNames="Category_ID"
    SelectMethod="formView1_SelectMethod"
    UpdateMethod="formView1_UpdateMethod">
    <ItemTemplate>
        <asp:Button ID="Button1" runat="server" CommandName="Edit" Text="Edit"/>
        <asp:Label runat="server" Text=">%#Item.Category_Name%>" ID="a"/>
    </ItemTemplate>
    <EditItemTemplate>
        <asp:Button ID="Button1" runat="server" CommandName="Update" Text="Update"/>
        <asp:TextBox runat="server" Text="<%#BindItem.Category_Name%>" ID="a" />
    </EditItemTemplate>

En plus des atrributs habituels des versions précédentes on notera :
  • ItemType qui permet d'indiquer le type de la donnée affichée
  • SelectMethod et UpdateMethod (éventuellement DeleteMethod, InsertMethod) qui permettent d'indiquer les procédures qui permettront de sélectionner, modifier, supprimer et ajouter les données depuis ces contrôles
  • Item et BindItem qui permettent d'accéder de façon fortement typé à l'entité à afficher dans le cadre d'une expression de "binding"

Finalement, ne reste que le "code-behind" qui va donc lier cette vue à notre "ViewModel" et qui se réduit à :

using System;
using System.Linq;
using System.Web;
using System.Web.UI;
namespace WebApplication1
{
    public partial class _Default : Page
    {
        private CategoriesViewModel categories = new CategoriesViewModel();
        public IQueryable gridView1_SelectMethod()
        {
            return categories.Select();
        }
        public Category formView1_SelectMethod([System.Web.ModelBinding.Control("gridView1", "SelectedValue")] object[] id)
        {
            return categories.Select(id);
        }
        public void formView1_UpdateMethod(Category a)
        {
            categories.Update(a);
            lbl.Text = categories.Error;
        }
    }
}
La sélection des données de la grille ne fait donc que sélectionner toutes les catégories. On remarque que la méthode de sélection de l'entité à afficher dans le contrôle FormView, utilise un attribut Control qui permet d'indiquer que le paramètre de cette méthode sera à extraire automatiquement de la propriété SelectedValue du contrôle gridView1 ce qui permet de récupérer la clé primaire de la catégorie concernée. Enfin la méthode Update se contente également d'appeler la méthode de mise à jour de notre "ViewModel", puis d'afficher les éventuelles erreurs.

Nous avons vu que cette nouvelle fonctionnalité de "Model Binding", avec le respect de certaines bonnes pratiques, devrait permettre de simplifier encore l'utilisation des WebForms ASP.NET en permettant de séparer plus clairement la vue et les méthodes de "Binding" qui l'alimentent et de réduire probablement le "code-behind" au rôle de "glue" entre la vue et le "ViewModel". A suivre...