65.9K
CodeProject 正在变化。 阅读更多。
Home

Silverlight 文件管理器

starIconstarIconstarIconstarIconstarIcon

5.00/5 (5投票s)

2012 年 1 月 8 日

CPL

4分钟阅读

viewsIcon

30697

downloadIcon

1286

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

Silverlight File Manager

引言

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 = err
  • allowUp - 是否有顶级目录(仅用于文件列表请求)
  • 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 中使用。例如,在 HomeControllerGateway 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 类可以包含文本数据和文件。第二个 - QueryItemCollectionQueryItem 的集合。

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 个按钮用于打开、重命名和删除项目。但是该类无法独立向服务器发送请求。这只能通过 FileListParent)完成。

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,该类包含两个事件:ProcessComplete

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,因为发送分两个阶段进行

  1. check
  2. 上传
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.csFileItem.csWebHelper.csUploadItem.csFileManage 项目)。服务器端 - Gateway.csFileManager.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
}

尽情享用!

© . All rights reserved.