Ready?
So, I just spent the entirety of my day yesterday trying to get one conceptually-simple thing to work. As part of an ongoing mobile web application development project, all I needed to do was to enable a smartphone or PDA user to upload a file from their device to a server. In today's alleged "Web 2.0" word, this type of file-upload capability is virtually ubiquitous across most interactive and community-based websites.
To enable file-upload in a standard desktop browser, all a web developer has to do is include something like the following code within a web form:
<form action="Upload.aspx" method="post" enctype="multipart/form-data">
<input type="file" name="MyFile">
<input type="submit" value="Upload!">
</form>
Additionally, if you're using ASP.NET as your development platform (as I am), you can just use this little snippet:
<asp:FileUpload runat="server" ID="MyFile">
<asp:Button runat="server" ID="btnUpload" OnClick="btnUpload_Click">
Continuing with the ASP.NET example for a moment, to actually process the upload, you write a handler for the btnUpload button's Click event, like so:
protected void btnUpload_Click(object sender, EventArgs e)
{
if (MyFile.HasFile)
{
// Do something with the file here, such as:
MyFile.SaveAs(Path.Combine(@"C:\Uploads", MyFile.FileName);
}
}
Simple, no? (I bet even some of you non-coders were able to follow along...)
Indeed, in most cases, it really is that simple. For just about any modern web browser, whether it's Internet Explorer, FireFox, Chrome, or Opera, and whether it's running on your PC, PDA, or smartphone, this example will work without any hassle. Of course, having read the title of this post, you know that there's one browser that's a glaring exception to this rule, and that's Internet Explorer Mobile. (Strangely enough, another one is the version of Safari that runs on the iPhone.)
"Now, what just a minute!" you might say. "Mobile browsers are still fairly primitive creatures, and only recently have a scant handful started to support file upload. I mean, yes, if I were in charge of a team that was building a mobile browser, I'd make file upload a high-priority task, since of course most mobile application developers are going to want to be able to take advantage of their users' rapidly advancing photo and video-capturing capabilities..."
Well, yes, this is technically true. Many mobile web browsers are just achieving a basic level of usability, and there are many mobile browser vendors who openly state that their products do not yet support file upload. Microsoft, for its part, has stated that Internet Explorer Mobile running on Windows Mobile 6 Professional Edition (but not Classic) does support file upload. And while that is also technically accurate, their implementation of this feature is so utterly broken (and in such an asinine way) that you'd be hard-pressed to convince yourself that they're telling anything resembling the truth.
So, what's wrong with file upload in IE Mobile? To the casual debugger stepping through the ASP.NET code such as the earlier example, you may (or may not) notice one or more of the following symptoms:
- The Click event of the Upload button never fires.
- The Request.Files collection is empty.
- The HasFile property of the FileUpload control is false.
- In spite of the above, the Request.ContentLength property is larger than the size of the uploaded file.
- The contents of Request.InputStream include the uploaded file's data, along with some (but not all) of the ASP.NET form values.
- All ViewState information on the page is lost. (This seems to happen with any ASP.NET multipart/form-data postback, in my experience, regardless of browser.)
- Many (but not all) members of the Request.Params collection are missing.
After spending many hours capturing, studying, and stepping through the raw HTTP requests and responses of a simple file-upload web application and a variety of desktop and mobile web browsers, I discovered that the following conditions will cause IE Mobile's multipart HTTP request to become corrupted:
- The user uploads a file whose name exceeds 15 characters in length. (This length includes the file's ".ext" extension, but does not include any directory path that the user supplies.) For example, if the user attempts to upload a file called File1234.doc (12 characters), the upload request will succeed, but if the user tries to upload a file called File12345678.doc (16 characters), the upload request will become corrupted.
- The <input type="file"> or asp:FileUpload tag appears on an ASP.NET page that uses a Master Page. This one baffles me. I can't even begin to think of why using an ASP.NET Master page would cause IE Mobile to corrupt the upload request. Does it have to do with the way ASP.NET names and renders a container's child controls with all of those funky dollar signs and such? I've read that it's because the Master Page form's enctype doesn't get set to multipart/form-data, but in my testing I did not find that to be true. At any rate, if anyone who knows the answer to this little puzzle, please provide your explanation in a comment to this post, and you'll rewarded with a gold star (possibly one gleaned from a Google Images search).
Of these two bugs, it's the first one that really gets me. 15 characters?! What is this, 1988 and MS-DOS 4.01?! My guess is that this is some kind of non-terminated string oversight, most likely because some nitwit declared and allocated a char* buffer that was 16 bytes long, but then forgot to check the size of the actual string he wanted to load into that buffer, causing some kind of memory corruption that cascades into the text of the HTTP request.
Given these obstacles, what's a mobile web developer to do to get around them? Well, keep in mind that Opera Mobile -- which is now the default browser on the Windows Mobile 6.1 phones that Verizon, Sprint and AT&T are offering -- handles file upload requests flawlessly. (In fact, when I compared the multipart HTTP requests of Opera Mobile and the desktop version of Internet Explorer side-by-side, I noticed virtually no difference.) So, really, all you have to worry about is dealing with IE Mobile.
Thankfully, IE Mobile has pretty good JavaScript support, and you can use this to your advantage when you design your file upload web form. One possibility is to have your web page inject a special JavaScript validation function if it detects that the browser is IE Mobile, and wire your Upload button to run this script when clicked. All the script has to do is check the length of the file upload control's text (making sure to strip the path from the filename), and if it exceeds 15 characters, warn the user about IE Mobile's limitation and prevent the form's submission.
Here's an example:
ASP.NET Web Form:
<script type="text/javascript">
function ValidateFile(ctlidFileUpload) {
var fileUpload = document.getElementById(ctlidFileUpload);
if (fileUpload) {
var sFilename = fileUpload.value;
var nLastSlash = sFilename.lastIndexOf('\\');
if (nLastSlash >= 0) {
sFilename = sFilename.substring(nLastSlash + 1, sFilename.length);
if (sFilename.length > 15) {
alert("Internet Explorer Mobile requires that the document's filename (including extension) be 15 characters or less. Please rename this file and try again.");
return false;
}
}
}
return true;
}
</script>
Code-behind:
protected void Page_Load(object sender, EventArgs e)
{
// Inject validation script if necessary
if (Request.Browser.IsBrowser("IEMobile"))
{
btnUpload.OnClientClick = "return ValidateFile('" + fileUpload.ClientID + "')";
}
}
As for the issues with Master Pages, well, think of your file-upload page as having the same role and function as an "Open File" dialog box would in a desktop application. Essentially, you're just prompting the user to select a file for some greater, more interesting purpose. Does the dialog box need to have the same look-and-feel as the overall application? Not really, and if you think about it, neither does your file-upload page. You can maintain a relatively smooth, continuous user experience by just including a few visual cues (colors, logos, etc.) from your Master Page on your file-upload page. Give it a try and walk through the application flow -- you'll be surprised.