Silverlight File Manager





5.00/5 (5 votes)
The Silverlight File Manager on the ListBox control based universal server handler may be working on the ASP .NET WebForms and MVC projects. All requests are sent asynchronously via helper class.

Introduction
Silverlight applications have no direct access to the file system of server. To gain access to the server file system needs to be done the proxy (gateway) page.
I created a gateway class for processing requests. The class can be used in the ASP .NET WebForm and ASP.NET MVC projects.
File Manager is created as custom control based on the ListBox
. The control has a method for sending requests to the server.
All requests are sent asynchronously via helper class. The helper class is optimized for File Manager, but it can be easily modified for other purposes.
Data exchange is carried out in the JSON. Is it optimal for traffic volume.
Server
Gateway
class for processing requests created on the Class Library project.
The class has a main method - GetResult
. The method for processing requests returns a JSON string
.
Requests data are taken from HttpContext.Current.Request
class. For it, I created a helper variable - Request
, and also Server
.
HttpRequest Request = HttpContext.Current.Request;
HttpServerUtility Server = HttpContext.Current.Server;
Server can only handle POST
requests. Request parameters are available in the Form
collection.
Server will handle the six operations:
check
- check file nameupload
- upload and save file on the servernewdir
- create a new directorydelete
- delete file or directoryrename
- change name of the file or directoryget
(default) - get file and directories list
Name of the operation will be contained in the parameter cmd
.
string cmd = "";
if (!String.IsNullOrEmpty(Request.Form["cmd"])) { cmd = Request.Form["cmd"].ToLower(); }
The root directory name contains a _Root
variable. The client must pass a relative path on the path
field.
string _Root = ""; // empty - server root directory
string path = "";
if (!String.IsNullOrEmpty(Request.Form["path"]))
{ path = Request.Form["path"]; } else { path = "/"; }
if (!path.EndsWith("/")) path += "/";
Before executing the operation (cmd), the server must verify the existence of a directory.
DirectoryInfo DI = new DirectoryInfo
(Server.MapPath(String.Format("~/{0}{1}", _Root, path)));
if (!DI.Exists)
{
result = GetError(String.Format("Error. The directory \"{0}\" not found.",
String.Format("~/{0}{1}", _Root, path)));
return result.ToString();
}
I did not create verification of the user authorization and access, but you can create it.
Server returns JSON string. I created two helper functions for it. First - GetError
return JSON object with error message. Second - GetJsonString
converting object to JSON.
private StringBuilder GetError(string msg)
{
return GetJsonString(new { stat = "err", msg = msg });
}
private StringBuilder GetJsonString(object source)
{
StringBuilder result = new StringBuilder();
JavaScriptSerializer myJSON = new JavaScriptSerializer();
myJSON.Serialize(source, result);
return result;
}
I used anonymous types with next properties:
stat
- server response code: ok - ok, err - errormsg
- error message, if stat = errallowUp
- has top-level directory (only for file list requests)data
- array of files and directories (only for file list requests):name
- file or directory namesize
- file size (only for files)type
- item type: 0 - directory, 1 - fileurl
- file URL
public class Gateway
{
private string _Root = "Custom"; // root directory
public Gateway() { }
public string GetResult()
{
if (HttpContext.Current == null) throw new Exception("HTTP request is required.");
HttpRequest Request = HttpContext.Current.Request;
HttpServerUtility Server = HttpContext.Current.Server;
StringBuilder result = new StringBuilder();
try
{
// ...
// here you can make authorization
// ..
string cmd = "", path = "";
if (!String.IsNullOrEmpty(Request.Form["cmd"]))
{ cmd = Request.Form["cmd"].ToLower(); }
if (!String.IsNullOrEmpty(Request.Form["path"]))
{ path = Request.Form["path"]; } else { path = "/"; }
if (!path.EndsWith("/")) path += "/";
DirectoryInfo DI = new DirectoryInfo
(Server.MapPath(String.Format("~/{0}{1}", _Root, path)));
if (!DI.Exists)
{
result = GetError(String.Format("Error.
The directory \"{0}\" not found.", String.Format("~/{0}{1}", _Root, path)));
return result.ToString();
}
if (cmd == "check")
{
#region check file name
if (File.Exists(Path.Combine(DI.FullName, Request.Form["name"])))
{
result = GetJsonString(new { stat = "err", msg = String.Format
("Sorry, file \"{0}\" is exists on the directory
\"{1}\".", Request.Form["name"], path) });
}
else
{
result = GetJsonString(new { stat = "ok" });
}
#endregion
}
else if (cmd == "upload")
{
#region save file
if (Request.Files["file1"] == null || Request.Files["file1"].ContentLength <= 0)
{
result = GetError("Error. File is required.");
}
else
{
// check file name
if (File.Exists(Path.Combine(DI.FullName, Request.Files["file1"].FileName)))
{
result = GetJsonString(new { stat = "err", msg = String.Format
("Sorry, file \"{0}\" is exists on the directory \"{1}\".",
Request.Files["file1"].FileName, path) });
}
else
{
// save
using (FileStream fs = System.IO.File.Create
(Path.Combine(DI.FullName, Request.Files["file1"].FileName)))
{
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = Request.Files["file1"].
InputStream.Read(buffer, 0, buffer.Length)) != 0)
{
fs.Write(buffer, 0, bytesRead);
}
}
result = GetJsonString(new { stat = "ok" });
}
}
#endregion
}
else if (cmd == "newdir")
{
#region create a new directory
if (String.IsNullOrEmpty(Request.Form["name"]))
{
result = GetError("Error. Directory name is required.");
}
else
{
// check name
DirectoryInfo d = new DirectoryInfo(Path.Combine
(DI.FullName, Request.Form["name"]));
if (d.Exists)
{
result = GetError("Sorry, directory is exists.");
}
else
{
// create directory
d.Create();
// is ok
result = GetJsonString(new { stat = "ok" });
}
}
#endregion
}
else if (cmd == "delete")
{
#region delete file/directory
if (String.IsNullOrEmpty(Request.Form["name"]))
{
result = GetError("Error. Name is required.");
}
else
{
if (File.GetAttributes(Path.Combine(DI.FullName,
Request.Form["name"])) == FileAttributes.Directory)
{
// is directory,
Directory.Delete(Path.Combine(DI.FullName, Request.Form["name"]), true);
}
else
{
// is file
File.Delete(Path.Combine(DI.FullName, Request.Form["name"]));
}
result = GetJsonString(new { stat = "ok" });
}
#endregion
}
else if (cmd == "rename")
{
#region rename file/directory
string oldName = Request.Form["oldName"], newName = Request.Form["newName"];
if (String.IsNullOrEmpty(oldName) || String.IsNullOrEmpty(newName))
{
result = GetError("Error. Name is required.");
}
else
{
if (newName != oldName)
{
if (File.GetAttributes(Path.Combine(DI.FullName,
oldName)) == FileAttributes.Directory)
{
// rename directory
Directory.Move(Path.Combine(DI.FullName, oldName),
Path.Combine(DI.FullName, newName));
}
else
{
// rename file
File.Move(Path.Combine(DI.FullName, oldName),
Path.Combine(DI.FullName, newName));
}
}
result = GetJsonString(new { stat = "ok" });
}
#endregion
}
else
{
#region file list
ArrayList files = new ArrayList();
// dicrectories
foreach (DirectoryInfo d in DI.GetDirectories())
{
files.Add(new
{
name = d.Name,
size = 0,
type = 0, // type = 0 - is directory
url = String.Format("http://{0}/{1}{2}{3}", Request.Url.Host +
(Request.Url.Port > 80 ? ":" + Request.Url.Port.ToString() : ""),
_Root, path, d.Name)
});
}
// files
foreach (FileInfo f in DI.GetFiles())
{
files.Add(new
{
name = f.Name,
size = f.Length,
type = 1,// type = 1 - is file
url = String.Format("http://{0}/{1}{2}{3}", Request.Url.Host +
(Request.Url.Port > 80 ? ":" + Request.Url.Port.ToString() : ""),
_Root, path, f.Name)
});
}
// check top-level directory
bool allowUp = !String.IsNullOrEmpty(path.Trim("/".ToCharArray()));
// create JSON
result = GetJsonString(new { stat = "ok", allowUp = allowUp, data = files });
#endregion
}
}
catch (Exception ex)
{
// error
result = GetError(ex.Message);
}
// result
return result.ToString();
}
/// <summary>
/// The helper function returning error in the JSON
/// </summary>
/// <param name="msg">Error message</param>
private StringBuilder GetError(string msg)
{
return GetJsonString(new { stat = "err", msg = msg });
}
/// <summary>
/// The helper function for converting object to JSON
/// </summary>
/// <param name="source">Object for converting JSON</param>
private StringBuilder GetJsonString(object source)
{
StringBuilder result = new StringBuilder();
JavaScriptSerializer myJSON = new JavaScriptSerializer();
myJSON.Serialize(source, result);
return result;
}
}
The Gateway
class is easy to use ASP. NET WebForms. For example, in the ASP .NET Handler (Gateway.ashx).
public class Gateway : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
Nemiro.FileManager.Common.Gateway myGateway =
new Nemiro.FileManager.Common.Gateway();
context.Response.ContentType = "application/json";
context.Response.Write(myGateway.GetResult());
}
public bool IsReusable
{
get
{
return false;
}
}
}
And also in the ASP .NET MVC. For example, to the Gateway
Action in HomeController
.
public class HomeController : Controller
{
[HttpPost]
public ActionResult Gateway()
{
Nemiro.FileManager.Common.Gateway myGateway =
new Nemiro.FileManager.Common.Gateway();
return new ContentResult() { Content = myGateway.GetResult(),
ContentType = "application/json", ContentEncoding = System.Text.Encoding.UTF8 };
}
}
Silveright (client)
WebHelper Class
The WebHelper
class implements the ability to send asynchronous HTTP requests.
For request parameters, I created two additional classes.
First - the QueryItem
class for parameter data. The QueryItem
class can contain text data and files. Second - the QueryItemCollection
collections of QueryItem
.
The WebHelper
class has a one public
method - Execute
. The method takes a reference to a callback function.
For callback function, I created delegate.
public delegate void WebCallback(string stat, string msg, bool allowUp,
JsonValue data, object tag);
As you can see, to the callback function will be transferred server response from JSON. It is special for File Manager, but you can change delegate and callback function, it is easy.
public class WebHelper
{
/// <summary>
/// The delegate for callback function
/// </summary>
/// <param name="stat">Server response code (ok, err)</param>
/// <param name="msg">Error message (only for stat = err)</param>
/// <param name="allowUp">Has top-level directory</param>
/// <param name="data">Array of file list</param>
public delegate void WebCallback
(string stat, string msg, bool allowUp, JsonValue data, object tag);
// public delegate void WebCallback(HttpWebResponse resp);
private string _Method = "POST";
private QueryItemCollection _Queries = new QueryItemCollection();
private string _Url = String.Empty;
private string _Boundary = String.Empty;
private WebCallback _Callback = null;
/// <summary>
/// GET or POST
/// </summary>
public string Method
{
get
{
return _Method;
}
set
{
_Method = value;
if (String.IsNullOrEmpty(_Method) || _Method.ToUpper() != "GET" ||
_Method.ToUpper() != "POST") _Method = "POST";
}
}
/// <summary>
/// Parameters of Request
/// </summary>
public QueryItemCollection Queries
{
get
{
return _Queries;
}
set
{
_Queries = value;
}
}
/// <summary>
/// Url for sending request
/// </summary>
public string Url
{
get
{
return _Url;
}
set
{
_Url = value;
}
}
/// <summary>
/// Additional custom property
/// </summary>
public object Tag { get; set; }
public WebHelper(string url) : this (url, "POST") { }
public WebHelper(string url, string method)
{
this.Url = url;
this.Method = method;
}
/// <summary>
/// Execute the Request
/// </summary>
/// <param name="callback">The callback function</param>
public void Execute(WebCallback callback)
{
if (String.IsNullOrEmpty(_Url))
{
// url is empty
return;
}
_Callback = callback;
string url = _Url;
#region add parameters to url for GET requests
if (_Method == "GET")
{
string qs = _Queries.GetQueryString();
if (url.EndsWith("?"))
{
url += "&" + qs;
}
else
{
url += "?" + qs;
}
}
#endregion
HttpWebRequest myReq = (HttpWebRequest)HttpWebRequest.Create(_Url);
myReq.Method = _Method;
#region Content-Type for POST requests
if (_Method == "POST")
{
if (_Queries.HasFiles())
{
// has files, this is multipart/form-data content type
_Boundary = "----------" + DateTime.Now.Ticks.ToString("x"); // random boundary
myReq.ContentType = "multipart/form-data; boundary=" + _Boundary;
}
else
{
// has not files, this is application/x-www-form-urlencoded content type
myReq.ContentType = "application/x-www-form-urlencoded";
}
}
#endregion
// start requests
myReq.BeginGetRequestStream(Execute_BeginGetRequestStream, myReq);
}
private void Execute_BeginGetRequestStream(IAsyncResult result)
{
HttpWebRequest r = result.AsyncState as HttpWebRequest; // get request
#region write parameters to request (only for POST)
if (_Queries.Count > 0 && _Method == "POST")
{
Stream myStream = r.EndGetRequestStream(result);
// no the boundary
if (String.IsNullOrEmpty(_Boundary))
{
// write parameters as string
byte[] buffer = Encoding.UTF8.GetBytes(_Queries.GetQueryString());
myStream.Write(buffer, 0, buffer.Length);
}
// has the boundary
else
{
// write parameters with headers
byte[] buffer = null;
foreach (QueryItem itm in _Queries)
{
if (!itm.IsFile)
{
// the text parameter
string q = String.Format("\r\n--{0}\r\nContent-Disposition:
form-data; name=\"{1}\";\r\n\r\n{2}",
_Boundary, itm.Name, itm.ValueAsString());
buffer = Encoding.UTF8.GetBytes(q);
myStream.Write(buffer, 0, buffer.Length);
}
else
{
// the file
string q = String.Format("\r\n--{0}\r\nContent-Disposition:
form-data; name=\"{1}\"; filename=\"{2}\"\r\nContent-Type: {3}\r\n\r\n",
_Boundary, itm.Name, itm.FileName, itm.GetContentType());
buffer = Encoding.UTF8.GetBytes(q);
// file headers
myStream.Write(buffer, 0, buffer.Length);
// file body
buffer = new byte[4096]; // 4 Kb
int bytesRead = 0; int totalSize = 0;
while ((bytesRead = ((Stream)itm.Value).Read
(buffer, 0, buffer.Length)) != 0) // read file data
{
myStream.Write(buffer, 0, buffer.Length); // write file to request
totalSize += bytesRead;
}
}
}
// close the boundary
buffer = Encoding.UTF8.GetBytes(String.Format("\r\n--{0}--\r\n", _Boundary));
myStream.Write(buffer, 0, buffer.Length);
}
myStream.Close();
}
#endregion
// get response
r.BeginGetResponse(Execute_Complete, r);
}
private void Execute_Complete(IAsyncResult result)
{
HttpWebRequest myReq = (HttpWebRequest)result.AsyncState;
HttpWebResponse myResp = (HttpWebResponse)myReq.EndGetResponse(result);
string stat = "", msg = "";
bool allowUp = false;
JsonValue data = null;
if (myResp.StatusCode == HttpStatusCode.OK) //HTTP 200 - OK
{
// read response
StreamReader reader = new StreamReader(myResp.GetResponseStream(), Encoding.UTF8);
string page = reader.ReadToEnd();
// parse JSON
JsonValue json = System.Json.JsonObject.Parse(page);
if (json.ContainsKey("stat")) stat = json["stat"];
if (json.ContainsKey("msg")) msg = json["msg"];
if (json.ContainsKey("allowUp")) allowUp = json["allowUp"];
if (json.ContainsKey("data")) data = json["data"];
}
else
{
stat = "err";
msg = String.Format("Server error {0}", myResp.StatusCode);
}
// callback
if (_Callback != null)
{
_Callback(stat, msg, allowUp, data, this.Tag);
// _Callback(myResp);
}
}
#region additional classes
/// <summary>
/// Collection parameters of request
/// </summary>
public class QueryItemCollection : List<QueryItem>
{
/// <summary>
/// Add text parameter
/// </summary>
/// <param name="name">Parameter name</param>
/// <param name="value">Parameter value</param>
public void Add(string name, string value)
{
this.Add(new QueryItem(name, value));
}
/// <summary>
/// Add file
/// </summary>
/// <param name="name">Parameter name</param>
/// <param name="fileName">File name</param>
/// <param name="stream">File stream</param>
public void Add(string name, string fileName, Stream stream)
{
this.Add(new QueryItem(name, fileName, stream));
}
/// <summary>
/// The function return parameters as string (par1=val1&par2=val2&par3=val3 etc.)
/// </summary>
public string GetQueryString()
{
string qs = "";
foreach (QueryItem itm in this)
{
if (!String.IsNullOrEmpty(qs)) qs += "&";
qs += String.Format("{0}={1}", itm.Name, itm.ValueForUrl());
}
return qs;
}
/// <summary>
/// The function search files. If has files, function returned "true".
/// </summary>
public bool HasFiles()
{
foreach (QueryItem itm in this)
{
if (itm.IsFile) return true;
}
return false;
}
}
/// <summary>
/// Parameter of request
/// </summary>
public class QueryItem
{
public string Name { get; set; }
public object Value{get;set;}
public string FileName { get; set; }
/// <summary>
/// Is file or is not file
/// </summary>
public bool IsFile
{
get
{
return this.Value != null && this.Value.GetType() == typeof(FileStream);
}
}
public QueryItem(string name, string value)
{
this.Name = name;
this.Value = value;
}
public QueryItem(string name, string fileName, Stream stream)
{
this.Name = name;
this.FileName = fileName;
this.Value = stream;
}
/// <summary>
/// UrlEncode value
/// </summary>
public string ValueForUrl()
{
return HttpUtility.UrlEncode(this.Value.ToString());
}
/// <summary>
/// Value as string
/// </summary>
public string ValueAsString()
{
return this.Value.ToString();
}
/// <summary>
/// Content-Type by file extension
/// </summary>
/// <returns></returns>
public string GetContentType()
{ // cut from article (please see the project source files)
return "application/data";
}
}
#endregion
}
FileList Control
The FileList
control inherited from ListBox
. Each item will also be custom.
FileItem
The FileItem
class inherited from StackPanel
. The Item will contain an icon, name, file size and 3 buttons for open, rename and delete item. But the class cannot independently send requests to server. This is only possible via FileList
(Parent
).
public class FileItem : StackPanel
{
/// <summary>
/// Item type: -1 - top-level directory, 0 - directory, 1 - file
/// </summary>
public int ItemType { get; set; }
/// <summary>
/// Directory/File name
/// </summary>
public string FileName { get; set; }
/// <summary>
/// File size (Kb)
/// </summary>
public double FileSize { get; set; }
/// <summary>
/// File url
/// </summary>
public string FileUrl { get; set; }
/// <summary>
/// True - item is in edit mode.
/// False - item is not in edit mode.
/// </summary>
public bool IsEdit { get; set; }
/// <summary>
/// Can edit the Item
/// </summary>
public bool CanEdit { get; set; }
private string _NewName = "";
/// <summary>
/// New Directory/File name
/// </summary>
public string NewName
{
get
{
return _NewName;
}
}
private int _ItemIndex = 0;
public FileItem(int type, string name, string url, double size)
{
this.ItemType = type;
this.FileName = name;
this.FileUrl = url;
this.FileSize = size;
this.CanEdit = type != -1; // top-level directory cannot be editable
this.Orientation = Orientation.Horizontal;
// item icon
Image myImg = new Image() { Width = 16, Height = 16 };
if (type == -1)
{
// top-level directory
myImg.Source = new System.Windows.Media.Imaging.BitmapImage
(new Uri("Images/folder2.png", UriKind.Relative));
}
else if (type == 0)
{
// directory
myImg.Source = new System.Windows.Media.Imaging.BitmapImage
(new Uri("Images/folder.png", UriKind.Relative));
}
else
{
// file
// set icon by extension
string fileExtension = System.IO.Path.GetExtension(name).ToLower();
string[] fileType = { ".exe", ".bat", ".cmd", ".asp", ".aspx", ".html",
".htm", ".cs", ".txt", ".doc", ".docx", ".php", ".gif", ".png", ".jpg",
".jpeg", ".bmp", ".js", ".xls", "xlsx", ".zip" };
string[] fileIcon = { "exe.png", "cmd.png", "cmd.png", "aspx.png", "aspx.png",
"html.png", "html.png", "csharp.png", "txt.png", "doc.png", "doc.png", "php.png",
"image.png", "image.png", "image.png", "image.png", "bmp.png",
"script.png", "xls.png", "xls.png", "zip.png" };
int idx = Array.IndexOf(fileType, fileExtension);
if (idx != -1)
{
myImg.Source = new System.Windows.Media.Imaging.BitmapImage
(new Uri("Images/" + fileIcon[idx], UriKind.Relative));
}
else
{
// default file icon
myImg.Source = new System.Windows.Media.Imaging.BitmapImage
(new Uri("Images/unknown.png", UriKind.Relative));
}
}
myImg.Margin = new Thickness(2, 0, 0, 0);
this.Children.Add(myImg);
// file/directory name
this.Children.Add(new TextBlock()
{ Text = name, Margin = new Thickness(2, 0, 0, 0) });
// control buttons
// open file or go into directory
Image myImg2 = new Image() { Width = 9, Height = 9, Cursor = Cursors.Hand };
myImg2.Margin = new Thickness(4, 0, 0, 0);
myImg2.Source = new System.Windows.Media.Imaging.BitmapImage
(new Uri("Images/open.png", UriKind.Relative));
myImg2.MouseLeftButtonUp += (sender, e) =>
{
Open();
};
this.Children.Add(myImg2);
// is not top-level directory
if (type != -1)
{
// rename directory/file
Image myImg4 = new Image() { Width = 9, Height = 9, Cursor = Cursors.Hand };
myImg4.Margin = new Thickness(4, 0, 0, 0);
myImg4.Source = new System.Windows.Media.Imaging.BitmapImage
(new Uri("Images/edit.png", UriKind.Relative));
myImg4.MouseLeftButtonUp += (sender, e) =>
{
EditStart();
};
this.Children.Add(myImg4);
// delete directory/file
Image myImg3 = new Image() { Width = 9, Height = 9, Cursor = Cursors.Hand };
myImg3.Margin = new Thickness(4, 0, 0, 0);
myImg3.Source = new System.Windows.Media.Imaging.BitmapImage
(new Uri("Images/del.png", UriKind.Relative));
myImg3.MouseLeftButtonUp += (sender, e) =>
{
Delete();
};
this.Children.Add(myImg3);
}
// file size
if (type == 1) // only for files
{
this.Children.Add(new TextBlock() { Text = String.Format
("{0:##,###,##0.00} Kb", size), HorizontalAlignment =
System.Windows.HorizontalAlignment.Right, Margin = new Thickness(8, 0, 0, 0),
FontSize = 9, Foreground =
new SolidColorBrush(Color.FromArgb(255, 128, 128, 128)) });
}
this.MouseLeftButtonUp += new MouseButtonEventHandler(FileItem_MouseLeftButtonUp);
}
private DateTime _lastClick = DateTime.Now;
private bool _firstClickDone = false;
private void FileItem_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
DateTime clickTime = DateTime.Now;
TimeSpan span = clickTime - _lastClick;
if (span.TotalMilliseconds > 350 || !_firstClickDone)//350 ms
{
// first click
_firstClickDone = true;
_lastClick = clickTime;
}
else
{
// second click
_firstClickDone = false;
// open file or go into directory
Open();
}
}
/// <summary>
/// Start editing (change TextBlock to TextBox and set IsEdit = true)
/// </summary>
public void EditStart()
{
if (this.IsEdit) return;
// remove TextBlock
this.Children.RemoveAt(1);
// add TextBox
this.Children.Insert(1, new TextBox()
{ Text = this.FileName, Margin = new Thickness(2, 0, 0, 0) });
// set TextBox LostFocus handler
((TextBox)this.Children[1]).LostFocus +=
new RoutedEventHandler(EditTextBox_LostFocus);
// select all text for directories
if (this.ItemType == 0)
{
((TextBox)this.Children[1]).SelectAll();
}
else
{
// select only file name for files (excluding file extension)
((TextBox)this.Children[1]).SelectionStart = 0;
((TextBox)this.Children[1]).SelectionLength = this.FileName.LastIndexOf(".");
}
// remember item index
_ItemIndex = ((FileList)this.Parent).SelectedIndex;
// set focus to TextBox
((TextBox)this.Children[1]).Focus();
this.IsEdit = true;
}
/// <summary>
/// TextBox LostFocus handler
/// </summary>
private void EditTextBox_LostFocus(object sender, RoutedEventArgs e)
{
EditComplete();
}
/// <summary>
/// Cancel editing (change TextBox to TextBlock and set IsEdit = false)
/// </summary>
public void EditCancel()
{
// remove TextBox
this.Children.RemoveAt(1);
// add TextBlock
this.Children.Insert(1, new TextBlock()
{ Text = this.FileName, Margin = new Thickness(2, 0, 0, 0) });
this.IsEdit = false;
((FileList)this.Parent).Focus();
}
/// <summary>
/// Finish editing and send request to server for rename item
/// </summary>
public void EditComplete()
{
// remove TextBox LostFocus handler
((TextBox)this.Children[1]).LostFocus -= EditTextBox_LostFocus;
// get new name
_NewName = ((TextBox)this.Children[1]).Text;
// send request for rename item
((FileList)this.Parent).SetNewName(this);
}
/// <summary>
/// Open file or go into the directory
/// </summary>
public void Open()
{
if (this.ItemType == 1)
{
// open file in new window
HtmlPage.PopupWindow(new Uri(this.FileUrl), "_blank", null);
}
else if (this.ItemType == 0)
{
// this is directory,
// append current item to patch
if (!((FileList)this.Parent).Path.EndsWith("/"))
((FileList)this.Parent).Path += "/";
((FileList)this.Parent).Path += this.FileName;
// update file list
((FileList)this.Parent).UpdateFileList();
}
else if (this.ItemType == -1)
{
// this is top-level directory,
// remove last item from path
string[] arr = ((FileList)this.Parent).Path .Split("/".ToCharArray());
Array.Resize(ref arr, arr.Length - 1);
((FileList)this.Parent).Path = String.Join("/", arr);
// update file list
((FileList)this.Parent).UpdateFileList();
}
}
/// <summary>
/// Delete item
/// </summary>
public void Delete()
{
if (this.ItemType == 0)
{
if (MessageBox.Show(String.Format("Are you want delete the directory
\"{0}\"?", this.FileName), "Delete",
MessageBoxButton.OKCancel) == MessageBoxResult.OK)
{
((FileList)this.Parent).DeleteItem(this);
}
}
else if (this.ItemType == 1)
{
if (MessageBox.Show(String.Format("Are you want delete the file
\"{0}\"?", this.FileName), "Delete",
MessageBoxButton.OKCancel) == MessageBoxResult.OK)
{
((FileList)this.Parent).DeleteItem(this);
}
}
}
}
FileList
The FileList
contains a helper method for showing errors via standard MessageBox
. Because the requests are executed in separate threads, MessageBox
can be called only from the main thread via BeginInvoke
.
private void ShowError(string msg)
{
this.Dispatcher.BeginInvoke(() =>
{
MessageBox.Show(msg, "Error", MessageBoxButton.OK);
});
}
To implement the ProgressBar
, the class contain two events: Process
and Complete
.
public event EventHandler Process;
public event EventHandler Complete;
ProgressBar
will not be to the FileList
, is it external (to page). For progress, I created child windows.
private string _Url = "http://localhost:58646/Gateway.ashx"; // gateway url for requests
private PleaseWait myPleaseWait = null;
public MainPage()
{
InitializeComponent();
fileList1.Url = _Url; //set url
// handlers for progress
fileList1.Process += new EventHandler(fileList1_Process);
fileList1.Complete += new EventHandler(fileList1_Complete);
}
private void fileList1_Process(object sender, EventArgs e)
{
this.Dispatcher.BeginInvoke(() =>
{
if (myPleaseWait != null &&
myPleaseWait.Visibility == System.Windows.Visibility.Visible) return;
// show porgress
myPleaseWait = new PleaseWait();
myPleaseWait.Show();
});
}
private void fileList1_Complete(object sender, EventArgs e)
{
this.Dispatcher.BeginInvoke(() =>
{
//close progress
if (myPleaseWait != null)
{
myPleaseWait.Close();
myPleaseWait = null;
}
// set new path from fileList
tbPath.Text = fileList1.Path;
});
}
For upload files, I created helper class UploadItem
because sending is done in two stages:
- check
- upload
public class UploadItem
{
/// <summary>
/// The event occurs after the request is sent
/// </summary>
public event EventHandler Complete;
/// <summary>
/// State code list
/// </summary>
public enum StateList
{
/// <summary>
/// File sent successfully
/// </summary>
OK,
/// <summary>
/// Error
/// </summary>
Error,
/// <summary>
/// File is waiting
/// </summary>
Wait
}
private StateList _State = StateList.Wait;
private string _Message = String.Empty;
private int _Index = 0;
private string _FileName = String.Empty;
private Stream _FileStream = null;
private string _Path = String.Empty;
private string _Url = String.Empty;
/// <summary>
/// Upload state
/// </summary>
public StateList State
{
get { return _State; }
}
/// <summary>
/// Error message
/// </summary>
public string Message
{
get { return _Message; }
}
/// <summary>
/// File name
/// </summary>
public string FileName
{
get { return _FileName; }
}
/// <summary>
/// File index
/// </summary>
public int Index
{
get { return _Index; }
}
/// <param name="f">File</param>
/// <param name="idx">File index</param>
/// <param name="url">Url for uploading</param>
/// <param name="path">Server path</param>
public UploadItem(int idx, FileInfo f, string url, string path)
{
_Index = idx;
_Path = path;
_Url = url;
_FileName = f.Name; // set file name
_FileStream = f.OpenRead(); // open file stream
}
public void Run()
{
try
{
// send request for check server
WebHelper w = new WebHelper(_Url);
w.Queries.Add("cmd", "check");
w.Queries.Add("path", _Path);
w.Queries.Add("name", _FileName);
w.Execute(CheckNameResult);
}
catch (Exception ex)
{
_State = StateList.Error;
_Message = ex.Message;
if(Complete != null) Complete(this, null);
}
}
private void CheckNameResult(string stat, string msg,
bool allowUp, JsonValue data, object tag)
{
try
{
if (stat == "ok")
{
// send file
WebHelper w = new WebHelper(_Url);
w.Queries.Add("cmd", "upload");
w.Queries.Add("path", _Path);
w.Queries.Add("file1", _FileName, _FileStream);//add file to request
w.Execute(UploadResult);
}
else
{
// error
_State = StateList.Error;
_Message = msg;
if(Complete != null) Complete(this, null);
}
}
catch (Exception ex)
{
_State = StateList.Error;
_Message = ex.Message;
if(Complete != null) Complete(this, null);
}
}
private void UploadResult(string stat, string msg, bool allowUp,
JsonValue data, object tag)
{
if (stat == "ok")
{
_State = StateList.OK;
}
else
{
// error
_State = StateList.Error;
_Message = msg;
}
if(Complete != null) Complete(this, null);
}
}
The FileList
control has a UploadList
collection.
private List<UploadItem> _UploadFiles = null;
And method for adding UploadItem
to collection.
public void AddUploadItem(FileInfo f)
{
if (_UploadFiles == null) _UploadFiles = new List<uploaditem />();
UploadItem itm = new UploadItem(_UploadFiles.Count, f, this.Url, this.Path);
itm.Complete += new EventHandler(UploadItem_Complete);
_UploadFiles.Add(itm);
}
The FileList
can take files via Drag and Drop.
this.Drop += new DragEventHandler(FileList_Drop);
private void FileList_Drop(object sender, DragEventArgs e)
{
// add selected files to upload list
FileInfo[] files = e.Data.GetData(DataFormats.FileDrop) as FileInfo[];
foreach (FileInfo f in files)
{
this.AddUploadItem(f);
}
// upload files
this.Upload();
}
Uploading files starting on the Upload
method each from _UploadList
.
public void Upload()
{
if (_UploadFiles == null || _UploadFiles.Count <= 0) return; // upload list is empty
_UploadErrorMessages = new StringBuilder();
if (Process!=null) Process(this, null);;
foreach (UploadItem itm in _UploadFiles)
{
itm.Run();//start upload file
}
}
After sending the file to the server, it is removed from _UploadList
on the UploadItem_Complete
handler. When the files over, FileList
update the list of files from server.
private void UploadItem_Complete(object sender, EventArgs e)
{
this.Dispatcher.BeginInvoke(() =>
{
UploadItem itm = sender as UploadItem;
if (itm.State == UploadItem.StateList.Error)
{
_UploadErrorMessages.AppendLine(itm.Message);
}
// remove file from upload list
_UploadFiles.Remove(itm);
if (_UploadFiles.Count == 0)
{
// upload list is empty
// start Complete
if (Complete != null) Complete(this, null);
// has error?
if (_UploadErrorMessages.Length <= 0)
{
// no error, update file list
UpdateFileList();
}
else
{
// show error message
ShowError(_UploadErrorMessages.ToString());
}
}
});
}
The entire code of the FileList
class.
public class FileList : ListBox
{
/// <summary>
/// The event occurs before the request is sent
/// </summary>
public event EventHandler Process;
/// <summary>
/// The event occurs after the request is sent
/// </summary>
public event EventHandler Complete;
private string _Url = "";
private string _Path = "/";
private List<UploadItem> _UploadFiles = null; // the file list to upload
private StringBuilder _UploadErrorMessages = null; // upload error messages
/// <summary>
/// The path of the directory on the server
/// </summary>
public string Path
{
get
{
return _Path;
}
set
{
_Path = value;
if (String.IsNullOrEmpty(_Path)) _Path = "/";
}
}
/// <summary>
/// The file list to upload
/// </summary>
public List<UploadItem> UploadFiles
{
get
{
return _UploadFiles;
}
}
/// <summary>
/// Gateway url
/// </summary>
public string Url
{
get
{
return _Url;
}
set
{
_Url = value;
if (!System.ComponentModel.DesignerProperties.IsInDesignTool)// is not
// Visual Studio designer
{ //update file list
UpdateFileList();
}
}
}
public FileList()
{
ImageBrush ib = new ImageBrush();
ib.ImageSource = new System.Windows.Media.Imaging.BitmapImage
(new Uri("Images/2.png", UriKind.Relative));
ib.Stretch = Stretch.Fill;
this.Background = ib;
this.AllowDrop = true;
this.KeyUp += new KeyEventHandler(FileList_KeyUp); // add KeyUp handler
this.Drop += new DragEventHandler(FileList_Drop); // add Drop handler
}
/// <summary>
/// FileList KeyUp handler
/// </summary>
private void FileList_KeyUp(object sender, KeyEventArgs e)
{
if (((FileList)sender).SelectedItem == null) return;
FileItem itm = ((FileList)sender).SelectedItem as FileItem;
if (e.Key == Key.Enter && !itm.IsEdit)
{ // open file or go into the directory
itm.Open();
}
else if (e.Key == Key.Enter && itm.IsEdit)
{ // finish editing
itm.EditComplete();
}
else if (e.Key == Key.F2 && itm.CanEdit && !itm.IsEdit)
{ // start editing
itm.EditStart();
}
else if (e.Key == Key.Escape && itm.IsEdit)
{ // cancel editing
itm.EditCancel();
}
else if (e.Key == Key.Delete && !itm.IsEdit)
{ // delete file or directory
itm.Delete();
}
else if (e.Key == Key.F5)
{ // update file list
UpdateFileList();
}
}
#region update file list
/// <summary>
/// Send request to the server for a list of files
/// </summary>
public void UpdateFileList()
{
if (Process!=null) Process(this, null);; // start Process
// send request
WebHelper w = new WebHelper(this.Url);
w.Queries.Add("cmd", "get");
w.Queries.Add("path", _Path);
w.Execute(UpdateFileListResult); // the server response
//we can find in the UpdateFileListResult method
}
private void UpdateFileListResult
(string stat, string msg, bool allowUp, JsonValue data, object tag)
{
if (stat == "ok")
{
// crear FileList items
this.Dispatcher.BeginInvoke(() =>
{
this.Items.Clear();
});
// add top-level directory
if (allowUp)
{
AddItem(-1, "...", "", 0);
}
// add files and directories
if (data != null && data.Count > 0)
{
foreach (JsonValue itm in data)
{
AddItem(itm["type"], itm["name"], itm["url"], itm["size"]);
}
}
// set focus to the first item
this.Dispatcher.BeginInvoke(() =>
{
this.SelectedIndex = -1;
this.Focus();
if (this.Items.Count > 0) this.SelectedIndex = 0;
});
}
else
{
// show error message
ShowError("Error. " + msg);
}
if (Complete != null) Complete(this, null); // start Complete
}
/// <summary>
/// The method adds an item to the list
/// </summary>
/// <param name="type">Item type: -1 - top-level directory,
/// 0 - directory, 1 - file</param>
/// <param name="name">Name</param>
/// <param name="size">File size (kb)</param>
private void AddItem(int type, string name, string url, double size)
{
this.Dispatcher.BeginInvoke(() =>
{
FileItem itm = new FileItem(type, name, url, size);
this.Items.Add(itm);
});
}
#endregion
#region rename file or directory
/// <summary>
/// Send request to the server for rename item
/// </summary>
public void SetNewName(FileItem itm)
{
if (itm == null || !itm.IsEdit)
{
MessageBox.Show("The item can not be changed!", "Error", MessageBoxButton.OK);
return;
}
if (Process!=null) Process(this, null);
// send request
WebHelper w = new WebHelper(this.Url);
w.Queries.Add("cmd", "rename");
w.Queries.Add("path", _Path);
w.Queries.Add("oldName", itm.FileName);
w.Queries.Add("newName", itm.NewName);
w.Tag = itm; //pass the item to instance of the WebHelper
w.Execute(SetNewNameResult);
}
private void SetNewNameResult(string stat, string msg,
bool allowUp, JsonValue data, object tag)
{
if (stat == "ok")
{
// rename item in the FileList
this.Dispatcher.BeginInvoke(() =>
{
FileItem itm = tag as FileItem;
itm.FileName = itm.NewName;
itm.FileUrl = itm.FileUrl.Substring
(0, itm.FileUrl.LastIndexOf("/") + 1) + itm.FileName;
itm.EditCancel(); // change TextBox to TextBlock
});
}
else
{
ShowError("Error. " + msg);
}
if (Complete != null) Complete(this, null);
}
#endregion
#region delete file or directory
/// <summary>
/// Send request to the server for delete item
/// </summary>
public void DeleteItem(FileItem itm)
{
if (Process!=null) Process(this, null);
// send request
WebHelper w = new WebHelper(this.Url);
w.Queries.Add("cmd", "delete");
w.Queries.Add("path", _Path);
w.Queries.Add("name", itm.FileName);
w.Tag = itm; //pass the item to instance of the WebHelper
w.Execute(DeleteItemResult);
}
private void DeleteItemResult
(string stat, string msg, bool allowUp, JsonValue data, object tag)
{
if (stat == "ok")
{
// delete item from the FileList
this.Dispatcher.BeginInvoke(() =>
{
FileItem itm = tag as FileItem;
this.Items.Remove(itm);
});
}
else
{
ShowError("Error. " + msg);
}
if (Complete != null) Complete(this, null);
}
#endregion
#region create new directory
/// <summary>
/// Send request to the server for create a new directory
/// </summary>
/// <param name="name">Directory name</param>
public void CreateDirectory(string name)
{
if (Process!=null) Process(this, null);
// send request
WebHelper w = new WebHelper(this.Url);
w.Queries.Add("cmd", "newdir");
w.Queries.Add("path", _Path);
w.Queries.Add("name", name);
w.Execute(CreateDirectoryResult);
}
private void CreateDirectoryResult
(string stat, string msg, bool allowUp, JsonValue data, object tag)
{
if (Complete != null) Complete(this, null);
if (stat == "ok")
{
// update file list
UpdateFileList();
}
else
{
ShowError("Error. " + msg);
}
}
#endregion
#region upload file
/// <summary>
/// Add file to the upload list
/// </summary>
public void AddUploadItem(FileInfo f)
{
if (_UploadFiles == null) _UploadFiles = new List<UploadItem>();
UploadItem itm = new UploadItem(_UploadFiles.Count, f, this.Url, this.Path);
itm.Complete += new EventHandler(UploadItem_Complete);
_UploadFiles.Add(itm);
}
/// <summary>
/// Send upload list to the server
/// </summary>
public void Upload()
{
if (_UploadFiles == null || _UploadFiles.Count <= 0) return; // upload list is empty
_UploadErrorMessages = new StringBuilder();
if (Process!=null) Process(this, null);;
foreach (UploadItem itm in _UploadFiles)
{
itm.Run();//start upload file
}
}
/// <summary>
/// Upload file complete handler
/// </summary>
private void UploadItem_Complete(object sender, EventArgs e)
{
this.Dispatcher.BeginInvoke(() =>
{
UploadItem itm = sender as UploadItem;
if (itm.State == UploadItem.StateList.Error)
{
_UploadErrorMessages.AppendLine(itm.Message);
}
// remove file from upload list
_UploadFiles.Remove(itm);
if (_UploadFiles.Count == 0)
{
// upload list is empty
// start Complete
if (Complete != null) Complete(this, null);
// has error?
if (_UploadErrorMessages.Length <= 0)
{
// no error, update file list
UpdateFileList();
}
else
{
// show error message
ShowError(_UploadErrorMessages.ToString());
}
}
});
}
/// <summary>
/// FileList Drop handler
/// </summary>
private void FileList_Drop(object sender, DragEventArgs e)
{
// add selected files to upload list
FileInfo[] files = e.Data.GetData(DataFormats.FileDrop) as FileInfo[];
foreach (FileInfo f in files)
{
this.AddUploadItem(f);
}
// upload files
this.Upload();
}
#endregion
/// <summary>
/// The method show error messages
/// </summary>
/// <param name="msg">Error text</param>
private void ShowError(string msg)
{
this.Dispatcher.BeginInvoke(() =>
{
MessageBox.Show(msg, "Error", MessageBoxButton.OK);
});
}
}
Using the code
Main files of FileList
control: FileList.cs, FileItem.cs, WebHelper.cs and UploadItem.cs (FileManage
project). For server - Gateway.cs (FileManager.Common
project).
Add FileList
to Silverlight page.
<my:FileList Height="256" HorizontalAlignment="Left"
Margin="12,41,0,0" x:Name="fileList1" VerticalAlignment="Top"
Width="573" Grid.ColumnSpan="2" Grid.RowSpan="2" />
Add the Gateway code to ASP .NET handler (ashx for WebForms) or action (for MVC) and run the server.
FileManager.Common.Gateway myGateway = new FileManager.Common.Gateway();
context.Response.ContentType = "application/json";
context.Response.Write(myGateway.GetResult());
Set Url
property for FileList
to the Gateway server page.
public MainPage()
{
InitializeComponent();
fileList1.Url = "http://localhost:58646/Gateway.ashx"; // you gateway url
// http://localhost:58646 - is default address for solution but maybe another
}
Enjoy!