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.
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:
<%@ Register Assembly="MyControls.Web" Namespace="MyControls.Web" TagPrefix="web" %>
..
<asp:ScriptManager ID="ScriptManager1" runat="server">
</asp:ScriptManager>
<web:AjaxAploadExtender ID="AjaxAploadExtender1" runat="server">
</web:AjaxAploadExtender>
..
The next step is to add properties to the extender which will be used in rendering
the settings of the control. These properties are:
TargetControlID: holds the id of the control which opens the browser's
dialog.
AutoPostBack: determines whether the selected file should be posted
once the dialog is closed (the default is false).
PostBackUrl: holds the URL to the page on which the postback should
be processed.
SubmitButtonID: holds the id of the button which submits the form (helpful
when AutoPostBack is set to false).
ResponseType: determines the type of data to be returned back from
the server (only useful when you are using json data as a response).
OnChangeFunction: holds the name of the function to be called once
the user selects a file.
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.
OnResetFunction: holds the name of the function to be called to reset
the displaying form.
ResetButtonID: holds the id of the button which rests the displaying
form.
ClearButtonID: holds the id of the button which the file inputs.
CauseValidation: determines whether the form should be validated before
submission.
ValidationGroup: holds the name of the validation group of the displaying
form (e.g. "*.jpg;*.bmp;*.gif;*.png|Supported Images Types (*.jpg;*.bmp;*.gif;*.png)|*.*|All
Files" see
FileDialog.Filter Property[^]).
Filter: holds the file filter string of the supported
file types.
OnUnspportedExtention: holds the name of the function to be called
when an unsupported file type is chosen.
<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:AjaxAploadExtender ID="AjaxAploadExtender1" 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:AjaxAploadExtender>
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.
<script type="text/javascript" >
function OnPreviewFunction(sender, e) { ... }
function OnPreviewCompleteFunction(sender, e) { ... }
</script>
<iframe ID="PrviewFrame" runat="server" ></iframe>
<web:AjaxAploadExtender ID="AjaxAploadExtender1" runat="server"
AutoPreview="true"
PrviewFrameID="PrviewFrame"
OnPreviewFunction="OnPreviewFunction"
OnPreviewCompleteFunction="OnPreviewCompleteFunction" >
</web:AjaxAploadExtender>
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).
<script type="text/javascript" >
function AjaxApload1_Evaluate(key) { ... }
</script>
<web:AjaxAploadExtender ID="AjaxAploadExtender1" runat="server"
EvaluateFunctionName="AjaxApload1_Evaluate" >
<ExtraData>
<web:DataEntry Name="Title" EvaluateKey="Title" />
<web:DataEntry Name="Description" EvaluateKey="Description" />
</ExtraData>
</web:AjaxAploadExtender>
Our control also has regular FileUplod 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.
protected void AjaxAploadExtender1_Submit(object sender, EventArgs e)
{
string FirstName = AjaxAploadExtender1.GetValue("first");
string Inits = AjaxAploadExtender1.GetValue("inits");
string LastName = AjaxAploadExtender1.GetValue("last");
int DepartmentID = int.Parse(AjaxAploadExtender1.GetValue("dept"));
if (AjaxAploadExtender1.HasFile)
{
HttpPostedFile userPostedFile = AjaxAploadExtender1.PostedFile;
string filename = AjaxAploadExtender1.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).
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:
<a href="javascript:;"
onclick="javascript:$find('<%= AjaxAploadExtender1.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.
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:
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.
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 = "";
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):
Sys.Application.add_init(function() {
(function () {
var component = new MyControls.Web.AjaxUpload($get('addImage'), { name: 'AjaxAploadExtender1',
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('AjaxAploadExtender1');
Sys.Application.addComponent(component);
if (creatingComponents) app._createdComponents
[app._createdComponents.length] = component;
component.endUpdate();
return component;
})();
});
History
- Wednesday, January 25, 2012: First version
- Friday, February 10, 2012:
- Enhance the examples to include exeption handling and
ResponseType.
- Add the properties
SubmissionFrameType and GetSubmissionFrameFunction
to control the response direction.
- Tuesday, March 6, 2012:
- 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.
- Wednesday March 14, 2012: Fix problems with the previous version.