Michael Sync

Michael Sync

Crisp, minimal personal blog

ASP.NET MVC – Multiple Files Upload Bug or by-design issue?


Update: Yes. Kiran confirmed that it’s by design.

This is by design. This is part of System.Web and so we are not too inclined to fix this issue currently. A similar related issue was filed against Web API. You can check the resolution information on it. MultipartFormDataStreamProvider is creating a blank file even though the multipart form-data request isn’t sending one

Thanks,
Kiran

The issue is that when you http-post the request that contains a file html element then you got 1 for files.Count() but null for files.First().

One of my ex-colleagues faced this issue. He shared me this stackoverflow question posted by someone who is having a similar issue.

Here is the code for replicating the issue. (I will upload the sample to my blog’s git repository later.)

View

1
2
3
4
5
6
@using (Html.BeginForm("Upload", "Home", FormMethod.Post,
                       new { enctype = "multipart/form-data"}))
{
    <input name="files" type="file" multiple="multiple" />
    <input type="submit" value="Upload" />
}

Controller

1
2
3
4
5
6
7
[HttpPost]
    public ActionResult Upload(IEnumerable<HttpPostedFileBase> files) {
        if (files.Count() > 0) Console.WriteLine(files.Count()); // display 1
        if (files.Any()) Console.WriteLine(files.Any()); // display true
        if (files.First() == null) Console.WriteLine("first null"); // display "first null"
        return View();
    }

Screenshots

asp.net mvc file upload issues

Steps to replicate the issue

  1. Run the program
  2. Click on “Upload” without selecting any file.
  3. Check the value of “files” enumerable. You will see 1 for files.Count() and null for files.First().

Ok. I am not sure whether this is the bug or by-design issue so I posted the questions in MVC codeplex forum.

Request.Files

While I am waiting the reply from MVC team, I did a few experiments on this. The first thing that I did is to use Request.Files instead of binder so I changed the code as below.

1
2
3
4
5
6
[HttpPost]
public ActionResult Upload() {
var files = Request.Files;

return View();
}

Here is what I got. I believe that MVC code is also using Request.Files in binder to do the transformation from raw request to object. The count for Request.Files is also 1 even we don’t even select any file.

Request.Files return

As the Request.Files has a similar logic, I captured the raw http request in fidder and here is what I found.

POST http://localhost:17002/Home/Upload HTTP/1.1 Accept: text/html, application/xhtml+xml, */* Referer: http://localhost:17002/ Accept-Language: en-US User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; MALC; rv:11.0) like Gecko Content-Type: multipart/form-data; boundary=—————————7de1d0220fe6 Accept-Encoding: gzip, deflate Connection: Keep-Alive Content-Length: 191 DNT: 1 Host: localhost:17002 Pragma: no-cache

—————————–7de1d0220fe6 Content-Disposition: form-data; name=“files”; filename="" Content-Type: application/octet-stream —————————–7de1d0220fe6–

You see, there is one empty item “filename=""” in form-data when we submit the request without selecting the file. This might be the reason why Request.Files is returning the count 1 instead of 0.

Request.Files (“file”) Vs HttpPostedFileBase (null)

Why do we got null for first index of “Files” enumerable?

HttpFileCollectionValueProvider.GetHttpPostedFileDictionary method create the directory based on http file request. HttpPostedFileBaseModelBinder.ChooseFileOrNull returns null value for filename="". HttpFileCollectionValueProvider added that value to directory without checking the null value so I think it might be a by-design issue.

Here is the test code that shows null value.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
[HttpPost]
public ActionResult Upload() {
var files = Request.Files;

//HttpFileCollectionValueProvider.GetHttpPostedFileDictionary
// build up the 1:many file mapping
List<KeyValuePair<string, HttpPostedFileBase>> mapping = new List<KeyValuePair<string, HttpPostedFileBase>>();
string[] allKeys = files.AllKeys;
for (int i = 0; i < files.Count; i++) {
string key = allKeys[i];
if (key != null) {
HttpPostedFileBase file = ChooseFileOrNull(files[i]);
}
}

return View();
}
internal static HttpPostedFileBase ChooseFileOrNull(HttpPostedFileBase rawFile) {
// case 1: there was no <input type="file" ... /> element in the post
if (rawFile == null) {
return null;
}

// case 2: there was an <input type="file" ... /> element in the post, but it was left blank
if (rawFile.ContentLength == 0 && String.IsNullOrEmpty(rawFile.FileName)) {
return null;
}

// case 3: the file was posted
return rawFile;
}

That’s all that I have tested.

If you are interested in this issue, you can probably check the following methods below.

  • HttpFileCollectionValueProvider.GetHttpPostedFileDictionary
  • HttpPostedFileBaseModelBinder.ChooseFileOrNull

I will update this blog post when I get any reply from MVC team.