• Redirection de dossier par un VirtualPathProvider

    L'un des grands points forts de Asp.net 2.0, c'est la simplicité avec laquelle ont peut étendre le système. Parmis les nombreuses possibilités d'extensions possible, je vous propose de nous servir des HttpModules et des VirtualPathProviders pour effectuer une chose toute simple : la redirection de certains path de votre site web vers d'autres dossiers.

    Imaginons par exemple, que vous ayez un très grand nombre d'images de produits : vous souhaitrez certainement ne pas les stocker directement dans votre application web - les mises à jours devenant vite des galères ingérables dans le cas contraire - mais plutôt dans un autre dossier. Vous avez alors deux possibilités :

    • écrire un HttpHandler qui accèdera au fichier et l'enverra dans la stream de sortie
    • écrire un VirtualPathProvider dont le but est de de "virtualiser" - ah bah, tiens, VirtualPathProvider, Virtualiser, j'l'avais pas vu :) - l'accès aux fichiers.

    Les blogs et forums regorgeant d'exemples pour la premiere solution, c'est l'utilisation d'un VirtualPathProvider qui sera détaillé dans la suite de ce billet.

    Si l'on regarde la signature - simplifiée pour ne présenter que ce qui nous interesse ici - de la classe VirtualPathProvider nous trouvons 4 méthodes très intéressantes :

       public abstract class VirtualPathProvider 
       {
            public virtual bool DirectoryExists(string virtualDir);
            public virtual bool FileExists(string virtualPath);
            public virtual VirtualDirectory GetDirectory(string virtualDir);
            public virtual VirtualFile GetFile(string virtualPath);
       }

    En implementant ces méthodes, nous allons pouvoir rediriger l'accès à un fichier 'virtuel' (disons par exemple ~/Factures/F200601003.pdf) vers un path "reel" (e:\data\legal\factures\200601003.pdf). Pour cela, il est indispensable de surcharger FileExists et GetFile, les méthodes concernant les directory n'étant ici pas très utiles. Voici le code de la classe :

       public class MyVirtualPathProvider : VirtualPathProvider
       {
            private string _virtualDir;
            private string _realDir;
            public MyVirtualPathProvider(string virtualDir, string realDir)
            {
                _realDir = realDir;
                _virtualDir = virtualDir;
            }
            internal string ToRealPath(string virtualPath)
            {
                virtualPath = VirtualPathUtility.ToAppRelative(virtualPath);
                if(virtualPath.StartsWith(_virtualDir, 
    StringComparison.InvariantCultureIgnoreCase)) return virtualPath.Replace(_virtualDir, _realDir); return null; } public override bool FileExists(string virtualPath) { string realPath = ToRealPath(virtualPath); if (realPath!=null && File.Exists(realPath)) return true; return Previous.FileExists(virtualPath); } public override VirtualFile GetFile(string virtualPath) { string realPath = ToRealPath(virtualPath); if (realPath!=null && File.Exists(realPath)) return new MyVirtualFile(realPath, virtualPath); return Previous.GetFile(virtualPath); } }

    Comme vous pouvez le constater, celle-ci est assez simple : le constructeur permet de définir le path virtuel à remplacer et le path réel correspondant, la méthode ToRealPath se charge de convertir le path, FileExists retourne simplement true si le fichier reel existe, seule la méthode GetFile est un tout petit peu plus complexe, puisqu'elle fait appel à une autre classe MyVirtualFile. Un certain nombre de points sont à prendre en compte lors de l'écriture de cette classe :

    • Comme nous le verrons d'ici peu, les VirtualPathProvider sont enregistrés globalement pour une application et ne sont pas montés/associés à un path particulier, il convient donc de ne répondre qu'aux demandes pour lesquelles nous avons quelque-chose à dire.
    • Toutes les requêtes qui ne nous concerne pas doivent être passées au VirtualPathProvider suivant -ooops, "précédent" - dans la pile. Celui-ci est disponible par l'intermédiaire de la propriété Previous.
    • Il est aussi nécessaire d'écrire une classe dérivant de VirtualFile  qui se chargera de l'accès effectif au fichier. La seule méthode à surcharger importante de cette classe est la méthode Open qui doit renvoyer une Stream sur les données du fichier.
       public class MyVirtualFile : VirtualFile
       {
            private string _realPath;
            public MyVirtualFile(string realPath, string virtualPath)
                : base(virtualPath)
            {
                _realPath = realPath;
            }
            public override System.IO.Stream Open()
            {
                return File.OpenRead(_realPath);
            }
       }
    

    Pour que tout cela fonctionne, il ne nous reste plus qu'à "enregistrer" un MyVirtualPathProvider auprès du moteur de asp.net. Cela est assez simple, mais doit être fait à certains endroits très précis : il faut qu'il soit enregistré avant toute requête. Pour cela, le plus simple est certainement de le faire au sein de la méthode Application_Start du global.asax

       void Application_Start(object sender, EventArgs e) 
       {
            System.Web.Hosting.HostingEnvironment.RegisterVirtualPathProvider(
                new MyVirtualPathProvider("~/Factures/", "e:\\data\\legal\\factures\\"));
       }

    Et voila ! Maintenant, toutes les requêtes pour un fichier sous le dossier virtuel ~/Factures sera intercepté par notre VirtualPathProvider et le fichier obtenu depuis un autre dossier. Il est bien entendu possible d'utiliser d'autres systèmes de stockage : en base de données, dans un coffre-fort numérique, en génération dynamique - le choix d'un VirtualPathProvider étant alors quelque peu discutable -, les possibilités ne sont peut-être pas infinies mais devraient correspondre à la plupart des besoins.