Accueil > Asp.Net MVC > Vues mobiles en ASP.NET MVC 3: profitez de l’innovation majeure de MVC 4 sans attendre!

Vues mobiles en ASP.NET MVC 3: profitez de l’innovation majeure de MVC 4 sans attendre!

Après mon dernier article sur ce blog: Vues mobiles en ASP.NET MVC 4, pas mal de gens m’ont dit « C’est dommage, c’est maintenant que je développe ma version mobile, pas le temps d’attendre MVC 4 ». Ne vous inquiétez pas, avec un peu d’effort vous pouvez profiter de ce mécanisme dès maintenant sans aucun risque.

Tout d’abord, recréons l’objet DisplayMode:

public class DisplayMode
{
    public Func<HttpContextBase, bool> ContextCondition { get; set; }
 
    public string Suffix { get; set; }
 
    public DisplayMode(string suffix)
    {
        this.Suffix = suffix;
    }
 
    public bool CanHandleContext(HttpContextBase httpContext)
    {
        return this.ContextCondition == null || this.ContextCondition(httpContext);
    }
}

Ensuite, La collection de DisplayModes statique pour les stocker:

public class DisplayModes
{
    private static Collection<DisplayMode> _modes;
 
    public static Collection<DisplayMode> Modes
    {
        get
        {
            if (_modes == null)
                _modes = new Collection<DisplayMode>();
            return _modes;
        }
    }
}

La création de la vue se fait dans la méthode CreateView du RazorViewEngine. Nous devons donc le dériver pour surcharger cette méthode. Il suffit ensuite de vérifier les conditions de chaque DisplayMode, de vérifier si la vue existe et de changer le path de la vue créée en lui ajoutant le suffixe correspondant.
Dans le cas où aucun DisplayMode particulier ne s’applique, il va nous falloir ensuite expliquer au moteur de vue que si il existe une version mobile de la vue qu’il recherche, il doit la sélectionner si le user agent correspond. Pour cela, j’ai utilisé une expression régulière assez monstrueuse mais très pratique qui détecte tous les navigateurs mobiles fournie sur http://detectmobilebrowsers.com/.

On arrive au ViewEngine suivant:

public class CustomViewEngine : RazorViewEngine
{
    private static Regex _mobileBrowsers1 = new Regex(@"android.+mobile|avantgo|bada\\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\\/|plucker|pocket|psp|symbian|treo|up\\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino", RegexOptions.IgnoreCase | RegexOptions.Multiline);
    private static Regex _mobileBrowsers2 = new Regex(@"1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\\-(n|u)|c55\\/|capi|ccwa|cdm\\-|cell|chtm|cldc|cmd\\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\\-s|devi|dica|dmob|do(c|p)o|ds(12|\\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\\-|_)|g1 u|g560|gene|gf\\-5|g\\-mo|go(\\.w|od)|gr(ad|un)|haie|hcit|hd\\-(m|p|t)|hei\\-|hi(pt|ta)|hp( i|ip)|hs\\-c|ht(c(\\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\\-(20|go|ma)|i230|iac( |\\-|\\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\\/)|klon|kpt |kwc\\-|kyo(c|k)|le(no|xi)|lg( g|\\/(k|l|u)|50|54|e\\-|e\\/|\\-[a-w])|libw|lynx|m1\\-w|m3ga|m50\\/|ma(te|ui|xo)|mc(01|21|ca)|m\\-cr|me(di|rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\\-2|po(ck|rt|se)|prox|psio|pt\\-g|qa\\-a|qc(07|12|21|32|60|\\-[2-7]|i\\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\\-|oo|p\\-)|sdk\\/|se(c(\\-|0|1)|47|mc|nd|ri)|sgh\\-|shar|sie(\\-|m)|sk\\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\\-|v\\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\\-|tdg\\-|tel(i|m)|tim\\-|t\\-mo|to(pl|sh)|ts(70|m\\-|m3|m5)|tx\\-9|up(\\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|xda(\\-|2|g)|yas\\-|your|zeto|zte\\-", RegexOptions.IgnoreCase | RegexOptions.Multiline);
 
    protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
    {
        string newPartial = GetViewPathWithSuffix(controllerContext, partialPath);
        return base.CreatePartialView(controllerContext, newPartial);
    }
 
    protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
    {
        string newView = GetViewPathWithSuffix(controllerContext, viewPath);
        string newMaster = GetViewPathWithSuffix(controllerContext, masterPath);
        return base.CreateView(controllerContext, newView, newMaster);
    }
 
    private string GetViewPathWithSuffix(ControllerContext controllerContext, string viewPath)
    {
        if (string.IsNullOrEmpty(viewPath))
            return viewPath;
        int index = viewPath.LastIndexOf('.');
        string pattern = viewPath.Substring(0, index + 1) + "{0}" + viewPath.Substring(index);
 
        DisplayMode currentMode = DisplayModes.Modes.Where(o => o.CanHandleContext(controllerContext.HttpContext) && FileExists(controllerContext, string.Format(pattern, o.Suffix))).FirstOrDefault();
 
        if (currentMode != null)
            return string.Format(pattern, currentMode.Suffix);
 
        string u = controllerContext.HttpContext.Request.UserAgent;
        if (_mobileBrowsers1.IsMatch(u) || _mobileBrowsers2.IsMatch(u.Substring(0, 4)))
        {
            if (FileExists(controllerContext, string.Format(pattern, "Mobile")))
                return string.Format(pattern, "Mobile");
        }
 
        return viewPath;
    }
 
}

La surcharge de vue mobile automatique ou par DisplayMode fonctionne grâce à ce code sur les vues, vues partielles et layouts. Il resterait à l’améliorer pour qu’il prenne en compte les Display et Editor Templates particuliers je ne crois pas que ça fonctionne pour ces vues spécifiques en l’état.(j’avoue ne pas avoir testé ce cas)

Si certains travaillent encore en aspx (si ça existe encore), il suffirait de dériver du ViewEngine aspx et non pas du RazorViewEngine pour que ça fonctionne.

La dernière chose à faire est de déclarer nos DisplayModes et notre ViewEngine dans le Global.asax:

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
 
    ViewEngines.Engines.Clear();
    ViewEngines.Engines.Add(new CustomViewEngine());
 
    DisplayMode iphoneMode = new DisplayMode("Iphone");
    iphoneMode.ContextCondition = o => o.Request.UserAgent.IndexOf("iphone", StringComparison.OrdinalIgnoreCase) > 0;
 
    DisplayModes.Modes.Insert(0, iphoneMode);
 
    RegisterGlobalFilters(GlobalFilters.Filters);
    RegisterRoutes(RouteTable.Routes);
}

L’avantage de cette technique est que le code sera très facilement migré en MVC4 le moment venu, il suffira de supprimer le custom view engine et de taper sur les DisplayModes du framework au lieu de ceux-ci.

Le code est disponible ICI

Enjoy…

Étiquettes : ,
  1. 21/02/2012 à 4:31

    En effet pourquoi attendre quand on peut en profiter tout de suite. Bien vu pour le ViewEngine personnalisé

  1. 09/06/2012 à 4:50
  2. 11/06/2012 à 12:14

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s