This is a fully functional file management
application with basic searching function provided by Microsoft Indexing
Service exposed via HTTP Web service. To unify the approach of file objects
presented to UI tier, I follow the design pattern to define an abstract class to
imitate the file object returned either from mounted folder or searching result.
Using a customized ICollection
implementation, DataGrid
can
use it as data source and list items with paging. In addition, file and folder
copying function, email file as attachment and web caching function are
included.
Features of this file management application include:
AppSetting
section setting.
DataGrid
uses data source from
an ICollection
implementation
with item of an abstract base class providing further extensible possibility.
Figure 1 - File Management Web Page in List View
Figure 2 - File Management Web Page in Iconic View
Figure 3 - Send File as attachment via SMTP server
Setup steps are as below:
Directories
property of
system catalog or not and add it if it is not already included. But I
suggest you create a new catalog and include the intended serving root folder
under the new catalog's Directories
property for performance purpose. In doing so, it will
improve response time when narrowing down the searching scope for your
application.
Figure 4 - Indexing Service Setup
For the filemanagement Web Application, please uncheck the Anonymous access and use only Integrated Windows Authentication to protect your folder as when accessing via Internet. It relies on NTFS authorization checking, so make sure that you have set proper access rights under your intended exposing folder.
Figure 5 - Authentication Setup under IIS
IndexCatalog
under
appSettings
section to match
with the new catalog you have created in the previous step:
<appSettings>
<add key="IndexCatalog" value="system" />
</appSettings>
Root
under
appSettings
section to match
with the folder path you intended to serve over your web site. You can also
setup your company name here:
<appSettings>
<add key="Company" value="Ever-Rising System (HK) Ltd" />
<add key="Root" value="C:\" />
</appSettings>
If however, you are accessing your Intranet web server and still have an annoying popup login dialog, you shall check your IE zone settings. In the Security tab, Custom Level setting, there is a parameter as below:
Figure 6 - Custom Zone Level setting
The default setup should allow automatic logon when using Intranet Zone resources. However, you shall also check the web server name is under Intranet Zone or not, for example, when browsing your web server, the IE status bar will indicate in which Zone does it reside:
Figure 7 - IE Zone type
Of course, it is normal if you are browsing your web server from remote and it indicates internet. But if you are using the same local network and it does not reside in Intranet Zone, you can still add the web server to the Intranet Zone as below by using the Local Intranet Sites setup and clicking Advanced button under the Security tab of IE Option menu to add it:
Figure 8 - Intranet Zone setting
<AUTHENTICATION mode="Windows" />
<IDENTITY impersonate="true" />
Impersonate is set to
true
, such that the current Windows user at client is
impersonated when accessing resources in web server.
appSettings
section of
WEB.CONFIG as below:
<ADD value="true" key="HasEmailService" />
<!-- Control email sending thread (use main thread or thread pool) -->
<ADD value="true" key="SendByThreadInPool" />
<ADD value="true" key="LogNeeded" />
<ADD value="in...@yourdomain.com" key="EmailFromUser" />
<ADD value="smtp.yourdomain.com" key="SMTPServer" />
<ADD value="SMTPUserID" key="SMTPUser" />
<ADD value="SMTPPAssword" key="SMTPPwd" />
<ADD value="log/FileManagementSiteLog.txt" key="FileManagementSiteLog" />
The settings are explained as below:
HasEmailService
controls
whether email service is provided.
SendByThreadInPoll
controls
whether email is sent by .NET provided thread pool. This can increase
response time for UI as another thread is used when email is to be sent. I
suggest you leave it as true
and only
set to false
for debugging purpose when email sending has
problem.
EmailFromUser
is the default
user filled in the From Address entry when a file item is needed to be sent.
That can be overridden in the SendMail.aspx page by the user.
STMPServer
, SMTPUser
and SMTPPwd
are optional settings for your SMTP
server location, SMTP
Authentication User and Password respectively. If they are not
set, localhost is assumed and default authentication with SMTP server is used.
FileManagementSiteLog
is the
location of the log file and default to log folder under application
root.appSettings
section of WEB.CONFIG with the parameter
ApplicationCacheTimeOut
.
Default is five minutes.As each file or folder object will have certain
properties in common, an abstract base class
SimpleFileInfoBase
is created
as below to provide necessary fields when binding with UI elements (e.g.
DataGrid
):
abstract public class SimpleFileInfoBase
{
public abstract string Name { get ; }
public abstract string FullName { get ; }
public abstract DateTime LastWriteTime { get ; }
public abstract long Size { get ; }
}
In this class, Name
denotes the short name,
FullName
denotes the full path
for the file or folder object. The actual implementation is to derive class
FileSystemInfoExtend
from
SimpleFileInfoBase
and it is
straight forward, and just wrapping the original
FileSystemInfo
class is
enough.:
public class FileSystemInfoExtend : SimpleFileInfoBase
{
private FileSystemInfo _file ;
public FileSystemInfoExtend(FileSystemInfo file)
{
_file = file ;
}
override public string Name
{
get { return _file.Name ; }
}
override public string FullName
{
get { return _file.FullName ; }
}
public bool IsDirectory
{
get
{
return (_file.Attributes & FileAttributes.Directory)
==FileAttributes.Directory ;
}
}
public string Type
{
get { return this.IsDirectory?"Dir":"File" ; }
}
override public long Size
{
get
{
if ( this.IsDirectory )
return 0L ;
else
return ((FileInfo)_file).Length ;
}
}
override public DateTime LastWriteTime
{
get { return _file.LastWriteTime ; }
}
}
Why I decide to define an abstract class
SimpleFileInfoBase
first before
implementing the actual class is because the searching result item type from
indexing service shares the same abstract class
SimpleFileInfoBase
with folder
browsing returned item type FileSystemInfoExtend
by deriving class
SearchResultItem
from it as
below:
public class SearchResultItem : SimpleFileInfoBase
{
private string _Name ;
private string _FullName ;
private DateTime _LastWriteTime ;
private long _Size ;
// Interface to Indexing Service used OleDb
//which returns System.Data.DataSet type object.
// By consuming the DataRow object, the data can be
//transformed before presenting to UI.
public SearchResultItem(DataRow row)
{
_Name=
row["Filename"]==DBNull.Value?string.Empty:(string)row["Filename"];
_FullName=
row["Path"]==DBNull.Value?string.Empty:(string)row["Path"] ;
_LastWriteTime =
row["Write"]==DBNull.Value?DateTime.MinValue:(DateTime)row["Write"];
_Size = row["Size"]==DBNull.Value?0L:(long)row["Size"] ;
}
override public string Name { get {return _Name ; } }
override public string FullName { get { return _FullName; } }
override public DateTime LastWriteTime {
get { return _LastWriteTime ; } }
override public long Size { get { return _Size ; } }
}
As I have use two different ways to list items,
folder browsing function using classes from System.IO
and searching function using classes from
System.Data.OleDb
(when
accessing Indexing Service), both results shall be factorized as common base
class before they are presented to DataGrid
as the data source.
The System.IO
classes create array of
FileSystemInfo
type objects
when we use them to list files or subfolders from the requested path, for
example, in the page WebFolder.aspx:
System.IO.DirectoryInfo CurrentRoot = new DirectoryInfo(this.RootPath);
FileSystemInfo[] files ;
On the other hand, result from Indexing
Service using ADO.NET OleDb
classes produces items in System.Data.DataTable
object, for example:
string connstring = "Provider=MSIDXS;Data Source=" + _Catalog ;
using ( OleDbConnection conn = new OleDbConnection(connstring) )
{
OleDbDataAdapter DataAdapter = new OleDbDataAdapter(_Query, conn);
DataSet DataSetSearchResult = new DataSet();
DataAdapter.Fill(DataSetSearchResult, "SearchResults");
return DataSetSearchResult ;
}
After implementing two
ICollection
classes with
collection of items from these two classes, they can share common properties
which can be accessed in UI (DataGrid
) consistently.
// ICollection implementation for FileSystemInfoExtend items
public class FileSystemInfosExtend : ICollection
{
private FileSystemInfoExtend[] _files ;
// ... Other stuffs
}
// ICollection implementation for SearchResultItem items
public class SearchResultItems : ICollection
{
private ArrayList _SearchResultItems ;
public SearchResultItems(DataTable ResultDataTable)
{
_SearchResultItems = new ArrayList() ;
_SearchResultItems.Clear() ;
foreach ( DataRow row in ResultDataTable.Rows )
_SearchResultItems.Add( new SearchResultItem(row) ) ;
}
// ... Other stuffs
}
Figure 9 - Class design diagram
After we implemented
ICollection
classes, we can set
the data source for the DataGrid
to these ICollection
class objects, for example:
// In WebFolder.aspx, bind the DataGrid as below
FileSystemInfosExtend FileInfosEx = new FileSystemInfosExtend(files) ;
DataGrid1.DataSource = FileInfosEx ;
DataGrid1.DataBind() ;
// In Search.aspx, bind the DataGrid as below
localhost.FileSearch FileSearcherInst = new localhost.FileSearch() ;
FileSearcherInst.Credentials = System.Net.CredentialCache.DefaultCredentials ;
DataTable results = FileSearcherInst.Search(RootPath, SearchText).Tables[0] ;
SearchResultItems searchResultItems = new SearchResultItems(results) ;
DataGrid1.DataSource = searchResultItems ;
DataGrid1.DataBind();
Obviously, requesting for folder and file item
are two different things; they need to be handled separately. Folder request
will trigger recursive link to same ASPX page (WebFolder.aspx or
WebFolderTNView.aspx) with different request
path
parameter (Query String
Parameter) but file request will be linked to another ASPX page
(FileSender.aspx) where file item requested will be handled. To address
the two situations, a function is created as below:
protected string FormatLink(object file)
{
FileSystemInfoExtend FileSystemInfoEx = file as FileSystemInfoExtend ;
if ( FileSystemInfoEx == null )
return "" ;
string FileFullName = Server.UrlEncode(FileSystemInfoEx.FullName) ;
if ( FileSystemInfoEx.IsDirectory )
return string.Format("{0}?path={1}"
, this.Request.Path, FileFullName ) ;
else
return string.Format("{0}?file={1}"
, "FileSender.aspx", FileFullName ) ;
}
This function will be used by the
Name
column of
DataGrid
as below:
<ASP:HYPERLINK
Text='<%# DataBinder.Eval(Container, "DataItem.Name") %>'
navigateurl='<%# FormatLink(DataBinder.Eval(Container, "DataItem")) %>'
runat="server">
</ASP:HYPERLINK>
Actually, the file item handling page is very
simple and I am sure making it as an IHttpHandler
handler is even better to improve performance.
ASPX page derived from IHttpHandler
too but provides more than needed service in this case!
Here is the listing from the FileSender.aspx source:
private void Page_Load(object sender, System.EventArgs e)
{
string FileFullPath = string.Format("{0}", Request["file"]) ;
if ( FileFullPath == "" )
return ;
FileInfo fileInfo = new FileInfo(FileFullPath) ;
if ( !fileInfo.Exists )
return ;
Response.ClearHeaders() ;
Response.ClearContent() ;
switch ( fileInfo.Extension.ToLower() )
{
case ".htm" :
case ".html" :
case ".asp" :
case ".aspx" :
case ".xml":
goto Send_File;
case ".txt":
case ".ini":
case ".log":
Response.ContentType = "text/plain" ;
goto Add_Disposition_Inline;
case ".jpg":
Response.ContentType =
string.Format("image/JPEG;name=\"{0}\"", fileInfo.Name) ;
goto Add_Disposition_Inline;
case ".gif":
case ".png":
case ".bmp":
Response.ContentType = string.Format("image/{0};name=\"{1}\""
, fileInfo.Extension.TrimStart('.')
, fileInfo.Name) ;
goto Add_Disposition_Inline;
case ".tif":
Response.ContentType =
string.Format("image/tiff;name=\"{0}\"", fileInfo.Name) ;
goto Add_Disposition_Inline;
case ".doc":
Response.ContentType = "Application/msword";
goto Add_Disposition_Inline;
case ".xls":
Response.ContentType = "Application/x-msexcel";
goto Add_Disposition_Inline;
case ".pdf":
Response.ContentType = "Application/pdf";
goto Add_Disposition_Inline;
case ".ppt":
case ".pps":
Response.ContentType = "Application/vnd.ms-powerpoint";
goto Add_Disposition_Inline;
case ".zip":
Response.ContentType = "application/x-zip-compressed" ;
goto Add_Disposition_Attachment;
// Others as attachment only!
default:
goto Add_Disposition_Attachment;
}
Add_Disposition_Attachment:
Response.AppendHeader("Content-Disposition"
, string.Format("attachment;filename=\"{0}\"", fileInfo.Name)) ;
goto Send_File;
Add_Disposition_Inline:
Response.AppendHeader("Content-Disposition"
, string.Format("inline;filename=\"{0}\"", fileInfo.Name)) ;
goto Send_File;
Send_File:
try
{
Response.WriteFile(FileFullPath) ;
}
catch (UnauthorizedAccessException)
{
string query = Request.UrlReferrer.Query ;
int i = query.ToLower().IndexOf("error=") ;
if ( i > -1 )
{
int j = query.IndexOf("&", i) ;
if ( j > -1 )
query = query.Remove(i, j-i+1) ;
else
query = query.Remove(i, query.Length-i) ;
}
if ( query == "" )
query = "?" ;
else if (!query.EndsWith("&"))
query += "&" ;
Response.Redirect( Request.UrlReferrer.LocalPath
+ query
+ string.Format("Error=You are not allow to access file {0}."
, fileInfo.Name)) ;
}
}
After all, to view the list as iconic items, we
need to get the associated icons first and in page
ShowFileIcon.aspx
, it will
handle icons retrieval and listing is as below:
icon = IconHandler.IconHandler.GetAssociatedIcon(fileinfo.FullName,
IconSizeUsed) ;
if ( icon != null )
{
Response.ContentType = "image/x-icon" ;
string TempFileName =
fileinfo.Extension != ""
? fileinfo.Name.Replace(fileinfo.Extension,".ico")
: fileinfo.Name+".ico" ;
Response.AppendHeader("Content-Disposition"
, string.Format("inline;filename=\"{0}\"", TempFileName)) ;
icon.Save(Response.OutputStream) ;
icon.Dispose() ;
}
The function
IconHandler.GetAssociatedIcon()
does the hard job to get icon resource for most file types registered in Windows
and details are as below:
public enum IconSize : uint
{
Small = 0x0, //16x16
Large = 0x1 //32x32
}
public class IconHandler
{
// Filename - the file name to get icon from
public static IntPtr GetAssociatedIconHandle(string Filename, IconSize size)
{
IntPtr hImgSmall; //the handle to the system image list
IntPtr hImgLarge; //the handle to the system image list
SHFILEINFO shinfo = new SHFILEINFO();
if ( size == IconSize.Small )
hImgSmall = Win32.SHGetFileInfo(Filename, 0
, ref shinfo,(uint)Marshal.SizeOf(shinfo)
, Win32.SHGFI_ICON |Win32.SHGFI_SMALLICON);
else
hImgLarge = Win32.SHGetFileInfo(Filename, 0
, ref shinfo, (uint)Marshal.SizeOf(shinfo)
, Win32.SHGFI_ICON | Win32.SHGFI_LARGEICON);
return shinfo.hIcon ;
}
// Filename - the file name to get icon from
public static Icon GetAssociatedIcon(string Filename, IconSize size)
{
return Icon.FromHandle(GetAssociatedIconHandle(Filename, size)) ;
}
}
[StructLayout(LayoutKind.Sequential)]
public struct SHFILEINFO
{
public IntPtr hIcon;
public IntPtr iIcon;
public uint dwAttributes;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string szDisplayName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
public string szTypeName;
};
internal class Win32
{
public const uint SHGFI_ICON = 0x100;
public const uint SHGFI_LARGEICON = 0x0; // 'Large icon
public const uint SHGFI_SMALLICON = 0x1; // 'Small icon
[DllImport("shell32.dll", CharSet=CharSet.Unicode)]
public static extern IntPtr SHGetFileInfo(
[MarshalAs(UnmanagedType.LPWStr)] // Use wide chars
string pszPath
, uint dwFileAttributes
, ref SHFILEINFO psfi
, uint cbSizeFileInfo
, uint uFlags);
}
It makes use of the Win32 Shell Api functions and no existing Managed class provides such file information for us. So that is the only way to go and hopefully, we can get over it in the coming release of .NET.
File upload function is too simple to discuss, just be aware that multiple files can be handled in server codes although I have not changed UI to allow it. Below is the function source:
private void buttonSubmit_ServerClick(object sender, System.EventArgs e)
{
for(int i = 0; i < Request.Files.Count ; ++i)
{
HttpPostedFile file = Request.Files[i] as HttpPostedFile;
string path = string.Format(@"{0}\{1}"
, this.RootPath.TrimEnd('\\'), Path.GetFileName(file.FileName)) ;
file.SaveAs(path) ;
}
Response.Redirect(Request.Url.PathAndQuery) ;
}
Email service is provided with an wrapper class
Email
which mainly packages all
functions provided by System.Web.Mail
. Although implementation is straight forward, some key
features are still worth to discuss.
I think one of the most ignored features of .NET framework is threading. Actually using threads in .NET is much easier than a lot of people expect. To send email via background thread is a very good candidate for such applicable area and codes for this feature are as below:
//
// In class Email send method
//
public void Send(string sTo, string sFrom, string sSubject
, string sBody, string sCc, string sBcc, bool SendByThreadInPool)
{
MailMessage mailMessage = new MailMessage();
// Other stuffs ...
SmtpMail.SmtpServer = _mailServer ;
if (!SendByThreadInPool)
{
SmtpMail.Send( mailMessage ) ;
if (this._Logger != null)
{
string messageInfo = string.Format(
"From:{0}\tTo:{1}\tSubject:{2}"
, mailMessage.From
, mailMessage.To
, mailMessage.Subject) ;
this._Logger.Write(messageInfo
+ " completed successfully.") ;
}
}
else
{
// Use Thread pool to give immediate UI response
if (!ThreadPool.QueueUserWorkItem(new WaitCallback(Start)
, mailMessage))
throw new ApplicationException(
"Cannot queue task to send email.") ;
}
// Other stuffs ...
}
//
// In class Email Start method
//
private void Start(object mailMessage)
{
MailMessage message = mailMessage as MailMessage ;
if (message != null)
{
string messageInfo = string.Format("From:{0}\tTo:{1}\tSubject:{2}"
, message.From, message.To, message.Subject) ;
try
{
SmtpMail.Send( message );
if (this._Logger != null)
this._Logger.Write(messageInfo + " completed successfully.") ;
}
catch(Exception excpt)
{
if (this._Logger != null)
{
this._Logger.Write(messageInfo + " failed.") ;
this._Logger.Write(excpt.ToString()) ;
}
else
// Re-throw the exception ;
throw ;
}
}
}
The ThreadPool
class is implemented in
System.Threading
assembly and
has a method QueueUserWorkItem
which gives us a handy feature to queue our task for processing by threads
picked up from the .NET provided system thread pool. To have the function
running by thread in the thread pool, we need to create a delegate
WaitCallBack
and to have the
function layout matched with this delegate. The layout of the function required
is:
void FunctioName(object state) ;
The method Start
of Email
class actually conforms this standard and I have passed
the MailMessage
object to the
called function when I queue the batch task.
When developing the background task program, we
need to pay attention to error logging. Obviously, when problem happens,
background task which does not have a UI, is not easy to alert user. So make
sure to implement a proper logging mechanism with background task. As I want to
decouple the logging mechanism from Email
class, I define an Interface to indicate how logging is
provided as below:
public interface ILogger
{
void Write(string MessageLine) ;
}
The actual implementation goes to the class
FileLogger
which simply uses
text file to provide logging. But as any class that implements
ILogger
can do the same job,
you can easily extend the application to log information to other data store.
One of the nice features given by ASP.NET is caching and you can specify WebForm to be cached automatically by adding declarative statement in the page source. This is the simplest way to have caching in your ASP.NET application. But to explore the real power of ASP.NET caching feature, you need to do more.
I have defined a class
CacheManager
to provide caching
in my application and implementation is as below:
public class CacheManager
{
static public FileSystemInfosExtend
GetFileItems(string CurrentRootPath, bool RefreshCache)
{
// Format cache key as FileSystemInfosExtend:{CurrentRootPath}
string keyName =
string.Format("FileSystemInfosExtend:{0}", CurrentRootPath) ;
if ( RefreshCache || HttpContext.Current.Cache[keyName] == null )
{
// Remove previous cached item
if ( HttpContext.Current.Cache[keyName] != null )
HttpContext.Current.Cache.Remove(keyName) ;
DirectoryInfo CurrentRoot =
new DirectoryInfo(CurrentRootPath) ;
FileSystemInfo[] files = CurrentRoot.GetFileSystemInfos() ;
FileSystemInfosExtend FileInfosEx =
new FileSystemInfosExtend(files) ;
// Create cache item
HttpContext.Current.Cache.Insert(
keyName
, FileInfosEx
, null
, DateTime.Now.Add(TimeSpan.FromMinutes(
AppSetting.ApplicationCacheTimeOut))
, Cache.NoSlidingExpiration) ;
}
return HttpContext.Current.Cache[keyName] as FileSystemInfosExtend ;
}
}
You should pay attention to the cache key
because, if we want to have multiple application objects cached, we need to
define some way to distinguish the objects and retrieve them later. As my
application objects are lists of folder items, the logical naming convention
shall be the path name of the current folder requested plus the object type
which is FileSystemInfosExtend
.
I have defined a special parameter
RefreshCache
to explicitly
refresh cache. When Refresh
button is pressed, cache will be refreshed by calling the Cache Manager
with parameter RefreshCache
set
to true
.
.NET framework
FileSystemWatcher
component
gives us access to the following events:
Created
� raised whenever a
directory or file is created.
Deleted
� raised whenever a
directory or file is deleted.
Renamed
� raised whenever the
name of a directory or file is changed.
Changed
� raised whenever
changes are made to the size, system attributes, last write time, last access
time, or security permissions of a directory or file. We can make use this component to refresh the application cache and then user of the application will always have latest copies of folder lists.
The file and folder copying function is
implemented with System.IO
classes. After selecting files or subfolders on the main file list screen,
clicking the COPY
button at the
bottom of screen, you will see the main File Copying screen at
FileCopy.aspx page:
Figure 10 - File(s) copy to destination directory selection
Clicking on the
Subfolder
link button will
bring you to the subfolders of the selected folder and clicking on the selected
folder itself will copy the files shown on the top list to it.
As I have provided paging in the list of file
items screen, when user clicks the checkbox
to select some of the items and switch to another page,
we need to remember the items selected. Save the selected item list in session
state may be the option but I see it is better to store them in
VeiwState
because this
information attaches to this particular page only and shall not make it
available to other pages by making the scope larger. This is the basic rule of
thumb when considering defining scope of variables in the first lesson of
programming.
A method
SaveSelectedItemKey
and a
property SelectedKey
is defined
in the page code-behind class as below:
// Property to stored selected datagrid
//key (File/directory item full path) to ViewState
private ArrayList SelectedKey
{
set
{
ViewState["SelectedKey"] = value ;
}
get
{
if ( ViewState["SelectedKey"] == null )
return new ArrayList() ;
else
return ViewState["SelectedKey"] as ArrayList ;
}
}
// Method to store datagrid keys to page property SelectedKey
private void SaveSelectedItemKey()
{
ArrayList arrayList = this.SelectedKey ;
foreach(DataGridItem listItem in this.DataGrid1.Items)
{
if (listItem.ItemType == ListItemType.AlternatingItem
|| listItem.ItemType == ListItemType.Item)
{
CheckBox selected = listItem.FindControl("Select") as CheckBox ;
string keyItem = DataGrid1.DataKeys[listItem.ItemIndex] as string ;
if (selected.Checked && !arrayList.Contains(keyItem))
arrayList.Add(keyItem) ;
if (!selected.Checked && arrayList.Contains(keyItem) )
arrayList.Remove(keyItem) ;
}
}
this.SelectedKey = arrayList ;
}
As you can see the
CheckBox
items will be checked
and all the selected DataKey
will be stored in the ArrayList
that is to be retrieved later when Copy
button is clicked to kick-off copying.
Copying list of files is easy but not so for
directory with tree of subdirectories under it. Any help? There is a
Move
method in
System.IO.Directory
class to
help us to move directory tree but no Copy
method is found!
So for us, the poor guys need to develop the
function ourselves. Fortunately, it is not difficult with bunch of
System.IO
classes helping us to
achieve the task. Following list of codes is how I do it in a single
method:
private int CopyFile(string SourceFile, string DestinationPath)
{
int FileCount = 0 ;
// Is SourceFile file or folder(directory) name?
if (File.Exists(SourceFile))
{
File.Copy(SourceFile
, DestinationPath
+ Path.DirectorySeparatorChar
+ Path.GetFileName(SourceFile)
, true) ;
++FileCount ;
}
else if (Directory.Exists(SourceFile))
{
// Create the detsination sub-folder(subdirectory) first
DirectoryInfo directoryInfo = new DirectoryInfo(SourceFile) ;
string DestinationSubDirectory = DestinationPath
+ Path.DirectorySeparatorChar
+ directoryInfo.Name ;
if (!Directory.Exists(DestinationSubDirectory))
Directory.CreateDirectory(DestinationSubDirectory) ;
// Copy all items under this folder(directory)
// to detsination sub-folder(subdirectory)
FileSystemInfo[] fileinfos = directoryInfo.GetFileSystemInfos() ;
foreach(FileSystemInfo fileinfo in fileinfos)
FileCount +=
this.CopyFile(fileinfo.FullName, DestinationSubDirectory) ;
}
else
throw new System.IO.FileNotFoundException("File not found!", SourceFile) ;
return FileCount ;
}
The key point to this
CopyFile
method implementation
is to use recursive calls to achieve copying of subdirectories. When source is
not a file, the program will list all the items beneath it and create the
necessary destination subdirectories before proceeding to call recursively the
function.
There is a long way to go before we can have
more features for this file management application. I hope when I have time,
will add more functions like move and delete or file items,
automatic Application Cache updating by using events from
FileSystemWatcher
and other
advanced features like file versioning. Anyway, I hope this is the start
and anyone can help me to make it perfect, based on my rudimentary work is
welcomed.