Publié jeudi 9 octobre 2008 23:39 par Rui

MVC Pratique #07 - Un projet concret et le transfert des objets avec les ModelBinders

Petit rappel des épisodes précédents:

Soit pour résumer les différents posts, nous avons vus rapidement le principe de MVC, la création d’un premier projet, les problématiques pratiques lors du déploiement d’un site en prod, la création de controles personalisés, comment fournir des données à la page par json et comment adapter la vue en fonction du besoin.

Néanmoins avant d’aller plus loin, il convient d’éclaircir un point qui peut paraitre obscur au premier abord:  Oui on peut travailler avec des objets et un fort typage à quelque niveau que ce soit dans le modèle MVC y compris au niveau de la vue. Pourquoi je dis ça? Tout simplement parce que la réaction première quand on voit du code MVC avec un appel de fonction et passage de paramètres par url d’un coté et l’écriture de code procédural directement au milieu du code Html, est que ce n’est pas naturel voir pas possible.

En fait, MVC fournit la tuyauterie nécessaire et même plus pour le passage d'objets entre les différents blocs du modèle.

Prennons un exemple. J'ai une application web qui doit me servir a gérer mes services Windows. Je reprendrai cet exemple dans les posts suivants et fournirait ça dès que j'aurais le packager ça plus ou moins proprement l'application complète. Donc, le principa est assez simple en soit mais permet de voir un modèle qui parle à tout le monde et qui nous apporte une problématique supplémentaire sur le traitement de l'asynchrone car 1) les services peuvent mettre du temps à démarrer, il s'agit donc de ne pas bloquer l'utilisateur et 2) je veux pouvoir monitorer mes services en temps réel et donc l'application doit pouvoir faire simplement du push dans ma page.

Voici la structure de mon projet:

09-10-2008 22-13-32

Nous avons donc le site web Asp.Net MVC qui nous intéresse, une couche business qui s'occupe de la gestion des services et qui nous intéresse un peu moins et une librairie d'outils dont nous reviendrons dessus plus tard.

Le modèle de données qui transite entre mes couche permet d'avoir toutes les informations nécessaires et suffisantes à la manipulation de mes services:
public class ServiceResponse
{
    public string Host { get; set; }
    public string Service { get; set; }
    public string StatusDemanded { get; set; }
    public string CurrentStatus { get; set; }
    public bool UpdateSuccess { get; set; }
    public string Name { get; set; }

    public override string ToString()
    {
        string id = Host + @"\" + Name;
        return id.EncodeTo64();
    }
}
Ce modèle contient de plus une surcharge au niveau du ToString() car n'ayant pas d'identifiant simple d'un service je l'isole de façon unique par le couple Host+service Name. C'est justement cet identifiant qui va nous permettre d'identifier un objet entre les couches.

Dans le modèle MVC tel qu'il est conçu aujourd'hui on fait transiter un identifiant d'objet qui va nous permettre soit de le recontistuer à partir des informations présentes dans l'identifiant soit d'aller par exemple le rechercher en base pour le traiter si cela colle avec votre besoin.
Quoi qu'il en soit cet identifiant doit être ressorti de l'objet par le ToString. Pourquoi me direz-vous avec raison? Parce que c'est cette méthode qui est automatiquement appelée lorque vous utilisez un objet du modèle dans votre vue:

09-10-2008 22-43-14

Lors du rendu de mon user control qui s'occupe du rendu de mon service, le système appele la méthode ToString de l'objet du modèle. Je me rends compte que je n'ai pas précisé un point très important lors de mes précédents posts, les vues peuvent elles aussi être typées. Par exemple ici j'ai un user control de type ServiceResponse:

public partial class ServiceItemControl : System.Web.Mvc.ViewUserControl<ServiceResponse>
{
}
Ceci permet directement d'indiquer à Asp Mvc que mon conteneur de données ViewData.Model est de type ServiceResponse.

Donc comme on le voit ici, c'est bien un objet que j'utilise dans ma page et tout est typé:

09-10-2008 22-53-48
  1. J'appelle une méthode qui me crée un lien d'action (ce qui me génère une route donc...)
  2. Cette action fait partie du controlleur "ServicesController"
  3. C'est la méthode Stop(ServiceResponse) que j'appelle par lamba expression
  4. Je lui passe en paramètre mon modèle qui est comme on vient de le voir de type ServiceResponse
  5. Le lien généré aura le texte "Stop"
Le lien généré, sera alors quelque chose comme: http://localhost:2959/Services/Stop?e=LlxNU1NRTFNFUlZFUg%3D%3D (on utilise assez souvent un encodage base64 pour rendre notre paramètre un peu plus anonyme).
Tout ça n'étant que la première partie qui montre l'usage du modèle au niveau de la vue, maintenant que va devenir cette donnée?

En fait de la même manière que l'on défini les routes applicatives possibles dans son application on déclare des modèles de mapping de données. Nous avons utilisé au niveau de notre page une fonction qui utilise un paramètre typé en entrée. Mvc va donc s'attendre à recevoir ce type d'objet lors de l'appel de l'action. Il faut donc lui indiquer comment transformer les paramètres reçus au travers la route vers un type d'objet, c'est le rôle des ModelBinders.

Tout d'abord il faut créer le ModelBinder qui va être capable en fonction du contexte de ressortir des objets de type ServiceResponse:
protected override object ConvertType(CultureInfo culture, object value, Type destinationType)
{
    if (destinationType != typeof(ServiceResponse))
    {
        return base.ConvertType(culture, value, destinationType);
    }
    string path = value as string;
    if (path == null && value is string[])
    {
        path = ((string[])value)[0];
    }
    else
    {
        return base.ConvertType(culture, value, destinationType);
    }
    path = path.DecodeFrom64();
    string[] data = path.Split(@"\".ToCharArray());
    return new ServiceResponse() { Host = data[0], Service = data[1] };
}
Le ModelBinder ne possède ici qu'une surcharge qui défini comment transformer la donnée. Nous recevons un identifiant (value) et précisons un Type (par config, voir plus loin). On regarde si le type correspond bien à ce que l'on veut et si la valeur n'est pas nulle puis on décode le base64 et on crée un nouvel objet correspondant à notre identifiant. Mon exemple n'est pas "super safe" et un vrai id est préférable dans un vrai modèle applicatif...

Il ne reste qu'à enregistrer notre ModelBinder dans le global.asax.cs tout comme nos routes:
public static void RegisterBinders()
{
    ModelBinders.Binders.Add(typeof(ServiceResponse), new ServiceResponseBinder());
}
protected void Application_Start()
{
    RegisterRoutes(RouteTable.Routes);
    RegisterBinders();
}
Pour finir voici l'action que l'on appele et qui traite notre objet ServiceResponse:
public ActionResult Stop(ServiceResponse e)
{
    e.StatusDemanded = "STOP";
    ServiceManager.Current.Update(e);
    return RedirectToAction("Index", new { Message = "you stop " + e.Service });
}
On ne fait ici que mettre le service en mode Stop, puis on se redirige vers l'action qui s'occupe de lister les services, soit en fonctionnement:
09-10-2008 23-18-38

Pour conclure:
Nous avons vu ici les différents usages des objets aux différents niveaux du modèle MVC. Nous avons au passage que garde une connotation objet et typage à tous les niveaux. On fera l'impasse sur le rendu par ToString() car il faut bien identifier son objet à un moment et de plus cela permet d'avoir un identifiant propre, des appels et passage de paramètre simplifiés (qui a dit que j'avais le ViewState en horreur?).

Néanmoins notre modèle pourrait être amélioré:
  • Je ne veux pas rafraichir ma page à chaque action -> requête ajax/json
  • Je veux avoir un rafraichissement "temps réel" -> timers et polling ajax
  • Je ne veux pas rester bloquer par une action -> pas de retour d'action mais remontée par un event.
Nous verons tout cela bien sur au prochain post MVC pratique!

Bon MVC!
Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :

Classé sous , , ,

# re: MVC Pratique #07 - Un projet concret et le transfert des objets avec les ModelBinders @ dimanche 12 octobre 2008 14:31

excellent !

d'autant plus que je ne connaissais pas les épisodes de la saison 1 ;-)

gpommier

# re: MVC Pratique #07 - Un projet concret et le transfert des objets avec les ModelBinders @ lundi 13 octobre 2008 02:18

tu vas voir c'est comme dans Heroes, à la saison 3 c'est les Vilains qui gagnent ;-)

Rui


Les 10 derniers blogs postés

- [Perso] Découvertes estivales : Linux (Part I) par Le blog de FremyCompany le il y a 2 heures et 10 minutes

- [Refactoring] ReSharper pour Visual Studio 2010 (Preview) par Thomas Jaskula le il y a 16 heures et 46 minutes

- [Refactoring] Analyser vos exceptions avec ReSharper Exceptional par Thomas Jaskula le il y a 18 heures et 0 minutes

- SharePoint 2007 : patterns & practices SharePoint Guidance par Philippe Sentenac [MVP SharePoint] le 07-03-2009, 09:56

- [Visual Studio 2010] Les tests cases c’est bien, mais je vais devoir tout réécrire ? par Etienne Margraff le 07-03-2009, 09:00

- MVP[Gribouillon].AddYear par The Grib's Lair [Sébastien PICAMELOT - MVP SharePoint] le 07-03-2009, 08:45

- Clinique INSIA - Projet de fin d’Etudes (Silverlight 3 MVVM et OutOfBrowser, WCF, TFS) - Part 1 par David REI le 07-02-2009, 23:38

- C’est la crise ? Bah pourquoi cramer du budget pub alors ? par Nix's Blog le 07-02-2009, 15:31

- Soyons MVP ! par TheSaib .NET blog le 07-02-2009, 12:15

- SharePoint : Gestion des Erreurs 6398, 7076 et 6482 par Blog Technique de Romelard Fabrice le 07-02-2009, 11:53