Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Silverlight File Manager

0.00/5 (No votes)
8 Jan 2012 2  
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.
Silverlight File Manager

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 name
  • upload - upload and save file on the server
  • newdir - create a new directory
  • delete - delete file or directory
  • rename - change name of the file or directory
  • get (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 - error
  • msg - error message, if stat = err
  • allowUp - has top-level directory (only for file list requests)
  • data - array of files and directories (only for file list requests):
    • name - file or directory name
    • size - file size (only for files)
    • type - item type: 0 - directory, 1 - file
    • url - 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:

  1. check
  2. 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!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here