Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

Asynchronous File Upload

4.96/5 (26 votes)
13 Jun 2014CPOL12 min read 130.5K   5K  
An Ajax control that enables a user to upload a file asynchronously with extra data

Introduction

In many web applications, you need to upload some files or context to the application server. This goal can be achieved by using the control FileUpload which is rendered as <input type="file" />. Unfortunately, FileUpload is not supported in asynchronous post-back which doesn't make sense because the whole web site uses partial page postback except these pages that use FileUpload.

One solution was presented by Sunasara Imdadhusen[^] in Lightweight Image Upload with Animation[^] to post the file in another form. In this article, I am representing the same control with some additions to meet the common requirements of web applications.

Generaly, the extender prsents an asynchronous file uploading with some aditional featurs:

  1. The ability to generate a proper preview of the selected file.
  2. The ability to send extra data to describe the file.
  3. The ability to send a set of files in a bulk. (New)
  4. Provide a progress bar to show the amount of work done. (New)

Using the Code

First, we convert the control from a chunk of JavaScript code to an extender control. So to create an instance of the extender, we need to register the namespace MyControls.Web which contains the control, add a ScriptManager tag, then we add the extender tag like the following:

ASP.NET
<%@ Register Assembly="MyControls.Web" Namespace="MyControls.Web" TagPrefix="web" %>
..
        <asp:ScriptManager ID="ScriptManager1" runat="server">
        </asp:ScriptManager>
        <web:AjaxUploadExtender ID="AjaxUploadExtender1" runat="server">
        </web:AjaxUploadExtender>
..

The next step is to set the properties of the extender which will be used in rendering the settings of the control. These properties are:

The Offline Properties

  • TargetControlID: holds the id of the control which opens the browser's dialog.
  • ResponseType: determines the type of data to be returned back from the server (only useful when you are using json data as a response). (obsolete)
  • Enabled: determines wheter the control functionalities are enabled.
  • Filter: holds the file filter string of the supported file types. (e.g. "*.jpg;*.bmp;*.gif;*.png|Supported Images Types (*.jpg;*.bmp;*.gif;*.png)|*.*|All Files" see FileDialog.Filter Property[^]).
  • ZIndex: specifies the stack order of the input element.
  • BrowseButtonHoverCssClass
  • OnChangeFunction: holds the name of the function to be called once the user selects a file.
  • OnUnsupportedFunction: holds the name of the function to be called when an unsupported file type is chosen.
  • ClearButtonID: holds the id of the button which the file inputs.
  • ResetButtonID: holds the id of the button which rests the displaying form.
  • OnResetFunction: holds the name of the function to be called to reset the displaying form.

The Submit Properties

  • CauseValidation: determines whether the form should be validated before submission.
  • ValidationGroup: holds the name of the validation group of the displaying form
  • AutoPostBack: determines whether the selected file should be posted once the dialog is closed (the default is false).
  • SubmitButtonID: holds the id of the button which submits the form (helpful when AutoPostBack is set to false).
  • PostBackUrl: holds the URL to the page on which the postback should be processed.
  • OnSubmitFunction: holds the name of the function to be called before form is submited(You can return false to cancel submission)
  • OnSubmitCompleteFunction: holds the name of the function to be called when form submission is completed.
JavaScript
<script type="text/javascript">
function OnChangeFunction(sender, e) { ... }
function OnSubmitFunction(sender, e) { ... }
function OnSubmitCompleteFunction(sender, e) { ... }
function OnUnsupportedFunction(sender, e) { ... }
</script>
<Button ID="cmdBrowse" runat="server" Text="Browse" />
<web:AjaxUploadExtender ID="AjaxUploadExtender1" runat="server"
 TargetControlID="cmdBrowse"
 AutoPostBack="true"
 PostBackUrl="~/CrossPostBack/saveupload.aspx"
 OnChangeFunction="OnChangeFunction"
 OnSubmitFunction="OnSubmitFunction"
 OnSubmitCompleteFunction="OnSubmitCompleteFunction"
 Filter="*.jpg;*.bmp;*.gif;*.png|Supported Images Types (*.jpg;*.bmp;*.gif;*.png)"
 OnUnsupportedFunction="OnUnsupportedFunction" >
</web:AjaxUploadExtender>    

The Preview Properties

As the control uses anothor form to post-back the file, it uses an iframe to capture the response. The control provides two properties to control the response direction.

  • SubmissionFrameType: which can bo set to one of two values AutoGenerated and UserDefined to determine whether to generate an iframe on submit or not.
  • GetSubmissionFrameFunction: holds the name of the function returning the user definde iframe (when SubmissionFrameType is set to UserDefined).

With addition to pre-submission preview ability on an iframe that has the attribute runat="server".

  • AutoPreview: determines whether the selected file should be previewed once the dialog is closed (the default is false).
  • PreviewFrameID: holds the id of the iframe on which the image should be previewed.
  • OnPreviewFunction: holds the name of the function to be called before the preview (You can return false to cancel the preview)
  • OnPreviewCompleteFunction: holds the name of the function to be called when preview is completed.
JavaScript
<script type="text/javascript" >
function OnPreviewFunction(sender, e) { ... }
function OnPreviewCompleteFunction(sender, e) { ... }
</script>
<iframe ID="PrviewFrame" runat="server" ></iframe>
<web:AjaxUploadExtender ID="AjaxUploadExtender1" runat="server"
 AutoPreview="true"
 PrviewFrameID="PrviewFrame"
 OnPreviewFunction="OnPreviewFunction"
 OnPreviewCompleteFunction="OnPreviewCompleteFunction" >
</web:AjaxUploadExtender>    

Perhaps the most important thing that should be added to this control is a way to add extra data to the form to be posted. Well, this was presented in version in Imdadhusen's article, but, in my opinion, it wasn't simple. So I added an inner property to the control to hold these DataEntrys as pairs of Name(represent the name of the hidden input) and EvaluateKey(which is used by EvaluateFunction to evaluate the value of the hidden input to be post).

JavaScript
<script type="text/javascript" >
function AjaxApload1_Evaluate(key) { ... }
</script>
<web:AjaxUploadExtender ID="AjaxUploadExtender1" runat="server"
 EvaluateFunctionName="AjaxApload1_Evaluate" >
 <ExtraData>
     <web:DataEntry Name="Title" EvaluateKey="Title" />
     <web:DataEntry Name="Description" EvaluateKey="Description" />
 </ExtraData>
</web:AjaxUploadExtender>   

The Progress Properties

These properties determines how should the Progress feather work to provides a visual feedback that shows the progress of the work happens on the server. This feather uses a session state to store the progress value ,a web service to read this value and a client function to visualize the returning data.

What is so important about the service is that the web method shoud have a read-only access to the session state, to be able to read the progress info and not to be blocked by the server.

  • EnableProgress: determine whether the progress service should be called during the subission process.
  • ContextKey: preserves a uniqe context key for the progress service.
  • ProgressInfo: gets the progress data structure associated with the control.
  • ProgressServicePath: the path to the progress service.
  • ProgressServiceMethod: the name of the web metod that present the progress service.
  • ProgressInterval: the time interval between two calls of the progress service.
  • OnProgressFeedbackFunction: the name of the callback function that should be executed after the return of the progress service.
  • SaveBufferSize: the size in bytes of the saving buffer. (useful for large files.)
<web:AjaxUploadExtender ID="AjaxUploadGalleryExtender1" runat="server"
    ContextKey="GIMG"
    ProgressInterval="500"
    SaveBufferSize="1024"
    EnableProgress="OnSubmit, OnPreview"
    ProgressServiceMethod="GetProgressValue"
    ProgressServicePath="~/CrossPostBack/GallerySavePage.aspx"
>
</web:AjaxUploadGalleryExtender>
C#
[WebMethod(EnableSession = true), ScriptMethod]
public static MyControls.Web.ProgressInfo GetProgressValue(string contextKey)
{
    MyControls.Web.ProgressInfo info = MyControls.Web.AjaxUploadExtender.GetUploadInfo(contextKey);
    return info;
}

As we can see, the ContextKey property is used in the web method to retrive the progress info for a specific control. The progress info is set during the server process using the read-only property ProgressInfo.

C#
 private void UpdateEmployee(int employeeId,string FirstName, string Inits, string LastName, int DepartmentID, string PictureName)
{
    if (AjaxUploadExtender1.GetValue("except") == "data")
        throw new Exception();

    // call the database.

    for (int i = 0; i <= 100; i++)
    {
        MyControls.Web.ProgressInfo prog = AjaxUploadExtender1.ProgressInfo;
        prog.Message = string.Format("Updating employee no. {0}.. ",employeeId);
        prog.Value = i;

        System.Threading.Thread.Sleep(20);
    }
}

The FileUpload regular properties

Our control also has regular FileUpload properties:

  • FileBytes: Gets an array of the bytes in the uploaded file.
  • FileContent: Gets a Stream object that points to uploaded file.
  • FileName: Gets the name of uploaded file.
  • HasFile: Gets a boolean value indicating whether the extender contains a file.
  • PostedFile: Gets the underlying HttpPostedFile object for a file that is uploaded by using the extender.

and also

  • SaveAs() function that saves the contents of an uploaded file to a specified path on the Web server.
C#
protected void AjaxAploadExtender1_Submit(object sender, EventArgs e)
{
    string FirstName = AjaxUploadExtender1.GetValue("first");
    string Inits = AjaxUploadExtender1.GetValue("inits");
    string LastName = AjaxUploadExtender1.GetValue("last");
    int DepartmentID = int.Parse(AjaxUploadExtender1.GetValue("dept"));

    if (AjaxUploadExtender1.HasFile)
    {
        HttpPostedFile userPostedFile = AjaxUploadExtender1.PostedFile;
        string filename = AjaxUploadExtender1.FileName;
        userPostedFile.SaveAs(Server.MapPath("~/UploadedImages/") + filename);
    }

    System.Threading.Thread.Sleep(10000);
}    

As we can see in the above block, we can get the ExtraData values simply by calling the function GetValue() the Name of the DataEntry as an argument.

This control has two server-side events Submit which raised when the submission form is submited, and Preview which is raised when an image file is selected (when AutoPreviw is set to true).

Multi-File Feather

Our control can be attached to another extender called AjaxUploadGalleryExtender throug the property GalleryID to provide multi-file feature. Also, AjaxUploadGalleryExtender can be used alone without AjaxUploadExtender because it has the same properties and events as AjaxUploadExtender in addition to four extra poperties:

  • MaxNoOfFiles: determines the maximum number of files allowed to add to the gallery.
  • OnMaxNumberExssededFunction: the name of the function to be executed on exeeding the MaxNoOfFiles.
  • OnAddingFileFunction: the client function to add the new UI for the new file.
  • OnRemovingFileFunction: the client function to remove the UI for the reomoved file.
JavaScript
<script type="text/javascript" >
    function AddingFileFunctionsender(sender, e){..}
    function OnMaxNumberExssededFunction(sender, e){..}
    function RemovingFileFunction(sender, e){..}
</script>

<web:AjaxUploadGalleryExtender ID="AjaxUploadGalleryExtender1" runat="server"
    MaxNoOfFiles="4" 
    OnAddingFileFunction="AddingFileFunction" 
    OnMaxNumberExssededFunction="OnMaxNumberExssededFunction"
    OnRemovingFileFunction="RemovingFileFunction"
>
</web:AjaxUploadGalleryExtender>

Files can be added to the AjaxUploadGalleryExtender though the property Files which is a collection of instances of type AjaxUploadFiles Each instanse of AjaxUploadFile can be connected ti the UI using the properties:

  • BrowseButton
  • ClearButton
  • RemoveButton
  • PreviewFrame
  • ExtraData
  • EvaluateFunction
C#
protected void grdPictures_ItemDataBound(object sender, RepeaterItemEventArgs  e)
{
    if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
    {

        Control cmdRemove = e.Item.FindControl("cmdRemove");
        HtmlGenericControl PreviewFrame = (HtmlGenericControl)e.Item.FindControl("PreviewFrame");
        PreviewFrame.Attributes.Add("name", PreviewFrame.UniqueID);
        Control cmdBrowse = e.Item.FindControl("cmdBrowse");
        Control cmdClear = e.Item.FindControl("cmdClear");

        MyControls.Web.AjaxUploadFile file = AjaxUploadGalleryExtender1.NewFile();
        file.RemoveButton = cmdRemove;
        file.PreviewFrame = PreviewFrame;
        file.BrowseButton = cmdBrowse;
        file.ClearButton = cmdClear;
        file.ExtraData.AddRange(new MyControls.Web.DataEntry[] { new MyControls.Web.DataEntry("pid", "pid"), new MyControls.Web.DataEntry("title", "title") });
        file.EvaluateFunction = string.Format("function(key) {{ switch (key){{ case 'pid': return $get('{0}').value; break; case 'title': return $get('{1}').value; break; }} }}", hdnpid.ClientID, hdnTitle.ClientID);

        AjaxUploadGalleryExtender1.Files.Add(file);
    }
}

And can be saved usimg the properties:

  • FileBytes
  • FileContent
  • FileName
  • HasFile

and also the methods:

  • GetValue
  • SaveAs
C#
string[] ImageNames = new string[AjaxUploadGalleryExtender1.Files.Count];
for(int i = 0; i < AjaxUploadGalleryExtender1.Files.Count; i++)
{
    MyControls.Web.AjaxUploadFile file = AjaxUploadGalleryExtender1.Files[i];
    string Title = file.GetValue("title");
    if(file.HasFile)
        ImageNames[i] = SaveImage(file);
}
C#
private string SaveImage(AjaxUploadFile file)
{
    string filename = System.IO.Path.GetFileNameWithoutExtension(file.FileName);
    string extention = System.IO.Path.GetExtension(file.FileName);
    int index = 0;
    string savename = filename + extention;
    while (System.IO.File.Exists(Server.MapPath("~/UploadedImages/") + savename))
    {
        savename = string.Format("{0} ({1}){2}", filename, index, extention);
        index++;
    }
    file.SaveAs(Server.MapPath("~/UploadedImages/") + savename);

    return savename;
}

The Client Code

The control also provides a client-side interface of four methods. These methods are:

  • clear() : clears the file input and the preview frame.
  • reset() : calls the clear() method and raises the client-side event Reset to reset the submission form.
  • submit(successCallback) : causes a submit post-back to raise the server-side event Submit. It raises also both the client-side events Submit and SubmitComplete(when the response get back) . I takes a callback function as a parameter to execute if the submition succeed.
  • preview(successCallback) : causes a submit post-back to raise the server-side event Preview. It raises also both the client-side events Preview and PreviewComplete(when the response get back) . I takes a callback function as a parameter to execute if the submition succeed.

These can be called by finding the client-side reference of the component that represent the control then simply calling the method just like:

HTML
<a href="javascript:;"
   onclick="javascript:$find('<%= AjaxUploadExtender1.ClientID %>').submit(function(){$find('<%= ModalPopupExtender1.ClientID %>').hide()});">
   Save & Close
</a>

As descriped above the control raises seven client events:

  • UnspportedExtention : raised when a user select a file of an invalid type with event argument of type MyControls.Web.UnSupportedFileEventArgs (the property OnUnspportedExtention).
  • Change : raised when a user select a file of a valid type with event argument of type MyControls.Web.UploadEventArgs (the property OnChangeFunction).
  • Reset : raised when reset() function is called either by Reset Button, or by page developer with event argument of type Sys.EventArgs (the property OnResetFunction).
  • Submit : raised when submit() function is called either by Auto Submit, Submit Button, or by page developer with event argument of type MyControls.Web.UploadEventArgs (the property OnSubmitFunction).
  • SubmitComplete : raised when a response get back after a submit request(post-back) with event argument of type MyControls.Web.UploadCompleteEventArgs (the property OnSubmitCompleteFunction).
  • Preview : raised when preview() function is called either by Auto Preview, or by page developer with event argument of type MyControls.Web.UploadEventArgs (the property OnPreviewFunction).
  • PreviewComplete : raised when a response get back after a preview request(post-back) with event argument of type MyControls.Web.UploadCompleteEventArgs (the property OnPreviewCompleteFunction).

The event arguments classes usee in these events are

MyControls.Web.UnSupportedFileEventArgs

This class provides two readonly properties:

  • File : holds the neme of the selected file.
  • Extention : holds the extention of the selected file.

MyControls.Web.UploadEventArgs

This class provides three readonly properties with addition to the cancel property:

  • File : holds the neme of the selected file.
  • Extention : holds the extention of the selected file.
  • Description : holds the description of the selected file.
  • cancel : holds the a flag that determines whether the event should be canceled.

MyControls.Web.UploadCompleteEventArgs

This class provides five readonly properties with addition to the Succeed property :

  • File : holds the neme of the selected file.
  • Extention : holds the extention of the selected file.
  • Description : holds the description of the selected file.
  • Response : holds the value of the reponse object.
  • ResponseType : holds the type of the reponse object.
  • Succeed : this property determines whether the submission is succeed or not.

Examples

Auto post-back Example

This Example shows a photo album where a user can add, remove and preview photos. These photos would be uploaded to the server once they are selected.

This example uses AjaxUploadExtender with the following properties:

  • AutoPostBack
  • PostBackUrl
  • OnSubmitCompleteFunction
  • OnSubmitFunction
  • EvaluateFunctionName
  • Filter
  • OnUnsupportedFunction
  • SubmissionFrameType
  • GetSubmissionFrameFunction
Image 1

Full Example

This example shows a list of employees. where a user can add new employees and edit their information (fakely; because this application is not connected to a database). And when the information is submitted, a progress bar appears that shows the progress of work done on the server.

This example uses AjaxUploadExtender with the following properties:

  • AutoPreview
  • ClearButtonID
  • ContextKey
  • EnableProgress
  • EvaluateFunctionName
  • Filter
  • OnChangeFunction
  • OnPreviewCompleteFunction
  • OnPreviewFunction
  • OnProgressFeedbackFunction
  • OnResetFunction
  • OnSubmitCompleteFunction
  • PostBackUrl
  • OnSubmitFunction
  • OnUnsupportedFunction
  • ProgressServiceMethod
  • ProgressServicePath
  • PreviewFrameID
  • ResetButtonID
  • SubmitButtonID
  • ZIndex
  • ExtraData
Image 2

Multi-File Example

This example shows a list of cars. where a user can add new cars and edit their information (fakely; because this application is not connected to a database).

This example AjaxUploadExtender with uses the following properties:

  • ClearButtonID
  • EvaluateFunctionName
  • Filter
  • GalleryID
  • OnChangeFunction
  • OnPreview
  • OnPreviewCompleteFunction
  • OnPreviewFunction
  • OnResetFunction
  • OnSubmit
  • OnSubmitCompleteFunction
  • OnSubmitFunction
  • OnUnsupportedFunction
  • PreviewFrameID
  • ResetButtonID
  • SubmitButtonID
  • ZIndex
  • ExtraData

and uses AjaxUploadGalleryExtender with the following properties:

  • AutoPreview
  • Filter
  • MaxNoOfFiles
  • OnAddingFileFunction
  • OnMaxNumberExssededFunction
  • OnPreview
  • OnRemovingFileFunction
  • OnResetFunction
  • ZIndex
Image 3

Gallary Example

This example shows a list of galleries. where a user can add new galleries and edit their information (fakely; because this application is not connected to a database). And when the information is submitted, a progress bar appears that shows the progress of work done on the server.

This example uses AjaxUploadGalleryExtender with the following properties:

  • AutoPreview
  • ContextKey
  • EnableProgress
  • EvaluateFunctionName
  • Filter
  • MaxNoOfFiles
  • OnAddingFileFunction
  • OnMaxNumberExssededFunction
  • OnProgressFeedbackFunction
  • OnRemovingFileFunction
  • OnResetFunction
  • OnSubmitCompleteFunction
  • OnSubmitFunction
  • OnUnsupportedFunction
  • PostBackUrl
  • ProgressServiceMethod
  • ProgressServicePath
  • ResetButtonID
  • OnPreviewCompleteFunction
  • SubmitButtonID
  • ZIndex
  • OnPreviewFunction
  • ExtraData
Image 4

Points of Interest

As we know, to make a regular postback, we need two hidden inputs to be submitted with the form. One is __EVENTTARGET which is to hold the unique id of the control caused the post-back, the other is __EVENTARGUMENT which is to hold the arguments of the post-back event. Like in the function GetData() which is used to get the submission inputs values used in JQuery.ajax() call:

JavaScript
function RemoveImage(img) {
    var counter = img.id.replace('close', '');
    $("#loading").show();
    $.ajax({
        type: "POST",
        url: "CrossPostBack/removeupload.aspx",
        data: GetData(img.getAttribute('title')),
        success: function (msg) {
            $('#current' + counter).fadeOut('slow', function () {
                $("#loading").hide();
                $("#message").show();
                $("#message").html("Removed successfully!");
                $("#message").fadeOut(3000);
            });
        }
    });
};

function GetData(file) {
    return {
    __EVENTTARGET : '<%= this.UniqueID %>',
    __EVENTARGUMENT : 'R',
    __PREVIOUSPAGE : $get('__PREVIOUSPAGE').value,

    filename: file
    };
}

As you can see, the hidden input __PREVIOUSPAGE is used in cross-page postbacking, actually it contains an encrypted version of the current page virtual path. We can add one by calling the method ClientScriptManager.GetPostBackEventReference() with an argument of type PostBackOptions with action URL not empty. In my control, I called the method ClientScriptManager.GetPostBackEventReference() and just did nothing with the result.

C#
if (!string.IsNullOrEmpty(this.PostBackUrl))
{
    string postbackReference = this.Page.ClientScript.GetPostBackEventReference
    (new PostBackOptions(this,"",this.PostBackUrl,this.AutoPostBack,false,false,
    false,false,this.ValidationGroup));
    postbackReference = ""; //do nothing with the result.

    startupscript.AppendFormat(",\n            action: '{0}'",
            ResolveClientUrl(this.PostBackUrl));
}

Another thing that can be helpful is to make a JavaScript class a regular Component that is managed automatically by the current instance of ScriptManager and can be found throw the function $find(). And of course, we need to register the class as a Component. To add a regular Component to the page, we need to render something like the code below (the italic text can be changed):

JavaScript
Sys.Application.add_init(function() {
    (function () {
        var component = new MyControls.Web.AjaxUpload($get('addImage'), { name: 'AjaxUploadExtender1',
            action: 'CrossPostBack/saveupload.aspx', onSubmit: OnSubmitFunction, onComplete:
            OnCompleteFunction, autoSubmit: true, filter: [{ extensions: ['jpg','bmp','gif','png'],
            description: 'Supported Images Types (*.jpg;*.bmp;*.gif;*.png)' }], onUnsupportedExtention:
            OnUnsupportedFunction, validationGroup: '' });
        var app = Sys.Application;
        var creatingComponents = app.get_isCreatingComponents();
        component.beginUpdate();
        component.set_id('AjaxUploadExtender1');
        Sys.Application.addComponent(component);
        if (creatingComponents) app._createdComponents
        [app._createdComponents.length] = component;
        component.endUpdate();
        return component;
    })();
});

History

  • Wednesday, January 25, 2012 (1.0.0): First version
  • Friday, February 10, 2012 (1.0.1):
    • Enhance the examples to include exeption handling and ResponseType.
    • Add the properties SubmissionFrameType and GetSubmissionFrameFunction to control the response direction.
  • Monday, March 5, 2012 (1.0.2):
    • Add the event argument classes and update the control to use them.
    • Enhance the examples to use the event argument and the client-side methods.
  • Saturday, December 15, 2012 (1.1.0): Add multi-file and progress feathers.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)