The simplest level of file
access just involves retrieving information about existing files and
directories and performing typical file system operations such as
copying files and creating directories.
.NET provides five basic
classes for retrieving this sort of information. They are all located in
the System.IO namespace (and, incidentally, can be used in desktop
applications in exactly the same way they are used in web applications).
They include the following:
The Directory and
File classes, which provide shared methods that allow you to retrieve
information about any files and directories visible from your server
The
DirectoryInfo and FileInfo classes, which use similar instance methods
and properties to retrieve the same sort of information
The
DriveInfo class, which provides shared methods that allow you to
retrieve information about a drive and the amount of free space it
provides
With the file access classes,
shared methods are more convenient to use because they don't require you
to create an instance of the class. That means you can use a quick
one-line code statement to perform a simple task such as checking
whether a file exists. On the other hand, if you need to retrieve
several pieces of information from the same file or directory, it's
easier to use the instance members. That way, you don't need to keep
specifying the name of the directory or file each time you call a
method. The instance approach is also a bit faster in this situation.
That's because the FileInfo and DirectoryInfo classes perform their
security checks once—when you create the object instance. The Directory
and File classes perform a security check every time you invoke a
method, which adds more overhead.
1. The Path Class
Along with the five
classes outlined in the previous section, .NET also includes a helper
class named Path in the same System.IO namespace. The Path class doesn't
include any real file management functionality. It simply provides a
few shared methods that are useful when manipulating strings that
contain file and directory paths.
For example, the Path class includes a GetFileName() method that pulls the file name out of a full string. Here's an example:
Dim file As String = Path.GetFileName( _
"c:\Documents\Upload\Users\JamesX\resume.doc")
' file now contains "resume.doc"
The Path class also includes a
Combine() method that can tack a relative path on the end of an
absolute path. Here it is at work, fusing two strings together:
Dim absolutePath As String = "c:\Users\MyDocuments"
Dim subPath As String = "Sarah\worksheet.xls"
Dim combined As String = Path.Combine(absolutePath, subPath)
' combined now contains "c:\Users\MyDocuments\Sarah\worksheet.xls"
You could perform all of these tasks on your own, but the Path class is a great way to avoid errors. Table 1 lists the methods of the Path class.
Table 1. Path Methods
Methods | Description |
---|
Combine() | Combines a path with a file name or a subdirectory. |
ChangeExtension() | Returns a copy of the string with a modified extension. If you don't specify an extension, the current extension is removed. |
GetDirectoryName() | Returns all the directory information, which is the text between the first and last directory separators (\). |
GetFileName() | Returns just the file name portion of a path, which is the portion after the last directory separator. |
GetFileNameWithoutExtension() | Returns just the file name portion of a path, but omits the file extension at the end. |
GetFullPath() | Changes
a relative path into an absolute path using the current directory. For
example, if c:\Temp\ is the current directory, calling GetFullPath() on a
file name such as test.txt returns c:\Temp\test.txt. This method has no
effect on an absolute path. |
GetPathRoot() | Retrieves
a string with the root drive (for example, "c:\"), provided that
information is in the string. For a relative path, it returns a null
reference. |
HasExtension() | Returns True if the path ends with an extension. |
IsPathRooted() | Returns True if the path is an absolute path and False if it's a relative path. |
2. The Directory and File Classes
The Directory and File classes provide a number of useful shared methods. Table 2 and Table 3
show an overview of the most important methods. Most of these methods
take the same parameter: a fully qualified path name identifying the
directory or file you want the operation to act on. A few methods, such
as Delete() and Move(), take additional parameters.
Table 2. Directory Class Members
Method | Description |
---|
CreateDirectory() | Creates a new directory. If you specify a directory inside another nonexistent directory, ASP.NET will thoughtfully create all the required directories. |
Delete() | Deletes
the corresponding empty directory. To delete a directory along with its
contents (subdirectories and files), add the optional second parameter
of True. |
Exists() | Returns True or False to indicate whether the specified directory exists. |
GetCreationTime(), GetLastAccessTime(), and GetLastWriteTime() | Returns a DateTime object that represents the time the directory was created, accessed, or written to. Each GetXxx() method has a corresponding SetXxx() method, which isn't shown in this table. |
GetDirectories() and GetFiles() | Returns
an array of strings, one for each subdirectory or file (depending on
the method you're using) in the specified directory. These methods can
accept a second parameter that specifies a search expression (such as
ASP*.*). |
GetLogicalDrives() | Returns an array of strings, one for each drive that's present on the current computer. Drive letters are in this format: "c:\". |
GetParent() | Parses
the supplied directory string and tells you what the parent directory
is. You could do this on your own by searching for the \ character (or,
more generically, the Path.DirectorySeparatorChar), but this function
makes life a little easier. |
GetCurrentDirectory() and SetCurrentDirectory() | Allows
you to set or retrieve the current directory, which is useful if you
need to use relative paths instead of full paths. Generally, these
functions aren't necessary. |
Move() | Accepts
two parameters: the source path and the destination path. The directory
and all its contents can be moved to any path, as long as it's located
on the same drive. (If you need to move files from one drive to another,
you'll need to pair up a copy operation and a delete operation
instead.) |
Table 3. File Class Members
Method | Description |
---|
Copy() | Accepts
two parameters: the fully qualified source file name and the fully
qualified destination file name. To allow overwriting, use the version
that takes a Boolean third parameter and set it to True. |
Delete() | Deletes the specified file but doesn't throw an exception if the file can't be found. |
Exists() | Indicates True or False in regard to whether a specified file exists. |
GetAttributes() and SetAttributes() | Retrieves or sets an enumerated value that can include any combination of the values from the FileAttributes enumeration. |
GetCreationTime(), GetLastAccessTime(), and GetLastWriteTime() | Returns a DateTime object that represents the time the file was created, accessed, or last written to. Each GetXxx() method has a corresponding SetXxx() method, which isn't shown in this table. |
Move() | Accepts
two parameters: the fully qualified source file name and the fully
qualified destination file name. You can move a file across drives and
even rename it while you move it (or rename it without moving it). |
The File and
Directory methods are quite intuitive. For example, consider the code
for a simple page that displays some information about the files in a
specific directory. You might use this code to create a simple admin
page that allows you to review the contents of an FTP directory (see Figure 1). Clients could use this page to review their documents and remove suspicious files.
You should begin by importing the namespace that has the IO classes:
Imports System.IO
The code for this page is as follows:
Public Partial Class ViewFiles
Inherits System.Web.UI.Page
Private ftpDirectory As String
Protected Sub Page_Load(ByVal sender As Object, _
ByVal e As EventArgs) Handles Me.Load
ftpDirectory = Path.Combine(Request.PhysicalApplicationPath, "FTP")
If Not Me.IsPostBack Then
CreateFileList()
End If
End Sub
Private Sub CreateFileList()
' Retrieve the list of files, and display it in the page.
' This code also disables the delete button, ensuring the
' user must view the file information before deleting it.
Dim fileList() As String = Directory.GetFiles(ftpDirectory)
lstFiles.DataSource = fileList
lstFiles.DataBind()
lblFileInfo.Text = ""
cmdDelete.Enabled = False
End Sub
Protected Sub cmdRefresh_Click(ByVal sender As Object, _
ByVal e As EventArgs) Handles cmdRefresh.Click
CreateFileList()
End Sub
Protected Sub lstFiles_SelectedIndexChanged(ByVal sender As Object, _
ByVal e As EventArgs) Handles lstFiles.SelectedIndexChanged
' Display the selected file information.
' Use the StringBuilder for the fastest way to build the string.
Dim fileName As String = lstFiles.SelectedItem.Text
Dim displayText As New System.Text.StringBuilder
displayText.Append("<b>")
displayText.Append(fileName)
displayText.Append("</b><br /><br />")
displayText.Append("Created: ")
displayText.Append(File.GetCreationTime(fileName).ToString())
displayText.Append("<br />Last Accessed: ")
displayText.Append(File.GetLastAccessTime(fileName).ToString())
displayText.Append("<br />")
' Show attribute information. GetAttributes can return a combination
' of enumerated values, so you need to evaluate it with the
' And keyword.
Dim Attr As FileAttributes = File.GetAttributes(fileName)
If (Attr And FileAttributes.Hidden) = FileAttributes.Hidden Then
displayText.Append("This is a hidden file.<br />")
End If
If (Attr And FileAttributes.ReadOnly) = FileAttributes.ReadOnly Then
displayText.Append("This is a read-only file.<br />")
cmdDelete.Enabled = False
Else
cmdDelete.Enabled = True
End If
' Display the information.
lblFileInfo.Text = displayText.ToString()
End Sub
Protected Sub cmdDelete_Click(ByVal sender As Object, _
ByVal e As EventArgs) Handles cmdDelete.Click
File.Delete(lstFiles.SelectedItem.Text)
CreateFileList()
End Sub
End Class
2.1. Dissecting the Code . . .
Every time the page
loads, it sets the ftpDirectory string. The path is set to the FTP
subfolder in the current web application directory (which is provided by
the Request.PhysicalApplicationPath property). These two details (the
current web application directory and the FTP subfolder) are fused
together into one path string using the Combine() method of the Path
class.
The
CreateFileList() procedure is easy to code, because it uses the data
binding feature of the ListBox. The array returned from the GetFiles()
method can be placed in the list with just a couple of lines of code.
The
AutoPostBack property of the ListBox is set to True. That way, when the
user chooses an item in the list, the ListBox posts the page back
immediately so the code can read the file information and refresh the
file details on the page.
When evaluating the FileAttributes enumeration, you need to use the And operator to perform bitwise arithmetic.
This is because the value returned from GetAttributes() can actually
contain a combination of more than one attribute. Using bitwise
arithmetic, you can pull out just the attribute that you're interested
in, and then determine whether it's set.
The
code that gets the file information builds a long string of text, which
is then displayed in a label. For optimum performance, this code uses
the System.Text.StringBuilder class. Without the StringBuilder, you'd
need to use string concatenation to join the string together. This is
much slower, because every time the code adds a piece of text to the
string, .NET creates an entirely new string object behind the scenes.
The
code that displays file information could benefit by switching to the
FileInfo class (as shown in the next section). As it is, every method
needs to specify the same file name. This is a bit tedious, and it's a
bit slower because each method requires a separate security check.
One ingredient this code
lacks is error handling. When using any external resource, including
files, it's essential that you defend yourself with a Try/Catch block.
This way you can deal with unpredictable occurrences that are beyond
your control—for example, if the file isn't accessible because it's
already open in another program, or the account running the code doesn't
have the required permissions. The code in this example is easy to
correct—simply wrap all the file operations into a Try/Catch block.
(You'll need three—one for the code that reads the files in the current
directory, one for the code that retrieves the information from the
selected file, and one for the code that deletes the file.)
When you're testing your
application in Visual Studio, you're unlikely to run into file
permission errors. However, when you deploy your application, life gets
more complicated. In a deployed website, ASP.NET runs under an account
with carefully limited privileges. Although the exact account depends on
the version of IIS , it's almost always a member of the IIS_IUSRS group.
If
you attempt to access a file using an account that doesn't have the
required permissions, you'll receive a SecurityException. To solve
problems like these, you can modify the permissions for a file or an
entire directory. To do so, right-click the file or directory, select
Properties, and choose the Security tab. Here you can add or remove
users and groups and configure what operations they're allowed to do.
Alternatively, you might find it easier to modify the account ASP.NET
uses or change its group membership.