Troubleshooting · Web Forms

FileUpload in an UpdatePanel: HasFile is always false.

The control renders, the user picks a file, the button posts back, and FileUpload1.HasFile is false every time. Nothing throws. This is not a bug in your code — it is what an asynchronous postback cannot do.

Why it happens

An UpdatePanel does not submit the form the normal way. It serialises the form fields and sends them with XMLHttpRequest as an ordinary application/x-www-form-urlencoded body. A file input's value is a file name; its bytes only travel when the browser submits the form as multipart/form-data, which a partial postback never does.

So the server sees the postback, runs your handler, and finds Request.Files empty. HasFile is false not because the control lost the file, but because the bytes were never sent. Microsoft documents FileUpload as incompatible with asynchronous postbacks for exactly this reason.

Two consequences worth knowing before you start fixing it: no error is raised anywhere, and the same page works perfectly the moment you take the panel away — which is why this costs people an afternoon.

The three fixes, and what each one costs

1. PostBackTrigger — make that one button post fully

The usual answer. Register the upload button as a full postback trigger, and it submits the form the ordinary way even though it sits inside the panel:

<asp:UpdatePanel ID="up" runat="server">
  <ContentTemplate>
    <asp:FileUpload ID="FileUpload1" runat="server" />
    <asp:Button ID="btnUpload" runat="server" Text="Upload"
                OnClick="btnUpload_Click" />
  </ContentTemplate>
  <Triggers>
    <asp:PostBackTrigger ControlID="btnUpload" />
  </Triggers>
</asp:UpdatePanel>

The cost: you have given up the thing the panel was for. That click is now a full page reload with a frozen page, no progress, and every other control on the page re-rendered. It fixes the exception-free failure by removing the asynchrony.

One gotcha: PostBackTrigger only works for a control declared inside the panel. A control added dynamically has to be registered in code instead:

ScriptManager.GetCurrent(Page).RegisterPostBackControl(btnUpload);

2. The enctype attribute — a real fix for a different bug

You will find this one everywhere:

protected void Page_Load(object sender, EventArgs e)
{
    Page.Form.Attributes.Add("enctype", "multipart/form-data");
}

It is worth knowing what it actually addresses. A form containing a file input is normally emitted with the right enctype automatically; when the control is added dynamically or the form is inside a master page, that can be missed, and then even a full postback yields no file. Setting it explicitly fixes that case.

It does not make an asynchronous postback carry the bytes. If the panel is still doing a partial postback, enctype alone changes nothing — which is why people report it as "worked for someone else, not for me". Use it alongside fix 1, not instead of it.

3. AsyncFileUpload from the AJAX Control Toolkit

The Toolkit's AsyncFileUpload sidesteps the problem by uploading through a hidden iframe rather than through the panel's postback. It is free, and for a single small file it is a reasonable answer.

The cost: it takes a dependency on the Toolkit, handles one file at a time, and gives you no real progress, no chunking, no resume and no client-side validation. It solves the UpdatePanel symptom without changing the underlying model.

The other way out: stop posting back

Every fix above works around the fact that the file rides on a form submission. An AJAX uploader does not put it there at all — it reads the file in the browser and sends it in its own request to a handler, so there is no postback to be partial, nothing for the UpdatePanel to interfere with, and the page keeps its state because it never reloads:

<%@ Register Assembly="AjaxUploader" Namespace="AjaxUploader.Controls" TagPrefix="au" %>

<au:AjaxFileUpload ID="Uploader1" runat="server" AllowMultiple="true"
                   OnFileUploaded="Uploader1_FileUploaded" />

The upload has already completed by the time your server code runs, and the file arrives under a GUID:

protected void Uploader1_FileUploaded(object sender, FileUploadedEventArgs e)
{
    new UploadService().CopyFile(e.FileGuid,
        Server.MapPath("~/uploads/" + e.FileName));
}

If you put it inside the UpdatePanel anyway, read this

The uploader does not need to be inside the panel — it does not postback, so it has nothing to gain from being there. Put it outside and none of this applies.

If it is inside a panel that re-renders, be aware of a sharp edge we would rather you heard from us: after a partial postback the server sends fresh markup, the original DOM nodes are replaced, and the uploader's event handlers go with them. The control looks completely normal — correct markup, no console error — and silently does nothing when a file is selected. Re-initialise it when the panel finishes updating:

<script>
Sys.WebForms.PageRequestManager.getInstance().add_endRequest(function () {
    AjaxUploader.create(document.getElementById('<%= Uploader1.ClientID %>'), {
        uploadUrl:     '/ajaxupload.axd/upload/multiple',
        autoUpload:    true,
        multiple:      true,
        hiddenFieldId: '<%= Uploader1.ClientID %>_guids'
    });
});
</script>

Pass the same options the control renders for you — view the page source and copy them from the generated AjaxUploader.create call, so the re-created instance matches the one the server configured.

Which one to pick

SituationUse
One small file, and a full reload on that click is acceptablePostBackTrigger
No file at all even on a full postbackSet enctype explicitly
One file, already using the Toolkit, no progress neededAsyncFileUpload
Multiple files, progress, large files, or the page must not reloadAn uploader that never posts back