
Pro ASP.NET 2.0 In CSharp 2005 (2005) [eng]
.pdf
478 C H A P T E R 1 3 ■ F I L E S A N D S T R E A M S
Table 13-6. Continued
Member |
Description |
Name |
Returns the drive letter name of the drive (such as C: or E:). |
VolumeLabel |
Returns the descriptive volume label for the drive. In an NTFS-formatted |
|
drive, the volume label can be up to 32 characters. If not set, this property |
|
returns null. |
RootDirectory |
Returns a DirectoryInfo object for the root directory in this drive. |
GetDrives() |
Retrieves an array of DriveInfo objects, representing all the logical drives |
|
on the current computer. |
|
|
■Tip Attempting to read from a drive that’s not ready (for example, a CD drive that doesn’t currently have a CD in it) will throw an exception. To avoid this problem, check the DriveInfo.IsReady property and attempt to read other properties only if the DriveInfo.IsReady property returns true.
Working with Attributes
The Attributes property of the FileInfo and DirectoryInfo classes represent the file system attributes for the file or directory. Because every file and directory can have a combination of attributes, the Attributes property contains a combination of values from the FileAttributes enumeration. Table 13-7 describes these values.
Table 13-7. Values for the FileAttributes Enumeration
Value |
Description |
Archive |
The item is archived. Applications can use this attribute to mark files |
|
for backup or removal, although it’s really just a holdover from older |
|
DOS-based operating systems. |
Compressed |
The item is compressed. |
Device |
Not currently used. Reserved for future use. |
Directory |
The item is a directory. |
Encrypted |
This item is encrypted. For a file, this means that all data in the file is |
|
encrypted. For a directory, this means that encryption is the default for |
|
newly created files and directories. |
Hidden |
This item is hidden and thus is not included in an ordinary directory |
|
listing. However, you can still see it in Windows Explorer. |
Normal |
This item is normal and has no other attributes set. This attribute is |
|
valid only if used alone. |
NotContentIndexed |
This item will not be indexed by the operating system’s content |
|
indexing service. |
Offline |
This file is offline and not currently available. |
ReadOnly |
This item is read-only. |
ReparsePoint |
This file contains a reparse point, which is a block of user-defined data |
|
associated with a file or a directory in an NTFS file system. |

C H A P T E R 1 3 ■ F I L E S A N D S T R E A M S |
479 |
Value |
Description |
SparseFile |
The file is a sparse file. Sparse files are typically large files with data |
|
consisting of mostly zeros. This item is supported only on NTFS file |
|
systems. |
System |
The item is part of the operating system or is used exclusively by the |
|
operating system. |
Temporary |
This item is temporary and can be deleted when the application is no |
|
longer using it. |
|
|
To find out all the attributes a file has, you can enumerate over the values in the Attributes property, as shown here:
foreach (FileAttributes attribute in myFile.Attributes) { ... }
You can also call the ToString() method of the Attributes property. This returns a string with a comma-separated list of attributes:
// This displays a string in the format "ReadOnly, Archive, Encrypted" lblInfo.Text = myFile.Attributes.ToString();
When testing for a single specific attribute, you need to use bitwise arithmetic. For example, consider the following faulty code:
if (myFile.Attributes == FileAttributes.ReadOnly) { ... }
This test succeeds only if the read-only attribute is the only attribute for the current file. This is rarely the case. If you want to successfully check whether the file is read-only, you need this code instead:
if ((myFile.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly) { ... }
This test succeeds because it filters out just the read-only attribute. Essentially, the Attributes setting consists (in binary) of a series of ones and zeros, such as 00010011. Each 1 represents an attribute that is present, and each 0 represents an attribute that is not. When you use the & operator with an enumerated value, it automatically performs a bitwise and operation, which compares each digit against each digit in the enumerated value. For example, if you combine a value of 00100001 (representing an individual file’s archive and read-only attributes) with the enumerated value 00000001 (which represents the read-only flag), the resulting value will be 00000001. It will have a
1 only where it can be matched in both values. You can then test this resulting value against the FileAttributes.ReadOnly enumerated value using the equal sign.
Similar logic allows you to verify that a file does not have a specific attribute:
if ((myFile.Attributes & FileAttributes.ReadOnly) =! FileAttributes.ReadOnly) { ... }
When setting an attribute, you must also use bitwise arithmetic. In this case, it’s needed to ensure that you don’t inadvertently wipe out the other attributes that are already set.
// This adds just the read-only attribute.
myFile.Attributes = myFile.Attributes | FileAttributes.ReadOnly;
// This removes just the read-only attribute.
myFile.Attributes = myFile.Attributes & ~FileAttributes.ReadOnly;

480 C H A P T E R 1 3 ■ F I L E S A N D S T R E A M S
■Note Some attributes can’t be set programmatically. For example, the Encrypted attributed is set by the operating system only if you are using EFS (Encrypting File System) to encrypt files.
Filter Files with Wildcards
The DirectoryInfo and Directory objects both provide a way to search the current directories for files or directories that match a specific filter expression. These search expressions can use the standard ? and * wildcards. The ? wildcard represents any single character, and the * wildcard represents any sequence of zero or more characters.
For example, the following code snippet retrieves the names of all the files in the c:\temp directory that have the extension .txt. The code then iterates through the retrieved FileInfo collection of matching files and displays the name and size of each one.
DirectoryInfo dir = new DirectoryInfo(@"c:\temp");
//Get all the files with the .txt extension. FileInfo[] files = dir.GetFiles("*.txt");
//Process each file.
foreach (FileInfo file in files) { ... }
You can use a similar technique to retrieve directories that match a specified search pattern by using the overloaded DirectoryInfo.GetDirectories() method.
The GetFiles() and GetDirectories() methods search only the current directory. If you want to perform a search through all the contained subdirectories, you’d need to use recursive logic.
Retrieving File Version Information
File version information is the information you see when you look at the properties of an EXE or DLL file in Windows Explorer. Version information commonly includes a version number, the company that produced the component, trademark information, and so on.
The FileInfo and File classes don’t provide a way to retrieve file version information. However, you can retrieve it quite easily using the static GetVersionInfo() method of the System.Diagnostics.FileVersionInfo class. The following example uses this technique to get a string with the complete version information and then displays it in a label:
string fileName = @"c:\Windows\explorer.exe";
FileVersionInfo info = FileVersionInfo.GetVersionInfo(fileName); lblInfo.Text = info.FileVersion;
Table 13-8 lists the properties you can read.
Table 13-8. FileVersionInfo Properties
Property |
Description |
FileVersion, FileMajorPart, |
Typically, a version number is displayed as |
FileMinorPart, FileBuildPart, |
[MajorNumber].[MinorNumber].[BuildNumber].[Private |
and FilePrivatePart |
PartNumber]. These properties allow you to retrieve the |
|
complete version as a string (FileVersion) or each individual |
|
component as a number. |
FileName |
Gets the name of the file that this instance of FileVersionInfo |
|
describes. |

C H A P T E R 1 3 ■ F I L E S A N D S T R E A M S |
481 |
Property |
Description |
OriginalFilename |
Gets the name the file was created with. |
InternalName |
Gets the internal name of the file, if one exists. |
FileDescription |
Gets the description of the file. |
CompanyName |
Gets the name of the company that produced the file. |
ProductName |
Gets the name of the product this file is distributed with. |
ProductVersion, |
These properties allow you to retrieve the complete product |
ProductMajorPart, |
version as a string (ProductVersion) or each individual |
ProductMinorPart, |
component as a number. |
ProductBuildPart, |
|
and ProductPrivatePart |
|
IsDebug |
Gets a Boolean value that specifies whether the file contains |
|
debugging information or is compiled with debugging features |
|
enabled. |
IsPatched |
Gets a Boolean value that specifies whether the file has been |
|
modified and is not identical to the original shipping file of the |
|
same version number. |
IsPreRelease |
Gets a Boolean value that specifies whether the file is a |
|
development version, rather than a commercially released |
|
product. |
IsPrivateBuild |
Gets a Boolean value that specifies whether the file was built |
|
using standard release procedures. |
IsSpecialBuild |
Gets a Boolean value that specifies whether the file is a special |
|
build. |
SpecialBuild |
If IsSpecialBuild is true, this property contains a string that |
|
specifies how the build differs from an ordinary build. |
Comments |
Gets the comments associated with the file. |
Language |
Gets the default language string for the version info block. |
LegalCopyright |
Gets all copyright notices that apply to the specified file. |
LegalTrademarks |
Gets the trademarks and registered trademarks that apply to |
|
the file. |
|
|
The Path Class
If you’re working with files, you’re probably also working with file and directory paths. Path information is stored as an ordinary string, which can lead to a number of problems ranging from minor headaches to serious security breaches.
For example, imagine you write the following block of code to add a filename to a path:
DirectoryInfo dirInfo = new DirectoryInfo(@"c:\temp\"); string file = "test.txt";
string path = dirInfo.FullName + @"\" + file;
At first, this code appears to work correctly. However, a problem occurs with the last line if you try to process the root directory. Here’s an example of the error:
DirectoryInfo dirInfo = new DirectoryInfo(@"c:\"); string file = "test.txt";
string path = dirInfo.FullName + @"\" + file;

482 C H A P T E R 1 3 ■ F I L E S A N D S T R E A M S
The problem here is that the FullName property never returns a trailing backslash. For example, c:\temp\ becomes just c:\temp. However, there’s one exception—the root directory c:\, which always includes a trailing backslash. As a result, this seemingly logical code generates the nonsensical path c:\\test.txt and fails.
The proper solution is to use the System.IO.Path class, which provides static helper methods that perform common path manipulation tasks. In this case, the Combine() method neatly solves the problem and works with any directory and file, as follows:
DirectoryInfo dirInfo = new DirectoryInfo(@"c:\"); string file = "test.txt";
string path = Path.Combine(dirInfo.FullName, file);
Minor hiccups like this are bothersome, but they aren’t serious. A more significant problem is the security risk of a canonicalization error. Canonicalization errors are a specific type of application error that can occur when your code assumes that user-supplied values will always be in a standardized form. Canonicalization errors are low-tech but quite serious, and they usually have the result of allowing a user to perform an action that should be restricted.
One infamous type of canonicalization error is SQL injection, whereby a user submits incorrectly formatted values to trick your application into executing a modified SQL command. (Chapter 7 covered SQL injection in detail). Other forms of canonicalization problems can occur with file paths and URLs.
For example, consider the following method that returns file data from a fixed document direc-
tory:
FileInfo file = new FileInfo(Server.MapPath("Documents\\" + txtBox.Text)); // (Read the file and display it in another control).
This code looks simple enough. It concatenates the user-supplied filename with the Documents path, thereby allowing the user to retrieve data from any file in this directory. The problem is that filenames can be represented in multiple formats. Instead of submitting a valid filename, an attacker can submit a qualified filename such as ..\filename. The concatenated path of WebApp\Documents\..\filename will actually retrieve a file from the parent of the Documents directory (WebApp). A similar approach will allow the user to specify any filename on the web application drive. Because the web service is limited only according to the restrictions of the ASP.NET worker process, the user may be allowed to download a sensitive server-side file.
The fix for this code is fairly easy. Once again, you can use the Path class. This time, you use the GetFileName() method to extract just the final filename portion of the string, as shown here:
fileName = Path.GetFileName(fileName); FileInfo file = new FileInfo(Server.MapPath(
Path.Combine("Documents", txtBox.Text));
This ensures that the user is constrained to the correct directory. If you are dealing with URLs, you can work similar magic with the System.Uri type. For example, here’s how you might remove query string arguments from a URI and make sure it refers to a given server and virtual directory:
string uriString = "http://www.wrongsite.com/page.aspx?cmd=run";
Uri uri = new Uri(uriString);
string page = System.IO.Path.GetFileName(uri.AbsolutePath); // page is now just "page.aspx"
Uri baseUri = new Uri("http://www.rightsite.com"); uri = new Uri(baseUri, page);
// uri now stores the path http://www.rightsite.com/page.aspx.
Table 13-9 lists the methods of the Path class.


484 C H A P T E R 1 3 ■ F I L E S A N D S T R E A M S
Figure 13-1 shows this program in action as it displays the contents of the c:\Documents and Settings directory.
Figure 13-1. Browsing the file system
The directory listing is built using two separate GridView controls, one on top of the other. The topmost GridView shows the directories, and the GridView underneath shows files. The only visible differences to the user are that the directories don’t display length information and have a folder icon next to their names. The ShowHeader property of the second GridView is set to false so that the two grids blend into each other fairly seamlessly. And because the GridView controls are stacked together, as the list of directories grows the list of files moves down the page to accommodate it.
Technically, you could handle the directory and file listing using one GridView object. That’s because all FileInfo and DirectoryInfo objects have a common parent—the FileSystemInfo object. However, in this grid you want to show the size in bytes of each file, and you want to differentiate the appearance (in this case, through different icons). Because the DirectoryInfo object doesn’t provide a Length property, trying to bind to it in a more generic list of FileSystemInfo objects would cause an error.
■Note This problem has another, equally effective solution. You could create a single GridView but not bind directly to the FileInfo.Length property. Instead, you would bind to a method in the page class that examines the current data object and return either the length (for FileInfo objects) or a blank string (for DirectoryInfo objects). You could construct a similar method to hand out the correct icon URL.
Here’s the declaration for the GridView control that provides the file list, without the formattingspecific style properties:

C H A P T E R 1 3 ■ F I L E S A N D S T R E A M S |
485 |
<asp:GridView ID="gridDirList" runat="server" AutoGenerateColumns="False" OnSelectedIndexChanged="gridDirList_SelectedIndexChanged" GridLines="None" CellPadding="0" CellSpacing="1" DataKeyNames="FullName">
<Columns>
<asp:TemplateField>
<ItemTemplate>
<img src="folder.jpg" /> </ItemTemplate>
</asp:TemplateField>
<asp:ButtonField DataTextField="Name" CommandName="Select" HeaderText="Name" />
<asp:BoundField HeaderText="Size" />
<asp:BoundField DataField="LastWriteTime" HeaderText="Last Modified" /> </Columns>
</asp:GridView>
This grid binds to an array of DirectoryInfo objects and displays the Name, Size, and LastWriteTime properties. In addition, the FullName property is designated as a key field so that you can return the full path after the user clicks one of the directories. You’ll also notice that one of the columns doesn’t actually display any information—that’s the BoundColumn for length that displays header text, but it doesn’t link to any data field.
The GridView for the files follows immediately. Here’s the slightly shortened control tag:
<asp:GridView ID="gridFileList" runat="server" AutoGenerateColumns="False" OnSelectedIndexChanged="gridFileList_SelectedIndexChanged" GridLines="None" CellPadding="0" CellSpacing="1" DataKeyNames="FullName">
<SelectedRowStyle BackColor="#C0FFFF" /> <Columns>
<asp:TemplateField>
<ItemTemplate>
<img src="file.jpg" /> </ItemTemplate>
</asp:TemplateField>
<asp:ButtonField DataTextField="Name" CommandName="Select" /> <asp:BoundField DataField="Length" />
<asp:BoundField DataField="LastWriteTime" /> </Columns>
</asp:GridView>
Note that the GridView for displaying files must define a SelectedRowStyle because it supports file selection.
The next step is to write the code that fills these controls. The star of the show is a private method named ShowDirectoryContents(), which retrieves the contents of the current folder and binds the two GridView controls. Here’s the complete code:
private void ShowDirectoryContents(string path)
{
//Define the current directory. DirectoryInfo dir = new DirectoryInfo(path);
//Get the DirectoryInfo and FileInfo objects. FileInfo[] files = dir.GetFiles(); DirectoryInfo[] dirs = dir.GetDirectories();

486C H A P T E R 1 3 ■ F I L E S A N D S T R E A M S
//Show the directory listing. lblCurrentDir.Text = "Currently showing " + path; gridFileList.DataSource = files; gridDirList.DataSource = dirs;
Page.DataBind();
//Clear any selection. gridFileList.SelectedIndex = -1;
//Keep track of the current path. ViewState["CurrentPath"] = path;
}
When the page first loads, it calls this method to show the current application directory:
protected void Page_Load(object sender, System.EventArgs e)
{
if (!Page.IsPostBack)
{
ShowDirectoryContents(Server.MapPath("."));
}
}
You’ll notice that the ShowDirectoryContents() method stores the currently displayed directory in view state. That allows the Move Up button to direct the user to a directory that’s one level above the current directory:
protected void cmdUp_Click(object sender, System.EventArgs e)
{
string path = (string)ViewState["CurrentPath"]; path = Path.Combine(path, "..");
path = Path.GetFullPath(path); ShowDirectoryContents(path);
}
To move down through the directory hierarchy, the user simply needs to click a directory link. This is raised as a SelectedIndexChanged event. The event handler then displays the new directory:
protected void gridDirList_SelectedIndexChanged(object source, EventArgs e)
{
// Get the selected directory.
string dir = (string)gridDirList.DataKeys[gridDirList.SelectedIndex].Value;
//Now refresh the directory list to
//show the selected directory. ShowDirectoryContents(dir);
}
But what happens if a user selects a file from the second GridView? In this case, the code retrieves the full file path, creates a new FileInfo object, and binds it to a FormView control, which uses a template to display several pieces of information about the file. Figure 13-2 shows the result.

C H A P T E R 1 3 ■ F I L E S A N D S T R E A M S |
487 |
Figure 13-2. Examining a file
Here’s the code that binds the file information when a file is selected:
protected void gridFileList_SelectedIndexChanged(object sender, System.EventArgs e)
{
// Get the selected file.
string file = (string)gridFileList.DataKeys[gridFileList.SelectedIndex].Value;
//The FormView shows a collection (or list) of items.
//To accommodate this model, you must add the file object
//to a collection of some sort.
ArrayList files = new ArrayList(); files.Add(new FileInfo(file));
// Now show the selected file. formFileDetails.DataSource = files; formFileDetails.DataBind();
}
The FormView uses the following template:
<asp:FormView id="formFileDetails" runat="server"> <ItemTemplate>
<b>File:
<%# DataBinder.Eval(Container.DataItem, "FullName") %></b><br> Created at
<%# DataBinder.Eval(Container.DataItem, "CreationTime") %><br> Last updated at
<%# DataBinder.Eval(Container.DataItem, "LastWriteTime") %><br> Last accessed at
<%# DataBinder.Eval(Container.DataItem, "LastAccessTime") %><br> <i><%# DataBinder.Eval(Container.DataItem, "Attributes") %></i><br> <%# DataBinder.Eval(Container.DataItem, "Length") %> bytes.