Rich file upload experience in ASP.NET MVC - part I

As you can figure out by the title, I'm going to write few posts about creating rich file upload experience in ASP.NET MVC. In my approach I will try to keep away from Flash (which is really often used for such scenarios) and stick only with jQuery.
So first, you should know how to handle file upload in ASP.NET MVC. The client side is simple, you need a form with enctype = "multipart/form-data" and an input of type file inside of it. On server side you can found files posted by user in Request.Files collection, where every file is represented by HttpPostedFileBase object.
Now, when we know the basics, we can start writting some code. First we will write some wrappers, which will allow us to keep our code unit testable:
public interface IHttpPostedFileBase
{
  #region Properties
  int ContentLength { get; }
  string ContentType { get; }
  string FileName { get; }
  Stream InputStream { get; }
  #endregion

  #region Methods
  void SaveAs(string filename);
  #endregion
}

public class HttpPostedFileBaseWrapper: IHttpPostedFileBase
{
  #region Fields
  private HttpPostedFileBase _postedFileBase;
  #endregion

  #region IHttpPostedFile Members
  public int ContentLength
  {
    get { return _postedFileBase.ContentLength; }
  }

  public string ContentType
  {
    get { return _postedFileBase.ContentType; }
  }

  public string FileName
  {
    get { return _postedFileBase.FileName; }
  }

  public System.IO.Stream InputStream
  {
    get { return _postedFileBase.InputStream; }
  }

  public void SaveAs(string filename)
  {
    _postedFileBase.SaveAs(filename);
  }
  #endregion

  #region Constructor
  public HttpPostedFileBaseWrapper(HttpPostedFileBase postedFileBase)
  {
    _postedFileBase = postedFileBase;
  }
  #endregion
}
So now, instead of HttpPostedFileBase object (which can't be created while unit testing), we have IHttpPostedFileBase which will allow us to provide different implementation when necessary.
Next we should take care of accessing Request.Files collection (remember, if you want to keep your controller unit testable, avoid accessing Request inside action method). We will prepare a class which will contain a collection of IHttpPostedFileBase and a model binder for this class:
[ModelBinder(typeof(HttpPostedFilesModelBinder))]
public class HttpPostedFiles
{
  #region Fields
  private List<IHttpPostedFileBase> _postedFiles;
  #endregion

  #region Properties
  public List<IHttpPostedFileBase> PostedFiles
  {
    get { return _postedFiles; }
  }
  #endregion

  #region Constructor
  public HttpPostedFiles()
  {
    _postedFiles = new List<IHttpPostedFileBase>();
  }
  #endregion
}

public class HttpPostedFilesModelBinder : IModelBinder
{
  #region IModelBinder Members
  public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
  {
    if (controllerContext == null)
      throw new ArgumentNullException("controllerContext");
    if (bindingContext == null)
      throw new ArgumentNullException("bindingContext");

    HttpPostedFiles postedFiles = new HttpPostedFiles();
    foreach (string postedInputName in controllerContext.HttpContext.Request.Files)
    {
      if (controllerContext.HttpContext.Request.Files[postedInputName] != null
          && controllerContext.HttpContext.Request.Files[postedInputName].ContentLength > 0)
        postedFiles.PostedFiles.Add(new HttpPostedFileBaseWrapper(controllerContext.HttpContext.Request.Files[postedInputName]));
    }
    return postedFiles;
  }
  #endregion
}
The issue of accessing Request.Files is solved.
In my sample solution I'm going to keep user uploads in Session, so I have to write two more classes which will take care of binding those for the action method:
[ModelBinder(typeof(SessionUploadedFilesModelBinder))]
public class SessionUploadedFiles
{
  #region Fields
  private List<FileInfo> _uploadedFiles;
  #endregion

  #region Properties
  public List<FileInfo> UploadedFiles
  {
    get { return _uploadedFiles; }
  }
  #endregion

  #region Constructor
  public SessionUploadedFiles()
  {
    _uploadedFiles = new List<FileInfo>();
  }
  #endregion
}

public class SessionUploadedFilesModelBinder : IModelBinder
{
  #region IModelBinder Members
  public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
  {
    if (controllerContext == null)
      throw new ArgumentNullException("controllerContext");
    if (bindingContext == null)
      throw new ArgumentNullException("bindingContext");

    SessionUploadedFiles uploadedFiles = (SessionUploadedFiles)controllerContext.HttpContext.Session["UPLOADED_FILES"];
    if (uploadedFiles == null)
    {
      uploadedFiles = new SessionUploadedFiles();
      controllerContext.HttpContext.Session["UPLOADED_FILES"] = uploadedFiles;
    }
    return uploadedFiles;
  }
  #endregion
}
After that, we can write our action methods:
public ActionResult FileUpload(SessionUploadedFiles uploadedFiles)
{
  return View(uploadedFiles);
}

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult FileUpload(SessionUploadedFiles uploadedFiles, HttpPostedFiles postedFiles)
{
  string uploadsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Uploads");
  if (!Directory.Exists(uploadsPath))
    Directory.CreateDirectory(uploadsPath);

  foreach (IHttpPostedFileBase postedFile in postedFiles.PostedFiles)
  {
    string postedFilePath = Path.Combine(uploadsPath, Path.GetFileName(postedFile.FileName));
    postedFile.SaveAs(postedFilePath);
    uploadedFiles.UploadedFiles.Add(new FileInfo(postedFilePath));
  }
  return View(uploadedFiles);
}
Finally, we can write our view markup (it will be strong typed View for SessionUploadedFiles class):
<ul class="uploaded-files-list">
  <% foreach (System.IO.FileInfo file in Model.UploadedFiles) { %>
    <li><img src="<%= Url.Content("~/HttpHandlers/SystemIconsHandler.ashx?type=file&size=small&ext=" + file.Extension)%>" alt="<%= file.Extension%>" /> <%= Html.Encode(file.Name)%> <%= file.Length.ToString() + " B"%></li>
  <% } %>
</ul>
<% using (Html.BeginForm("FileUpload", "Home", FormMethod.Post, new { name = "frmUpload", enctype = "multipart/form-data" })) { %>
  <input type="file" name="fiUpload" id="fiUpload" />
  <input type="submit" name="btnUpload" id="btnUpload" value="Upload next file" />
<% } %>
Our sample looks like this (I'm using SystemIconsHandler for displaying icons):
It's hard to call it a rich user experience yet, you should rather treat it like a base, which will be extended in the fallowing parts.