Silverlight多文件(大文件)上传的开源项目

在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统一访问和操作:
    


/// <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(
thisnew 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(
thisnull);
        }
    }

    
/// <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<stringstring> 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, 下载链接:)
 

posted @ 2011-09-14 17:32  Areas  阅读(314)  评论(0编辑  收藏  举报