How to use virtual path providers to dynamically load and compile content from virtual paths in ASP.NET 2.0 (910441)



The information in this article applies to:

  • Microsoft ASP.NET 2.0

ASP.NET Support Voice Column

How to use virtual path providers to dynamically load and compile content from virtual paths in ASP.NET 2.0

To customize this column to your needs, we want to invite you to submit your ideas about topics that interest you and issues that you want to see addressed in future Knowledge Base articles and Support Voice columns. You can submit your ideas and feedback using the Ask For It form. There's also a link to the form at the bottom of this column.

Introduction

Hello and welcome back! My name is Parag Agarwal, a support engineer at Microsoft. This month we are going to discuss virtual path providers in Microsoft ASP.NET 2.0.

Overview

This month, we will discuss how to extend ASP.NET by providing virtual access to content and files for compilation in ASP.NET 2.0. This feature can be used to create applications similar to Microsoft SharePoint Portal Server, where the content is stored in a database instead of on the physical file system. In this article, we will build a sample where the content of the requested Web form page is stored in a Microsoft SQL Server database.

Virtual path provider

A virtual path provider provides a mechanism with which we can extend ASP.NET to serve virtual content to the compilation system. For example, a virtual path provider provides a means to supply content from locations other than the file system. Developers who want to provide virtual content must perform the following tasks:
  • Create a VirtualPathProvider class, and implement all the required methods to handle files and folder requests.
  • Register the virtual path provider to let the ASP.NET hosting environment know where the content will be served from.
  • Create VirtualFile and VirtualDirectory objects to stream the content.
For more information about the VirtualPathProvider class, visit the following Microsoft Developer Network (MSDN) Web site:

Which content can be virtualized?

Browseable types, such as ASPX, master pages, ASCX, and themes, are the only items that can be virtualized.

The application is initialized after calls are made to the AppInitialize static method and to events that are defined inside the Global.asax file. These method calls are only two places where the VirtualPathProvider class can be registered.

The compilation of top-level items, such as the App_Code and App_Data folders, cannot be affected at any point in the life cycle of the application for the provider that you want to register.

To virtualize non-default browsable content, you need to map a BuildProvider class. For more information about the BuildProvider class and how the ASP.NET build environment uses the BuildProvider class to generate source code for different file types, visit the following MSDN Web site:

Compilation model

Before we create a sample for virtual path providers, we will go through an overview of the major components in the ASP.NET 2.0 compilation model. This overview will help us understand how content is compiled by the ASP.NET build system, from opening and creating the Web site in Microsoft Visual Studio to browsing a Web page.

The ClientBuildManager class

The ClientBuildManager class offers APIs for building assemblies, generating source code, and performing pre-compilation by interacting with the ASP.NET build system. The ClientBuildManager class provides access to the build system outside Microsoft Internet Information Services (IIS). By using the ClientBuildManager class, Visual Studio 2005 provides cool features such as IntelliSense, statement completion, and real-time error reporting. The ClientBuildManager class also provides both virtual and physical paths to the file or files. For more information, visit the following MSDN Web site:

The BuildManager class

The BuildManager class manages the process for compiling assemblies and pages in the application. For more information, visit the following MSDN Web site:

The BuildProvider class

The BuildProvider class provides functionality to parse a particular file and generate the corresponding code of the file. For more information, visit the following MSDN Web site:

The AssemblyBuilder class

The AssemblyBuilder class represents a dynamic assembly with a list of all assembly dependencies. This class expects source code or a CodeCompileUnit object provided by the build provider during a compilation process. For more information, visit the following MSDN Web site:

Extending virtual path providers to serve virtual content coming from a database

Now that we have a general understanding of virtual path providers and the compilation model, we can create a small SharePoint Portal Services-like application that provides access to non file-based content.

Note Before we start creating the sample application, let us look at the database structure and the Web site hierarchy used in the sample:

There is only one table that is named VirtualFileSystem in the database. This table looks like the following:
The VirtualFileSystem table
The Web site hierarchy inside Visual Studio looks like the following:
The hierarchy inside Visual
		  Studio

Notes
  • The App_Code folder contains all the classes necessary to implement a virtual path provider, such as the VirtualPathProvider class, the VirtualDirectory class, the VirtualFile class, and a utility class.
  • The SharePointDir folder represents a virtual directory. The contents of this virtual directory are stored in the database. Any request for a page inside this folder will be considered as a request to a virtual file by the ASP.NET runtime.
  • The AdminstrationPage.aspx page displays a user interface where we can perform CRUD (Create, Read, Update, and Delete) operations on the virtual content with the help of a GridView control. The AdminstrationPage.aspx page looks like the following:
A user interface where we can
		  perform CRUD operations
Let us start building the sample.

To do this, follow these steps:
  1. Start Visual Studio 2005.
  2. Create a Web site that has the exact hierarchy and files that appear in the earlier image.
  3. Open the SharePointDirectory.cs source file.
  4. Verify that the following namespaces are included in the SharePointDirectory.cs file:
    using System;
    using System.Collections;
    using System.Data;
    using System.Security.Permissions;
    using System.Web;
    using System.Web.Hosting;
    
  5. Declare a SharePointProvider class, and inherit the VirtualPathProvider class.
    public class SharePointProvider : 
    VirtualPathProvider
  6. Declare two private members.
    //It contains the file name and content as a key-value pair retrieved 
    //from the database.
    Hashtable virtualFiles = null; 
    DBUtility utility = null;
  7. Add the AppInitialize method. The AppInitialize method is the most important method in this process. The ASP.NET runtime engine calls this method when the application is starting up.

    Note This method can be considered as a trick where we make the ASP.NET runtime load this class without any Web.config file.

    If we have more than one class together with this method, a build error occurs. Inside this method, we are registering our provider with the ASP.NET hosting environment. Now, every request for a Web page will pass through this provider.
    public static void AppInitialize()
    {
       HostingEnvironment.RegisterVirtualPathProvider(new SharePointProvider());
    }
  8. Implement the constructor for the SharePointProvider class. This step is where we will retrieve all the virtual files and their content in memory for fast access. Until the content is invalidated, we will use the in-memory result set. As soon as the content is changed in the database, the content needs to be updated.
    public SharePointProvider(): base() 
    {
       utility = new DBUtility();
       virtualFiles = utility.GetVirtualFiles();
    }
  9. Next,add the IsPathVirtual method. By using this method, we can determine whether the requested file is from a virtual path. We have already decided that file requested within the SharePointDir folder will be considered as a virtual file.
    private bool IsPathVirtual(string virtualPath)
    {
       String checkPath = 
          VirtualPathUtility.ToAppRelative(virtualPath);
       return checkPath.StartsWith("~/SharePointDir".ToLower().ToString(), StringComparison.InvariantCultureIgnoreCase);    
    }
  10. The FileExists method returns whether the file exists. It does this by verifying that the requested file exists in the database. If the file does not exist, we will obtain the reference of the previously registered virtual path provider object in the compilation system and we will try to find the file again by calling the FileExists method. If the file is still not found, we will receive another "404 (File Not Found)" error message.
    public override bool FileExists(string virtualPath)
    {
             if (IsPathVirtual(virtualPath))
             {
                SharePointVirtualFile file = (SharePointVirtualFile)GetFile(virtualPath);
                // Determine whether the file exists on the virtual file 
                // system.
                if (utility.CheckIfFileExists(virtualPath))
                    return true;
                else
                    return Previous.FileExists(virtualPath);
              }
              else
              return Previous.FileExists(virtualPath);
     }
  11. Similarly we have the DirectoryExists method. This method will return true if the provider can service the virtual file path that is passed as an argument. Otherwise, the provider will give the virtual file path of the previously registered provider as usual, and we will receive 404 error messages again if the folder does not exist.
    public override bool DirectoryExists(string virtualDir)
    {
       if (IsPathVirtual(virtualDir))
       { 
          // Right now, we are not storing the directory information in  
          // our SharePoint Portal Services database. We assume that all of the virtual 
          // content is served from a directory that is named SharePointDir and was
          // created inside the ASP.NET Web site. Therefore, we will always 
          // return TRUE in this case.	
          SharePointVirtualDirectory dir = (SharePointVirtualDirectory)GetDirectory(virtualDir);
          return true;
       }
       else
          return Previous.DirectoryExists(virtualDir);
     }
  12. Next we have the GetFile and GetDirectory methods. The ASP.NET runtime calls these methods in the virtual path provider after the calls to the FileExists and DirectoryExists methods are successful.
    //This method is used by the compilation system to obtain a VirtualFile instance to 
    //work with a given virtual file path.
    public override VirtualFile GetFile(string virtualPath)
    {
       if (IsPathVirtual(virtualPath))
          return new SharePointVirtualFile(virtualPath, this);
       else
          return Previous.GetFile(virtualPath);
    }
    
    //This method is used by the compilation system to obtain a VirtualDirectory 
    //instance to work with a given virtual directory.
    public override VirtualDirectory GetDirectory(string virtualDir)
    {
       if (IsPathVirtual(virtualDir))
           return new SharePointVirtualDirectory(virtualDir, this);
       else
           return Previous.GetDirectory(virtualDir);
    }
  13. We must add a few utility methods that will be used by the VirtualFile class later in this example.
    public string GetFileContents(string virPath)
    {
       return utility.GetFileContents(virPath);	
    }
    
    public Hashtable GetVirtualData
    {
       get { return this.virtualFiles; }
       set { this.virtualFiles = value; }
    }
    NoteIn this example, we are not using the mechanism to notify the build system of any changes made to the virtual content. The virtual provider exposes an API that is named GetCacheDependency that is used to help cache virtual resources. We can invalidate the cache when any of the files become invalid. For more information, visit the following MSDN Web site:
  14. We are done with the main provider class. Now open the SharePointDirectory.cs source file. This class will serve as an abstraction for virtualized resources. For more information about the VirtualDirectory class, visit the following MSDN Web site:
  15. Because the VirtualDirectory class is an abstract class, we need to override all the abstract methods. However, we will not provide the extra implementation on those overridden methods because in this sample we assume that virtual content is coming within a single virtual directory. Therefore, the class definition will look like the following:
    public class SharePointVirtualDirectory : VirtualDirectory
    {
       SharePointProvider spp;
       public SharePointVirtualDirectory(string virtualDir, SharePointProvider provider) : base(virtualDir){spp = provider;}
       private ArrayList children = new ArrayList();
       public override IEnumerable Children {get {return children;}}
       private ArrayList directories = new ArrayList();
       public override IEnumerable Directories{get {return directories;}}
       private ArrayList files = new ArrayList();
       public override IEnumerable Files{get { return files;}}
    }
  16. Now, we are ready to define one more class that is named SharePointVirtualFile that extends the VirtualFile abstract class. This class will have one overridden abstract method that is named OpenFile. The OpenFile method returns a stream instance that is used by the ASP.NET build environment to consume the virtual file.
    public class SharePointVirtualFile : VirtualFile
    {
       private SharePointProvider spp;
       private string virPath;
    
       public SharePointVirtualFile(string virtualPath, SharePointProvider provider) : base(virtualPath)
       {
          this.spp = provider;
          this.virPath = virtualPath;
        }
    
        public override Stream Open()
        {
           string fileContents = spp.GetFileContents(virPath);
           Stream stream = new MemoryStream();
           if (fileContents != null || fileContents.Equals(String.Empty))
           {
               // Put the page content on the stream.
               StreamWriter writer = new StreamWriter(stream);
               writer.Write(fileContents);
               writer.Flush();
               stream.Seek(0, SeekOrigin.Begin);
            }
            return stream;
          }
    }
  17. At last, we will define the DBUtiliity class in the DBUtility.cs file. The method implementation is not provided. The DBUtiliity class is used by provider classes to obtain the content from database together with a few other utility functions.
    public class DBUtility
    {
       SqlConnection cnn;
       string connectionString = "connectionstring to DB.";
        
       //Run a select query to obtain all files and their content, and 
       //store the result in a in memory hashtable to obtain fast access.	
       string cmdSelectAllFiles = "SELECT FileName,FileData FROM VirtualFileSystem";
       Hashtable virtualFiles = null;
    
       public DBUtility(){ virtualFiles = new Hashtable();}
    
       public Hashtable GetVirtualFiles()
       {
          /* 1. Open a connection.  
             2. Select all the files.
             3. Iterate through the result and store the file name as a Key and
                content as a Value in a hashtable.
             4. Finally return hashtable.
          */
          return virtualFiles;
        }
    
       public string GetFileContents(string virPath)
       {
          //Obtain a file name from the virtual path. 
          string fileName = ExtractFileName(virPath);
            
          //Ater you obtain the file name, find it in the hashtable, and then
          //return the content for that file from the Values collection.	
        }
    
       private string ExtractFileName(string virPath)
       {
           //Extract a file name from the virtual path and return it.
       }
    
       public bool CheckIfFileExists(string virPath)
       {
           string fileName = ExtractFileName(virPath);
           //After you extract the file name, find it in the hashtable of 
           //virtual files. If the file name is found, return true. Otherwise, return false.	
        }
    }
    
    Note One thing to keep in mind is that registering a provider is a privileged operation because it can change the content rendered. Custom classes for the VirtualPathProvider, VirtualDirectory, and VirtualFile classes need to run under Full trust permissions. For more information, visit the following MSDN Web site:
  18. We are done writing the provider classes, and now our application is ready to serve the virtual content. To give the application the appearance and behavior of a SharePoint Portal Services administration Web page, I have created an AdministrationPage.aspx page at the root folder of the Web site. The page contains a GridView control that is bound with a virtual table.
    <asp:GridView ID="GridView1" runat="server"  
          AutoGenerateColumns="False" DataSourceID="SqlDataSource1" >
        <Columns>
          <asp:BoundField DataField="FileName" HeaderText="FileName" />
          <asp:TemplateField HeaderText="Remote Content" >
            <ItemTemplate>
              <asp:Label ID="Label1" runat="server"  
                         Text="FileContent...."></asp:Label>
            </ItemTemplate>
            <EditItemTemplate>
              <asp:TextBox ID="Label1" runat="server" Text='<%# 
                    Eval("FileData", "{0}") %>'></asp:TextBox>
            </EditItemTemplate>
            </asp:TemplateField>
            <asp:HyperLinkField HeaderText="Virtual Path" 
                 DataTextField="VirtualPath" Target="_self" 
                 DataNavigateUrlFormatString= "{0}"  
                 DataNavigateUrlFields="VirtualPath" />   
            <asp:CommandField  ShowEditButton="True" 
                 ShowDeleteButton="True" CausesValidation="false" 
                 HeaderText="Operations" CancelText="Cancel" />   
       </Columns>
    </asp:GridView>
    <asp:SqlDataSource ID="SqlDataSource1" runat="server" 
                       ConnectionString="<%$ 
        ConnectionStrings:VirtualProviderDBConnectionString %>"
        SelectCommand="SELECT [FileName], [FileData], [VirtualPath] FROM 
        [VirtualFileSystem] Where [FileName] LIKE '%aspx%'">
    </asp:SqlDataSource>
    

Conclusion

For virtual path providers, this is all for now. I hope that this column will help you understand the basic compilation process for ASP.NET 2.0 and how we can let the ASP.NET runtime work with non-file based content, such as content served from a database.

Thank you for your time. We expect to write more on the new features added in ASP.NET 2.0. For more information and samples, visit the MSDN Web sites:

ASP.NET compilation overview
http://msdn2.microsoft.com/en-us/library/ms178466.aspx

Virtualizing access to content: Serving your Web site from a ZIP File
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnaspp/html/vpp_vga.asp

As always, feel free to submit ideas on topics you want addressed in future columns or in the Knowledge Base using the Ask For It form.

Modification Type:MajorLast Reviewed:3/16/2006
Keywords:kbhowto kbASP KB910441 kbAudITPRO kbAudDeveloper