Silverlight 文件管理器





5.00/5 (5投票s)
基于 ListBox 控件的 Silverlight 文件管理器,使用通用的服务器处理程序,可以在 ASP .NET WebForms 和 MVC 项目中使用。所有请求都通过助手类异步发送。

引言
Silverlight 应用程序无法直接访问服务器的文件系统。要获得对服务器文件系统的访问权限,需要创建一个代理(网关)页面。
我创建了一个用于处理请求的网关类。该类可以在 ASP .NET WebForm 和 ASP.NET MVC 项目中使用。
文件管理器是作为一个自定义控件创建的,它基于 ListBox
。该控件有一个用于向服务器发送请求的方法。
所有请求都通过助手类异步发送。助手类针对文件管理器进行了优化,但也可以轻松地修改用于其他目的。
数据交换以 JSON 格式进行。这对于流量来说是最优的。
服务器
Gateway
类用于处理在类库项目中创建的请求。
该类有一个主要方法 - GetResult
。该方法处理请求并返回一个 JSON string
。
请求数据来自 HttpContext.Current.Request
类。为此,我创建了一个助手变量 - Request
,以及 Server
。
HttpRequest Request = HttpContext.Current.Request;
HttpServerUtility Server = HttpContext.Current.Server;
服务器只能处理 POST
请求。请求参数可在 Form
集合中找到。
服务器将处理六种操作
check
- 检查文件名upload
- 上传并保存文件到服务器newdir
- 创建新目录delete
- 删除文件或目录rename
- 更改文件或目录的名称get
(默认) - 获取文件和目录列表
操作的名称将包含在 cmd
参数中。
string cmd = "";
if (!String.IsNullOrEmpty(Request.Form["cmd"])) { cmd = Request.Form["cmd"].ToLower(); }
根目录名称包含一个 _Root
变量。客户端必须在 path
字段中提供相对路径。
string _Root = ""; // empty - server root directory
string path = "";
if (!String.IsNullOrEmpty(Request.Form["path"]))
{ path = Request.Form["path"]; } else { path = "/"; }
if (!path.EndsWith("/")) path += "/";
在执行操作 (cmd) 之前,服务器必须验证目录是否存在。
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();
}
我没有创建用户授权和访问的验证,但您可以自行创建。
服务器返回 JSON 字符串。我为此创建了两个助手函数。第一个 - GetError
返回带有错误消息的 JSON 对象。第二个 - GetJsonString
将对象转换为 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;
}
我使用了具有以下属性的匿名类型
stat
- 服务器响应代码:ok - 成功,err - 错误msg
- 错误消息,如果 stat = errallowUp
- 是否有顶级目录(仅用于文件列表请求)data
- 文件和目录数组(仅用于文件列表请求)name
- 文件或目录名称size
- 文件大小(仅限文件)type
- 项目类型:0 - 目录,1 - 文件url
- 文件 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;
}
}
Gateway
类易于在 ASP.NET WebForms 中使用。例如,在 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;
}
}
}
同样也可以在 ASP.NET MVC 中使用。例如,在 HomeController
的 Gateway
Action 中。
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 };
}
}
Silverlight (客户端)
WebHelper 类
WebHelper
类实现了发送异步 HTTP 请求的能力。
对于请求参数,我创建了两个附加类。
第一个 - QueryItem
类用于参数数据。QueryItem
类可以包含文本数据和文件。第二个 - QueryItemCollection
是 QueryItem
的集合。
WebHelper
类有一个 public
方法 - Execute
。该方法接受一个回调函数的引用。
对于回调函数,我创建了一个委托。
public delegate void WebCallback(string stat, string msg, bool allowUp,
JsonValue data, object tag);
如您所见,回调函数将接收来自 JSON 的服务器响应。这对于文件管理器是特殊的,但您可以更改委托和回调函数,这很简单。
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 控件
FileList
控件继承自 ListBox
。每个项目也将是自定义的。
FileItem
FileItem
类继承自 StackPanel
。该项目将包含一个图标、名称、文件大小和 3 个按钮用于打开、重命名和删除项目。但是该类无法独立向服务器发送请求。这只能通过 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
FileList
包含一个用于通过标准 MessageBox
显示错误的助手方法。因为请求是在单独的线程中执行的,所以 MessageBox
只能通过 BeginInvoke
从主线程调用。
private void ShowError(string msg)
{
this.Dispatcher.BeginInvoke(() =>
{
MessageBox.Show(msg, "Error", MessageBoxButton.OK);
});
}
为了实现 ProgressBar
,该类包含两个事件:Process
和 Complete
。
public event EventHandler Process;
public event EventHandler Complete;
ProgressBar
不会在 FileList
中,它是外部的(到页面)。为了显示进度,我创建了子窗口。
private string _Url = "https://: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;
});
}
为了上传文件,我创建了助手类 UploadItem
,因为发送分两个阶段进行
- check
- 上传
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);
}
}
FileList
控件有一个 UploadList
集合。
private List<UploadItem> _UploadFiles = null;
以及将 UploadItem
添加到集合的方法。
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);
}
FileList
可以通过拖放方式接收文件。
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();
}
文件上传从 _UploadList
中的 Upload
方法开始。
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
}
}
文件发送到服务器后,在 UploadItem_Complete
处理程序中从 _UploadList
中删除。当所有文件上传完毕后,FileList
会从服务器更新文件列表。
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());
}
}
});
}
FileList
类的全部代码。
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);
});
}
}
使用代码
FileList
控件的主要文件:FileList.cs、FileItem.cs、WebHelper.cs 和 UploadItem.cs(FileManage
项目)。服务器端 - Gateway.cs(FileManager.Common
项目)。
将 FileList
添加到 Silverlight 页面。
<my:FileList Height="256" HorizontalAlignment="Left"
Margin="12,41,0,0" x:Name="fileList1" VerticalAlignment="Top"
Width="573" Grid.ColumnSpan="2" Grid.RowSpan="2" />
将 Gateway 代码添加到 ASP.NET Handler (WebForms 的 ashx) 或 Action (MVC 的) 并运行服务器。
FileManager.Common.Gateway myGateway = new FileManager.Common.Gateway();
context.Response.ContentType = "application/json";
context.Response.Write(myGateway.GetResult());
为 FileList
设置 Url
属性指向 Gateway 服务器页面。
public MainPage()
{
InitializeComponent();
fileList1.Url = "https://:58646/Gateway.ashx"; // you gateway url
// https://:58646 - is default address for solution but maybe another
}
尽情享用!