在Silverlight上实现文件上传的例子在网上的还不多,特别是多文件上传和大文件上传的例子就更少了。当然
那些商品软件公司的产品除外。
目前的CodePlex上就有这样一个项目,其链接:http://www.codeplex.com/SLFileUpload/ ,他的个人主
站链接:http://www.michielpost.nl/
我在本地下载运行其代码后,发现“果然”很好用,而且代码写的也很规范。当然其也是免费的,但作者并不
拒绝各种名义上的“捐助(Donate)”。
下面就是其“汉化”后的运行截图,首先是多文件上传:
然后是大文件上传:
根据作者的README文件,其支持下面几个初始化参数:
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中,其支持两种初始化方式,一种是:
另一种是在ServiceReferences.ClientConfig中进行文件配置:
<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"];
}
好了,今天的内容就先到这里了,感兴趣的朋友可以在回复中进行讨论或给他(作者)留言,contact@MichielPost.nl
作者:代震军,daizhj
tags:silverlight,uploade, 文件上传, 多文件,大文件
中文注释的源码下载,请点击这里。
CodePlex, 下载链接:)