vendredi 13 novembre 2009

ASP.NET MVC : Changer de ViewEngine (2/2)

Nous avons vu dans mon précédent post comment apporter des modifications au ViewEngine utilisé par défaut par MVC. Aujourd’hui, ça va se compliquer puisque nous allons voir ce qu’il est nécessaire de savoir pour créer un ViewEngine complet.

Contrairement à ce que nous avons vu précédemment, où nous ne faisions qu’apporter des modification au WebFormViewEngine, 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é vers Tech Head Brothers

mardi 10 novembre 2009

ASP.NET MVC : Changer de ViewEngine (1/2)

Nous avons vu dans un précédent post 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 plutot je vais étendre celui utilisé par défaut pour redéfinir les dossiers où chercher.

Avant de faire ça, 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 verrons dans un prochain post comment modifier le ViewEngine plus en profondeur. Nous n’avons ici modifié que l’appel des fichiers aspx, mais il est possible de modifier totalement le système de rendu, si par exemple vous ne souhaitez pas utiliser de webforms.

Cross-posté vers Tech Head Brothers

samedi 31 octobre 2009

ASP.NET MVC : Pas de cache dans le Controller Factory

J’ai récemment expliqué comment changer de Controller Factory dans MVC, mais depuis ce jour il y a un truc qui me tracasse : l’exemple que j’ai donné n’était pas du tout optimisé. En effet, on instancie un nouveau contrôleur à chaque requête, alors qu’on pourrait très bien en instancier un seul et le mettre en cache. Vu que ça prenait que quelques lignes de code, j’ai décidé d’étendre le DefaultControllerFactory pour y ajouter un cache des contrôleurs. Et là, déception, j’ai une exception lorsque lance mon application :

A single instance of controller 'MyMvcApplication.Controllers.HomeController' cannot be used to handle multiple requests. If a custom controller factory is in use, make sure that it creates a new instance of the controller for each request.

A la première requête, la charge se charge correctement, mais lors du chargement de la seconde page (et donc quand le cache est utilisé), le controller refuse de faire son boulot.

Je suis allé faire un petit tour dans le code source pour voir comment était créé le contrôleur, et effectivement à chaque requête une nouvelle instance est créée, comme dans mon ControllerFactory perso. D’après Scott Hanselman, mettre en cache une instance d’un contrôleur pourrait avoir des effets de bord non désirés (source). Ne vous embêtez donc pas à vouloir optimiser plus que ça, le fonctionnement de base fonctionne très bien… ou alors il vous faudra recoder complètement le contrôleur de base de MVC pour le rendre capable de gérer le cache (bon courage).

mardi 27 octobre 2009

Article : les nouveautés d’ASP.NET 4

Pour une fois, on va oublier un peu MVC pour en revenir aux bonnes vieilles WebForm. Au programme de ce nouvel article : les nouveautés d’ASP.NET 4 !

Au programme, de nombreuses optimisations, et quelques nouveautés intéressantes, notamment avec ASP.NET Ajax et le JavaScript.

Découvrez-le sur le site de Bewise :

http://www.bewise.fr/article/138/Les-nouveautes-d-ASP-NET4.aspx

jeudi 22 octobre 2009

Article : Utiliser Ajax avec ASP.NET MVC

Après mon précédent article, qui présentait les bases de ASP.NET MVC, je me suis dit qu’il pourrait être sympa de faire un peu d’Ajax.

Dans cet article, pas de JavaScript ni de WebServices (bien qu’il soit tout à fait possible, et certainement très intéressant, d’en faire avec MVC), je me concentre uniquement sur les moyens de faire de l’Ajax spécifiques à MVC, en utilisant l’AjaxHelper du framework.

Vous allez voir qu’encore une fois, c’est très simple à mettre en place, et la séparation des couches est toujours de la partie puisqu’on utilise toujours les contrôleurs et les vues, de la même manière qu’avec des requêtes http classiques.

Je vous laisse découvrir l’article sur Tech Head Brothers :

http://www.techheadbrothers.com/Articles.aspx/ajaxhelper-aspnet-mvc

lundi 19 octobre 2009

Tip : des ActionLink plus propres avec ASP.NET MVC

Malgré tout le bien que je pense d’ASP.NET MVC (qui est, je le rappelle, le meilleur framework de développement web du monde, que dis-je, de l’univers !), je me vois parfois obligé de reconnaître qu’il a des défauts. L’un des plus ennuyeux est l’utilisation de chaînes de caractères et d’objets anonymes dans les helpers Html, source potentielle d’erreurs, qui ne seront pas détectées à la compilation.

Pour remédier à ce soucis, nous avons la possibilité d’étendre le Html Helper, afin d’ajouter nos propres méthodes de création de liens.

Pour commencer, nous allons créer une classe par contrôleur, dans laquelle nous définirons les méthodes qui renverront les liens vers chaque action. Cette classe va se baser sur le HtmlHelper pour créer les liens, donc n’oublions pas de le passer dans le constructeur :

    public class HomeLinks
    {
       
private HtmlHelper html;
       
public HomeLinks(HtmlHelper html)
        {
           
this.html = html;
        }

       
public String Index(String linkText)
        {
           
return html.ActionLink(linkText, "Index", "Home");
        }

       
public String Details(String linkText, int id)
        {
           
return html.ActionLink(linkText, "Details", "Home", new { id = id });
        }
    }

Une fois cette classe créée, on va ajouter une méthode au HtmlHelper pour pouvoir y accéder depuis notre vue :

    public static class LinkHelper
    {
       
public static HomeLinks HomeLinks(this HtmlHelper Html)
        {
           
return new HomeLinks(Html);
        }

       
public static AccountLinks AccountLinks(this HtmlHelper Html)
        {
           
return new AccountLinks(Html);
        }
    }

Notez que j’ai séparé les liens de chaque contrôleur dans des classes distinctes par soucis de clarté, mais ce n’est absolument pas obligatoire : vous pouvez si vous le souhaitez étendre directement le HtmlHelper par vos liens :

    public static String Index(this HtmlHelper Html, String linkText)
    {
       
return Html.ActionLink(linkText, "Index", "Home");
    }

Pour pouvoir appeler vos méthodes depuis vos pages aspx, n’oubliez pas d’ajouter leur namespace dans le web.config :

    <pages>
      <
namespaces>
        <
add namespace="MyMvcApp.LinkHelpers"/>
      </
namespaces>
    </
pages>

Et voilà, plus besoin de faire passer de chaines de caractères en dur dans vos liens, vous pouvez utiliser ces méthodes avec des vrais noms et des paramètres typés !

Ainsi, les 2 lignes suivantes créeront le même lien :

<%= Html.ActionLink("Details pour l'id numero 5", "Details", "Home", new { id = 5 }  %>
<%
= Html.HomeLinks().Details("Details pour l'id numero 5", 5) %>

vendredi 16 octobre 2009

ASP.NET MVC : changer de ControllerFactory

Le framework ASP.NET MVC utilise ce qu’on appelle “convention over configuration”, ce qui signifie qu’on n’a pas besoin de configurer manuellement notre application pour avoir une application qui fonctionne, tant que l’on respecte certaines conventions. Si l’on ne souhaite pas utiliser ces conventions, on a toujours la possibilité de configurer l’application pour travailler autrement.

En pratique, qu’est-ce que ça signifie ?

Lorsque l’on crée un nouveau projet MVC, les contrôleurs sont disposés dans le dossier « Controllers » de l’application, et leur nom doit forcément se terminer par « Controller ». Si on crée un contrôleur dans un autre namespace, ou si son nom est incorrect, le contrôleur ne sera pas reconnu par MVC. Pour pouvoir changer cette convention, on a la possibilité de créer notre propre ControllerFactory, dans lequel on pourra définir nos règles d’appel de nos contrôleurs.

Pour créer un ControllerFactory, il va falloir créer une nouvelle classe qui implémentera l’interface IControllerFactory, contenant 2 méthodes :

IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName)
et
void ReleaseController(IController controller)

La première va nous permettre de renvoyer une instance d’un contrôleur en fonction de son nom, et éventuellement de paramètres venant de la requête ; la seconde va nous permettre de libérer un contrôleur qui n’est plus utilisé. Le résultat ressemblera à quelque chose comme ça :

    public class CustomControllerFactory : IControllerFactory
    {

       
public IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName)
        {
           
if (string.IsNullOrEmpty(controllerName))
               
throw new ArgumentNullException("Controller name can't be empty");

           
String controllerType = String.Concat("MyMvcApplication.CustomController.", controllerName);

           
IController controller = Activator.CreateInstance(Type.GetType(controllerType)) as IController;

           
return controller;
        }

       
public void ReleaseController(IController controller)
        {
           
IDisposable disposableController = controller as IDisposable;
           
if (disposableController != null)
                disposableController.Dispose();
        }

    }

Une fois notre classe créée, il faut l’enregistrer pour qu’elle soit utilisée à la place de la Factory par défaut. Pour ça, on a une méthode SetControllerFactory qu’il nous faut appeler au démarrage de l’application, dans le global.asax :

    protected void Application_Start()
    {
        RegisterRoutes(
RouteTable.Routes);

       
ControllerBuilder.Current.SetControllerFactory(typeof(CustomControllerFactory));
    }

Une fois ceci fait, nos anciens contrôleurs ne sont plus appelés, la recherche se fera dans le namespace MyMvcApplication.CustomController comme cela a été défini dans notre CustomControllerFactory.

Crossposté vers Tech Head Brothers