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:
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:
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é:
- J'appelle une méthode qui me crée un lien d'action (ce qui me génère une route donc...)
- Cette action fait partie du controlleur "ServicesController"
- C'est la méthode Stop(ServiceResponse) que j'appelle par lamba expression
- Je lui passe en paramètre mon modèle qui est comme on vient de le voir de type ServiceResponse
- 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:
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 :