在Silverlight上实现文件上传的例子在网上的还不多,特别是多文件上传和大文件上传的例子就更少了。当然 那些商品软件公司的产品除外。
目前的CodePlex上就有这样一个项目,其链接: http://www.codeplex.com/SLFileUpload/ ,他的个人主 站链接: http://www.michielpost.nl/ 我在本地下载运行其代码后,发现“果然”很好用,而且代码写的也很规范。当然其也是免费的,但作者并不拒 绝各种名义上的“捐助(Donate)”。
下面就是其“汉化”后的运行截图,首先是多文件上传 :
然后是大文件 上传:
根据作者的README文件,其支持下面几个初始化参数:
MaxFileSizeKB: File size in KBs. MaxUploads: Maximum number of simultaneous uploads FileFilter: File filter, for example ony jpeg use: FileFilter=Jpeg (*.jpg) |*.jpg CustomParam: Your custom parameter, anything here will be available in the WCF webservice DefaultColor: The default color for the control, for example: LightBlue
当然,里面的服务端采用WCF方法。为了考虑在.net1框架上也可以使用,我在保留原有代码结构的基础上,将WCF 用ASMX格式拷贝了一份,经过编译,完成可以运行:)
同时为了便于大家阅读源码,我还加入了中文说明(源码中注释很少,而且是EN文)。下面就是其主要的几个类的 定义和说明:
FileCollection 上传文件集合类,用于UI统一访问和操作:
Code
/// <summary> /// 文件集合管理类 /// 注:ObservableCollection是个泛型集合类,往其中添加或去除条目时(或者其中的条目实现了INotifyPropertyChanged的话,在属性变动时), /// 它会发出变化通知事件(先执行集合类中的同名属性)。这在做数据绑定时会非常方便,因为UI控件可以使用这些通知来知道自动刷新它们的值, /// 而不用开发人员编写代码来显式地这么做。 /// </summary> public class FileCollection : ObservableCollection < UserFile > { /// <summary> /// 已上传的累计(多文件)字节数 /// </summary> private double _bytesUploaded = 0 ; /// <summary> /// 已上传字符数占全部字节数的百分比 /// </summary> private int _percentage = 0 ; /// <summary> /// 当前正在上传的文件序号 /// </summary> private int _currentUpload = 0 ; /// <summary> /// 上传初始化参数,详情如下: /// MaxFileSizeKB: File size in KBs. /// MaxUploads: Maximum number of simultaneous uploads /// FileFilter: File filter, for example ony jpeg use: FileFilter=Jpeg (*.jpg) |*.jpg /// CustomParam: Your custom parameter, anything here will be available in the WCF webservice /// DefaultColor: The default color for the control, for example: LightBlue /// </summary> private string _customParams; /// <summary> /// 最大上传字节数 /// </summary> private int _maxUpload; /// <summary> /// 已上传的累计(多文件)字节数,该字段的修改事件通知会发给page.xmal中的TotalKB /// </summary> public double BytesUploaded { get { return _bytesUploaded; } set { _bytesUploaded = value; this .OnPropertyChanged( new PropertyChangedEventArgs( " BytesUploaded " )); } } /// <summary> /// 已上传字符数占全部字节数的百分比,该字段的修改事件通知会发给page.xmal中的TotalProgress /// </summary> public int Percentage { get { return _percentage; } set { _percentage = value; this .OnPropertyChanged( new PropertyChangedEventArgs( " Percentage " )); } } /// <summary> /// 构造方法 /// </summary> /// <param name="customParams"></param> /// <param name="maxUploads"></param> public FileCollection( string customParams, int maxUploads) { _customParams = customParams; _maxUpload = maxUploads; this .CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(FileCollection_CollectionChanged); } /// <summary> /// 依次加入所选的上传文件信息 /// </summary> /// <param name="item"></param> public new void Add(UserFile item) { item.PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged); base .Add(item); } /// <summary> /// 单个上传文件属性改变时 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> void item_PropertyChanged( object sender, PropertyChangedEventArgs e) { // 当属性变化为“从上传列表中移除” if (e.PropertyName == " IsDeleted " ) { UserFile file = (UserFile)sender; if (file.IsDeleted) { if (file.State == Constants.FileStates.Uploading) { _currentUpload -- ; UploadFiles(); } this .Remove(file); file = null ; } } // 当属性变化为“开始上传” else if (e.PropertyName == " State " ) { UserFile file = (UserFile)sender; // 此时file.State状态为ploading if (file.State == Constants.FileStates.Finished || file.State == Constants.FileStates.Error) { _currentUpload -- ; UploadFiles(); } } // 当属性变化为“上传进行中” else if (e.PropertyName == " BytesUploaded " ) { // 重新计算上传数据 RecountTotal(); } } /// <summary> /// 上传文件 /// </summary> public void UploadFiles() { lock ( this ) { foreach (UserFile file in this ) { // 当上传文件未被移除(IsDeleted)或是暂停时 if ( ! file.IsDeleted && file.State == Constants.FileStates.Pending && _currentUpload < _maxUpload) { file.Upload(_customParams); _currentUpload ++ ; } } } } /// <summary> /// 重新计算数据 /// </summary> private void RecountTotal() { // Recount total double totalSize = 0 ; double totalSizeDone = 0 ; foreach (UserFile file in this ) { totalSize += file.FileSize; totalSizeDone += file.BytesUploaded; } double percentage = 0 ; if (totalSize > 0 ) percentage = 100 * totalSizeDone / totalSize; BytesUploaded = totalSizeDone; Percentage = ( int )percentage; } /// <summary> /// 当添加或取消上传文件时触发 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> void FileCollection_CollectionChanged( object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { // 当集合信息变化时(添加或删除项)时,则重新计算数据 RecountTotal(); } }
上传文件信息类:
Code
/// <summary> /// 上传文件信息类 /// </summary> public class UserFile : INotifyPropertyChanged { /// <summary> /// 上传文件名称 /// </summary> private string _fileName; /// <summary> /// 是否取消上传该文件 /// </summary> private bool _isDeleted = false ; /// <summary> /// 上传文件的流信息 /// </summary> private Stream _fileStream; /// <summary> /// 当前上传文件状态 /// </summary> private Constants.FileStates _state = Constants.FileStates.Pending; /// <summary> /// 当前已上传的字节数(这里与FileCollection中的同名属性意义不同,FileCollection中的是已上传的所有文件的字节总数) /// </summary> private double _bytesUploaded = 0 ; /// <summary> /// 当前文件大小 /// </summary> private double _fileSize = 0 ; /// <summary> /// 已上传文件的百分比 /// </summary> private int _percentage = 0 ; /// <summary> /// 上传文件操作类 /// </summary> private FileUploader _fileUploader; /// <summary> /// 上传文件名称 /// </summary> public string FileName { get { return _fileName; } set { _fileName = value; NotifyPropertyChanged( " FileName " ); } } /// <summary> /// 当前上传文件的状态,注意这时使用了NotifyPropertyChanged来通知FileRowControl控件中的FileRowControl_PropertyChanged事件 /// </summary> public Constants.FileStates State { get { return _state; } set { _state = value; NotifyPropertyChanged( " State " ); } } /// <summary> /// 当前上传文件是否已被移除,注意这时使用了NotifyPropertyChanged来通知FileCollection类中的item_PropertyChanged事件 /// </summary> public bool IsDeleted { get { return _isDeleted; } set { _isDeleted = value; if (_isDeleted) CancelUpload(); NotifyPropertyChanged( " IsDeleted " ); } } /// <summary> /// 上传文件的流信息 /// </summary> public Stream FileStream { get { return _fileStream; } set { _fileStream = value; if (_fileStream != null ) _fileSize = _fileStream.Length; } } /// <summary> /// 当前文件大小 /// </summary> public double FileSize { get { return _fileSize; } } /// <summary> /// 当前已上传的字节数(这里与FileCollection中的同名属性意义不同,FileCollection中的是已上传的所有文件的字节总数) /// </summary> public double BytesUploaded { get { return _bytesUploaded; } set { _bytesUploaded = value; NotifyPropertyChanged( " BytesUploaded " ); Percentage = ( int )((value * 100 ) / _fileStream.Length); } } /// <summary> /// 已上传文件的百分比(这里与FileCollection中的同名属性意义不同,FileCollection中的是已上传字符数占全部字节数的百分比,该字段的修改事件通知会发给page.xmal中的TotalProgress) /// </summary> public int Percentage { get { return _percentage; } set { _percentage = value; NotifyPropertyChanged( " Percentage " ); } } /// <summary> /// 上传当前文件 /// </summary> /// <param name="initParams"></param> public void Upload( string initParams) { this .State = Constants.FileStates.Uploading; _fileUploader = new FileUploader( this ); _fileUploader.UploadAdvanced(initParams); _fileUploader.UploadFinished += new EventHandler(fileUploader_UploadFinished); } /// <summary> /// 取消上传,注:该文件仅在本类中的IsDeleted属性中使用 /// </summary> public void CancelUpload() { if (_fileUploader != null && this .State == Constants.FileStates.Uploading) { _fileUploader.CancelUpload(); } } /// <summary> /// 当前文件上传完成时 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> void fileUploader_UploadFinished( object sender, EventArgs e) { _fileUploader = null ; this .State = Constants.FileStates.Finished; } #region INotifyPropertyChanged Members private void NotifyPropertyChanged( string prop) { if (PropertyChanged != null ) { PropertyChanged( this , new PropertyChangedEventArgs(prop)); } } public event PropertyChangedEventHandler PropertyChanged; #endregion }
上传文件操作类(实现文件上传功能代码):
Code
/// <summary> /// 文件上传类 /// </summary> public class FileUploader { private UserFile _file; private long _dataLength; private long _dataSent; private SilverlightUploadServiceSoapClient _client; private string _initParams; private bool _firstChunk = true ; private bool _lastChunk = false ; public FileUploader(UserFile file) { _file = file; _dataLength = _file.FileStream.Length; _dataSent = 0 ; // 创建WCF端,此处被注释 // BasicHttpBinding binding = new BasicHttpBinding(); // EndpointAddress address = new EndpointAddress(new CustomUri("SilverlightUploadService.svc")); // _client = new UploadService.UploadServiceClient(binding, address); // _client = new UploadService.UploadServiceClient(); // _client.InnerChannel.Closed += new EventHandler(InnerChannel_Closed); // 创建webservice客户端 _client = new SilverlightUploadServiceSoapClient(); // 事件绑定 _client.StoreFileAdvancedCompleted += new EventHandler < System.ComponentModel.AsyncCompletedEventArgs > (_client_StoreFileAdvancedCompleted); _client.CancelUploadCompleted += new EventHandler < System.ComponentModel.AsyncCompletedEventArgs > (_client_CancelUploadCompleted); _client.ChannelFactory.Closed += new EventHandler(ChannelFactory_Closed); } #region /// <summary> /// 关闭ChannelFactory事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> void ChannelFactory_Closed( object sender, EventArgs e) { ChannelIsClosed(); } void _client_CancelUploadCompleted( object sender, System.ComponentModel.AsyncCompletedEventArgs e) { // 当取消上传完成后关闭Channel _client.ChannelFactory.Close(); } /// <summary> /// Channel被关闭 /// </summary> private void ChannelIsClosed() { if ( ! _file.IsDeleted) { if (UploadFinished != null ) UploadFinished( this , null ); } } /// <summary> /// 取消上传 /// </summary> public void CancelUpload() { _client.CancelUploadAsync(_file.FileName); } #endregion /// <summary> /// 上传完成事件处理对象声明 /// </summary> public event EventHandler UploadFinished; public void UploadAdvanced( string initParams) { _initParams = initParams; UploadAdvanced(); } /// <summary> /// 上传文件 /// </summary> private void UploadAdvanced() { byte [] buffer = new byte [ 4 * 4096 ]; int bytesRead = _file.FileStream.Read(buffer, 0 , buffer.Length); // 文件是否上传完毕? if (bytesRead != 0 ) { _dataSent += bytesRead; if (_dataSent == _dataLength) _lastChunk = true ; // 是否是最后一块数据,这样WCF会在服务端根据该信息来决定是否对临时文件重命名 // 上传当前数据块 _client.StoreFileAdvancedAsync(_file.FileName, buffer, bytesRead, _initParams, _firstChunk, _lastChunk); // 在第一条消息之后一直为false _firstChunk = false ; // 通知上传进度修改 OnProgressChanged(); } else { // 当上传完毕后 _file.FileStream.Dispose(); _file.FileStream.Close(); _client.ChannelFactory.Close(); } } /// <summary> /// 修改进度属性 /// </summary> private void OnProgressChanged() { _file.BytesUploaded = _dataSent; // 注:此处会先调用FileCollection中的同名属性,然后才是_file.BytesUploaded属性绑定 } void _client_StoreFileAdvancedCompleted( object sender, System.ComponentModel.AsyncCompletedEventArgs e) { // 检查WEB服务是否存在错误 if (e.Error != null ) { // 当错误时放弃上传 _file.State = Constants.FileStates.Error; } else { // 如果文件未取消上传的话,则继续上传 if ( ! _file.IsDeleted) UploadAdvanced(); } } }
服务端WCF代码如下(ASMX文件代码与其基本相同):
Code
[AspNetCompatibilityRequirements (RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] public class UploadService : IUploadService { private string _tempExtension = " _temp " ; #region IUploadService Members /// <summary> /// 取消上传 /// </summary> /// <param name="fileName"></param> public void CancelUpload( string fileName) { string uploadFolder = GetUploadFolder(); string tempFileName = fileName + _tempExtension; if (File.Exists(@HostingEnvironment.ApplicationPhysicalPath + " / " + uploadFolder + " / " + tempFileName)) File.Delete(@HostingEnvironment.ApplicationPhysicalPath + " / " + uploadFolder + " / " + tempFileName); } public void StoreFileAdvanced( string fileName, byte [] data, int dataLength, string parameters, bool firstChunk, bool lastChunk) { string uploadFolder = GetUploadFolder(); string tempFileName = fileName + _tempExtension; // 当上传文件的第一批数据时,先清空以往的相同文件名的文件(同名文件可能为上传失败造成) if (firstChunk) { // 删除临时文件 if (File.Exists(@HostingEnvironment.ApplicationPhysicalPath + " / " + uploadFolder + " / " + tempFileName)) File.Delete(@HostingEnvironment.ApplicationPhysicalPath + " / " + uploadFolder + " / " + tempFileName); // 删除目标文件 if (File.Exists(@HostingEnvironment.ApplicationPhysicalPath + " / " + uploadFolder + " / " + fileName)) File.Delete(@HostingEnvironment.ApplicationPhysicalPath + " / " + uploadFolder + " / " + fileName); } FileStream fs = File.Open(@HostingEnvironment.ApplicationPhysicalPath + " / " + uploadFolder + " / " + tempFileName, FileMode.Append); fs.Write(data, 0 , dataLength); fs.Close(); if (lastChunk) { // 将临时文件重命名为原来的文件名称 File.Move(HostingEnvironment.ApplicationPhysicalPath + " / " + uploadFolder + " / " + tempFileName, HostingEnvironment.ApplicationPhysicalPath + " / " + uploadFolder + " / " + fileName); // Finish stuff . FinishedFileUpload(fileName, parameters); } } /// <summary> /// 删除上传文件 /// </summary> /// <param name="fileName"></param> protected void DeleteUploadedFile( string fileName) { string uploadFolder = GetUploadFolder(); if (File.Exists(@HostingEnvironment.ApplicationPhysicalPath + " / " + uploadFolder + " / " + fileName)) File.Delete(@HostingEnvironment.ApplicationPhysicalPath + " / " + uploadFolder + " / " + fileName); } protected virtual void FinishedFileUpload( string fileName, string parameters) { } /// <summary> /// 获取上传路径 /// </summary> /// <returns></returns> protected virtual string GetUploadFolder() { return " Upload " ; } #endregion }
当然在该DEMO中,其支持两种初始化方式,一种是:
< asp:Silverlight ID ="Xaml1" runat ="server" Source ="~/ClientBin/mpost.SilverlightMultiFileUpload.xap" MinimumVersion ="2.0.30523" Width ="415" Height ="280" InitParameters ="MaxFileSizeKB=1000,MaxUploads=2,FileFilter=,CustomParam=1,DefaultColor=LightBlue" />
另一种是在ServiceReferences.ClientConfig中进行文件配置:
<
appSettings
>
<
add
key
="MaxFileSizeKB"
value
="50"
/>
<
add
key
="FileFilter"
value
="Photo's (*.jpg)|*.jpg"
/>
<
add
key
="FileFilter"
value
=""
/>
<
add
key
="MaxUploads"
value
="2"
/>
</
appSettings
>
而加载顺序要是自上而下,代码段如下(摘自Page.xaml.cs):
Code
/// <summary> /// 加载配置参数 then from .Config file /// </summary> /// <param name="initParams"></param> private void LoadConfiguration(IDictionary < string , string > initParams) { string tryTest = string .Empty; // 加载定制配置信息串 if (initParams.ContainsKey( " CustomParam " ) && ! string .IsNullOrEmpty(initParams[ " CustomParam " ])) _customParams = initParams[ " CustomParam " ]; if (initParams.ContainsKey( " MaxUploads " ) && ! string .IsNullOrEmpty(initParams[ " MaxUploads " ])) { int .TryParse(initParams[ " MaxUploads " ], out _maxUpload); } if (initParams.ContainsKey( " MaxFileSizeKB " ) && ! string .IsNullOrEmpty(initParams[ " MaxFileSizeKB " ])) { if ( int .TryParse(initParams[ " MaxFileSizeKB " ], out _maxFileSize)) _maxFileSize = _maxFileSize * 1024 ; } if (initParams.ContainsKey( " FileFilter " ) && ! string .IsNullOrEmpty(initParams[ " FileFilter " ])) _fileFilter = initParams[ " FileFilter " ]; // 从配置文件中获取相关信息 if ( ! string .IsNullOrEmpty(ConfigurationManager.AppSettings[ " MaxFileSizeKB " ])) { if ( int .TryParse(ConfigurationManager.AppSettings[ " MaxFileSizeKB " ], out _maxFileSize)) _maxFileSize = _maxFileSize * 1024 ; } if ( ! string .IsNullOrEmpty(ConfigurationManager.AppSettings[ " MaxUploads " ])) int .TryParse(ConfigurationManager.AppSettings[ " MaxUploads " ], out _maxUpload); if ( ! string .IsNullOrEmpty( ConfigurationManager.AppSettings[ " FileFilter " ])) _fileFilter = ConfigurationManager.AppSettings[ " FileFilter " ]; }
好了,今天的内容就先到这里了