在Silverlight上实现文件上传的例子在网上的还不多,特别是多文件上传和大文件上传的例子就更少了。当然那些商品软件公司的产品除外。
目前的CodePlex上就有这样一个项目,其链接: ,他的个人主站链接: 我在本地下载运行其代码后,发现“果然”很好用,而且代码写的也很规范。当然其也是免费的,但作者并不拒绝各种名义上的“捐助(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统一访问和操作:
/// <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(); }}
上传文件信息类:
/// <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}
上传文件操作类(实现文件上传功能代码):
/// <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文件代码与其基本相同): [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):
/// <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"];}
好了,今天的内容就先到这里了,感兴趣的朋友可以在回复中进行讨论或给他(作者)留言, 作者:代震军,daizhj tags:silverlight,uploade, 文件上传, 多文件,大文件 中文注释的源码下载,请。 CodePlex, :)