We will start the dissection by looking at the Interface
design for this application.
Interface design
The main component in this sample online storage system is
an interface—IDisk
which declares nearly all the underlying methods associated with folders and
files, such as method GetAllDirectoryFile() for
getting information of sub folders and files, method GetFiles()
for obtaining all the related files. The following gives us the class diagram
of interface IDisk.
Figure 4
As shown in Figure 4, we encapsulate all the basic file and
folder operation-related declarations into a single interface, which is a good
practice in real life. Since all the modules are small and basic, and even self-explanatory,
we do not dwell too much on them here; we will clarify some of them in the next
section and in later use.
Data tier design
The data access tier of the whole system mainly relies on
one class, Disk, which is derived from the interface IDisk depicted above and should implement all the methods
declared in it. The sketch in Figure 5 gives us a more intuitive depiction of
the relationships between the modules we will use.
Figure 5
Besides all the methods in interface IDisk,
class Disk defines its own two methods—ShowDirectory(DropDownList
dirList, int nParent) and CreateChildNode(DropDownList
dirList, DataTable dataTable, int nParent, string sParentName). The two
methods are cooperatively responsible for displaying folder information
according to the corresponding folder hierarchy. To achieve this goal, we take
the steps as follows.
1.
Obtain folders data from inside the database and store up the results
via a object dataTable.
2.
Append the root folder info into the DropDownList dirList.
3.
Retrieve all the sub folder info whose folder ID is nParent
and add it into the control dirList by invoking
method CreateChildNode.
4.
Repeat step 3 until all the related sub folders are added into the
control dirList.
In the above flow, steps 1-3 mainly depend on method ShowDirectory, while step 4 is accomplished through method CreateChildNode. Below are their corresponding codes.
Listing 1: Code for the two own functions of class
Disk
public void ShowDirectory(DropDownList dirList,int nParentID)
{
DataTable dataTable = SystemTools.ConvertDataReaderToDataTable(GetDirectorys());
dirList.Items.Clear(); ///clear all the nodes
DataRow[] rowList = dataTable.Select("ParentID='-1'");
if(rowList.Length <= 0) return;
///Create and add a root node
dirList.Items.Add(new ListItem("/",rowList[0]["DirID"].ToString()));
///Create other nodes
CreateChildNode(dirList,dataTable,Int32.Parse(rowList[0]["DirID"].ToString()),"/");
}
private void CreateChildNode(DropDownList dirList,
DataTable dataTable,int nParentID,string sParentName)
{
///when selecting data, we use sort expression-"OrderBy"
DataRow[] rowList =
dataTable.Select("ParentID='" + nParentID.ToString() +
"'","CreateDate DESC");
foreach(DataRow row in rowList)
{
string sName = sParentName + row["Name"].ToString() + "/";
///create a new node
dirList.Items.Add(new ListItem(sName,row["DirID"].ToString()));
///Call this function recursively to create other nodes
CreateChildNode(dirList,dataTable,Int32.Parse(row["DirID"].ToString()),sName);
}
}
Here, for brevity, we chose to omit all other methods for
there are so many of them (see the downloaded source code for detail). Nevertheless,
there are some interesting points inside method AddFile.
This method will add a new file into the database using a stored procedure
called Pr_AddFile which will use parameters—Name, Parent, Contain, Url and Type that represent file
name, the parent directory ID, the size, the linking address, and its type.
Listing 2: Code for function AddFile
public int AddFile(string sName,int nParentID,int nContain,string sUrl,string sType)
{
///create the connection
SqlConnection myConnection = new SqlConnection(
ConfigurationManager.ConnectionStrings["SQLCONNECTIONSTRING"].ConnectionString);
SqlCommand myCommand = new SqlCommand("Pr_AddFile",myConnection);
myCommand.CommandType = CommandType.StoredProcedure;
///add parameters for the stored procedure
SqlParameter pName = new SqlParameter("@Name",SqlDbType.VarChar,200);
pName.Value = sName;
myCommand.Parameters.Add(pName);
SqlParameter pParentID = new SqlParameter("@ParentID",SqlDbType.Int,4);
pParentID.Value = nParentID;
myCommand.Parameters.Add(pParentID);
SqlParameter pContain = new SqlParameter("@Contain",SqlDbType.Int,4);
pContain.Value = nContain;
myCommand.Parameters.Add(pContain);
SqlParameter pUrl = new SqlParameter("@Url",SqlDbType.VarChar,255);
pUrl.Value = sUrl;
myCommand.Parameters.Add(pUrl);
SqlParameter pType = new SqlParameter("@Type",SqlDbType.VarChar,200);
pType.Value = sType;
myCommand.Parameters.Add(pType);
///define the returned value
int nResult = -1;
try
{
///open the connection
myConnection.Open();
///execute the SQL clause
nResult = myCommand.ExecuteNonQuery();
}
catch(SqlException ex)
{
///throw exception
throw new Exception(ex.Message,ex);
}
finally
{ ///close the connection
myConnection.Close();
}
///return nResult
return nResult;
}
From above, we can easily see that calling stored procedure Pr_AddFile will result in returning the ID of the new
record, which is just what we expect. Here, we see the typical database
operation. And since it is a fairly straightforward function and self-explained,
we will not examine it in detail any more.
Creating folders
Here comes the page AddFolder.aspx
on which we accomplish the task of creating folders. To begin, let us look at
its page layout, as shown in Figure 5.
Figure 5: Design-time view of page AddFolder.aspx
Yes, it is a simple but typical interface structure with
merely several common controls on the page! Here we also enumerate their functions.
·
DropdownList DirList—for displaying directory structure info
·
TextBox Name—for user entering the name of the folder
·
RequiredFieldValidator rfN—for checking whether the directory info is
empty
·
Button ReturnBtn—for navigating to page Default.aspx
·
Button AddBtn—for creating a new folder
When initialized, page AddFolder.aspx
will show directory info in the DropdownList DirList,
which is accomplished by calling function BindDirectoryData
from inside the event handler Page_Load(object sender,
EventArgs e).
Listing 3
protected void Page_Load(object sender, EventArgs e)
{
if(!Page.IsPostBack)
{ ///display directoy info
BindDirectoryData();
}
}
private void BindDirectoryData()
{ ///display directory info
Disk disk = new Disk();
disk.ShowDirectory(DirList,-1);
}
Clicking the button OK will
trigger the event AddBtn_Click(object sender, EventArgs e)
which will create a new folder. Inside this event function AddDirecty
is called to add the newly-created folder into the underlying database.
When we click the button Return,
we are navigated back to page Default.aspx. Listing
12 is the corresponding code for the event ReturnBtn_Click(object
sender, EventArgs e).
Listing 4
protected void AddBtn_Click(object sender,EventArgs e)
{
try
{ ///define a Disk object
IDisk disk = new Disk();
///execute the database operation
disk.AddDirectory(Name.Text.Trim(),Int32.Parse(DirList.SelectedValue));
Response.Write("<script>alert('" +
"You've succeded in adding the info,safekeep you data!');</script>");
}
catch(Exception ex)
{ ///jump to the exception handling page
Response.Redirect("ErrorPage.aspx?ErrorMsg=" +
ex.Message.Replace("<br>","").Replace("\n","")
+ "&ErrorUrl=" +
Request.Url.ToString().Replace("<br>","").Replace("\n",""));
}
}
Renaming
In this sample, renaming the file and renaming the folder
are achieved in different web pages, EditFolder.aspx
(see Figure 6) and EditFile.aspx (see Figure 7).
Figure 6: Design-time view of page EditFolder.aspx
Figure 7: Design-time view of page EditFile.aspx
Seeing the screenshots above, some readers may ask,
"Why not put the two together?" The reason is that when we design
interface IDisk, we define two independent functions,
BindDirectoryData and BindFileData,
to gain loose coupling. Since the two parts have nearly the same logic, let us
merely concentrate on the code snippet associated with renaming the folder in
Listing 11.
Listing 5: the key code in relation to renaming the
folder
int nDirID = 2;
protected void Page_Load(object sender, EventArgs e)
{
///obtain the value of parameter DirID
if(Request.Params["DirID"] != null)
{
if(Int32.TryParse(Request.Params["DirID"].ToString(),out nDirID) == false)
{
return;
}
}
if(!Page.IsPostBack)
{ ///display the name of the directory
if(nDirID > -1)
{
BindDirectoryData(nDirID);
}
}
}
private void BindDirectoryData(int nDirID)
{
IDisk disk = new Disk();
SqlDataReader dr = disk.GetSingleDirectory(nDirID);
if(dr.Read())
{
Name.Text = dr["Name"].ToString();
}
dr.Close();
}
protected void EditBtn_Click(object sender,EventArgs e)
{
try
{ ///define the object
IDisk disk = new Disk();
///fulfill the database operation
disk.EditDirectory(nDirID,Name.Text.Trim());
Response.Write("<script>alert('" +
"Modifying sucessfully,safekeep you data! " + "');</script>");
}
catch(Exception ex)
{ ///jump to exception handling page
Response.Redirect("ErrorPage.aspx?ErrorMsg=" +
ex.Message.Replace("<br>","").Replace("\n","")
+ "&ErrorUrl=" +
Request.Url.ToString().Replace("<br>","").Replace("\n",""));
}
}
Here, function BindDirectoryData(int
nDirID) get info of the specified folder from the database using
parameter nDirID and then display the folder's name
in the TextBox Name. When we click the button OK, the event EditBtn_Click is
triggered to call function EditDirectory to rename
the folder and persist the new name in the database. As far as renaming a file
is concerned, please see the downloadable source code accompanying this article.
Deleting
Deleting files/folders is also accomplished inside page Default.aspx. To do so, you can just click the button X and then the selected object will be deleted.
When we use Windows Explorer.exe to delete something, a
dialog is always appearing to remind us of the possible danger. To also achieve
such an effect, we add the related code into the RowDataBound
event of the control DiskView (see Listing 6).
Listing 6
protected void DiskView_RowDataBound(object sender,GridViewRowEventArgs e)
{
ImageButton deleteBtn = (ImageButton)e.Row.FindControl("DeleteBtn");
if(deleteBtn != null)
{
deleteBtn.Attributes.Add("onclick",
"return confirm('Are you sure to delete all the selected items?');");
}
}
Here, if the button deleteBtn is
not null, then we attach a reminding dialog to it.
The main task to make the deletion is done within the
GridView's RowCommand event.
Listing 7
protected void DiskView_RowCommand(object sender,GridViewCommandEventArgs e)
{
if(e.CommandName == "delete")
{
try
{ ///delete data
IDisk disk = new Disk();
disk.DeleteFile(Int32.Parse(e.CommandArgument.ToString()));
///rebind the controls' data
BindDirectoryData(Int32.Parse(DirList.SelectedValue));
Response.Write("<script>alert('" +
"Deleting successfully,safekeep your data!" + "');</script>");
}
catch(Exception ex)
{ ///jump to the page dealing with exception handling
Response.Redirect("ErrorPage.aspx?ErrorMsg=" +
ex.Message.Replace("<br>","").Replace("\n","")
+ "&ErrorUrl=" +
Request.Url.ToString().Replace("<br>","").Replace("\n",""));
}
}
}
Here, event RowCommand obtains the
value of the property CommandName of the button X and save it in parameter CommandName.
Next, we get the value of the property CommandArgument
and convert it into an integer which is stored in the parameter nDirID. If all the requirements are satisfied, then we
start to delete the selected object (file or folder) which is fulfilled by
calling the function DeleteFile(int nDirID) of
interface IDisk, removing the item (whose value of DirID is nDirID) from the
database. Finally, an appropriate dialog appears to indicate whether the
operation is successful or not.
Moving the files/folders
Moving files or folders is also accomplished through page Default.aspx. When clicking the button MoveTo,
you can move the selected files or folders to another path. To do this, we will
follow the steps listed below.
1.
Select the files/folders to move.
2.
Select the destination folder.
3.
Click button MoveTo to finish the task.
All the moving functions are fulfilled inside function MoveBtn_Click(object sender, EventArgs e), whose total code
is shown below.
Listing 8
protected void MoveBtn_Click(object sender,EventArgs e)
{
try
{ ///define a Disk object
IDisk disk = new Disk();
foreach(GridViewRow row in DiskView.Rows)
{
CheckBox dirCheck = (CheckBox)row.FindControl("DirCheck");
if(dirCheck != null)
{
if(dirCheck.Checked == true)
{
///fulfill database operation
disk.MoveDirectory(Int32.Parse(
DiskView.DataKeys[row.RowIndex].Value.ToString()),
Int32.Parse(MoveDirList.SelectedValue));
}
}
}
///rebind the data of the controls
BindDirectoryData(Int32.Parse(DirList.SelectedValue));
Response.Write("<script>alert('" +
"Modifying successfully,safekeep your data!" + "');</script>");
}
catch(Exception ex)
{ ///jump to the exception handling page
Response.Redirect("ErrorPage.aspx?ErrorMsg=" +
ex.Message.Replace("<br>","").Replace("\n","")
+ "&ErrorUrl=" +
Request.Url.ToString().Replace("<br>","").Replace("\n",""));
}
}
To begin, we obtain the files or folders to move, get the DirID of the destination folder, and finally call the function
MoveDirectory(int nDirID, int nParentID) to achieve
the movement. Since there is nothing especially unusual going on here and the
code is self-explaining, we do not chatter much.
Retrieving attributes
Now we turn our attention to the page ViewDisk.aspx
on which we accomplish the task of retrieving file attributes, whose buddy
code-behind file is ViewDisk.aspx.cs. As usual, let
us first look at its page layout.
Figure 9: Design-time view of page ViewDisk.aspx
Since the controls in Figure 9 are all self-explaining, here
we choose to omit detailing them and focus on the page initialization.
Listing 9: Initialization of page ViewDisk.aspx
public partial class ViewDisk : System.Web.UI.Page
{
int nFileID = -1;
private int nParentID = -1;
protected void Page_Load(object sender,EventArgs e)
{
///Obtain the value of parameter DirID
if(Request.Params["DirID"] != null)
{
if(Int32.TryParse(Request.Params["DirID"].ToString(),out nFileID) == false)
{
return;
}
}
///Get the value of parameter ParentID
if(Request.Params["ParentID"] != null)
{
if(Int32.TryParse(Request.Params["ParentID"].ToString(),out nParentID) == false)
{
return;
}
}
if(!Page.IsPostBack)
{ //Retrieve file attributes from the database using parameter nDirID
if(nFileID > -1)
{
BindFileData(nFileID);
}
}
}
private void BindFileData(int nDirID)
{
IDisk disk = new Disk();
SqlDataReader dr = disk.GetSingleFile(nFileID);
if(dr.Read())
{ ///Get the file name(including the extension)
Name.Text = dr["Name"].ToString();
Type.Text = dr["Type"].ToString();
Contain.Text = dr["Contain"].ToString() + "B";
CreateDate.Text = dr["CreateDate"].ToString();
///Dynamically create the folder under which the file lies
Dir.Text = CreateDir(Int32.Parse(dr["DirID"].ToString()));
}
dr.Close();
}
public string CreateDir(int nDirID)
{
StringBuilder dirSB=new StringBuilder();// create a new string - dirSB
//Obtain folders from inside the database
IDisk disk = new Disk();
DataTable dataTable = SystemTools.ConvertDataReaderToDataTable(disk.GetAllDirectoryFile());
DataRow[] rowList = dataTable.Select("DirID='" + nDirID.ToString() + "'");
if(rowList.Length != 1) return("");
///Create other folders
InsertParentDir(dataTable,Int32.Parse(rowList[0]["ParentID"].ToString()),dirSB);
return (dirSB.ToString());
}
private void InsertParentDir(DataTable dataTable,int nParentID,StringBuilder sDir)
{
if(nParentID <= -1)
{
return;
}
DataRow[] rowList = dataTable.Select("DirID='" + nParentID.ToString() + "'");
if(rowList.Length != 1) return;
//Insert the folder info
sDir.Insert(0,rowList[0]["Name"].ToString() + "/");
//Call this function recursively and insert corresponding folder info
InsertParentDir(dataTable,Int32.Parse(rowList[0]["ParentID"].ToString()),sDir);
}
protected void ReturnBtn_Click(object sender,EventArgs e)
{
Response.Redirect("~/Default.aspx?ParentID=" + nParentID.ToString());
}
}
When initialized, page ViewDisk.aspx
first obtains parameters nDirID and nParentID from the address bar and then show the retrieved
file attributes onto the page, which is achieved by calling function BindFileData(int nDirID). With the help of parameter nDirID, function BindFileData
retrieves file attributes from the database and then we use controls Name, Dir, Type, Contains and CreateDate to display the name, hosting folder, type, size,
and creating date of the file respectively. There is one thing to notice since
we have not directly persisted the folder under which the file lies into the
database, we have to dynamically create it. This, obviously seen from above, is
finished in functions CreateDir(int nDirID) and InsertParentDir(DataTable dataTable, int nParentID, StringBuilder
sDir). The following steps are taken so as to create the folder the file
belongs to.
1.
Define a string variable to hold the folder.
2.
Retrieve all the folders from the database and save the results into a
DataTable instance.
3.
Get the DirID of the parent directory of the
current file and add its name to the string dirSB.
4.
Get the parent directory of the file in Step 3 and iterate over Step 3
until the variable ParentID becomes 1. Here, Steps 1
and 2 are done in function CreateDir(int nDirID),
while Steps 3 and 4 are finished within function InsertparentDir.
When we click the button Return,
we are navigated back to page Default.aspx (the main
page), which is accomplished by only one-line code above.
The last note should be mentioned that, as with the typical
ASP.NET applications, in this sample we have also constructed an error handling
page—ErrorPage.aspx.
Whenever there is something wrong, we take the policy to redirect the current
page to page ErrorPage.aspx, letting it deal with the
error-related information.