Guillaume Lacasa
ASP.NET MVC : changer de View Engine
Après avoir changé nos contrôleurs par défaut, nous allons maintenant voir comment changer le ViewEngine de notre application MVC
Par Guillaume Lacasa publié le 18/11/2009 à 09:10, lu 2576 fois,
Nous avons vu dans une précédente astuce comment changer de ControllerFactory, maintenant nous allons voir comment changer de ViewEngine.
Mon problème, comme la dernière fois, est que je n’aime pas la convention par défaut de MVC : si je ne mets pas mes vues dans le dossier “Views”, il ne les trouve pas. Je vais donc créer mon propre ViewEngine ; ou plutôt je vais étendre celui utilisé par défaut pour redéfinir les dossiers où chercher.
Avant de commencer, nous allons d’abord regarder comment fonctionne le WebFormViewEngine : le code source est disponible, ne nous privons pas d’aller y jeter un oeil ! La classe hérite de VirtualPathProviderViewEngine, classe abstraite qui permet d’aller chercher les fichiers dans le site à partir de chemins virtuels asp.net (vous savez, ceux qui commencent par ~). Regardons son constructeur :

public WebFormViewEngine() {

    MasterLocationFormats = new[] {

        "~/Views/{1}/{0}.master",

        "~/Views/Shared/{0}.master"

    };

 

    ViewLocationFormats = new[] {

        "~/Views/{1}/{0}.aspx",

        "~/Views/{1}/{0}.ascx",

        "~/Views/Shared/{0}.aspx",

        "~/Views/Shared/{0}.ascx"

    };

 

    PartialViewLocationFormats = ViewLocationFormats;

}

Ici on initialise 3 tableaux de String, qui contiennent les emplacements où chercher nos vues, pour les master pages, les vues et les vues partielles. Ainsi, si je veux redéfinir l’emplacement de mes vues, je pourrai me contenter d’hériter de WebFormViewEngine et de remplacer ces valeurs par les miennes :

public class MyCustomViewEngine : WebFormViewEngine

{

    public MyCustomViewEngine()

    {

        MasterLocationFormats = new[] {

        "~/MesMasterPages/{1}/{0}.master",

        "~/MesMasterPages/{0}.master"

    };

 

        ViewLocationFormats = new[] {

        "~/MesVues/{1}/{0}.aspx",

        "~/MesVues/{0}.aspx"

    };

 

        PartialViewLocationFormats = new[] {

        "~/MesVues/{1}/{0}.ascx",

        "~/MesVues/{0}.ascx"

    };

    }

}

On va maintenant aller un peu plus loin, en créant un ViewEngine qui va renvoyer des vues différentes en fonction du contexte. Un exemple pratique : on veut créer des vues spécifiques pour les appareils mobiles.
Par exemple, si je veux définir un site “normal” à l’adresse http://www.monsite.com, et un site mobile à l’adresse http://m.monsite.com, je vais router les dns de mes deux sous-domaines vers le même site, et mon ViewEngine fera le tri :

public class MobileViewSelector : WebFormViewEngine

{

    public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName,

        string masterName, bool useCache)

    {

        // on récupère le domaine utilisé pour venir sur le site

        String domain = controllerContext.RequestContext.HttpContext.Request.Url.Authority.ToLower();

 

        // si on est sur un domaine pour mobile, on va chercher la vue dans un sous-dossiers "mobile"

        if (domain.StartsWith("m") || domain.StartsWith("iphone"))

            viewName = "Mobile/" + viewName;

 

        // on appelle la requête de base

        return base.FindView(controllerContext, viewName, masterName, useCache);

    }

}

Dans cet exemple, si on se connecte au site en utilisant le sous domaine “m” (ou même “iphone”, parce que c’est à la mode, même si le résultat est exactement le même), la vue sera recherchée dans un sous-dossier “Mobile” du dossier des vues, sinon on récupère la vue normale. Bien sûr, l’utilisation du sous domaine n’est qu’un exemple, vous pouvez router vos vues en fonction de variables de sessions, de paramètres passés dans la requête ou tout ce que vous désirez.
Il suffit ensuite de créer toutes nos vues en double : une vue normale, et une vue mobile, et ça fonctionnera. C’est là un des points forts de MVC, le contrôleur n’a pas besoin d’être modifié : il fait uniquement son boulot de contrôleur et envoie des informations à la vue. Ensuite que la vue soit faite pour un PC ou un mobile ne le concerne plus.
Dernière petite chose pour que ça fonctionne, il ne faut pas oublier d’enregistrer notre ViewEngine dans le global.asax :

protected void Application_Start()

{

    RegisterRoutes(RouteTable.Routes);

 

    // On supprime le ViewEngine par défaut, on n'en a plus besoin

    ViewEngines.Engines.Clear();

 

    // On enregistre notre ViewEngine

    ViewEngines.Engines.Add(new MobileViewSelector());

}

Nous savons maintenant modifier le ViewEngine par défaut, mais ça ne nous laisse pas énormément de liberté. Voyons maintenant comment faire un ViewEngine complet, qui ne se basera pas forcément sur des fichiers aspx. Cette fois nous aurons un contrôle total du code HTML qui sera généré, libre à vous d’utiliser des pages PHP ou de créer votre propre langage serveur ! Par contre, je ne vous cache pas que mettre ça en place vous demandera un peu plus de boulot que d’utiliser les webforms.
Pour créer un nouveau View Engine from scratch, il va nous falloir implémenter 2 interfaces : IView, qui représentera nos vues (ce qui nous permettre de renvoyer le code html au client), et IViewEngine, qui nous servira à charger la vue correcte en fonction du contexte et du nom de la vue.
Commençons par la vue : l’interface IView nous demande de faire une méthode Render, qui reçoit des informations dans le viewContext, et devra écrire le code HTML à renvoyer au client dans un TextWriter.

public class MyCompletelySpecificView : IView

{

    #region IView Members

 

    public void Render(ViewContext viewContext, System.IO.TextWriter writer)

    {

        // on va faire notre rendu ici

    }

 

    #endregion

}

 

Nous avons ici toute liberté pour faire ce que nous voulons ! Nous allons par exemple aller chercher notre vue en base de données : pour cela, nous avons une table toute simple avec 2 colonnes : le nom de la vue, et le code HTML correspondant.
Nous pouvons créer notre DatabaseView qui ira chercher en base de données le code html, au lieu de chercher un fichier.

public class DatabaseView : IView

{

    private String viewName;

 

    public DatabaseView(String viewName)

    {

        this.viewName = viewName;

    }

 

    #region IView Members

 

    public void Render(ViewContext viewContext, System.IO.TextWriter writer)

    {

        using (DatabaseViewsDataContext context = new DatabaseViewsDataContext())

        {

            ViewContent content = context.ViewContents.SingleOrDefault(v => v.ViewName == viewName);

 

            if (content != null)

                writer.Write(content.Text);

        }

    }

 

    #endregion

}

Il ne nous reste plus qu’à créer le View Engine qui va bien, pour pouvoir appeler correctement nos vues. Nous avons 3 méthodes à implémenter : pour récupérer une vue, pour récupérer une vue partielle, et pour libérer la mémoire de notre vue.

public class DatabaseViewEngine : IViewEngine

{

    #region IViewEngine Members

 

    public ViewEngineResult FindPartialView(ControllerContext controllerContext,

        string partialViewName, bool useCache)

    {

        return new ViewEngineResult(new DatabaseView(partialViewName), this);

    }

 

    public ViewEngineResult FindView(ControllerContext controllerContext, string viewName,

        string masterName, bool useCache)

    {

        return new ViewEngineResult(new DatabaseView(viewName), this);

    }

 

    public void ReleaseView(ControllerContext controllerContext, IView view)

    {

        IDisposable disposableView = view as IDisposable;

        if (disposableView != null)

            disposableView.Dispose();

    }

 

    #endregion

}

J’ai ici fait au plus simple, mais vous avez tout loisir pour faire plus compliqué si vous le désirez, pour gérer les vues mobiles, ou bien différentes langues. N’oubliez pas non plus de gérer le cache lorsqu’on vous le demande (et oui, refaire un ViewEngine, ça veut dire qu’il faut TOUT refaire).
Voilà, vous avez toutes les clés en main pour créer votre propre View Engine, maintenant à vous de jouer !
Cross-posté depuis mon blog
 
» Démarrer une discussion