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 Web site hierarchy inside Visual Studio looks like the following:
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:
Let us start building the sample.
To do this, follow these steps:
- Start Visual Studio
2005.
- Create a Web site that has the exact hierarchy and files that appear in the earlier image.
- Open the SharePointDirectory.cs source file.
- 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;
- Declare a SharePointProvider class, and inherit the VirtualPathProvider class.
public class SharePointProvider :
VirtualPathProvider
- 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;
- 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());
}
- 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();
}
- 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);
}
- 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);
}
- 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);
}
- 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);
}
- 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: - 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:
- 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;}}
}
- 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;
}
}
- 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: - 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: