关于多文件上传,以前我一直使用JQuery去动态创建文件选择组件,然后POST到服务器去。最近一段时间一直在Flash身边打滚,Flash对于多文件上传有很好的支持,在CodePrject上有一个Flash的多文件上传组件的开源项目,将其封装为ASP.NET控件,当我们在开发ASP.NET程序的时候可以像使用普通控件一样,从工具箱里拉到界面上就可以使用。
Flash采用Flex开发的,实现了选择文件和统计文件大小的功能,选择了多个文件时会动态的创建Flex组件显示到界面上,并且可以移除所选择的文件。以下是Flex项目结构图:
Flex是完全基于事件驱动的开发模型,通过自定义一个Flex的Application来实现文件的多选和文件数量以及上传文件大小的统计等功能。如上图的FileUpload就是自定义的一个组件,扩展自VBox实现了动态创建组件和处理事件。详细参考下面代码:
package components
{
import flash.events.*;
import flash.net.*;
import mx.containers.*;
import mx.controls.*;
import mx.core.Application;
import mx.events.*;
/**//**
* 自定义一个Flex的Application,继承于Application
* */
public class CustomApplication extends Application
{
//文件上传列表
private var fileRefList:FileReferenceList;
private var fileRefListener:Object;
private var _totalSize:Number;
private var _uploadedBytes:Number;
private var _currentUpload:FileUpload;
private var _uploadFileSize:Number;
private var _totalUploadSize:Number;
private var _fileTypeDescription:String;
private var _fileTypes:String;
public var fileContainer:VBox;
public var fileUploadBox:VBox;
public var uploadStats:HBox;
public var totalFiles:Text;
public var totalSize:Text;
public var totalProgressBar:ProgressBar;
public var browseButton:Button;
public var clearButton:Button;
public var uploadButton:Button;
public var cancelButton:Button;
public var mytext:Text;
public function CustomApplication()
{
super();
addEventListener(FlexEvent.CREATION_COMPLETE,onLoad);
}
private function onLoad(evt:Event):void
{
fileRefList = new FileReferenceList();
_totalSize = 0;
_uploadedBytes = 0;
fileRefList.addEventListener(Event.SELECT,OnSelect);
browseButton.addEventListener(MouseEvent.CLICK,OnAddFilesClicked);
clearButton.addEventListener(MouseEvent.CLICK,OnClearFilesClicked);
uploadButton.addEventListener(MouseEvent.CLICK,OnUploadFilesClicked);
cancelButton.addEventListener(MouseEvent.CLICK,OnCancelClicked);
var temp:String = Application.application.parameters.fileSizeLimit;
if(temp != null && temp != "")
_uploadFileSize = new Number(temp);
else
_uploadFileSize = 0;
temp = Application.application.parameters.totalUploadSize;
if(temp != null && temp != "")
_totalUploadSize = new Number(temp);
else
_totalUploadSize = 0;
_fileTypeDescription = Application.application.parameters.fileTypeDescription;
_fileTypes = Application.application.parameters.fileTypes;
}
//选择文件
private function OnAddFilesClicked(event:Event):void
{
if(_fileTypes != null && _fileTypes != "")
{
if(_fileTypeDescription == null || _fileTypeDescription == "")
_fileTypeDescription = _fileTypes;
var filter:FileFilter = new FileFilter(_fileTypeDescription, _fileTypes);
fileRefList.browse([filter]);
}
else
fileRefList.browse();
}
private function OnClearFilesClicked(event:Event):void
{
if(_currentUpload != null)
_currentUpload.CancelUpload();
fileUploadBox.removeAllChildren();
SetLables();
_uploadedBytes = 0;
_totalSize = 0;
_currentUpload == null;
}
/**//**
* 上传文件事件处理函数
* */
private function OnUploadFilesClicked(event:Event):void
{
//获取所有的文件
var fileUploadArray:Array = fileUploadBox.getChildren();
var fileUploading:Boolean = false;
_currentUpload = null;
uploadButton.visible = false;
cancelButton.visible = true;
for(var x:uint=0;x<fileUploadArray.length;x++)
{
if(!FileUpload(fileUploadArray[x]).IsUploaded)
{
fileUploading = true;
_currentUpload = FileUpload(fileUploadArray[x]);
_currentUpload.Upload();
break;
}
}
//所有文件上传完毕
if(!fileUploading)
{
OnCancelClicked(null);
// 获取JavaScript方法
var completeFunction:String = Application.application.parameters.completeFunction;
// 执行JavaScript方法(completeFunction)给flash设置参数值
if(completeFunction != null && completeFunction != "")
navigateToURL(new URLRequest("javascript:"+completeFunction),"_self");
}
}
private function OnCancelClicked(event:Event):void
{
if(_currentUpload != null)
{
_currentUpload.CancelUpload();
_uploadedBytes -= _currentUpload.BytesUploaded;
_currentUpload = null;
}
SetLables();
uploadButton.visible = true;
cancelButton.visible = false;
}
/**//**
* 选择文件事件处理函数
* */
private function OnSelect(event:Event):void
{
var uploadPage:String = Application.application.parameters.uploadPage;
var tempSize:Number = _totalSize;
for(var i:uint=0;i<fileRefList.fileList.length;i++)
{
//动态创建组件
if(_uploadFileSize > 0 && fileRefList.fileList[i].size > _uploadFileSize)
OnFileSizeLimitReached(fileRefList.fileList[i].name);
if(_totalUploadSize > 0 && tempSize + fileRefList.fileList[i].size > _totalUploadSize)
{
OnTotalFileSizeLimitReached();
break;
}
if((_uploadFileSize == 0 || fileRefList.fileList[i].size < _uploadFileSize) && (_totalUploadSize == 0 || tempSize + fileRefList.fileList[i].size < _totalUploadSize))
{
var fu:FileUpload = new FileUpload(fileRefList.fileList[i],uploadPage);
fu.percentWidth = 100;
fu.addEventListener("FileRemoved",OnFileRemoved);
fu.addEventListener("UploadComplete",OnFileUploadComplete);
fu.addEventListener("UploadProgressChanged",OnFileUploadProgressChanged);
fu.addEventListener(HTTPStatusEvent.HTTP_STATUS,OnHttpError);
fu.addEventListener(IOErrorEvent.IO_ERROR,OnIOError);
fu.addEventListener(SecurityErrorEvent.SECURITY_ERROR,OnSecurityError);
fileUploadBox.addChild(fu);
tempSize += fileRefList.fileList[i].size;
}
}
SetLables();
}
private function OnFileRemoved(event:FileUploadEvent):void
{
_uploadedBytes -= FileUpload(event.Sender).BytesUploaded;
fileUploadBox.removeChild(FileUpload(event.Sender));
SetLables();
if(_currentUpload == FileUpload(event.Sender))
OnUploadFilesClicked(null);
}
private function OnFileUploadComplete(event:FileUploadEvent):void{
_currentUpload == null;
OnUploadFilesClicked(null);
}
private function OnTotalFileSizeLimitReached():void
{
Alert.show("文件总大小超出上传限制.");
}
private function OnFileSizeLimitReached(fileName:String):void
{
Alert.show("文件 '" + fileName + "' 太大不能添加到上传队列.");
}
private function OnHttpError(event:HTTPStatusEvent):void
{
Alert.show("HTTP错误: 状态码 " + event.status);
}
private function OnIOError(event:IOErrorEvent):void
{
Alert.show("文件流错误: " + event.text);
}
private function OnSecurityError(event:SecurityErrorEvent):void
{
Alert.show("安全错误: " + event.text);
}
//处理文件上传进度
private function OnFileUploadProgressChanged(event:FileUploadProgressChangedEvent):void
{
_uploadedBytes += event.BytesUploaded;
SetProgressBar();
}
private function SetProgressBar():void
{
totalProgressBar.setProgress(_uploadedBytes,_totalSize);
totalProgressBar.label = "已上传 " + FileUpload.FormatPercent(totalProgressBar.percentComplete) + "% - "
+ FileUpload.FormatSize(_uploadedBytes) + " of " + FileUpload.FormatSize(_totalSize);
}
private function SetLables():void
{
var fileUploadArray:Array = fileUploadBox.getChildren();
if(fileUploadArray.length > 0)
{
totalFiles.text = String(fileUploadArray.length);
_totalSize = 0;
for(var x:uint=0;x<fileUploadArray.length;x++)
{
_totalSize += FileUpload(fileUploadArray[x]).FileSize;
}
totalSize.text = FileUpload.FormatSize(_totalSize);
SetProgressBar();
clearButton.visible = uploadButton.visible = totalProgressBar.visible = uploadStats.visible = true;
}
else
{
clearButton.visible = uploadButton.visible = totalProgressBar.visible = uploadStats.visible = false;
}
}
}
}
package components
{
import flash.events.Event;
import flash.events.HTTPStatusEvent;
import flash.events.IOErrorEvent;
import flash.events.MouseEvent;
import flash.events.ProgressEvent;
import flash.events.SecurityErrorEvent;
import flash.net.FileReference;
import flash.net.URLRequest;
import mx.containers.HBox;
import mx.containers.VBox;
import mx.controls.Button;
import mx.controls.ProgressBar;
import mx.controls.ProgressBarMode;
import mx.controls.Text;
import mx.core.ScrollPolicy;
public class FileUpload extends VBox
{
private var bar:ProgressBar;
private var _file:FileReference;
private var nameText:Text;
private var _uploaded:Boolean;
private var _uploading:Boolean;
private var _bytesUploaded:uint;
private var _uploadUrl:String;
private var button:Button;
public function FileUpload(file:FileReference,url:String)
{
super();
//初始化参数
_file = file;
_uploadUrl = url;
_uploaded = false;
_uploading = false;
_bytesUploaded = 0;
//设置样式
setStyle("backgroundColor","#eeeeee");
setStyle("paddingBottom","10");
setStyle("paddingTop","10");
setStyle("paddingLeft","10");
verticalScrollPolicy = ScrollPolicy.OFF;
//添加事件监听
_file.addEventListener(Event.COMPLETE,OnUploadComplete);
_file.addEventListener(ProgressEvent.PROGRESS,OnUploadProgressChanged);
_file.addEventListener(HTTPStatusEvent.HTTP_STATUS,OnHttpError);
_file.addEventListener(IOErrorEvent.IO_ERROR,OnIOError);
_file.addEventListener(SecurityErrorEvent.SECURITY_ERROR,OnSecurityError);
//添加控件
var hbox:HBox = new HBox();
nameText = new Text();
nameText.text = _file.name + "-" + FormatSize(_file.size);
this.addChild(nameText);
bar = new ProgressBar();
bar.mode = ProgressBarMode.MANUAL;
bar.label = "已上传 0%";
bar.width = 275;
hbox.addChild(bar);
button = new Button();
button.label = "移除";
hbox.addChild(button);
button.addEventListener(MouseEvent.CLICK,OnRemoveButtonClicked);
this.addChild(hbox);
}
private function OnRemoveButtonClicked(event:Event):void
{
if(_uploading)
_file.cancel();
this.dispatchEvent(new FileUploadEvent(this,"FileRemoved"));
}
private function OnUploadComplete(event:Event):void
{
_uploading = false;
_uploaded = true;
this.dispatchEvent(new FileUploadEvent(this,"UploadComplete"));
}
private function OnHttpError(event:HTTPStatusEvent):void
{
this.dispatchEvent(event);
}
private function OnIOError(event:IOErrorEvent):void
{
this.dispatchEvent(event);
}
private function OnSecurityError(event:SecurityErrorEvent):void{
this.dispatchEvent(event);
}
// 处理文件上传进度
private function OnUploadProgressChanged(event:ProgressEvent):void
{
var bytesUploaded:uint = event.bytesLoaded - _bytesUploaded;
_bytesUploaded = event.bytesLoaded;
bar.setProgress(event.bytesLoaded,event.bytesTotal);
bar.label = "已上传 " + FormatPercent(bar.percentComplete) + "%";
this.dispatchEvent(new FileUploadProgressChangedEvent(this,bytesUploaded,"UploadProgressChanged"));
}
public function get IsUploading():Boolean{
return _uploading;
}
public function get IsUploaded():Boolean{
return _uploaded;
}
public function get BytesUploaded():uint{
return _bytesUploaded;
}
public function get UploadUrl():String{
return _uploadUrl;
}
public function set UploadUrl(uploadUrl:String):void{
_uploadUrl = uploadUrl;
}
public function get FileSize():uint
{
var size:uint = 0;
try{
size = _file.size;
}
catch (err:Error) {
size = 0;
}
return size;
}
public function Upload():void{
_uploading = true;
_bytesUploaded = 0;
_file.upload(new URLRequest(_uploadUrl));
}
public function CancelUpload():void{
_uploading = false;
_file.cancel();
}
//格式化文件大小
public static function FormatSize(size:uint):String{
if(size < 1024)
return PadSize(int(size*100)/100) + " bytes";
if(size < 1048576)
return PadSize(int((size / 1024)*100)/100) + "KB";
if(size < 1073741824)
return PadSize(int((size / 1048576)*100)/100) + "MB";
return PadSize(int((size / 1073741824)*100)/100) + "GB";
}
public static function FormatPercent(percent:Number):String{
percent = int(percent);
return String(percent);
}
public static function PadSize(size:Number):String{
var temp:String = String(size);
var index:int = temp.lastIndexOf(".");
if(index == -1)
return temp + ".00";
else if(index == temp.length - 2)
return temp + "0";
else
return temp;
}
}
}
package components
{
import flash.events.Event;
public class FileUploadEvent extends Event
{
private var _sender:Object;
public function FileUploadEvent(sender:Object,type:String,bubbles:Boolean=false,cancelable:Boolean=false)
{
super(type,bubbles,cancelable);
_sender = sender;
}
public function get Sender():Object
{
return _sender;
}
}
}
package components
{
public class FileUploadProgressChangedEvent extends FileUploadEvent
{
private var _bytesUploaded:uint;
public function FileUploadProgressChangedEvent(sender:Object,bytesUploaded:uint,type:String,bubbles:Boolean=false,cancelable:Boolean=false)
{
super(sender,type,bubbles,cancelable);
_bytesUploaded = bytesUploaded;
}
public function get BytesUploaded():Object
{
return _bytesUploaded;
}
}
}
在Flex的App程序里就使用上面自定义的Application组件来进行设计,详细如下代码块:
<custom:CustomApplication xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:local="*"
xmlns:custom="components.*" layout="horizontal"
backgroundAlpha="0" fontSize="12">
<mx:Style>
.button {fontWeight:normal;}
</mx:Style>
<mx:HBox>
<mx:VBox width="400" id="fileContainer">
<mx:VBox id="fileUploadBox" maxHeight="250" width="100%" label="Files to Upload" >
</mx:VBox>
<mx:HBox id="uploadStats" width="100%" visible="false">
<mx:Text text="文件总数:" /><mx:Text id="totalFiles" />
<mx:Text text="文件总大小:" /><mx:Text id="totalSize" />
</mx:HBox>
<mx:ProgressBar id="totalProgressBar" width="275" mode="manual" visible="false" />
</mx:VBox>
<mx:VBox>
<mx:Button id="browseButton" label="添加文件" width="100" styleName="button"/>
<mx:Button id="clearButton" label="移除文件" visible="false" width="100" styleName="button"/>
<mx:Button id="uploadButton" label="上传文件" visible="false" width="100" styleName="button"/>
<mx:Button id="cancelButton" label="取消上传" visible="false" width="100" styleName="button"/>
<mx:Text id="mytext" />
</mx:VBox>
</mx:HBox>
</custom:CustomApplication>
完成了多文件选择功能的Flash开发,现在就是想法将其生成的.swf封装到dll成为ASP.NET的服务器端控件,实现这一步很非常简单,将生成的.swf作为资源嵌入到ASP.NET的dll文件里就OK。然后自己定义一个类继承于Control,进行相应方法的重写就达到了目的,这里我们就需要重写Render()方法,在方法里通过调用资源的方法从.dll里读取出嵌入里面的.swf文件资源,然后向客户端输出嵌入flash文件(.swf)的编码就完成了。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace FlashUpload
{
//[DefaultProperty("FlashUpLoad")]
//[ToolboxData("<{0}:FlashUpLoad runat=server></{0}:FlashUpLoad>")]
public class FlashUpLoad : Control
{
/// <summary>
/// Flash文件地址
/// </summary>
private const string FLASH_SWF = "FlashUpload.FlashUpLoad.swf";
[Category("Behavior")]
[Description("上传文件的ASP.NET处理页面")]
[DefaultValue("")]
public string UploadPage
{
get
{
object o = ViewState["UploadPage"];
if (o == null)
return "";
return o.ToString();
}
set { ViewState["UploadPage"] = value; }
}
[Category("Behavior")]
[Description("上传页参数")]
[DefaultValue("")]
public string QueryParameters
{
get
{
object o = ViewState["QueryParameters"];
if (o == null)
return "";
return o.ToString();
}
set { ViewState["QueryParameters"] = value; }
}
[Category("Behavior")]
[Description("文件上传后JavaScript调用的设置参数的方法.")]
[DefaultValue("")]
public string OnUploadComplete
{
get
{
object o = ViewState["OnUploadComplete"];
if (o == null)
return "";
return o.ToString();
}
set { ViewState["OnUploadComplete"] = value; }
}
[Category("Behavior")]
[Description("设置允许上传的最大文件大小")]
public decimal UploadFileSizeLimit
{
get
{
object o = ViewState["UploadFileSizeLimit"];
if (o == null)
return 0;
return (decimal)o;
}
set { ViewState["UploadFileSizeLimit"] = value; }
}
[Category("Behavior")]
[Description("上传文件的总大小")]
public decimal TotalUploadSizeLimit
{
get
{
object o = ViewState["TotalUploadSizeLimit"];
if (o == null)
return 0;
return (decimal)o;
}
set { ViewState["TotalUploadSizeLimit"] = value; }
}
[Category("Behavior")]
[Description("上传文件的类型描述 (如. Images (*.JPG;*.JPEG;*.JPE;*.GIF;*.PNG;))")]
[DefaultValue("")]
public string FileTypeDescription
{
get
{
object o = ViewState["FileTypeDescription"];
if (o == null)
return "";
return o.ToString();
}
set { ViewState["FileTypeDescription"] = value; }
}
[Category("Behavior")]
[Description("上传文件的类型(如. *.jpg; *.jpeg; *.jpe; *.gif; *.png;)")]
[DefaultValue("")]
public string FileTypes
{
get
{
object o = ViewState["FileTypes"];
if (o == null)
return "";
return o.ToString();
}
set { ViewState["FileTypes"] = value; }
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
Page.Form.Enctype = "multipart/form-data";
}
protected override void Render(HtmlTextWriter writer)
{
base.Render(writer);
//生成Flash在页面的嵌入代码并设置其参数值
string url = Page.ClientScript.GetWebResourceUrl(this.GetType(), FLASH_SWF);
string obj = string.Format("<object classid=\"clsid:d27cdb6e-ae6d-11cf-96b8-444553540000\"" +
" codebase=\"http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,0,0\"" +
" width=\"575\" height=\"375\" id=\"fileUpload\" align=\"middle\">" +
"<param name=\"allowScriptAccess\" value=\"sameDomain\" />" +
"<param name=\"movie\" value=\"{0}\" />" +
"<param name=\"quality\" value=\"high\" />" +
"<param name=\"wmode\" value=\"transparent\" />" +
"<param name=\"FlashVars\" value='{3}{4}{5}{6}{7}&uploadPage={1}?{2}'/>" +
"<embed src=\"{0}\"" +
" FlashVars='{3}{4}{5}{6}{7}&uploadPage={1}?{2}'" +
" quality=\"high\" wmode=\"transparent\" width=\"575\" height=\"375\" " +
" name=\"fileUpload\" align=\"middle\" allowScriptAccess=\"sameDomain\" " +
" type=\"application/x-shockwave-flash\" " +
" pluginspage=\"http://www.macromedia.com/go/getflashplayer\" />" +
"</object>",
url,
ResolveUrl(UploadPage),
HttpContext.Current.Server.UrlEncode(QueryParameters),
string.IsNullOrEmpty(OnUploadComplete) ? "" : "&completeFunction=" + OnUploadComplete,
string.IsNullOrEmpty(FileTypes) ? "" : "&fileTypes=" + HttpContext.Current.Server.UrlEncode(FileTypes),
string.IsNullOrEmpty(FileTypeDescription) ? "" : "&fileTypeDescription=" + HttpContext.Current.Server.UrlEncode(FileTypeDescription),
TotalUploadSizeLimit > 0 ? "&totalUploadSize=" + TotalUploadSizeLimit : "",
UploadFileSizeLimit > 0 ? "&fileSizeLimit=" + UploadFileSizeLimit : ""
);
writer.Write(obj);
}
}
}
控件中定义了多个属性,方便可视化设置控件的属性值,其中最为主要的属性为:UploadPage,该属性的作用就是指定通过那一个处理程序来处理上传文件的请求,实现请求中上传文件的保存功能。在示例程序里,我自定义了一个HttpHandler来处理上传文件的请求,实现IHttpHandler接口。程序代码如下:
{
/// <summary>
/// 上传文件处理程序
/// </summary>
public class UploadHandler : IHttpHandler, IRequiresSessionState
{
#region IHttpHandler 成员
public bool IsReusable
{
get { return true; }
}
public void ProcessRequest(HttpContext context)
{
//文件上传存储路径
string uploadPath = context.Server.MapPath(context.Request.ApplicationPath + "/Upload");
for (int i = 0; i < context.Request.Files.Count; i++)
{
HttpPostedFile uploadFile = context.Request.Files[i];
if (uploadFile.ContentLength > 0)
{
uploadFile.SaveAs(Path.Combine(uploadPath, uploadFile.FileName));
}
}
HttpContext.Current.Response.Write(" ");
}
#endregion
}
}
定义好了请求处理程序,这时只需要通过配置文件简单的配置就可以设置控件的处理程序页地址了,如下:
<remove verb="POST,GET" path="Upload.axd"/>
<add verb="POST,GET" path="Upload.axd" type="FlashUpLoadWeb.UploadHandler"/>
</httpHandlers>
另外一个属性就是OnUploadComplete,通过它指定当文件上传完毕后调用客户端的那一个JavaScript提交更新页面状态,详细请查看示例代码。最终效果图下图:
控件还有许多需要完善的地方,比如请求处理程序那一块,上传文件的时候对文件的选择进行判断,不允许重复选择,判断选择队列中的文件在服务器上是否已经存在等等。有兴趣的朋友可以下去改善这些功能!源代码下载