主要内容:
一、组件特点
二、实现原理
三、编码实现
对于今天要说的组件姑且叫做"CmjUpload"吧,方便称呼。目前有很多上传组件来辅助完成日常开发,"CmjUpload"有什么特点呢:
OK,就说那么多吧,主要是让大家有兴趣看下去,其实之所以有今天的话题主要还是为了学习以及满足实际开发需求。
在Silverlight中要实现上传有很多方式,例如说使用WCF或者WebService,但是考虑到实际情况,这里没有选择以上两种方式,而是选择了WebRequest方式。原因比较简单,部署十分方便,不需要为了上传组件而进行额外的配置。Silverlight中使用WebRequest同其他.Net开发中的使用方式是类似的,不同的是Silverlight中很多操作都是异步的,当然WebRequest也不例外。此外,在这里需要对一个文件分块发送,一方面可以解决大文件上传问题,另一方面可以实时显示文件上传进度。下面一个简单的交互过程:
当然要完成整个组件远不止上面说的这些,UI的设计,组件的本地化,用户接口的设计等都是必须思考的问题。下面是组件界面原型:
界面分为两个区域:文件显示区域和操作区域,当然这里的文件区域本身也是可以操作的,例如如果你不想点击按钮选择文件的话,可以选择直接拖拽一个或多个文件到文件区域。还可以对已添加的文件进行删除操作,对正在上传的文件进行暂停和续传操作。此外文件区域的设计主要提供文件信息显示,例如缩略图、上传进度、文件名称、文件大小等信息。操作区域一方面提供文件整体信息的显示(例如文件总数、已上传数等),另一方面提供了文件浏览、上传、清空操作。
下面是类的设计:
在上图中我们可以看出有三个包:Core、Config、Util。
Core是核心包,里面主要包括文件队列管理(FileQueue)、文件上传控制(FileUpload)、文件界面区域(FileArea)、文件大小单位转换(FileSize)、缩略图控制(FileIcon)。
Config是配置和接口包,主要包括组件设计级别常量(注意不是用户级别也不是开发级别,开发级别配置在接口中进行)(UploadConstant)、客户端开发接口(ExposeInterface)、本地化实现(Localization)、接口注册(ClientInteraction)。
Util包主要包括一些常用辅助类,主要包括xml操作(XmlHelper)、服务器端文件保存辅助类(CmjUpload)。
有了上面的分析相信下面的实现就相当容易理解了,首先看一下文件上传类FileUpload:
using System; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Ink; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; using System.Text; using System.IO; using System.Windows.Threading; using CmjUpload.Util; using CmjUpload.Config; namespace CmjUpload { public class FileUpload { //开始上传 public delegate void StartUploadHanler( object sender,EventArgs e); public event StartUploadHanler StartUpload; public void OnStartUpload( object sender, EventArgs e) { if (StartUpload != null ) { StartUpload(sender, e); } } // 上传 public delegate void UploadingHanler( object sender, ProgressArgs e); public event UploadingHanler Uploading; public void OnUploading( object sender, ProgressArgs e) { if (Uploading != null ) { Uploading(sender,e); } } //上传结束 public delegate void UploadCompletedHanler( object sender, EventArgs e); public event UploadCompletedHanler UploadCompleted; public void OnUploadCompleted( object sender, EventArgs e) { if (UploadCompleted != null ) { UploadCompleted(sender, e); } } private string _fileName = "" ; private long _fileLength = 0; private long _blockLength = 4096; //单次上传文件大小 private long _postedLength = 0; //已传输文件大小 private long _nextLength = 0; //下次传输的文件大小 private bool _firstUpload = true ; private BinaryReader _fileReader = null ; private UploadStatus _uploadStatus = UploadStatus.Start; public FileInfo File { get ; set ; } //public long PostedLength //{ // get // { // return _postedLength; // } // set // { // _postedLength = value; // } //} public UploadStatus Status { get { return _uploadStatus; } set { _uploadStatus = value; } } public void Upload(FileInfo file) { this .File = file; //XmlHelper xmlHelper = new XmlHelper("Config/CmjUploadConfig.xml"); //_requestUrl=xmlHelper.GetAttibuteValue("Upload", "RequestUrl"); _requestUrl = ExposeInterface.Instance().RequestUrl; this ._fileName = this .File.Name; this ._fileLength = this .File.Length; this ._blockLength = FileSize.GetLockSize( this ._fileLength); //this._postedLength = 0; _fileReader = new BinaryReader(file.OpenRead()); //_uploadStatus = UploadStatus.Start; if (_fileLength < _blockLength) { _nextLength = _fileLength; } else { _nextLength = _blockLength; } OnStartUpload( this , new EventArgs()); UploadInBlock(); } public void UploadInBlock() //上传一块数据 { UriBuilder uriBuilder = new UriBuilder( new Uri(_requestUrl, UriKind.Absolute)); uriBuilder.Query = string .Format( "fileName={0}&status=" +_uploadStatus, this ._fileName); WebRequest request = WebRequest.Create(uriBuilder.Uri); request.Method = "POST" ; request.ContentType = "multipart/mixed" ; //注意这里 request.ContentLength = _nextLength; if (_firstUpload) { _uploadStatus = UploadStatus.Uploading; _firstUpload = false ; } request.BeginGetRequestStream((IAsyncResult asyncResult) => { WebRequest rqst = asyncResult.AsyncState as WebRequest; Stream rqstStm = rqst.EndGetRequestStream(asyncResult); byte [] buffer = new byte [_blockLength]; int size = _fileReader.Read(buffer, 0, buffer.Length); if (size>0) { rqstStm.Write(buffer, 0, size); rqstStm.Flush(); _postedLength += size; if ((_fileLength - _postedLength) < _blockLength) { _nextLength = _fileLength-_postedLength; } } rqstStm.Close(); rqst.BeginGetResponse((IAsyncResult ascResult) => //开始数据传输 { OnUploading( this , new ProgressArgs() { Percent = (( double )_postedLength / ( double )_fileLength) }); WebRequest webRequest = ascResult.AsyncState as WebRequest; WebResponse webResponse = (WebResponse)webRequest.EndGetResponse(ascResult); StreamReader reader = new StreamReader(webResponse.GetResponseStream()); string responsestring = reader.ReadToEnd(); reader.Close(); if (_postedLength >= _fileLength) { _uploadStatus = UploadStatus.Complelte; } if (_uploadStatus == UploadStatus.Uploading) { UploadInBlock(); } //else if(_uploadStatus==UploadStatus.Cancel) //{ // return; //} else if (_uploadStatus==UploadStatus.Complelte) { _fileReader.Close(); OnUploadCompleted( this , new EventArgs()); } }, request); }, request); } /// /// 继续上传 /// /// /// //public static void ContinueUplaod(string fileName,long uploadedLength) //{ //} } //上传进度参数 public class ProgressArgs:EventArgs { public double Percent { get ; set ; } } public enum UploadStatus { Start, Uploading, Cancel, Complelte } } |
在这个类中需要注意的是状态的控制,因为组件需要实现文件暂停、续传功能,并且每次请求时需要发送相应的操作状态;另一点就是对外公开了三个事件,用于给UI提供进度支持和状态通知。
FileQueue管理整个文件队列,控制着界面UI、文件上传等信息:
using System; using System.Collections.Generic; using System.IO; namespace CmjUpload { /// /// 文件队列管理者 /// public class FileQueue { private static object _lock = new object (); private static FileQueue _fileQueue = null ; private Dictionary< string , int > _fileIndexs = null ; //文件同索引对应关系 private Dictionary< string ,FileInfo> _files = null ; private Dictionary< string ,FileArea> _fileAeas = null ; private Dictionary< string
|