81

I'm attempting upload multiple files in ASP.NET MVC and I have this simple foreach loop in my controller

foreach (HttpPostedFileBase f in Request.Files)
{
    if (f.ContentLength > 0)
        FileUpload(f);
}

The previous code generates this error:

Unable to cast object of type 'System.String' to type 'System.Web.HttpPostedFile'. 

What I don't understand is why Request.Files[1] returns an HttpPostedFileBase but when it's iterated over, it returns strings (presumably the file names).

Note: I know this can be solved with a for loop. Also, I tried using HttpPostedFile, with the same error.

Omar
  • 39,496
  • 45
  • 145
  • 213

7 Answers7

118

The enumerator on the HttpFileCollection returns the keys (names) of the files, not the HttpPostedFileBase objects. Once you get the key, use the Item ([]) property with the key (filename) to get the HttpPostedFileBase object.

foreach (string fileName in Request.Files)
{
    HttpPostedFileBase file = Request.Files[fileName];

    ...
}
tvanfosson
  • 524,688
  • 99
  • 697
  • 795
  • 1
    Is there a way to have it return an HttpPostedFile instead? – Omar Nov 19 '09 at 02:35
  • Not that I know of. It derives from NameObjectCollectionBase and the enumerator for that class iterates over the keys. – tvanfosson Nov 19 '09 at 11:33
  • The compiler throws an error for me and wants me to use `HttpPostedFileBase` instead of `HttpPostedFile`. – Cody Jun 04 '13 at 19:36
  • @tvanfosson in my case fileName always becomes "Image", but when I say Request.Files[0].FileName it gives the correct name any idea? I'm using MVC5. – Geethanga Mar 22 '14 at 04:36
  • 1
    @Geethanga - the "name" that is the key is the name on the HTML file input tag, it's not the name of the file that is uploaded. – tvanfosson Mar 22 '14 at 14:55
  • I'd caution anyone using this answer that this was a problem for us on iOS because the "take photo" feature names every file "image.jpg." I'd recommend @harvey 's answer below. – Brian Davis Jul 13 '20 at 16:30
  • @tvanfosson in my case its saving first file xtimes the x number of files uploaded – Heemanshu Bhalla Sep 02 '20 at 01:38
40

With my tab HTML is:

<input class="valid" id="file" name="file" multiple="" type="file">

Request.Files will have duplicate name in array. So you should solve like this:

for (int i = 0; i < Request.Files.Count; i++ ){
    HttpPostedFileBase fileUpload = Request.Files[i];
nguyenhoai890
  • 1,189
  • 15
  • 20
  • 1
    This one worked better for me than the answer because, as their html points out, it allows for multiple files in the same file input. – Caffeinius Sep 29 '15 at 17:53
  • 5
    This is the correct answer as you should be able to get the filename with `file.FileName`. If you do it with `foreach` and have duplicate names the first file will be saved multiple times – Arijoon May 03 '16 at 11:02
15

We can use LINQ to do this and still use foreach as asked:

var files = Enumerable.Range(0, Request.Files.Count)
    .Select(i => Request.Files[i]);

foreach (var file in files)
{
    // file.FileName
}

As @tvanfosson said, the enumerator returns the file names as strings, not the HttpPostedFileBase. This method HttpPostedFileBase this[string name] returns the object we want. If HttpFileCollectionBase implemented IEnumerable<HttpPostedFileBase> then we could do the foreach normally. However, it implements a non-generic IEnumerable.

Akira Yamamoto
  • 4,685
  • 4
  • 42
  • 43
6

You might try iterating the strings and casting them to HttpPostedFile instead, like this:

foreach (string file in Request.Files)
    {
        HttpPostedFile hFile = Request.Files[file] as HttpPostedFile;
        if (hFile.ContentLength > 0)
            FileUpload(hFile);
    }
Joe Barone
  • 3,112
  • 3
  • 24
  • 20
6

Unfortunately tvanfosson's answer did not work for me. Although the files would upload just fine, and no error would be thrown, a problem would occur where only one of the files would be used, so the same file would be saved twice rather than using them both.

It seemed to be a problem with the foreach statement looping through the names of each file in the Request.Files, for some reason it wasn't working as a key for me, and only the first file would be selected every time.

HttpFileCollectionBase files = Request.Files;

for(var i = 0; i < files.Count; i++)
{
    HttpPostedFileBase file = files[i];

    ...
}
harvzor
  • 2,832
  • 1
  • 22
  • 40
  • This was a problem for us on iOS because the "take photo" feature names every file "image.jpg" - but this change resolved it. Thank you. – Brian Davis Jul 13 '20 at 16:28
6

The following code worked for me.

  HttpResponseMessage result = null;
  var httpRequest = System.Web.HttpContext.Current.Request;
  HttpFileCollection uploadFiles = httpRequest.Files;
  var docfiles = new List<string>();

  if (httpRequest.Files.Count > 0){
      int i;
      for (i = 0; i < uploadFiles.Count; i++) {
          HttpPostedFile postedFile = uploadFiles[i];
          var filePath = @"C:/inetpub/wwwroot/test1/reports/" + postedFile.FileName;
          postedFile.SaveAs(filePath);
          docfiles.Add(filePath);
      }
      result = Request.CreateResponse(HttpStatusCode.Created, docfiles);
  } else {
      result = Request.CreateResponse(HttpStatusCode.BadRequest);
  }

  return result;
}
piet.t
  • 11,718
  • 21
  • 43
  • 52
Ivan DCosta
  • 79
  • 1
  • 2
0

You can get the HttpPostedFile out of the HttpFileCollection using foreach like this:

foreach (var obj in fileCollection)
{
    HttpPostedFile file = fileCollection.Get(obj.ToString());
}
Jnr
  • 1,504
  • 2
  • 21
  • 36