Virtual Path Providers in EPiServer CMS 5

Product version:

EPiServer CMS 5 R2 SP2

Document version:

1.0

Document last saved:

Introduction

EPiServer CMS can use Virtual Path Providers (VPP) to serve static files to visitors. The reason for using VPPs is to allow EPiServer CMS 5 programmatic control of what files to send as well as additional security checks. VPP is the mechanism that ASP.NET uses to load and compile content. For a brief introduction, see ASP.NET Compilation Overview at MSDN.com.

Table of Contents

Behind the Scenes

When ASP.NET 2.0 gets a request to supply a file based on a virtual path it looks in the registered chain of providers and feeds the virtual path to the first provider in the chain.

It is up to the provider to interpret the path to determine if this is a file which belongs in the providers underlying file system. If so it serves the file back to ASP.NET, otherwise it calls the next provider to give it a chance to serve the file and so on.

A provider can of course accept the virtual path as valid for the file system, but the file doesn’t exist. In such case a null reference is returned, eventually turning up as an HTTP 404 response.

A file served from a provider must extend the abstract class of System.Web.Hosting.VirtualFile. For serving directories the base class is System.Web.Hosting.VirtualDirectory. The core API is as mentioned only intended for serving files but this behavior is extended in the EPiServer API.

In order to create your own provider implementation and to be compliant with the UI interface of EPiServer your classes must derive from EPiServer.Web.Hosting.UnifiedFile. This class is derived from VirtualFile and specifies the necessary methods for FileManager UI support.

In order for you to use the providers on all requests, not only those served by ASP.NET you can add a wildcard mapping in IIS to have ASP.NET serve for example image files etc. This will give your provider implementation to get requests for virtual paths e.g. "/EpiServerSample/upload/myImage.gif".

Virtual Paths For Providers in EPiServer CMS 5

When requesting instances using the HostingEnvironment you must provide a virtual path. This path is always relative to an URL. The EPiServer implementations of providers around the native file system and the versioning supports the ASP.NET standard of handling relative paths.

Examples of Virtual Paths (assuming the application root folder is http://localhost/EpiServerSample)

VirtualPath Absolute URL
~/upload http://localhost/EpiServerSample/upload
/EPiServerSample/upload http://localhost/EpiServerSample/upload
/upload http://localhost/upload

Note that the two first examples map to the same URL. It is up to the provider implementation to handle the two different virtual path requests similarly.

A virtual path cannot be relative to a host subfolder because the hosting environment cannot determine what it’s relative to. This means it must be absolute from the host (as above) or resolved to absolute if in syntax of "~/". It also follows the URI syntax of an HTTP path, i.e. forward slash, no query string etc.

A good recommendation for handling virtual paths is to use the class System.Web.VirtualPathUtility for converting between relative and absolute paths and for handling slashes.
There is a specific distinction between ~/upload and ~/upload/. The first refers to a file, the second to a directory.

Configuring a Virtual Path Provider

ASP.NET does not provide a section in Web.config for registering providers. This can only be made at runtime. EPiServer has created a custom section which helps you with this. The section is located under the XPath node "/configuration/EPiServer/virtualPath".

The syntax for the provider configuration follows the same standard as membership and role providers. In fact it uses the same classes for reading the section from web.config. The registration of providers is made at application startup.

Note that the order of the configured providers will matter. The provider at top will be instantiated first and put at the top of the provider chain. The next provider will also be put at the top of the chain pushing the previous down one step and so on.

Example of configuration:

<virtualPath customFileSummary="~/docrepository/summary.config">
  <providers>
    <add 
      showInFileManager="false" 
      virtualName="Page Files"
      virtualPath="~/upload/" 
      bypassAccessCheck="false"
      indexingServiceCatalog="Web" 
      name="SitePageFiles"
      type="EPiServer.Web.Hosting.VirtualPathVersioningProvider,EPiServer"/>

    <add 
        showInFileManager="false" 
        virtualName="DontCare"
        virtualPath="~/upload88/" 
        bypassAccessCheck="false"
        name="EPiServerUrlMappingVPP"
        type="EPiServer.Web.Hosting.VirtualPathMappedProvider,EPiServer" />

    <add 
        showInFileManager="true" 
        virtualName="Upload"
        virtualPath="~/upload2/" 
        bypassAccessCheck="false"
        indexingServiceCatalog="Web" 
        name="EPiServerNativeVPP"
        type="EPiServer.Web.Hosting.VirtualPathNativeProvider,EPiServer"/>

    <add 
        showInFileManager="true" 
        virtualName="Documents"
        virtualPath="~/docrepository/" 
        bypassAccessCheck="false"
        maxVersions="5" 
        name="EPiServerVersioningVPP"
        type="EPiServer.Web.Hosting.VirtualPathVersioningProvider,EPiServer" />
  </providers>
</virtualPath> 

Attribute explanation:

Attribute Comments
customFileSummary Definition of XForm holding custom META data for all file systems. NOTE that this differs from older EPiServer versions which had support for one definition per file system.
showInFileManager true/false - This provider supports the FileManager UI. Must implement the EPiServer VPP extension interfaces.
name Unique name of provider instance.
type Type and Assembly information for instance creation using reflection API.
virtualPath Virtual Path to file system Root.
virtualName Name of Root catalog. Shows in FileManager UI instead of the virtual path root.
bypassAccessCheck true/false If EPiServer access rights shall be checked before supplying a file or directory.
* Implementation specific. Can be any name and arbitrary in number.

VirtualPathNativeProvider implementation specific:

Attribute Comments
indexingServiceCatalog The catalog name from Indexing Service where searching in UI operates on.

VirtualPathVersioningProvider implementation specific:

Attribute Comments
maxVersions Numeric number of max versions a file can have.

IIS location settings

IIS6 and IIS5 handles virtual file wildcard mapping differently. In order for a site running under IIS6 to properly function with Virtual Path Providers, each section defined in the <add> element of the VPP settings need to have a matching <location> setting. See the following example on how this can be done.

<location path="Upload">
  <staticFile expirationTime="-1.0:0:0"/>
  <system.web>
    <httpHandlers>
      <add path="*" verb="GET,HEAD" type="EPiServer.Web.StaticFileHandler, EPiServer" validate="true" />
    </httpHandlers>
  </system.web>
</location> 

IIS7 has a different scheme to configure the httpHandler. The following is an example on how the same configuration is done in IIS7. 

<location path="Upload">
  <staticFile expirationTime="-1.0:0:0"/>
  <system.webServer>
    <handlers>
      <add name="wildcard" path="*" verb="*" type="EPiServer.Web.StaticFileHandler, EPiServer"/>
    </handlers>
  </system.webServer>
</location> 

File Manager in EPiServer UI

For page folders and the files connected to a page by its folder, id is handled by a VirtualPathUnifiedProvider. This is by default configured in Web.config and is the only provider which must exist. The providers have a unique name in configuration and the page files provider name is referred to by the attribute pageFolderVirtualPathProvider of a siteSettings node.

The provider for page files can be replaced with any custom provider implementation as longs it’s built upon the EPiServer extension of the VPP API.

All other registered providers based on VirtualPathUnifiedProvider (except providers configured with attribute showInFileManager="false") will show up in the File Manager in the edit and admin UI.

A VPP can be created without support in the FileManager UI. Such a provider does not need to extend implementations from the EPiServer extended classes of the core API. Those classes can be derived directly from VirtualPathProvider, VirtualFile and VirtualDirectory. An example of this is the EPiServer.Web.Hosting.VirtualPathMappedProvider.

Creating a Custom Provider Implementation

To create a custom implementation with UI support your provider needs to be derived from VirtualPathUnifiedProvider. This gives you some utility methods and validation on required configuration parameters as well as access to them.

The class is not intended to be used as a provider for delivering files and therefore abstract. It’s mainly used to ease implementation of custom providers.

The other classes which wraps up a complete provider implementation are handlers for Directories, Files, Summary and Versions (if you intend for your file system to support it). The following base classes are available be derived from:

  • UnifiedFile
  • UnifiedDirectory
  • UnifiedSummary 
  • UnifiedVersion

Note the difference from earlier EPiServer versions that the Unified classes are base classes, not top level abstraction classes. The abstraction layer is provided by ASP.NET and the implementation by you. Note again that the Unified classes are only there to ease your implementation if you want your provider to be supported in the edit and admin UI.

If you want your provider to act for other purposes you should use the core Virtual Path Provider API. You can still use the EPiServer config section to register your provider. For a sample of a provider implementation, see the code samples section.

In the EPiServer sample code there is a sample implementation of a provider (not supporting versioning). This is an in-memory file system which looses it structure if/when the application is restarted. It also has limitations in number of files and file size. The sample implementation will give you an idea on how to write a custom provider, which classes to use and how the EPiServer extended VPP API works.

The class is abstract and you need to provide implementations for methods GetFile, GetDirectory, FileExists and DirectoryExists.

Following configuration in virtualPath node enables the sample provider.

<add showInFileManager="true" 
     virtualName="Sample" 
     virtualPath="~/sample/"
     bypassAccessCheck="true" 
     name="EPiServerSampleVPP"
     type="EPiServer.Web.Hosting.VirtualPathSampleProvider,AssemblyName" /> 

Note: Replace "AssemblyName" with the name of your assembly.

Raise And Subscribe to Events

The UnifiedFile and UnifiedDirectory classes provide handlers for events. They can be raised and subscribed to for most of the events mapped to UI actions. For example rename, add and delete handlers. They all have pre and post mechanisms, i.e. an On[eventName]ing handler for an action about to take place and an On[eventName]ed after which the action is fulfilled.

A subscriber (for example a workflow) can, if it for some reason disapproves with the action to be executed, set the Cancel property of the event to true and the action will be aborted. It is up to the implementing provider to raise events. The Unified classes define a set of standard events but a provider can define its own events and raise those for certain actions or call the standard set, or even both.
The implementation in VersioningFile for delete action is as below:

Both directory and file events work on a generic event class UnifiedVirtualPathEventArgs. The first argument is the new virtual path the file will accept for the action. In delete this has no purpose and is set to null. In other actions it might be of importance, for instance for rename, copy and move.

OnDeleting raises the action to listeners which have the option to cancel the action. In such case an exception is thrown which is handled in the UI. If a custom implementation throws any other exception this is not handled in the UI. Only exceptions of type InvalidOperationException and UnauthorizedAccessException are supported.

If no cancellation is made the post event is raised after the action is complete. As mentioned before, raising events is an option you can use in your own provider implementation.