Displaying Additional Attributes
Just displaying the names of the child directories and files
is not enough. We want to display much more information: an icon
to describe the type of item, the
attributes, size, the date of creation, and date of last modification. The
first thing to do is to create new columns in the FoldersAndFiles
table in BrowseFiles.aspx:
<asp:Table
ID="FoldersAndFiles" runat="server"
CssClass="Grid_General" Width="100%">
<asp:TableRow CssClass="Grid_Header">
<asp:TableCell Width="36px"
/>
<asp:TableCell Text="Index"
/>
<asp:TableCell Text="Attribs"
Width="50px" />
<asp:TableCell Text="Size"
Width="80px" HorizontalAlign="Right" />
<asp:TableCell Text="Created"
Width="140px" />
<asp:TableCell Text="Last
Modified" Width="140px" />
</asp:TableRow>
</asp:Table>
We also need to declare new cells and controls in the FillFoldersAndFilesTable
procedure in the code-behind, BrowseFiles.aspx.cs:
TableRow rowItem;
TableCell cellItemLink;
HyperLink linkItem;
TableCell
cellItemIcon;
TableCell
cellSize;
TableCell
cellAttributes;
TableCell
cellCreated;
TableCell
cellLastModified;
Label
labelAttributes;
System.Web.UI.WebControls.Image
imgItemIcon;
Now we have to actually create, set, and add these new cells
for each directory and file shown in the table. We're going to present the code
required for the new columns, one column at a time.
Displaying the Item Icon
Creating the icon for the subdirectories and the link to the
parent directory is straightforward, since we use a fixed icon. We just create
the Image
control named imgItemIcon,
set its ImageUrl
property, and add it to the first cell. Here's the code for the parent link block, in BrowseFiles.aspx.cs:
if (folderPath != "/")
{
rowItem = new TableRow();
cellItemLink = new TableCell();
linkItem = new HyperLink();
cellItemIcon = new TableCell();
imgItemIcon = new System.Web.UI.WebControls.Image();
//
create the cell with the FolderUp icon
imgItemIcon.ImageUrl = "./Images/ParentFolder.gif";
cellItemIcon.Controls.Add(imgItemIcon);
cellItemIcon.HorizontalAlign = HorizontalAlign.Right;
//
add the link that points to the parent directory
...
//
add the cells to the new row
rowItem.Cells.Add(cellItemIcon);
rowItem.Cells.Add(cellItemLink);
//
add the row to the table
rowItem.ApplyStyle(styleFolderRow);
FoldersAndFiles.Rows.Add(rowItem);
}
The code for each child directory is very similar:
foreach (DirectoryInfo childDir in
childDirs)
{
//
create the required cells and controls
rowItem = new TableRow();
cellItemLink = new TableCell();
linkItem = new HyperLink();
cellItemIcon = new TableCell();
imgItemIcon = new System.Web.UI.WebControls.Image();
//
add the icon with the closed folder
imgItemIcon.ImageUrl = "./Images/ClosedFolder.gif";
cellItemIcon.Controls.Add(imgItemIcon);
cellItemIcon.HorizontalAlign = HorizontalAlign.Right;
//
create the link that points to this sub-directory
...
//
add the cells to the new row, and add the row to the table
...
}
So far, so good. Now here's the more interesting part
adding an icon for each file. If we only wanted to differentiate files from
folders, we could just associate a fixed icon for all the files (different from
the one used for the folders). But we want to do something more. We want to
display the icon that is usually shown in Windows Explorer to describe the file
type. In Windows each file extension
is identified by an icon, which is provided by the associated program. This
icon is usually stored within a binary EXE or DLL file, and its index is stored
in the registry. There are API functions to programmatically retrieve the icon
index within a file, and then show the icon through the Windows ListView
control, based on the icon's handle.
This is very tricky in ASP.NET, since the srcattribute of an <img>
tag should point directly to an image file.
It is possible to extract the icon associated
with the file extension from a binary file, save it to disk, and show it with
the <img>
tag, or even directly send a binary stream to the browser. However, this would
require a lot of work, would slow down the execution, and would not bring about
any valuable improvements to the application, so we decided not to cover this
method here.
We will create a predefined set of fixed images for the most
common files used for a website. These images will be assigned to the files
based on their extension. If we name such images according to the file
extensions (for example, aspx.gif, html.gif,
ascx.gif,
zip.gif,
bmp.gif,
gif.gif,
etc.) we'll just need to check if there is an image with a name corresponding
to the file extension, and if so show it. If there is no image with that name,
we'll show a predefined image, unknown.gif. The file icons for many
extensions are in the code download, but of course you can add your own.
To speed up the test that verifies the existence of a
certain icon, we define an array of extensions for which we've created the
appropriate image, and the check is done against this array. Here's how the
array is defined, as a private variable for the BrowseFiles
class:
private string[] extensions = new
string[]{".aspx", ".html", ".ascx",
".zip", ".bmp", ".gif", ...};
The rest of the file extensions have been left out here and
you can add any others appropriate to your site's requirements. In the code
download you'll find that 52 different extensions are already supported!
Thanks to the Array.IndexOf shared method (which
returns the index of the element being searched for in the array), checking
whether the extension of a file is present within the extensions
array is a matter of a single line of code. Here is the updated block that adds
all the child files to the table:
foreach (FileInfo childFile in childFiles)
{
//
create the required cells and controls
rowItem = new TableRow();
cellItemLink = new TableCell();
linkItem = new HyperLink();
cellItemIcon = new TableCell();
imgItemIcon = new System.Web.UI.WebControls.Image();
int
extIndex;
//
create and add the link that points to this file in a new window
...
//
search for the extension in the array,
//
and if it's found show the respective icon
//
if not found add a general icon for unknown files
extIndex = Array.IndexOf(extensions, childFile.Extension.ToLower());
if
(extIndex > -1)
imgItemIcon.ImageUrl = "./Images/" +
childFile.Extension.Substring(1) + ".gif";
else
imgItemIcon.ImageUrl = "./Images/unknown.gif";
//
add the icon to the first cell
cellItemIcon.Controls.Add(imgItemIcon);
cellItemIcon.HorizontalAlign = HorizontalAlign.Right;
//
add the cells to the new row
rowItem.Cells.Add(cellItemIcon);
rowItem.Cells.Add(cellItemLink);
//
add the new row to the table
...
}
Displaying the Item Attributes
Let's go ahead with the creation of the third column, the
one that displays a list of attributes for the
subdirectories and files. The modifications for the block
that adds the directories and the block that adds the files are exactly the
same. For the first row, which points to the parent directory (if any), we just
add an empty cell. This is shown in the following code:
if (folderPath != "/")
{
...
//
create the cell with the FolderUp icon
//
add the link that points to the parent directory
...
//
add the cells to the new row
rowItem.Cells.Add(cellItemIcon);
rowItem.Cells.Add(cellItemLink);
rowItem.Cells.Add(new TableCell());
//
add the row to the table
...
}
foreach (DirectoryInfo childDir in
childDirs)
{
//
create the required cells and controls
...
cellAttributes = new TableCell();
labelAttributes = new Label();
//
add the icon with the closed folder
...
//
create the link that points to this sub-directory
...
// set the description label to the
Attributes cell
labelAttributes.Text =
GetAttributesDescription(childDir.Attributes);
labelAttributes.Font.Name =
"Courier";
cellAttributes.Controls.Add(labelAttributes);
//
add the cells to the new row and add the row to the table
rowItem.Cells.Add(cellAttributes);
...
}
// now add each child file
foreach (FileInfo childFile in childFiles)
{
//
create the required cells and controls
...
cellAttributes = new TableCell();
labelAttributes = new Label();
//
create the icon and the link that points to this file
...
// set the description label to the
Attributes cell
labelAttributes.Text =
GetAttributesDescription(childFile.Attributes);
labelAttributes.Font.Name =
"Courier";
cellAttributes.Controls.Add(labelAttributes);
//
add the cells to the new row and add the row to the table
rowItem.Cells.Add(cellAttributes);
...
}
As you can see, the string that describes the item's
attributes is not built directly in the code above, but is returned by a custom
procedure called GetAttributesDescriptions,
which receives as input the value of the Attributes property, exposed by both the DirectoryInfo
and FileInfo
data type. Here is its code:
protected string
GetAttributesDescription(FileAttributes attribs)
{
string itemAttribs = "";
//
check if the Archive attribute is set
if
((attribs & FileAttributes.Archive) == FileAttributes.Archive)
itemAttribs += "A";
else
itemAttribs += " ";
//
check if the ReadOnly attribute is set
if
((attribs & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
itemAttribs += "R";
else
itemAttribs += " ";
//
check if the Hidden attribute is set
if
((attribs & FileAttributes.Hidden) == FileAttributes.Hidden)
itemAttribs += "H";
else
itemAttribs += " ";
//
check if the System attribute is set
if
((attribs & FileAttributes.System) == FileAttributes.System)
itemAttribs += "S";
else
itemAttribs += " ";
//
return the final result
return itemAttribs;
}
This procedure checks four attributes: Archive,
ReadOnly,
Hidden,
and System.
If the attribute being checked is set, the procedure adds its first letter to
the description string that will be returned. Otherwise, if the attribute is
not set, a space is added. So, for example, the string "A S" means Archive + System,
while "ARH
" means Archive + ReadOnly + Hidden.
If you look back at the code that calls this procedure,
you'll see that the font of the label displaying the attribute description is
set to Courier.
This is because Courier is a fixed-width font, meaning that, for example, 'i'
and 'Z'
have the same width. Another reason for using Courier is that spaces have the
same width as letters, and this allows us to create virtual columns and have
all the A letters in the column aligned, and the same for the letters R, H, and
S. If an attribute is not set for an item, you see a blank space as large as
the letter above it, and then the other attribute letters (if any).
You may also wonder why we created a Label
control instead of directly setting the cell's Text
property. The reason is that we'll add another control to this column later in
the chapter, and it's easier to add two controls to the Controls
collection than to set a very long string for the cell's
Text
property.
Displaying the Item Size
The next column we want to add displays the size of the
item. It is easy to get this information for the files, since the FileInfo
class exposes a Length
property. As the DirectoryInfo class does not have a Length
property, we need to calculate the size of a directory by summing the size of
all its child files. Here is a procedure that uses recursion to sum the child files of all
subdirectories and obtain the overall size of the directory:
protected long GetDirectorySize(string path)
{
long dirSize = 0;
DirectoryInfo dir = new DirectoryInfo(path);
//
add the size of each file
foreach (FileInfo file in dir.GetFiles())
dirSize += file.Length;
// add the size of each subdirectory, retrieved by
// recursively calling this same routine
foreach (DirectoryInfo subdir in dir.GetDirectories())
dirSize += GetDirectorySize(subdir.FullName);
return dirSize;
}
Now we can easily get the size for both the files and the
directories. But the size is returned in bytes, and a series of six or seven
figures is not very readable. It would be better to show the size in bytes,
kilobytes, or megabytes according to the number of bytes, as follows:
q
Less than 1024 bytes: show the size in bytes
q
Between 1024 and 1,048,576 bytes: show the size in KB
(for example 2.52 KB, instead of 2,580 bytes)
q
1,048,576 bytes and above: show the size in MB
The following procedure accepts the size in double
format, and returns a string with the size formatted in bytes, KB, or MB by following the rules above:
protected string FormatSize(double fileSize)
{
if
(fileSize < 1024)
return String.Format("{0:N0} B", fileSize);
else if (fileSize < 1024*1024)
return String.Format("{0:N2} KB", fileSize/1024);
else
return String.Format("{0:N2} MB", fileSize/(1024*1024));
}
Now that we can get the size for a directory, and format any
size in the proper way, let's add the Size column for each item. As we've
done for the Attributes
column, we're going to insert an empty cell for the first row, which links to
the parent folder, and a new cell with the size for all files and folders. Here
are the necessary updates:
if (folderPath != "/")
{
//
create the cell with the FolderUp icon,
//
and add the link that points to the parent directory
...
//
add the cells to the new row
rowItem.Cells.Add(cellItemIcon);
rowItem.Cells.Add(cellItemLink);
rowItem.Cells.Add(new TableCell());
rowItem.Cells.Add(new TableCell());
//
add the row to the table
...
}
foreach (DirectoryInfo childDir in
childDirs)
{
//
create the required cells and controls
cellSize = new TableCell();
...
//
create the icon, the link that points to this subdirectory
//
and the cell that shows the attributes
...
//
set the cell that displays the item's size
cellSize.Text = FormatSize(GetDirectorySize(childDir.FullName));
cellSize.HorizontalAlign = HorizontalAlign.Right;
//
add the cells to the new row and add the row to the table
rowItem.Cells.Add(cellSize);
...
}
// now add each child file
foreach (FileInfo childFile in childFiles)
{
//
create the required cells and controls
cellSize = new TableCell();
...
//
create the icon, the link that points to this file
//
and the cell that shows the attributes
...
//
set the cell that displays the item's size
cellSize.Text = FormatSize(childFile.Length) + " ";
cellSize.HorizontalAlign = HorizontalAlign.Right;
//
add the cells to the new row and add the row to the table
rowItem.Cells.Add(cellSize);
...
}