异步文件上传






4.96/5 (26投票s)
一个 AJAX 控件,
引言
在许多 Web 应用程序中,您都需要将某些文件或上下文上传到应用程序服务器。可以使用 `FileUpload` 控件实现此目标,该控件会呈现为 ``。不幸的是,`FileUpload` 在异步回发中不受支持,这没有意义,因为整个网站都使用部分页面回发,除了那些使用 `FileUpload` 的页面。
Sunasara Imdadhusen[^] 在 带有动画的轻量级图像上传[^] 中提出了一个解决方案,即在另一个表单中回发文件。在本文中,我将展示相同的控件并添加一些功能以满足 Web 应用程序的常见需求。
总的来说,该扩展器提供了一个异步文件上传功能,并附带一些额外的功能。
- 能够为所选文件生成适当的预览。
- 能够发送额外数据来描述文件。
- 能够批量发送一组文件。(新增)
- 提供一个进度条来显示已完成的工作量。(新增)
Using the Code
首先,我们将控件从一段 JavaScript 代码转换为一个扩展器控件。因此,要创建扩展器的实例,我们需要注册包含该控件的命名空间 `MyControls.Web`,添加一个 `ScriptManager` 标签,然后像下面这样添加扩展器标签:
<%@ 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>
..
下一步是设置扩展器的属性,这些属性将用于呈现控件的设置。这些属性是:
离线属性
- `TargetControlID`:保存打开浏览器对话框的控件的 ID。
- `ResponseType`:确定从服务器返回的数据类型(仅在使用 JSON 数据作为响应时有用)。(已废弃)
- `Enabled`:确定控件功能是否已启用。
- `Filter`:保存支持文件类型的过滤器字符串。(例如,“*.jpg;*.bmp;*.gif;*.png|支持的图像类型(*.jpg;*.bmp;*.gif;*.png)|*.*|所有文件”请参阅 `FileDialog.Filter` 属性[^])。
- `ZIndex`:指定输入元素的堆叠顺序。
BrowseButtonHoverCssClass
- `OnChangeFunction`:保存用户选择文件后要调用的函数名。
- `OnUnsupportedFunction`:保存选择不支持的文件类型时要调用的函数名。
- `ClearButtonID`:保存清除文件输入的按钮的 ID。
- `ResetButtonID`:保存重置显示表单的按钮的 ID。
- `OnResetFunction`:保存用于重置显示表单的函数名。
提交属性
- `CauseValidation`:确定是否应在提交前验证表单。
- `ValidationGroup`:保存显示表单的验证组的名称。
- `AutoPostBack`:确定关闭对话框后是否应自动回发选定的文件(默认为 `false`)。
- `SubmitButtonID`:保存提交表单的按钮的 ID(当 `AutoPostBack` 设置为 `false` 时很有用)。
- `PostBackUrl`:保存回发应在其中处理的页面的 URL。
- `OnSubmitFunction`:在表单提交之前要调用的函数名(您可以返回 `false` 来取消提交)。
- `OnSubmitCompleteFunction`:在表单提交完成后要调用的函数名。
<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>
预览属性
由于该控件使用另一个表单来回发文件,因此它使用 iframe 来捕获响应。该控件提供两个属性来控制响应方向。
- `SubmissionFrameType`:可以设置为 `AutoGenerated` 或 `UserDefined` 中的一个值,以确定是在提交时生成 iframe 还是不生成。
- `GetSubmissionFrameFunction`:保存返回用户定义的 iframe 的函数名(当 `SubmissionFrameType` 设置为 `UserDefined` 时)。
此外,还可以在具有 `runat="server"` 属性的 iframe 上实现预提交预览功能。
- `AutoPreview`:确定关闭对话框后是否应自动预览选定的文件(默认为 `false`)。
- `PreviewFrameID`:保存应在哪个 iframe 上预览图像的 ID。
- `OnPreviewFunction`:在预览之前要调用的函数名(您可以返回 `false` 来取消预览)。
- `OnPreviewCompleteFunction`:在预览完成后要调用的函数名。
<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>
也许应该添加到此控件中最重要的一点是添加额外数据到表单以供回发的途径。嗯,这在 Imdadhusen 的文章的版本中已经提出了,但 IMHO,它并不简单。所以我添加了一个内部属性来保存这些 `DataEntry`s,它们是 `Name`(表示隐藏输入的名称)和 `EvaluateKey`(由 `EvaluateFunction` 用于评估要回发的隐藏输入的价值)的对。
<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>
进度属性
这些属性决定了进度功能如何工作,以提供视觉反馈,显示服务器上正在进行的工作的进度。此功能使用会话状态存储进度值,使用 Web 服务读取该值,并使用客户端函数可视化返回的数据。
关于该服务之所以如此重要的原因在于,Web 方法应该对会话状态具有只读访问权限,以便能够读取进度信息而不被服务器阻止。
- `EnableProgress`:确定在提交过程中是否应调用进度服务。
- `ContextKey`:为进度服务保留一个唯一的上下文密钥。
- `ProgressInfo`:获取与控件关联的进度数据结构。
- `ProgressServicePath`:进度服务的路径。
- `ProgressServiceMethod`:提供进度服务的 Web 方法的名称。
- `ProgressInterval`:进度服务两次调用之间的时间间隔。
- `OnProgressFeedbackFunction`:进度服务返回后应执行的回调函数名。
- `SaveBufferSize`:保存缓冲区的大小(以字节为单位)(适用于大文件)。
<web:AjaxUploadExtender ID="AjaxUploadGalleryExtender1" runat="server" ContextKey="GIMG" ProgressInterval="500" SaveBufferSize="1024" EnableProgress="OnSubmit, OnPreview" ProgressServiceMethod="GetProgressValue" ProgressServicePath="~/CrossPostBack/GallerySavePage.aspx" > </web:AjaxUploadGalleryExtender>
[WebMethod(EnableSession = true), ScriptMethod]
public static MyControls.Web.ProgressInfo GetProgressValue(string contextKey)
{
MyControls.Web.ProgressInfo info = MyControls.Web.AjaxUploadExtender.GetUploadInfo(contextKey);
return info;
}
正如我们所见,`ContextKey` 属性在 Web 方法中用于检索特定控件的进度信息。进度信息在服务器进程中使用只读属性 `ProgressInfo` 设置。
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);
}
}
FileUpload 的常规属性
我们的控件也有常规的 `FileUpload` 属性。
- `FileBytes`:获取上传文件中的字节数组。
- `FileContent`:获取一个指向上传文件的 `Stream` 对象。
- `FileName`:获取上传文件的名称。
- `HasFile`:获取一个布尔值,指示扩展器是否包含文件。
- `PostedFile`:获取使用扩展器上传的文件的底层 `HttpPostedFile` 对象。
还有
- `SaveAs()` 函数,它将上传文件的内容保存到 Web 服务器上的指定路径。
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);
}
如上所示,我们可以通过调用 `GetValue()` 函数来获取 `ExtraData` 值,参数是 `DataEntry` 的 `Name`。
此控件有两个服务器端事件:`Submit`,在提交表单时引发;`Preview`,在选择图像文件时(当 `AutoPreviw` 设置为 true 时)引发。
多文件功能
我们的控件可以通过 `GalleryID` 属性附加到另一个名为 `AjaxUploadGalleryExtender` 的扩展器,以提供多文件功能。此外,`AjaxUploadGalleryExtender` 也可以单独使用,而无需 `AjaxUploadExtender`,因为它具有与 `AjaxUploadExtender` 相同的属性和事件,此外还有四个附加属性:
- `MaxNoOfFiles`:确定允许添加到图库的最大文件数。
- `OnMaxNumberExssededFunction`:超过 `MaxNoOfFiles` 时要执行的函数名。
- `OnAddingFileFunction`:添加新文件 UI 的客户端函数。
- `OnRemovingFileFunction`:移除已移除文件的 UI 的客户端函数。
<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` 属性添加到 `AjaxUploadGalleryExtender` 中,这是一个 `AjaxUploadFile` 类型实例的集合。`AjaxUploadFile` 的每个实例都可以通过以下属性连接到 UI:
BrowseButton
ClearButton
RemoveButton
PreviewFrame
ExtraData
EvaluateFunction
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);
}
}
并且可以使用以下属性保存:
FileBytes
FileContent
FileName
HasFile
还有方法:
GetValue
SaveAs
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);
}
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;
}
客户端代码
该控件还提供了一个四种方法的客户端接口。这些方法是:
- `clear()`:清除文件输入和预览框架。
- `reset()`:调用 `clear()` 方法并引发客户端事件 `Reset` 来重置提交表单。
- `submit(successCallback)`:导致提交回发以引发服务器端事件 `Submit`。它还会引发客户端事件 `Submit` 和 `SubmitComplete`(在响应回来时)。它接受一个回调函数作为参数,以便在提交成功时执行。
- `preview(successCallback)`:导致提交回发以引发服务器端事件 `Preview`。它还会引发客户端事件 `Preview` 和 `PreviewComplete`(在响应回来时)。它接受一个回调函数作为参数,以便在提交成功时执行。
可以通过查找表示控件的组件的客户端引用来调用这些方法,然后像这样简单地调用方法:
<a href="javascript:;"
onclick="javascript:$find('<%= AjaxUploadExtender1.ClientID %>').submit(function(){$find('<%= ModalPopupExtender1.ClientID %>').hide()});">
Save & Close
</a>
如上所述,该控件引发七个客户端事件:
- `UnspportedExtention`:当用户选择无效类型的文件时引发,事件参数为 `MyControls.Web.UnSupportedFileEventArgs` 类型(`OnUnspportedExtention` 属性)。
- `Change`:当用户选择有效类型的文件时引发,事件参数为 `MyControls.Web.UploadEventArgs` 类型(`OnChangeFunction` 属性)。
- `Reset`:当调用 `reset()` 函数时引发(通过 Reset 按钮或页面开发者),事件参数为 `Sys.EventArgs` 类型(`OnResetFunction` 属性)。
- `Submit`:当调用 `submit()` 函数时引发(通过 Auto Submit、Submit Button 或页面开发者),事件参数为 `MyControls.Web.UploadEventArgs` 类型(`OnSubmitFunction` 属性)。
- `SubmitComplete`:在提交请求(回发)后收到响应时引发,事件参数为 `MyControls.Web.UploadCompleteEventArgs` 类型(`OnSubmitCompleteFunction` 属性)。
- `Preview`:当调用 `preview()` 函数时引发(通过 Auto Preview 或页面开发者),事件参数为 `MyControls.Web.UploadEventArgs` 类型(`OnPreviewFunction` 属性)。
- `PreviewComplete`:在预览请求(回发)后收到响应时引发,事件参数为 `MyControls.Web.UploadCompleteEventArgs` 类型(`OnPreviewCompleteFunction` 属性)。
这些事件中使用的事件参数类是:
MyControls.Web.UnSupportedFileEventArgs
此类提供两个只读属性:
- `File`:保存所选文件的名称。
- `Extention`:保存所选文件的扩展名。
MyControls.Web.UploadEventArgs
此类提供三个只读属性,此外还有一个 `cancel` 属性。
- `File`:保存所选文件的名称。
- `Extention`:保存所选文件的扩展名。
- `Description`:保存所选文件的描述。
- `cancel`:保存一个标志,用于确定是否应取消事件。
MyControls.Web.UploadCompleteEventArgs
此类提供五个只读属性,此外还有一个 `Succeed` 属性。
- `File`:保存所选文件的名称。
- `Extention`:保存所选文件的扩展名。
- `Description`:保存所选文件的描述。
- `Response`:保存响应对象的值。
- `ResponseType`:保存响应对象的类型。
- `Succeed`:此属性确定提交是否成功。
示例
自动回发示例此示例显示了一个相册,用户可以在其中添加、删除和预览照片。这些照片将在选择后上传到服务器。 此示例使用 `AjaxUploadExtender`,并具有以下属性:
|
![]() |
完整示例此示例显示了一个员工列表。用户可以在其中添加新员工并编辑他们的信息(虚构;因为此应用程序未连接到数据库)。当信息提交时,将出现一个进度条,显示服务器上完成的工作进度。 此示例使用 `AjaxUploadExtender`,并具有以下属性:
|
![]() |
多文件示例此示例显示了一个汽车列表。用户可以在其中添加新汽车并编辑他们的信息(虚构;因为此应用程序未连接到数据库)。 此示例的 `AjaxUploadExtender` 使用以下属性:
并使用 `AjaxUploadGalleryExtender`,具有以下属性:
|
![]() |
图库示例此示例显示了一个图库列表。用户可以在其中添加新图库并编辑他们的信息(虚构;因为此应用程序未连接到数据库)。当信息提交时,将出现一个进度条,显示服务器上完成的工作进度。 此示例使用 `AjaxUploadGalleryExtender`,并具有以下属性:
|
![]() |
关注点
如我们所知,要进行常规回发,我们需要两个隐藏输入字段与表单一起提交。一个是 `__EVENTTARGET`,用于保存引起回发的控件的唯一 ID;另一个是 `__EVENTARGUMENT`,用于保存回发事件的参数。就像在 `GetData()` 函数中一样,它用于获取 `JQuery.ajax()` 调用使用的提交输入值。
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
};
}
如您所见,隐藏输入字段 `__PREVIOUSPAGE` 用于跨页面回发,实际上它包含当前页面虚拟路径的加密版本。我们可以通过调用 `ClientScriptManager.GetPostBackEventReference()` 方法并传入一个 `PostBackOptions` 类型且 `Action` URL 不为空的参数来添加一个。在我的控件中,我调用了 `ClientScriptManager.GetPostBackEventReference()` 方法,但对其结果什么都没做。
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));
}
另一件有用的事情是使 JavaScript 类成为一个由 `ScriptManager` 的当前实例自动管理的常规 `Component`,并且可以通过 `$find()` 函数找到。当然,我们需要将该类注册为 `Component`。要向页面添加一个常规 `Component`,我们需要呈现类似下面的代码(斜体文本可以更改):
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;
})();
});
历史
- 2012 年 1 月 25 日,星期三(1.0.0):第一个版本
- 2012 年 2 月 10 日,星期五 (1.0.1):
- 增强示例以包含异常处理和 `ResponseType`。
- 添加 `SubmissionFrameType` 和 `GetSubmissionFrameFunction` 属性以控制响应方向。
- 2012 年 3 月 5 日,星期一 (1.0.2):
- 添加事件参数类并更新控件以使用它们。
- 增强示例以使用事件参数和客户端方法。
- 2012 年 12 月 15 日,星期六(1.1.0):添加多文件和进度功能。