Using the VirtualPathProvider Class


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:

  • Previous Returns 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:

  • IsDirectory Always returns False.

  • Name Returns the name of the file.

  • VirtualPath Returns 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:

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

  • Directories Returns all the directories that are children of the current directory.

  • Files Returns all the files that are children of the current directory.

  • IsDirectory Always returns TRue.

  • Name Returns the name of the directory.

  • VirtualPath Returns 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.7named SqlVirtualPathProvideruses both database tables.

Listing 19.7. SqlVirtualPathProvider

[View full width]

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.

Listing 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.

Listing 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.

Listing 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.

Listing 19.11. VirtualFiles.vb

[View full width]

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.

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


Figure 19.4. Adding a new virtual file.





ASP. NET 2.0 Unleashed
ASP.NET 2.0 Unleashed
ISBN: 0672328232
EAN: 2147483647
Year: 2006
Pages: 276

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net