Virtualizing Access to Content: Serving Your Web Site from a ZIP File

 

Victor Garcia Aprea
Clarius Consulting

January 2006

Applies to:
   Microsoft ASP.NET 2.0

Summary: Virtual path providers are a new feature of Microsoft ASP.NET 2.0 that enables serving many of the files of a Web site that are not stored in a file system. In this article, Victor Garcia Aprea creates a virtual path provider that serves a Web site stored in a .zip file. (15 printed pages)

Click here to download VPP.msi.

Contents

Introduction
Skeleton of a VirtualPathProvider
VirtualFile and VirtualDirectory
The Built-In MapPathBasedVirtualProvider
Provider Registration
Use the Virtual Path Provider, Not the File System!
Virtualize This!
Hands On: Serving the Files of a ZIP File!
More Virtual Path Provider Samples to Try!
Conclusion

Introduction

The new Microsoft ASP.NET 2.0 feature we are going to look at in this article provides virtualized access to files and folders used by the ASP.NET build system (with the exception of the special folders, such as App_Code, App_Data, and so on). As you already know, Microsoft ASP.NET 1.0 introduced a build system that supported dynamic compilation for some file extensions, such as .aspx, .asmx, .ashx and .asax, meaning that you could drop a new Web page, Web service, or handler file, or even modify an existent one, and ASP.NET would automatically parse and compile the file before using it.

Of course, this build system had to retrieve the files to be compiled from somewhere, and for version 1.x, that place was the file system and no other. In version 2.0, the default location is still the file system, but now with a proper pluggable model in place to allow you to easily change this. This means ASP.NET is asking you "where should I get the content from?" and the new Virtual Path Provider feature is what you can use to answer this.

The ASP.NET team did take this feature seriously, as you can discern from the fact that all access to the file system itself, which in previous versions was done directly by using the classes in the System.IO namespace, are now going instead through a virtual path provider, the MapPathBasedVirtualPathProvider, which we will be looking at in greater detail in this article.

In order to take advantage of this feature, you must simply write your own custom virtual path provider, extending a few base classes and overriding key methods to handle files and directories requests, and register your new provider with ASP.NET.

During the course of this article, we will be looking at each of the classes that make up this feature, as well as some other built-in classes, such as the MapPathBasedVirtualPathProvider mentioned above, and we will write our own custom virtual path provider. In order to better understand how things work, we will be peeking at the source code for some of these classes, for which we will use the superb Reflector tool (http://www.aisto.com/roeder/dotnet/) written by Lutz Roeder.

Skeleton of a VirtualPathProvider

A virtual path provider is modeled by the abstract VirtualPathProvider class. In order to provide your own custom virtual path provider, you need to subclass it and override key methods that will be called by ASP.NET in order to access the virtualized directories and files. Let's start looking at its methods:

  • public virtual VirtualFile GetFile(String virtualPath);
  • public virtual VirtualDirectory GetDirectory(String virtualDir);

These two methods, as you can easily tell from their names, are called every time ASP.NET needs to access a file or a directory, respectively. Both take as their only parameter a single string containing the virtual path to be accessed. What they return, however, is different: a VirtualFile in the first case, and a VirtualDirectory in the second one. These classes represent a virtual file and a virtual directory, respectively, and we will be taking a look at them soon.

When ASP.NET needs to check for the existence of a request target, it will call a provider's FileExists method, passing to it the virtual path to check; this method signature looks like the following

public virtual Boolean 
  System.Web.Hosting.VirtualPathProvider.FileExists(String virtualPath)

A provider should return a Boolean value or, in the case of chained virtual path providers, the value provided by the Previous property, indicating whether it can serve the provided virtual path. This method will also be called in other circumstances—for example, when checking for special files such as global.asax. There is also a corresponding DirectoryExists method that should tell whether the virtual path provider can access the specified virtual directory.

For performance reasons, it is very important to determine when a file changes, in order to perform "heavy" tasks as such as a file parsing and compilation only when absolutely necessary. In a world with only file systems in mind, this could be accomplished by file change notifications (for example, using System.IO.FileSystemWatcher), but for an extensible model scenario, where the provider may go anywhere looking for its content, this does not look that easy. To help with this, each virtual path provider should implement the GetFileHash and GetCacheDependecy methods in order to help ASP.NET determine when files have changed. Note that you need to implement only one of them for a given virtual path, not both. ASP.NET calls GetCacheDependency first, because it relies on change notifications and is more efficient. If GetCacheDependency returns null, ASP.NET will call GetFileHash instead, which is heavier, because it needs to be called every time the resource is accessed.

ASP.NET will call a virtual path provider's GetFileHash method, passing in a virtual path (for example, /webapp/default.aspx) and a list of its dependencies (for example, /webapp/default.aspx.cs), and expecting to get back a string that contains a hash representing the current state of the provided input, as you can see from the method's signature.

public virtual String GetFileHash(String virtualPath, 
  IEnumerable virtualPathDependencies)

The returned hash will be preserved (in memory, and to disk to preserve application restarts) by ASP.NET in order to compare it with the one returned in a future call to the same method for the same virtual path; if they do not match, GetFileHash returns null, and this means the specified virtual path has changed and that it is necessary to incur the cost of building it again (which, besides compilation work, may include parsing files, generating proxies classes, and so on).

Let's now look at the signature for the GetCacheDependency method.

public virtual CacheDependency GetCacheDependency(string virtualPath, 
  IEnumerable virtualPathDependencies, DateTime utcStart)

As you can see, it takes the same set of arguments as the previous GetFileHash method, plus an extra one, utcStart, which is a DateTime representing the time at which files were read. The virtual path provider is expected to use this information in order to build a proper CacheDependecy instance that will be invalidated when the specified virtual path or any of its dependant virtual paths become invalid. Counting on this, ASP.NET will use the returned CacheDependency instance when caching compiled resources.

It is important to note that in ASP.NET 2.0, the CacheDependency class is no longer marked as sealed, thus enabling its subclassing for the creation custom types of dependencies (the public built-in SqlCacheDependency is a good example of this). Any virtual path provider could have its own cache dependency type to be returned from the GetCacheDependecy method.

Lastly, there is the Initialize method.

protected virtual void Initialize()

By overriding this method, a virtual path provider can add any required tasks that should be performed at initialization time, before any files or directories are accessed; as we will see later on, this method will be called as soon as the provider is registered within ASP.NET.

VirtualFile and VirtualDirectory

It is time now to look at the VirtualFile and VirtualDirectory abstract classes. These classes provide an abstraction for virtualized files and directories, respectively, and all virtual path providers are expected to extend both of them in order to implement the necessary logic on how to handle files and directories for that specific virtual path provider.

They both derive from the abstract common ancestor class, VirtualFileBase, which defines the following three properties.

  • public abstract bool IsDirectory { get; }
  • public virtual string Name { get; }
  • public string VirtualPath { get; }

Of these three properties, only IsDirectory is declared as abstract, and therefore any derived class is forced to implement it; the other two just provide a default implementation that would be just enough for most cases: the Name property returns just the resource name and file extension (for example, page.aspx), whereas the VirtualPath property returns the complete virtual path (for example, /webapp/page.aspx).

VirtualFile models a virtualized file, and for this reason it overrides the IsDirectory property so that it always return false, and it adds an abstract method named Open, taking no arguments and returning a Stream.

public abstract Stream Open();

Overridden versions of this method are expected to implement whatever logic is necessary to return a Stream containing the contents of the requested virtual file. For a virtual path provider that gets its content from a database, this could mean going to the database, searching for a specific record, wrapping its content into a stream, and returning that. As of this writing, ASP.NET does not require that the returned stream be writable or seekable, and most probably it is going to stay this way, so it should be safe enough for you to return a read-only and non-seekable stream (although it will not hurt to return a writable and/or seekable one).

On the other hand, VirtualDirectory (aside from the obvious overriding of the IsDirectory property so that it always return true) contributes the following three new abstract and read-only properties returning an IEnumerable instance.

Table 1

Property Name Description
Files Returns an IEnumerable instance to enumerate all of the virtual files of the virtual directory.
Directories Returns an IEnumerable instance to enumerate all of the child virtual directories of the virtual directory.
Children Returns an IEnumerable instance to enumerate all of the virtual directories and virtual files of the virtual directory.

These properties will be accessed by ASP.NET when it needs to enumerate files and directories while performing tasks that may require this—for example, batch compilation. If there are no directories or children to return from the Directories and Children properties, the ASP.NET team recommends that they should return an empty collection instead of returning null.

The Built-In MapPathBasedVirtualProvider

In order to get a good grasp of how to build a virtual path provider, let's start by taking an in-depth look at how the built-in MapPathBasedVirtualProvider was implemented. This is ASP.NET's default virtual path provider, and its purpose is to map virtual paths to corresponding files stored in the file system.

As any provider is required to do, the MapPathBasedVirtualProvider derives from the VirtualPathProvider abstract class. We have already discussed the most relevant VirtualPathProvider's methods, and now we will see how they were implemented for this specific provider.

The overridden FileExists and DirectoryExists methods have a very simple goal: map the requested virtual path (for example, vpp/webform1.aspx) to a physical path (for example, C:\inetpub\wwwroot\vpp\webform1.aspx) by using an internal MapPath method (similar to the Server.MapPath method you are used to), and then feed this physical path to either System.IO.File.Exists or System.IO.Directory.Exists, respectively, for checking that there is a corresponding file or directory.

Things get a bit more interesting with the GetFile and GetDirectory methods. As we have already seen, these methods must return a VirtualFile and VirtualDirectory instance, respectively; but these classes are abstract classes, and therefore they cannot be instantiated to be returned from the previous methods. Because of this, two classes had to be written, MapPathBasedVirtualFile and MapPathBasedVirtualDirectory, both being specializations of each of the previously mentioned classes.

MapPathBasedVirtualFile overrides the base Open method, and its implementation consists in mapping the virtual path to a physical path, and then returning a FileStream wrapping the physical file and opened in read-only mode. Note that this returned stream is a seekable one (FileStream) although, as I commented before, this does not happen to be a requirement, because there are no Build Providers in ASP.NET that actually take advantage of this.

As its file counterpart, MapPathBasedVirtualDirectory does not perform much work either; it just overrides the three properties I mentioned when looking at the VirtualDirectory class—Children, Directories, and Files—in order to return an IEnumerable instance that can be used to enumerate through files, directories, or both at the same time. The real work was factored out into the internal MapPathBasedVirtualPathEnumerator class, which does not perform a simple enumeration on what is on the file system but will also take into account IIS metabase information (for instance, it will skip files and folders whose hidden attribute is set, and it will also ignore files that do not belong to the current application).

The other two methods that are overridden by MapPathBasedVirtualDirectory and that I have not mentioned yet are GetFileHash and GetCacheDependency, which you should remember are there to help ASP.NET decide when a file was modified, so that it can take appropriate actions.

The first method, GetFileHash, needs to produce a good enough hash string based on one or more virtual paths provided as input. For this purpose, it uses the internal HashCodeCombiner class, which basically works by receiving different kinds of inputs and generating a hash; one of the methods of this class can take a path to a file and use the file attributes (name, creation time, last write time, and length) in the hash generation; this method is the one called by GetFileHash repeatedly for each of the received virtual paths (the main one and its dependencies).

The implementation for GetCacheDependecy is also pretty simple. It will just convert the virtual paths that it received as input to physical paths on the file system, and use these to create an instance of the CacheDependecy class calling the constructor that takes an array of filenames.

Provider Registration

When I was first looking at this feature, one of the first things that came to mind was the popular "the chicken or the egg" dilemma. That is, if I need to specify which virtual path provider to use in the web.config file, and if at the same time I want that file to be stored into, for instance, a database, how could this ever work? The solution was pretty simple: registration must be done programmatically, and all files that are required to start up the application (for example, web.config) cannot be virtualized. This leads to a new interesting question: What is the right moment to programmatically register a custom virtual path provider? The answer is: As soon as possible, which translates to one of the following two options:

  • Global.asax's Application_OnStart event
  • AppInitialize static method

The first option is a well known application event that any ASP.NET developer is already familiar with, whereas the second is new to ASP.NET 2.0. Suppose that you have a public static method in a public class whose signature is exactly as follows.

public static void AppInitialize ()

This class needs to be put into the App_Code folder (or any folder below this one, provided that the class gets compiled into the main App_Code assembly), and then ASP.NET will call that method when the application starts.

Note that although registering a virtual path provider at some other point—for example, in a page Load event—may work, it may cause some unexpected behavior as the very first request that is served from a different location than following requests; that is why I strongly recommend that registration be made in any of the two previously mentioned methods.

Now that we know when we should register a virtual path provider, let's see how we can actually do it in code. Registration is done by calling the static method RegisterVirtualPathProvider of the HostingEnvironment class which takes the virtual path provider to be registered as its only parameter, and returns no value. This amounts to the following single line of code.

HostingEnvironment.RegisterVirtualPathProvider (YourVPPInstance);

An interesting thing to note is that this method has the following metadata attribute applied.

[AspNetHostingPermission(SecurityAction.Demand, 
  Level=AspNetHostingPermissionLevel.High)]

This means that, in order to be able to register a provider, your code needs to run with high trust; otherwise, a SecurityException will be thrown. This is a welcomed security check, because changing from where the compilation sources are read is quite a powerful operation.

With the knowledge that registering a virtual path provider takes a single line of code, let's now see some of the details that go under the covers when registering one, by looking at the implementation of the previously mentioned RegisterVirtualPathProvider method.

First of all, two checks will be performed; these happen before actually registering the passed in provider, and they consist of making sure that:

  • There is no hosting environment in place.
  • The application is precompiled.

If either of the preceding conditions is true, registration will fail, with an InvalidOperationException being thrown in first case, and silently in the latter case.

If both checks are passed, the specified virtual path provider is saved as the current virtual path provider to be used by ASP.NET, and the provider's Initialize method is called in order to give it a chance to perform any setup task it may require.

It is worth noting that the Initialize method called by HostingEnvironment is an internal one, not the public virtual one that we may have overridden in our custom VirtualPathProvider-derived class. Its simple implementation looks like the following.

  
    internal virtual void Initialize(VirtualPathProvider previous)
{
      _previous = previous;
      Initialize();
}

  

The preceding code makes sure that the new registered provider keeps a reference to the previously registered one, thus enabling the former to access the latter (by means of the Previous property) in case it may want to support a "fallback" scenario where part of the namespace may be still served by the previous provider.

After saving that reference, our overriden Initialize method finally gets called, and any custom initialization that we may have written for our provider takes place.

Use the Virtual Path Provider, Not the File System!

It is important that you start thinking more about virtual paths and less about the file system when it comes to accessing files from your Web application. Of course, this does not mean "the death of System.IO.File for Web applications" (a much more catchy and dramatic title that I could have used for this section), but just that you should consider when one or the other makes the most sense.

Note that this is only in cases where the virtual path provider is the only source of content. In many cases, you will want to have a fallback provider, to provide content not provided by your virtual path provider. For example, some of your Web site may be provided from a database, which would default to the default File Provider if the content is not in the database.

Along with all of your current ASP.NET 1.x code, you are likely to have at least a few file accesses here and there. You should examine them carefully and—where it makes sense—replace the common file opening code as follows.

Stream st = File.Open("myconfig.xml", FileMode.Open);

with the following code:

VirtualPathProvider vpathProvider = 
  HostingEnvironment.VirtualPathProvider;
VirtualFile virtualFile = vpathProvider.GetFile("somefile.xml");
Stream st = virtualFile.Open();

The above code is just getting the current virtual path provider, asking it for the somefile.xml file, and calling the Open method on the returned VirtualFile instance just to get the stream we need in the first place. Although this is really straightforward, you may be wondering "What about the promised code reduction in ASP.NET 2.0? Was I not supposed to write less and not more code than in previous versions?" This is true, and it is why the public static VirtualPathProvider.OpenFile method exists, just for wrapping the common pattern of opening virtual files in one line of code, as follows.

Stream st = VirtualPathProvider.OpenFile("myconfig.xml");

Let's talk a bit now about when it makes sense to use virtual paths instead of directly accessing the file system. One of the most common cases is when you are into writing custom controls, and have a property that is used to specify a file from which to read configuration or any other persisted data. If you write your custom control in such a way that it accesses only the file system and knows nothing about the new Virtual Path Provider feature, your users who are trying to use this new feature will not be happy with you at all. They will still need to keep some configuration and data files on the file system in order for your control to work properly. This is one of the most common cases where you should write your file access code in such a way that it goes through the current virtual path provider and not directly to the file system (the previous VirtualPathProvider.OpenFile we just saw is useful here).

The Control base class was modified to include an OpenFile method whose signature is as follows.

protected internal Stream OpenFile(string path)

The purpose of this method is to help you handle the opening of files that are specified with an absolute physical path or a virtual path, indistinctly. You can feed any of these two kinds of path to OpenFile, and it will detect the path type, use the appropriate API to get at the file, and return a Stream wrapping its content.

Note that what is described in this section applies not only to the code for custom controls, but to all code found in your Web application.

Take your time, review every file-access your application has, and where it makes sense, start using the new virtual path feature. The ASP.NET team did this, and as a result, several file system access were modified to use the Virtual Path Provider feature. An example of this is the built-in AdRotator control, modified so that it now get its configuration file from a virtual path provider instead of the file system, as it was done in version 1.x, thus allowing its consumers to easily virtualize access to that file.

Virtualize This!

It is not possible to have virtualized access to all files and directories, because there are a few that were left out by design—namely, the web.config and global.asax files, and the App_Data, App_Code, Bin, App_GlobalResources and App_LocalResources directories (note that the App_Theme can actually be virtualized).

Also, keep in mind that if your Web application contains files with a custom extension that was not mapped in IIS to ASP.NET (for example, ".customer" files), you will need to add the mapping, so that ASP.NET can get to service requests for that extension. Otherwise, you will not get virtualization for those file extensions (if you are running a simpler Web server, such as the one that comes built-in with Microsoft Visual Studio .NET 2005, you do not need to add any additional mappings to get this feature to work).

Hands On: Serving the Files of a ZIP File!

We have already looked at the object model for virtual path providers, and we looked at the implementation of the MapPathBasedVirtualPath provider. Now, armed with all of this knowledge, we are ready to start coding our own custom virtual path provider.

Although the most common example that comes to mind when thinking about a custom virtual path provider is one that looks for content in a database, I will try to be a bit more original for this article, and I will present one that will go looking for content in… a .zip file! For easily accessing the contents of zipped files, I am not writing any new code, but just using the open source project SharpZipLib library, which you can freely download from http://www.icsharpcode.net/OpenSource/SharpZipLib/Default.aspx.

Please note that this is just demo code to showcase how this feature works; the code is by no means meant to be used in a production application.

First, we need to have our custom virtual path provider class, which we will name ZipFileVirtualPathProvider, inherit from the VirtualPathProvider base class. We will provide a constructor that can be used to specify the location of the .zip file from which contents are going to be served, as follows.

public ZipFileVirtualPathProvider (string zipFilename)
    : base () {
    _zipFile = new ZipFile (zipFilename);
}

The constructor will create a ZipFile instance (which is a class from the SharpZipLib library used to manage a .zip file), and save a reference to it in the private _zipFile member variable for later use.

Now let's turn to the FileExists and DirectoryExists methods, which will check whether the specified virtual path can be served from the provider. They look almost the same, and therefore I will be spending time only with the first one.

public override bool FileExists (string virtualPath)
{
    string zipPath = 
      Util.ConvertVirtualPathToZipPath (virtualPath, true);
    ZipEntry zipEntry = _zipFile.GetEntry (zipPath);
    if (zipEntry != null)
    {
       return !zipEntry.IsDirectory;
    }
    else
    {
        return false;
    }
}

Remember that FileExists is the method that ASP.NET is going to call every time it needs to check for the existence of a virtual path, either when a request for that virtual path is received, or when it needs to access one well-known file. Therefore, what our implementation of FileExists is doing is checking that the specified path is contained in the compressed file, and that it is of the right type, file, or folder. For this, it uses the ZipFile instance we created in the class constructor to manage the compressed file. Note that this method is also using one of the two utility methods I had to code, ConvertVirtualPathToZipPath and ConvertVirtualPathToZipPath, because virtual paths are not compatible with the way SharpZipLib stores paths in the compressed file (for example, one type contains a leading slash while the other type does not, and so on). As a result, there was a need to convert back and forth between both formats. Note that, in the preceding code, I am not including a call to Previous.FileExists in the event that our provider cannot serve a given virtual path, because I want our provider to serve only files stored in the .zip file; however, you may want to consider adding such a call, thus making the request go down in the chain of registered providers, depending on what your requirements are.

After checking that a given file or directory exists, ASP.NET may want to get at it by calling the GetFile or GetDirectory methods. The implementation coded in our custom virtual path provider for these two methods is simple, because it will just limit itself to returning a proper ZipVirtualFile or ZipVirtualDirectory instance wrapping the specified virtual file or directory. The GetFile method looks as follows (GetDirectory is almost identical).

public override VirtualFile GetFile (string virtualPath) {
            return new ZipVirtualFile (virtualPath, _zipFile);
}

We will not provide any support for file change notifications and caching for our custom virtual path provider. We will only override GetFileHash so that it returns null, thus causing ASP.NET to think the file never changes. This is good enough for our sample, because we will not be supporting the updating of compressed files.

Now that we have all the proper overridden methods of ZipVirtualPathProvider in place, we will look at ZipVirtualFile and ZipVirtualDirectory, the classes that will abstract how a file and a directory look for our custom virtual path provider.

ZipVirtualFile overrides the Open method in order to get at the compressed file inside the .zip file, extract it contents, wrap them into a MemoryStream, and return that to the caller; it does this with the following code.

public override System.IO.Stream Open () {
    ZipEntry entry = 
      _zipFile.GetEntry( 
      Util.ConvertVirtualPathToZipPath(base.VirtualPath,true));
    using (Stream st = _zipFile.GetInputStream (entry))
    {
         MemoryStream ms = new MemoryStream ();
         ms.SetLength (entry.Size);

         byte[] buf = new byte[2048];
         while (true) {
           int r = st.Read (buf, 0, 2048);
           if (r == 0)
               break;
           ms.Write (buf, 0, r);
         }
         ms.Seek (0, SeekOrigin.Begin);
         return ms;
    }
}

In this way, when ASP.NET calls the Open method on a ZipVirtualFile instance, it will end up getting an uncompressed stream containing the contents of the file that can be used by the build system.

As was the case for the built-in MapPathBasedVirtualDirectory, our ZipVirtualDirectory does not contain any interesting code, aside from the code overriding the Children, Directories and Files properties.

public override System.Collections.IEnumerable Children
    {
        get {
           return new ZipVirtualPathCollection (base.VirtualPath, 
             VirtualPathType.All, _zipFile);
        }
    }

    public override System.Collections.IEnumerable Directories
    {
        get {
            return new ZipVirtualPathCollection (base.VirtualPath, 
              VirtualPathType.Directories, _zipFile);
        }
    }

    public override System.Collections.IEnumerable Files
    {
        get {
            return new ZipVirtualPathCollection (base.VirtualPath, 
              VirtualPathType.Files, _zipFile);
        }
    }

Note that each one of the preceding properties is returning a ZipVirtualPathCollection instance where the real work of enumerating files and folders will take place. This collection can enumerate files, folders, or both, and we use an enumeration type, VirtualPathType, to indicate what we want enumerated. The virtual path to enumerate, and a reference to the class managing the .zip file, are also passed to the constructor, because they will be needed by the collection in order for it to do its work. The enumeration code itself is performed in the private PerformEnumeration method, and it is mostly based on code that uses the classes provided by the SharpZipLib library in order to examine the compressed files and folders stored in the .zip file and iterate them.

Test-Driving the ZipFileVirtualPathProvider

It is time to check that everything works as it should. Open up the VirtualPathProviderDemo.sln solution file found in the code download for this article, and look for a Web site in there. You will notice that there is almost no Web site at all: only two files and a Bin folder. One of the files is global.asax, and it is there so that we can add the code (only two lines of it, actually) to the Application_Start event in order to register our custom virtual path provider. The other file is ZippedWebSite.zip, and it is the file containing the actual contents for the test Web site.

If you check the properties for the Web site, by right-clicking its node in the Solution Explorer and clicking Property Pages, you will notice on the Start Options tab that Start action is set to the WebForm1.aspx page, as shown in Figure 1.

Aa479502.vpp_vga1(en-us,MSDN.10).gif

Figure 1. Setting the start page

There was no WebForm1.aspx page when you last checked the files in the Web site, but there is such a file in the ZippedWebSite.zip file, and therefore we should expect this page to still be served. Now, press CTRL+F5 to start the Web application and have a browser pointing to the start page; the simple output of WebForm1.aspx, which contains embedded code to output the current time, should be rendered to your browser.

The zipped file also contains a WebForm2.aspx file. This has a CodeFile attribute pointing to the WebForm2.aspx.cs file, which is also stored into the zip file. If you now point your browser to WebForm2.aspx you will not notice much difference in the rendered output, because the codebehind file is doing the same as the previous page—namely, outputting the current time; however, it is interesting to note that in this particular case, the build system had also retrieved a code only (Microsoft C#) file through our virtual path provider.

Now, for a final test, we will point the browser to index.htm. This page includes several other files—.htm, .css, .js and .swf files that will be automatically requested by the browser, thus putting some real work onto our virtual path provider! You should be able to navigate the whole site (with all the content being served from the .zip file) by clicking the different links provided on the index page. Remember that if you are using IIS, you will need to map all file extensions to ASP.NET for this test to work.

More Virtual Path Provider Samples to Try!

At the time of writing, there is another demo implementation of a virtual path provider that you can try out. It is a pretty simple, yet powerful one that will look for content inside a database instead of the file system. It was written by Scott Guthrie, and you can download the source code from his Web site, http://scottgu.com (look for the Whidbey Tips & Tricks presentation download).

Conclusion

In this article, we saw the base classes that make up the new Virtual Path Provider feature, looking at the key methods you have to override, and when and why they will be called by ASP.NET. We also looked inside one of Microsoft's implementation of a virtual path provider, the built-in MapPathBasedVirtualPathProvider, learning a few more things along the way about how to implement a custom virtual path provider. Finally, we implemented our own custom virtual path provider to put into action all that we learned before.

The first applications that will benefit from this new feature are most probably content management systems and portal applications, because they work by storing their content into a database: they usually translate a request path into a database record, and then process and serve what is in there. Products such as Microsoft SharePoint that rely heavily on a database instead of the file system for storing most content, will greatly benefit from having such a feature backed right into ASP.NET instead of having to write their own code to do this. In fact, the next version for Microsoft SharePoint was designed to take advantage of this new feature from the ground up.

 

About the author

Victor Garcia Aprea is founder of Clarius Consulting S.A., providing training, consulting, and development in Microsoft .NET technologies.

Victor has been involved with ASP.NET since its very early bits, and has been awarded as a Microsoft MVP for ASP.NET each year since 2002. He has written books and articles, and has done a lot of reviewing for Wrox Press, APress, and Microsoft Press, and he is also a regular speaker at Microsoft Argentina (MSDN DevDays, Ask the Experts panel, and so on), .NET Local User Groups, and international conferences such as VSLive!.

© Microsoft Corporation. All rights reserved.