ASP.Net MVC – Comment charger une vue depuis une base de données ?

Si, comme moi, parfois vous avez des idées étranges et désirez sortir des sentiers battus, alors vous êtes au bon endroit ! Tout d’abord, il faut préciser que le cas que nous allons étudier n’est pas spécifique à la base de données, nous pouvons charger une vue MVC depuis n’importe quelle source de données… D’ailleurs nous partirons de ce principe pour commencer, puis exposerons le cas particulier de la base de données.

Pour commencer, il est important de préciser la raison d’un tel choix : pourquoi charger une vue autrement que par le système de fichier classique ? Pourquoi pas… Plus sérieusement, l’intérêt de cette solution est de pouvoir charger dynamiquement des vues sans en connaitre l’emplacement ou le nombre et sans avoir accès au serveur web.

Pour réussir notre entreprise nous allons modifier le comportement du moteur de résolution du chemin d’une vue. Pour cela nous devons créer une nouvelle classe qui héritera de System.Web.Hosting.VirtualPathProvider.

Nous allons pour cela surcharger deux méthodes :

public virtual bool FileExists(string virtualPath);
public virtual VirtualFile GetFile(string virtualPath);

FileExists

La fonction FileExits comme son nom l’indique permet de savoir si un fichier est physiquement présent. Le paramètre VirtualPath correspond au chemin de la vue (ex : ~/Views/Email/Template.cshtml).

Etant donné que notre vue ne sera pas présente dans le dossier habituel, la méthode FileExists retournera false et vous devriez avoir une belle erreur 404 sur votre navigateur.

Il nous faut donc surcharger le comportement pour prendre en compte nos vues expatriées du dossier Views sans pour autant rompre le fonctionnement habituel pour les vues présentes à l’emplacement par défaut. Ce qui nous donne :

    private const string Filter = @"/Views/Email/Template.cshtml";

    public override bool FileExists(string virtualPath)
    {
        if (!virtualPath.Contains(Filter))
            return base.FileExists(virtualPath);
        return true;
    }

Évidemment vous êtes libres d’appliquer le filtre de votre choix.

GetFile

La fonction GetFile permet de récupérer le fichier qui est stocké dans l’objet VirtualFile.

Il nous faut donc avoir notre propre version de la classe VirtualFile. Pour cela nous allons la surcharger également.

Cette dernière est très simple car une seule méthode doit être surchargée :

    public class CellenzaVirtualFile : VirtualFile
    {
        private readonly byte[] _data;

        public CellenzaVirtualFile(string virtualPath, byte[] data)
            : base(virtualPath)
        {
            _data = data;
        }

        public override Stream Open()
        {
            return new MemoryStream(_data);
        }
    }

Ici le principe est simple, on va construire une nouvelle instance à partir du VirtualPath vu précédemment et d’un tableau de byte qui comme vous l’aurez deviné est issu du fichier que l’on souhaite traiter.

Revenons à la classe VirtualPathProvider :

    public override VirtualFile GetFile(string virtualPath)
    {
        if (!virtualPath.Contains(Filter))
            return base.GetFile(virtualPath);
        return new CellenzaVirtualFile(virtualPath, GetViewBytesData());
    }

Attention à toujours conserver le comportement de base ! Dans le cas de notre vue expatriée, on crée donc une instance de CellenzaVirtualFile. Par soucis de lisibilité le tableau de byte correspondant au fichier est fourni par la fonction GetViewBytesData().

private byte[] GetViewBytesData()
{
     return  Encoding.ASCII.GetBytes("Les vues, comment ca marche???");
}

Pour commencer notre source de données est une simple chaîne de caractères.

CellenzaVirtualPathProvider

Au final notre classe ressemble à ceci :

public class CellenzaVirtualPathProvider : VirtualPathProvider
{

    private const string Filter = @"/Views/Email/PrintTemplate.cshtml";

    ///
    /// Obtient une valeur qui indique si un fichier existe dans le système de fichiers virtuel.
    ///
    ///
    /// true si le fichier existe dans le système de fichiers virtuel ; sinon, false.
    ///
    /// Chemin d'accès au fichier virtuel.
    public override bool FileExists(string virtualPath)
    {
        if (!virtualPath.Contains(Filter))
            return base.FileExists(virtualPath);
        return true;
    }

    ///
    /// Obtient un fichier virtuel à partir du système de fichiers virtuel.
    ///
    ///
    /// Descendant de la classe  qui représente un fichier dans le système de fichiers virtuel.
    ///
    /// Chemin d'accès au fichier virtuel.
    public override VirtualFile GetFile(string virtualPath)
    {
        if (!virtualPath.Contains(Filter))
            return base.GetFile(virtualPath);
        return new CellenzaVirtualFile(virtualPath, GetViewBytesData());
    }

    ///
    /// Obtient le fichier sous forme binaire.
    ///
    ///
    private byte[] GetViewBytesData()
    {
        string htmlBody = "Les vues, comment ca marche???";
        return Encoding.ASCII.GetBytes(htmlBody);
    }
}

Controller

Et voilà, le travail est presque fini. Pour que tout ceci fonctionne nous avons besoin d’un élément essentiel : un controller.

public class EmailController : Controller
{
     public ActionResult Template(int id)
     {
          return View(@"/Views/Email/PrintTemplate.cshtml");
     }
}

Vous remarquerez que la vue que nous passons ici correspond au filtre que nous avons défini précédemment, ce qui vous laisse libre d’inventer votre propre règle de filtrage. Attention l’extension est importante, vous ne pourrez pas faire de MVC avec un fichier « aspx ».

Autre point important, votre dossier Views doit contenir un répertoire correspondant au chemin demandé :

Email view folder

Vous pouvez très bien éviter cette étape en surchargeant la fonction DirectoryExists de la même façon que nous avons surchargé la fonction FileExists.

Et enfin, dernier point pour que notre code soit pris en compte, il faut la déclarer dans le Global.asax.cs :

protected void Application_Start()
{
     HostingEnvironment.RegisterVirtualPathProvider(new CellenzaVirtualPathProvider());
     […]
}

Et si la magie fonctionne correctement vous devriez obtenir quelque chose comme ça :

virtual view 1

En mode MVC

Pas très dynamique comme vue me direz-vous… c’est pas faux !

Soyons fous et créons un nouveau model :

public class EmailModel
{
    public string Expediteur { get; set; }
    public string Destinataire { get; set; }
    public string Objet { get; set; }
    public string Message { get; set; }
}

Modifions notre controller pour utiliser ces quelques informations supplémentaires :

public class EmailController : Controller
{
    public ActionResult Template(int id)
    {
        ViewData["Title"] = "Template " + id;
        ViewBag.Message = "Bienvenue dans la vue fantome!!!";

        var model = new EmailModel
        {
            Expediteur = "moi",
            Destinataire = "séverine",
            Objet = "rdv",
            Message = "ce soir je t'emmène au vent..."
        };

        return View(@"/Views/Email/PrintTemplate.cshtml", model);
    }
}

Évidemment il est nécessaire de modifier notre vue si l’on désire afficher les informations :

private byte[] GetViewBytesData()
{
    string htmlBody = @"@{    Layout = null;}
                        <hgroup class=""title"">    
                            <h1>@ViewBag.Title.</h1>
                            <h2>@ViewBag.Message</h2>

                            <div>Expediteur:   @Model.Expediteur      </div>
                            <div>Destinataire: @Model.Destinataire    </div>
                            <div>Objet:        @Model.Objet           </div>
                            <div>Message:      @Model.Message         </div>
                        </hgroup>  ";
    
    return Encoding.ASCII.GetBytes(htmlBody);
}

Ce qui nous donne:

virtual view 2

Source de données

Pour l’instant nous n’avons toujours pas vu la couleur d’une quelconque base de données.

Pour l’exemple, je me suis basé sur un objet string  par simplicité, mais vous l’aurez bien compris il vous suffit de modifier GetViewBytesData() afin de charger votre vue depuis n’importe quel emplacement.

Un commentaire. Laisser un commentaire

Excellent!

C’est exactement ce qu’il me fallait pour mettre au point une idée que j’avais en tête!

Répondre

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *