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: