Chapter 19. Advanced Navigation

<feature><title>In this Chapter</title> <objective>

Remapping URLs

</objective>
<objective>

Using the VirtualPathProvider Class

</objective>
<objective>

Summary

</objective>
</feature>

Websites tend to be organic—they grow and change over time. This can create problems when other applications link to your application. You need some way of modifying your website without breaking all the existing links to your website.

In this chapter, you learn how to remap URLs. In other words, you learn how to serve a different page than the page a user requests. In the first section of the chapter, you learn how to remap URLs in the web configuration file.

Next, you learn how to remap URLs by creating a custom HTTP module. Using a module is useful when you need to support wildcard matches and other types of pattern matching when remapping a URL.

Finally, you learn how to use the VirtualPathProvider class to remap URLs. You learn how you can store all your website pages in a database. In the last section of this chapter, a simple CMS (Content Management System) is built with the VirtualPathProvider class.

Remapping URLs

The simplest way to remap a URL is to specify the remapping in your application’s web configuration file. For example, the web configuration file in Listing 19.1 remaps the Home.aspx page to the Default.aspx page.

Example 19.1. Web.Config

<?xml version="1.0"?>
<configuration>
<system.web>
  <urlMappings>
    <add
      url="~/Home.aspx"
      mappedUrl="~/Default.aspx"/>
  </urlMappings>
</system.web>
</configuration>

The configuration file in Listing 19.1 contains a <urlMappings> element. This element can contain one or more elements that remap a page from a URL to a mapped Url.

CD Note

The code samples in this section can be found in the UrlMappingsApp application on the CD that accompanies this book.

The mappedUrl attribute can contain query strings. However, it cannot contain wildcards. You can use the <urlMappings> element only when performing simple page-to-page mappings.

After you add the web configuration file in Listing 19.1 to your application, any requests for the Home.aspx page are modified automatically to requests for the Default.aspx page. It doesn’t matter whether the Home.aspx page actually exists. If the Home.aspx page does exist, you can never open the page.

Note

The tilde character (~) has a special meaning when used with a path. It represents the current application root. A forward slash (/) at the start of a URL, on the other hand, represents the website root.

You can use the tilde only with properties of ASP.NET controls. For example, you can use it with the ASP.NET Image control’s ImageUrl property, but you cannot use it with the HTML <img> src attribute.

In code, you can use the tilde character with a path by using the Page.ResolveUrl() method. This method automatically expands the tilde to the application root.

When working with remapped URLs, you often need to determine the original URL that a user requested. For example, you might want to display a message that tells users to update their bookmarks (favorites) to point to the new URL.

There are three properties you can use to determine the current URL:

  • Request.RawUrlReturns the original URL (before being remapped).

  • Request.PathReturns the current URL (after being remapped).

  • Request.AppRelativeCurrentExecutionFilePathReturns the application relative URL (after being remapped).

The last property automatically replaces the name of the web application with a tilde (~) character.

For example, the Default.aspx page in Listing 19.2 illustrates all three properties.

Example 19.2. Default.aspx

<%@ Page Language="VB" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<script runat="server">

    Private Sub Page_Load()
        If String.Compare(Request.Path, Request.RawUrl, True) <> 0 Then
            lblMessage.Text = "The URL to this page has changed, " _
                & "please update your bookmarks."
        End If
    End Sub
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <style type="text/css">
        html
        {
            font:14px Georgia,Serif;
        }
        .message
        {
            border:Dotted 2px red;
            background-color:yellow;
        }
    </style>
    <title>Default Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <h1>The Default Page</h1>

    <p>
    <asp:Label
        id="lblMessage"
        CssClass="message"
        Runat="server" />
    </p>

    The original request was for:
    <blockquote>
        <%=Request.RawUrl%>
    </blockquote>
    which got remapped to:
    <blockquote>
        <%= Request.Path %>
    </blockquote>
    and the application relative version is:
    <blockquote>
        <%= Request.AppRelativeCurrentExecutionFilePath %>
    </blockquote>

    </div>
    </form>
</body>
</html>

If you request the Home.aspx page, the request is remapped to the Default.aspx page by the web configuration file in Listing 19.1. The Page_Load() event handler displays a message asking users to update their bookmarks when the RawUrl does not match the path (see Figure 19.1).

Remapping the Home page.

Figure 19.1. Remapping the Home page.

Each property displayed in the body of the page displays a different value:

Request.RawUrl = /UrlMappingsApp/Home.aspx
Request.Path = /UrlMappingsApp/Default.aspx
Request.AppRelativeCurrentExecutionFilePath = ~/Default.aspx

Creating a Custom UrlRemapper Module

The <urlMappings> configuration element discussed in the previous section performs a very simple task. It remaps one page to another. However, you’ll quickly discover that you need to perform more complex remappings.

For example, imagine that you have a database that contains a table of product categories and a table of products. You want your website’s users to request a URL that contains a product category and be able to see matching products. For example, if someone requests the /Products/Soda.aspx page, you want to display all the products in the Soda category. If someone requests the /Products/Milk.aspx page, you want to display all the products in the Milk category.

In that case, you need to use a wildcard when matching URLs. When someone requests any path that matches the pattern /Products/*, you want to redirect the user to a page where you can display matching products for the category specified in the path.

In this section, we create a custom HTTP module that remaps one URL to another. The module supports regular expression matching. Therefore it supports wildcard matches.

CD Note

The code samples in this section are located in the UrlRemapperApp application on the CD that accompanies this book.

The code for the custom module—named UrlRemapper—is contained in Listing 19.3.

Example 19.3. UrlRemapper.vb

Imports System
Imports System.Web
Imports System.Xml
Imports System.Web.Caching
Imports System.Text.RegularExpressions

Namespace AspNetUnleashed

    Public Class UrlRemapper
        Implements IHttpModule

        Public Sub Init(ByVal app As HttpApplication) Implements IHttpModule.Init
            AddHandler app.BeginRequest, AddressOf app_BeginRequest
        End Sub

        Public Sub app_BeginRequest(ByVal s As Object, ByVal e As EventArgs)
            ' Get HTTP Context
            Dim app As HttpApplication = CType(s, HttpApplication)
            Dim context As HttpContext = app.Context

            ' Get current URL
            Dim currentUrl As String = context.Request.AppRelativeCurrent ExecutionFilePath

            ' Get URL Mappings
            Dim urlMappings As XmlDocument = GetUrlMappings(context)

            ' Compare current URL against each URL from mappings file
            Dim nodes As XmlNodeList = urlMappings.SelectNodes("//add")
            For Each node As XmlNode In nodes
                Dim url As String = node.Attributes("url").Value
                Dim mappedUrl As String = node.Attributes("mappedUrl").Value
                If Regex.Match(currentUrl, url, RegexOptions.IgnoreCase).Success Then
                    context.RewritePath(mappedUrl)
                End If
            Next
        End Sub

        Private Function GetUrlMappings(ByVal context As HttpContext) As XmlDocument
            Dim urlMappings As XmlDocument = CType(context.Cache("UrlMappings"), XmlDocument)
            If urlMappings Is Nothing Then
                urlMappings = New XmlDocument()
                Dim path As String = context.Server.MapPath("~/UrlMappings.config")
                urlMappings.Load(path)
                Dim fileDepend As CacheDependency = New CacheDependency(path)
                context.Cache.Insert("UrlMappings", urlMappings, fileDepend)
            End If
            Return urlMappings
        End Function

        Public Sub Dispose() Implements System.Web.IHttpModule.Dispose
        End Sub

    End Class
End Namespace

Notice that the class in Listing 19.3 implements the IHttpModule interface. An HTTP module is a special class that executes whenever you make a page request. HTTP Modules are discussed in detail in Chapter 25, “Working with the HTTP Runtime.”

The module in Listing 19.3 includes an Init() method. This method adds an event handler for the Application BeginRequest event. The BeginRequest event is the first event that is raised when you request a page.

The BeginRequest handler gets a list of URL remappings from an XML file named UrlMappings.config. The contents of this XML file are cached in memory until the UrlMappings.config file is changed on the hard drive.

Next, the module iterates through each remapping from the XML file and performs a regular expression match against the current URL. If the match is successful, then the Context.RewritePath() method is used to change the current path to the remapped path.

Before you can use the module in Listing 19.3 in an application, you must first register the module in your application’s web configuration file. The web configuration file in Listing 19.4 contains an <httpModules> element that includes the UrlRemapper module.

Example 19.4. Web.Config

<?xml version="1.0"?>
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<system.web>

  <httpModules>
    <add
      name="UrlRemapper"
      type="AspNetUnleashed.UrlRemapper" />
  </httpModules>

</system.web>
</configuration>

A sample UrlMappings.config file is contained in Listing 19.5.

Example 19.5. UrlMappings.config

<?xml version="1.0"?>
<urlMappings>
  <add
    url="~/Home.aspx"
    mappedUrl="~/Default.aspx" />
  <add
    url="/Products/.*"
    mappedUrl="~/Products/Default.aspx" />
</urlMappings>

The XML file in Listing 19.5 contains two remappings. First, it remaps any request for the Home.aspx page to the Default.aspx page. Second, it remaps any request for any page in the Products directory to the Default.aspx page located in the Products folder.

The second mapping uses a regular expression to match the incoming URL. The .* expression matches any sequence of characters.

The Default.aspx page in the Products folder is contained in Listing 19.6.

Example 19.6. Products/Default.aspx

<%@ Page Language="VB" %>
<%@ Import Namespace="System.IO" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<script runat="server">

    Private  Sub Page_Load()
        If Not Page.IsPostBack Then
            Dim category As String = Path.GetFileNameWithoutExtension(Request.RawUrl)
            ltlCategory.Text = category
            srcProducts.SelectParameters("Category").DefaultValue = category
        End If
    End Sub
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <style type="text/css">
        .grid td,.grid th
        {
            padding:4px;
            border-bottom:solid 1px black;
        }
    </style>
    <title>Products</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <h1>
    <asp:Literal
        ID="ltlCategory"
        runat="server" />
    </h1>

    <asp:GridView
        id="grdProducts"
        DataSourceID="srcProducts"
        CssClass="grid"
        GridLines="None"
        AutoGenerateColumns="false"
        Runat="server">
        <Columns>
        <asp:BoundField
            HeaderText="Product Name"
            DataField="Name" />
        <asp:BoundField
            HeaderText="Price"
            DataField="Price"
            DataFormatString="{0:c}" />
        </Columns>
    </asp:GridView>

    <asp:SqlDataSource
        id="srcProducts"
        ConnectionString="<%$ ConnectionStrings:Products %>"
        SelectCommand="SELECT Products.* FROM Products
            JOIN Categories ON Products.CategoryId=Categories.Id
            WHERE Categories.Name=@Category"
        Runat="server">
        <SelectParameters>
        <asp:Parameter Name="Category" />
        </SelectParameters>
    </asp:SqlDataSource>

    </div>
    </form>
</body>
</html>

The Page_Load() event handler in Listing 19.6 grabs the path of the original request, using the Request.RawUrl property. Next, it extracts the filename from the path, using the System.IO.Path.GetFileNameWithoutExtension() method. Finally, it assigns the name of the page (the category name) to a Label and SqlDataSource control. Products that match the category are displayed in a GridView control.

For example, if you request the /Products/Soda.aspx page, then all the products in the Soda category are displayed (see Figure 19.2). If you request the /Products/Milk.aspx page, then all products in the Milk category are displayed.

Displaying matching products.

Figure 19.2. Displaying matching products.

Using the VirtualPathProvider Class

The VirtualPathProvider class enables you to abstract the pages in a web application from the file system. In other words, it enables you to store your ASP.NET pages any way you please.

For example, you can use the VirtualPathProvider class to store all the pages in your application in a database. This would be an appropriate choice when you need to build a Content Management System. If you store pages in a database, then users can update the pages easily in an application through an HTML form interface and save the changes to the database.

In this section, you learn how to store the pages in an ASP.NET application in a Microsoft SQL Server 2005 Express database. But first, it’s a good idea to examine the classes related to the VirtualPathProvider class in more detail.

Limitations of the VirtualPathProvider Class

Unfortunately, you can’t use the VirtualPathProvider with every type of file. In particular, the following types of files must always be located on the file system:

  • The Global.asax file

  • Web.Config files

  • The App_Data folder

  • The App_Code folder

  • The App_GlobalResources folder

  • App_LocalResource folders

  • The Bin folder

Every other type of file is fair game. This includes ASP.NET pages, User Controls, Themes, and Master Pages.

Understanding the VirtualPathProvider Class

The VirtualPathProvider class is a MustInherit (abstract) class. It contains the following methods, which you can override:

  • CombineVirtualPaths()Returns a combined path from two paths.

  • DirectoryExists()Returns true when a directory exists.

  • FileExists()Returns true when a file exists.

  • GetCacheDependency()Returns a cache dependency object that indicates when a file has been changed.

  • GetCacheKey()Returns the key used by the cache dependency.

  • GetDirectory()Returns a VirtualDirectory.

  • GetFile()Returns a VirtualFile.

  • GetFileHash()Returns a hash of the files used by the cache dependency.

  • OpenFile()Returns the contents of a file.

Typically, you override the FileExists() and GetFile() methods to retrieve a file from your data store. If you want to represent directories, then you also need to override the DirectoryExists() and GetDirectory() methods.

Notice that several of these methods are related to caching. The VirtualPathProvider needs to know when a file has been modified so that it can retrieve the new version of the file and compile it. By default, the ASP.NET Framework uses a file dependency to determine when a file has been modified on the hard drive. However, in this situation a SqlCacheDependency is used because the files will be stored in a database.

The VirtualPathProvider also includes a very useful property:

  • PreviousReturns the previously registered VirtualPathProvider.

The Previous property enables you to use the default VirtualPathProvider. For example, if you want to store some files in the file system and other files in the database, then you can use the Previous property to avoid rewriting all of the logic for working with files in the file system.

The GetFile() method returns an instance of the VirtualFile class. When using the VirtualPathProvider, you must create a new class that inherits from the VirtualFile class. This class contains the following properties:

  • IsDirectoryAlways returns False.

  • NameReturns the name of the file.

  • VirtualPathReturns the virtual path of the file.

The VirtualFile class also contains the following method:

  • Open()Returns the contents of the file.

Typically, when creating a class that inherits from the VirtualFile class, you override the Open() method. For example, we’ll override this method to get the contents of a file from a database table in the code sample built in this section.

The GetDirectory() method returns an instance of the VirtualDirectory class. This class contains the following properties:

  • ChildrenReturns all the files and directories that are children of the current directory.

  • DirectoriesReturns all the directories that are children of the current directory.

  • FilesReturns all the files that are children of the current directory.

  • IsDirectoryAlways returns True.

  • NameReturns the name of the directory.

  • VirtualPathReturns the virtual path of the directory.

There is another class in the ASP.NET Framework that you’ll want to use when working with the VirtualPathProvider class. The VirtualPathUtility class contains several useful methods for working with virtual paths:

  • AppendTrailingSlash()Returns a path with at most one forward slash appended to the end of the path.

  • Combine()Returns the combination of two virtual paths.

  • GetDirectory()Returns the directory portion of a path.

  • GetExtension()Returns the file extension of a path.

  • GetFileName()Returns the file name from a path.

  • IsAbsolute()Returns True when a path starts with a forward slash.

  • IsAppRelative()Returns True when a path starts with a tilde (~).

  • MakeRelative()Returns a relative path from an application-relative path.

  • RemoveTrailingSlash()Removes trailing slash from the end of a path.

  • ToAbsolute()Returns a path that starts with a forward slash.

  • ToAppRelative()Returns a path that starts with a tilde (~).

By taking advantage of the VirtualPathUtility class, you can avoid doing a lot of tedious string parsing on paths.

Registering a VirtualPathProvider Class

Before you can use an instance of the VirtualPathProvider class, you must register it for your application. You can register a VirtualPathProvider instance with the HostingEnvironment.RegisterVirtualPathProvider() method.

You need to register the VirtualPathProvider when an application first initializes. You can do this by creating a shared method named AppInitialize() and adding the method to any class contained in the App_Code folder. The AppInitialize() method is automatically called by the ASP.NET Framework when an application starts.

For example, the following AppInitialize method registers a VirtualPathProvider named MyVirtualPathProvider:

Public Shared Sub AppInitialize()
  Dim myProvider As New MyVirtualPathProvider()
  HostingEnvironment.RegisterVirtualPathProvider(myProvider)
End Sub

In our VirtualPathProvider application, we’ll include the AppInitialize() method in the VirtualPathProvider class itself.

Storing a Website in Microsoft SQL Server

In this section, we’ll create a VirtualPathProvider that stores files and directories in two Microsoft SQL Server database tables named VirtualFiles and VirtualDirectories. The VirtualFiles table looks like this:

Path

Name

Content

~/

Test.aspx

The time is now <%= DateTime.Now.ToString() %>

~/Products/

FirstProduct.aspx

The first product

~/Products/

SecondProduct.aspx

The second product

The Path column represents the directory that contains the file. The Name column contains the name of the file. Finally, the Content column contains the actual file content.

Notice that the file can contain scripts. The Test.aspx page displays the current date and time. You can place anything that you would place in a normal ASP.NET page, including ASP.NET controls, in the Content column.

The VirtualDirectories table looks like this:

Path

ParentPath

~/

NULL

~/Products

~/

The Path column represents the entire directory path. The ParentPath column represents the entire directory path of the directory that contains the directory.

The VirtualPathProvider class in Listing 19.7—named SqlVirtualPathProvider—uses both database tables.

Example 19.7. SqlVirtualPathProvider

Imports System
Imports System.Web
Imports System.Web.Caching
Imports System.Collections
Imports System.Collections.Generic
Imports System.Web.Hosting

Namespace AspNetUnleashed

    Public Class SqlVirtualPathProvider
        Inherits VirtualPathProvider

        ''' <summary>
        ''' Register VirtualPathProvider for the application
        ''' </summary>
        Public Shared Sub AppInitialize()
            Dim sqlProvider As New SqlVirtualPathProvider()
            HostingEnvironment.RegisterVirtualPathProvider(sqlProvider)
        End Sub

        Public Sub New()
            MyBase.New()
        End Sub

        ''' <summary>
        ''' Returns true when the file is a virtual file
        ''' instead of a normal filesystem file
        ''' </summary>
        Private Function IsVirtualFile(ByVal virtualPath As String) As Boolean
            Dim appVirtualPath As String = VirtualPathUtility.ToAppRelative(virtualPath)
            Return Not appVirtualPath.StartsWith("~/admin/", StringComparison.InvariantCultureIgnoreCase)
        End Function

        ''' <summary>
        ''' Returns true when a file exists
        ''' </summary>
        Public Overrides Function FileExists(ByVal virtualPath As String) As Boolean
            If IsVirtualFile(virtualPath) Then
                Return VirtualFiles.FileExists(virtualPath)
            Else
                Return Previous.FileExists(virtualPath)
            End If
        End Function

        ''' <summary>
        ''' Gets a SqlVirtualFile that corresponds
        ''' to a file with a certain path
        ''' </summary>
        Public Overrides Function GetFile(ByVal virtualPath As String) As VirtualFile
            If IsVirtualFile(virtualPath) Then
                Return New SqlVirtualFile(virtualPath)
            Else
                Return Previous.GetFile(virtualPath)
            End If
        End Function

        ''' <summary>
        ''' Returns true when a directory exists
        ''' </summary>
        Public Overrides Function DirectoryExists(ByVal virtualPath As String) As Boolean
            If IsVirtualFile(virtualPath) Then
                Return VirtualFiles.DirectoryExists(virtualPath)
            Else
                Return Previous.DirectoryExists(virtualPath)
            End If
        End Function

        ''' <summary>
        ''' Returns a SqlVirtualDirectory that corresponds
        ''' to a virtual path
        ''' </summary>
        Public Overrides Function GetDirectory(ByVal virtualPath As String) As VirtualDirectory
            If IsVirtualFile(virtualPath) Then
                Return New SqlVirtualDirectory(virtualPath)
            Else
                Return Previous.GetDirectory(virtualPath)
            End If
        End Function

        ''' <summary>
        ''' Gets the SqlCacheDependency object for the VirtualFilesDB
        ''' database
        ''' </summary>
        Public Overrides Function GetCacheDependency(ByVal virtualPath As String, ByVal virtualPathDependencies As IEnumerable, ByVal utcStart As DateTime) As CacheDependency
            If IsVirtualFile(virtualPath) Then
                Return New SqlCacheDependency("VirtualFiles", "VirtualFiles")
            Else
                Return Previous.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart)
            End If
        End Function

    End Class
End Namespace

The class in Listing 19.7 overrides the FileExists(), GetFile(), DirectoryExists(), and GetDirectory() methods of the base VirtualPathProvider class.

The class also includes a private method named IsVirtualFile(). This method returns the value True when a file is not contained in the Admin folder. The Admin directory contains a normal file system file. You’ll notice that each method, such as the FileExists() method, checks the IsVirtualFile() method. If the method returns False, the Previous property is used to pass the handling of the file to the file system.

The SqlVirtualPathProvider class also overrides the GetCacheDependency() method. This method returns a SqlCacheDependency. The SQL cache dependency is configured with the Web configuration file in Listing 19.8.

Example 19.8. Web.Config

<?xml version="1.0"?>
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
  <connectionStrings>
    <add
      name="VirtualFiles"
      connectionString="Data Source=.SQLExpress;Integrated
  Security=True;AttachDbFileName=|DataDirectory|VirtualFilesDB.mdf;
  User Instance=True"/>
  </connectionStrings>
    <system.web>
      <caching>
        <sqlCacheDependency enabled="true">
          <databases>
            <add
              name="VirtualFiles"
              connectionStringName="VirtualFiles"
              pollTime="5000"/>
          </databases>
        </sqlCacheDependency>
      </caching>
    </system.web>
</configuration>

To use the SQL cache dependency, you must configure the SQL database to support the cache dependency. You can enable SQL cache dependencies for the VirtualFilesDB database and the two database tables contained in the database by executing the following two commands from a Command Prompt after navigating to the application’s App_Data folder:

enableNotifications "VirtualFilesDB.mdf", "VirtualDirectories"
enableNotifications "VirtualFilesDB.mdf", "VirtualFiles"

Note

SQL cache dependencies are discussed in detail in Chapter 23, “Caching Application Pages and Data.”

The GetFile() method in the SqlVirtualPathProvider class returns an instance of the SqlVirtualFile class. This class is contained in Listing 19.9.

Example 19.9. SqlVirtualFile.vb

Imports System
Imports System.Data
Imports System.Data.SqlClient
Imports System.Web.Hosting
Imports System.IO
Imports System.Web

Namespace AspNetUnleashed

    Public Class SqlVirtualFile
        Inherits VirtualFile

        Public Sub New(ByVal virtualPath As String)
            MyBase.New(virtualPath)
        End Sub

        Public Overrides Function Open() As Stream
            ' Get content from database
            Dim content As String = VirtualFiles.FileContentSelect(Me.VirtualPath)

            ' return results as stream
            Dim mem As MemoryStream = New MemoryStream()
            Dim writer As StreamWriter = New StreamWriter(mem)
            writer.Write(content)
            writer.Flush()
            mem.Seek(0, SeekOrigin.Begin)
            Return mem
        End Function

        Public ReadOnly Property Content() As String
            Get
                Return VirtualFiles.FileContentSelect(Me.VirtualPath)
            End Get
        End Property

    End Class
End Namespace

The SqlVirtualFile class overrides the Open() method of the base VirtualFile class. The Open() method grabs the contents of the file from the Content column of the VirtualFiles database table.

The GetDirectory() method returns an instance of the SqlVirtualDirectory class. This class is contained in Listing 19.10.

Example 19.10. SqlVirtualDirectory.vb

Imports System
Imports System.Collections
Imports System.Web.Hosting
Imports System.Web

Namespace AspNetUnleashed

    Public Class SqlVirtualDirectory
    Inherits VirtualDirectory

        Public  Sub New(ByVal virtualPath As String)
            MyBase.New(virtualPath)
        End Sub

        Public ReadOnly Property AppPath() As String
        Get
            Return VirtualPathUtility.ToAppRelative(VirtualPath)
        End Get
        End Property

        Public Overrides ReadOnly Property Children() As IEnumerable
        Get
            Return VirtualFiles.DirectorySelectChildren(VirtualPath)
        End Get
        End Property

        Public Overrides ReadOnly Property Directories() As IEnumerable
        Get
           Return VirtualFiles.DirectorySelectDirectories(VirtualPath)
        End Get
        End Property

        Public Overrides ReadOnly Property Files() As IEnumerable
        Get
           Return VirtualFiles.DirectorySelectFiles(VirtualPath)
        End Get
        End Property
    End Class
End Namespace

The SqlVirtualDirectory class overrides three properties of the base VirtualDirectory class: the Children, Directories, and Files properties. These properties return files and subfolders from the VirtualFiles and VirtualDirectories database tables.

The VirtualPathProvider classes use the VirtualFiles class to interact with the SQL database. The VirtualFiles class acts as the data access layer. The code for this class is contained in Listing 19.11.

Example 19.11. VirtualFiles.vb

Imports System
Imports System.Web
Imports System.Web.Configuration
Imports System.Data
Imports System.Data.SqlClient
Imports System.Collections
Imports System.Collections.Generic

Namespace AspNetUnleashed

    Public Class VirtualFiles

        Shared ReadOnly _connectionString As String

        ''' <summary>
        ''' Get the connection string from Web.Config
        ''' </summary>
        Shared Sub New()
            _connectionString = WebConfigurationManager.ConnectionStrings("VirtualFiles").ConnectionString
        End Sub


        ''' <summary>
        ''' Check whether file exists in database
        ''' </summary>
        Public Shared Function FileExists(ByVal virtualPath As String) As Boolean
            ' Relativize path
            virtualPath = VirtualPathUtility.ToAppRelative(virtualPath)

            ' Break virtual path
            Dim fileName As String = VirtualPathUtility.GetFileName(virtualPath)
            Dim path As String = VirtualPathUtility.GetDirectory(virtualPath)

            ' Initialize command
            Dim con As New SqlConnection(_connectionString)
            Dim commandText As String = "SELECT Name FROM VirtualFiles WHERE Path=@Path AND Name=@Name"
            Dim cmd As New SqlCommand(commandText, con)

            ' Create parameter
            cmd.Parameters.AddWithValue("@Path", path)
            cmd.Parameters.AddWithValue("@Name", fileName)

            ' Execute command
            Dim result As Boolean
            Using con
                con.Open()
                Dim reader As SqlDataReader = cmd.ExecuteReader()
                result = reader.HasRows
            End Using
            Return result
        End Function

        ''' <summary>
        ''' Add new file to database
        ''' </summary>
        Public Shared Sub FileInsert(ByVal virtualPath As String, ByVal name As String, ByVal content As String)
            ' Relativize path
            virtualPath = VirtualPathUtility.ToAppRelative(virtualPath)

            ' Initialize command
            Dim con As New SqlConnection(_connectionString)
            Dim commandText As String = "INSERT VirtualFiles (Path,Name,Content) VALUES (@Path,@Name,@Content)"
            Dim cmd As New SqlCommand(commandText, con)

            ' Create parameters
            cmd.Parameters.AddWithValue("@Path", virtualPath)
            cmd.Parameters.AddWithValue("@Name", name)
            cmd.Parameters.AddWithValue("@Content", content)

            ' Execute command
            Using con
                con.Open()
                cmd.ExecuteNonQuery()
            End Using
        End Sub

        ''' <summary>
        ''' Get contents of file
        ''' </summary>
        Public Shared Function FileContentSelect(ByVal virtualPath As String) As String
            ' Relativize path
            virtualPath = VirtualPathUtility.ToAppRelative(virtualPath)

            ' Break virtualPath
            Dim path As String = VirtualPathUtility.GetDirectory(virtualPath)
            Dim fileName As String = VirtualPathUtility.GetFileName(virtualPath)

            ' Initialize command
            Dim con As New SqlConnection(_connectionString)
            Dim commandText As String = "SELECT Content FROM VirtualFiles WHERE Path=@Path AND Name=@Name"
            Dim cmd As New SqlCommand(commandText, con)

            ' Create parameter
            cmd.Parameters.AddWithValue("@Path", path)
            cmd.Parameters.AddWithValue("@Name", fileName)

            ' Execute command
            Dim content As String
            Using con
                con.Open()
                content = CType(cmd.ExecuteScalar(), String)
            End Using
            Return content
        End Function



        ''' <summary>
        ''' Update File ccntent
        ''' </summary>
        Public Shared Sub FileContentUpdate(ByVal virtualPath As String, ByVal content As String)
            ' Relativize path
            virtualPath = VirtualPathUtility.ToAppRelative(virtualPath)

            ' Break virtualPath
            Dim path As String = VirtualPathUtility.GetDirectory(virtualPath)
            Dim fileName As String = VirtualPathUtility.GetFileName(virtualPath)

            ' Initialize command
            Dim con As New SqlConnection(_connectionString)
            Dim commandText As String = "UPDATE VirtualFiles SET Content=@Content WHERE Path=@Path AND Name=@Name"
            Dim cmd As New SqlCommand(commandText, con)

            ' Create parameter
            cmd.Parameters.AddWithValue("@Path", path)
            cmd.Parameters.AddWithValue("@Name", fileName)
            cmd.Parameters.AddWithValue("@Content", content)

            ' Execute command
            Using con
                con.Open()
                cmd.ExecuteScalar()
            End Using
        End Sub


        ''' <summary>
        ''' Returns a single virtual file
        ''' </summary>
        Public Shared Function FileSelect(ByVal virtualPath As String) As SqlVirtualFile
            Return New SqlVirtualFile(virtualPath)
        End Function

        ''' <summary>
        ''' Deletes a file from the database
        ''' </summary>
        Public Shared Sub FileDelete(ByVal virtualPath As String)
            ' Relativize path
            virtualPath = VirtualPathUtility.ToAppRelative(virtualPath)

            ' Break virtualPath
            Dim path As String = VirtualPathUtility.GetDirectory(virtualPath)
            Dim fileName As String = VirtualPathUtility.GetFileName(virtualPath)

            ' Initialize command
            Dim con As New SqlConnection(_connectionString)
            Dim commandText As String = "DELETE VirtualFiles WHERE Path=@Path AND Name=@Name"
            Dim cmd As New SqlCommand(commandText, con)

            ' Create parameters
            cmd.Parameters.AddWithValue("@Path", path)
            cmd.Parameters.AddWithValue("@Name", fileName)

            ' Execute command
            Using con
                con.Open()
                cmd.ExecuteNonQuery()
            End Using
        End Sub


        ''' <summary>
        ''' Check whether directory exists in database
        ''' </summary>
        Public Shared Function DirectoryExists(ByVal virtualPath As String) As Boolean
            ' Relativize path
            virtualPath = VirtualPathUtility.ToAppRelative(virtualPath)

            ' Initialize command
            Dim con As New SqlConnection(_connectionString)
            Dim commandText As String = "SELECT Path FROM VirtualDirectories WHERE Path=@Path"
            Dim cmd As New SqlCommand(commandText, con)

            ' Create parameter
            cmd.Parameters.AddWithValue("@Path", virtualPath)

            ' Execute command
            Dim result As Boolean
            Using con
                con.Open()
                Dim reader As SqlDataReader = cmd.ExecuteReader()
                result = reader.HasRows
            End Using
            Return result
        End Function


        ''' <summary>
        ''' Create a new directory
        ''' </summary>
        Public Shared Sub DirectoryInsert(ByVal virtualPath As String, ByVal path As String)
            ' Relativize path
            virtualPath = VirtualPathUtility.ToAppRelative(virtualPath)

            ' Initialize command
            Dim con As New SqlConnection(_connectionString)
            Dim commandText As String = "INSERT VirtualDirectories (Path,ParentPath) VALUES (@Path,@ParentPath)"
            Dim cmd As New SqlCommand(commandText, con)

            ' Create parameters
            cmd.Parameters.AddWithValue("@Path", VirtualPathUtility.Combine(virtualPath, path))
            cmd.Parameters.AddWithValue("@ParentPath", virtualPath)

            ' Execute command
            Using con
                con.Open()
                cmd.ExecuteNonQuery()
            End Using
        End Sub


        ''' <summary>
        ''' Deletes a directory
        ''' </summary>
        Public Shared Sub DirectoryDelete(ByVal virtualPath As String)
            ' Relativize path
            virtualPath = VirtualPathUtility.ToAppRelative(virtualPath)

            ' Initialize command
            Dim con As New SqlConnection(_connectionString)
            Dim commandText As String = "DELETE VirtualDirectories WHERE Path + '/'=@Path OR ParentPath=@Path"
            Dim cmd As New SqlCommand(commandText, con)

            ' Create parameters
            cmd.Parameters.AddWithValue("@Path", virtualPath)

            ' Execute command
            Using con
                con.Open()
                cmd.ExecuteNonQuery()
            End Using
        End Sub


        ''' <summary>
        ''' Get a directory
        ''' </summary>
        Public Shared Function DirectorySelect() As List(Of SqlVirtualDirectory)
            Dim dirs As New List(Of SqlVirtualDirectory)()

            ' Initialize command
            Dim con As New SqlConnection(_connectionString)
            Dim commandText As String = "SELECT Path FROM VirtualDirectories"
            Dim cmd As New SqlCommand(commandText, con)

            Using con
                con.Open()
                Dim reader As SqlDataReader = cmd.ExecuteReader()
                While reader.Read()
                    dirs.Add(New SqlVirtualDirectory(CType(reader("Path"), String)))
                End While
            End Using
            Return dirs
        End Function


        ''' <summary>
        ''' Get all files in a directory
        ''' </summary>
        Public Shared Function DirectorySelectFiles(ByVal virtualPath As String) As List(Of SqlVirtualFile)
            ' Relativize path
            virtualPath = VirtualPathUtility.ToAppRelative(virtualPath)

            ' Initialize command
            Dim con As SqlConnection = New SqlConnection(_connectionString)
            Dim commandText As String = "SELECT Path,Name FROM VirtualFiles " _
                & "WHERE Path=@Path ORDER BY Path"
            Dim cmd As New SqlCommand(commandText, con)

            ' Create parameter
            cmd.Parameters.AddWithValue("@Path", virtualPath)

            ' Execute command
            Dim files As New List(Of SqlVirtualFile)()
            Using con
                con.Open()
                Dim reader As SqlDataReader = cmd.ExecuteReader()
                While reader.Read()
                    Dim fullName As String = VirtualPathUtility.Combine(reader("Path").ToString(), reader("Name").ToString())
                    files.Add(New SqlVirtualFile(fullName))
                End While
            End Using

            Return files
        End Function

        ''' <summary>
        ''' Retrieves all subdirectories for a directory
        ''' </summary>
        Public Shared Function DirectorySelectDirectories(ByVal virtualPath As String) As List(Of SqlVirtualDirectory)
            ' Relativize path
            virtualPath = VirtualPathUtility.ToAppRelative(virtualPath)

            ' Initialize command
            Dim con As SqlConnection = New SqlConnection(_connectionString)
            Dim commandText As String = "SELECT Path FROM VirtualDirectories " _
                & "WHERE ParentPath=@Path ORDER BY Path"
            Dim cmd As New SqlCommand(commandText, con)

            ' Create parameters
            cmd.Parameters.AddWithValue("@Path", virtualPath)

            ' Execute command
            Dim dirs As New List(Of SqlVirtualDirectory)()
            Using con
                con.Open()
                Dim reader As SqlDataReader = cmd.ExecuteReader()
                While reader.Read()
                    dirs.Add(New SqlVirtualDirectory(reader("Path").ToString()))
                End While
            End Using
            Return dirs
        End Function

        ''' <summary>
        ''' Returns all files and subdirectories from a directory
        ''' </summary>
        Public Shared Function DirectorySelectChildren(ByVal virtualPath As String) As ArrayList
            Dim filesAndDirs As ArrayList = New ArrayList()

            Dim dirs As List(Of SqlVirtualDirectory) = DirectorySelectDirectories(virtualPath)
            For Each dir As SqlVirtualDirectory In dirs
                filesAndDirs.Add(dir)
            Next

            Dim files As List(Of SqlVirtualFile) = DirectorySelectFiles(virtualPath)
            For Each file As SqlVirtualFile In files
                filesAndDirs.Add(file)
            Next
            Return filesAndDirs
        End Function

    End Class
End Namespace

The CD that accompanies this book includes an application named SqlVirtualPathProviderApp, which contains all the files discussed in this section. The application also includes an Admin folder with a Default.aspx page which enables you to add, edit, and delete virtual directories and files (see Figure 19.3 and Figure 19.4). You can use this page to build an entire application that is stored in the database.

Listing virtual files in the virtual Products directory.

Figure 19.3. Listing virtual files in the virtual Products directory.

Adding a new virtual file.

Figure 19.4. Adding a new virtual file.

Summary

This chapter explored several advanced topics related to website navigation. In the first two sections, you learned how to map URLs from one path to another. In the first section, you learned how to configure remappings in the Web configuration file. In the second section, you learned how to build a custom HTTP module, which enables you to use wildcard matches when remapping a URL.

In the final section of this chapter, you learned how to abstract pages in your application from the file system by using the VirtualPathProvider class. You saw the creation of an application that enables you to store application files in a Microsoft SQL Server database table.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset