Comparison · v5.3
The control that ships with ASP.NET is genuinely fine for a lot of forms. This page is about the point where it stops being fine - and it is a specific, recognisable point, not a matter of taste.
Use the built-in control when it fits
If you are attaching a CV to a job form or a logo to a settings page, <asp:FileUpload> is the right answer. It is part of the framework, there is nothing to license, deploy or keep updated, and the whole thing is four lines:
<asp:FileUpload ID="FileUpload1" runat="server" />
<asp:Button ID="Upload" runat="server" Text="Upload" OnClick="Upload_Click" />
protected void Upload_Click(object sender, EventArgs e)
{
if (FileUpload1.HasFile)
FileUpload1.SaveAs(Server.MapPath("~/App_Data/uploads/") + FileUpload1.FileName);
}
Nothing below is an argument against that page. Keep it.
Where teams outgrow it
The built-in control publishes nine members you would actually use: AllowMultiple, HasFile, HasFiles, PostedFile, PostedFiles, FileName, FileBytes, FileContent and SaveAs. That list is the whole story, and what is absent from it matters more than what is present: there is no progress, no chunking, no resume and no drop target, because the control is a thin wrapper over an ordinary form post.
Four consequences follow from that one design fact:
- The upload is a postback. The browser sends the entire file as part of the form. Your
Click handler does not run until the last byte has arrived, so there is nothing to report progress from and nothing to cancel.
- One request means one size ceiling. The whole file has to fit inside
maxRequestLength (4 MB by default) and IIS's maxAllowedContentLength. Raising them raises the ceiling for every request on the site.
- A failure at 99% is a failure at 0%. There is no resume, so a dropped connection on a large file means starting again.
- Validation happens after the transfer. The file is already on the server before you can reject it for being the wrong type or the wrong size.
Side by side
| | <asp:FileUpload> | AjaxUploader |
| Transfer | One form post | Single, chunked, tus, or direct to S3 / Azure / GCS |
| Progress | None | Per-file and overall, with speed and ETA |
| Practical size | Bounded by request limits | Multi-gigabyte via chunking |
| Interrupted upload | Starts over | Resumes from the last completed chunk |
| Drag and drop | Not provided | On by default, plus full-page and folder drops |
| Validation | Server-side, after transfer | In the browser first, re-checked on arrival |
| Cost | In the framework | Commercial licence |
The honest summary: if your files are small and your users are on a good connection, the framework control wins on simplicity and price. The case for a component starts when files get big, connections get bad, or users need to see what is happening.
Migrating a page
The server-side shape is deliberately similar - a control on the page and one handler - so a swap is usually two edits:
<au:AjaxFileUpload ID="Uploader1" runat="server" AllowMultiple="true"
AutoPostBack="true" OnFileUploaded="Uploader1_FileUploaded" />
protected void Uploader1_FileUploaded(object sender, FileUploadedEventArgs e)
{
var destination = Path.Combine(Server.MapPath("~/App_Data/uploads"), e.FileName);
new UploadService().CopyFile(e.FileGuid, destination);
}
The difference is that the handler now runs per file as each one finishes, rather than once after the whole form has been posted.