
摘要:
文件上传是日常开过程中最常用的功能之一,目前实现文件上传的方式多种多样。这其中较为复杂的情况就是关于大文件、多文件上传的问题,目前解决大文件、多文件上传一般借助于Js或者flash组件,今天就同大家一起看一下如何使用silverlight实现这个功能,而且功能和用户体验相对会更好一些。
主要内容:
一、组件特点
二、实现原理
三、编码实现
一、组件特点对于今天要说的组件姑且叫做"CmjUpload"吧,方便称呼。目前有很多上传组件来辅助完成日常开发,"CmjUpload"有什么特点呢:
解决大文件、多文件上传问题 基于asp.net上传,不需要部署WCF、WebService *** 作方便 接口丰富、灵活性强,配置使用方便。 支持选择、拖拽两种文件添加方式上传,用户体验好。 支持取消、暂停、继续 *** 作满足续传要求。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 _requestUrl = ""; 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,buffer.Length); if(size>0) { rqstStm.Write(buffer,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); } /// <summary> /// 继续上传 /// </summary> /// <param name="filename"></param> /// <param name="uploadedLength"></param> //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{ /// <summary> /// 文件队列管理者 /// </summary> 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,fileUpload> _fileUploader = null; private int index = 0; private fileQueue() { _fileIndexs = new Dictionary<string,int>(); _files = new Dictionary<string,fileInfo>(); _fileAeas = new Dictionary<string,fileArea>(); _fileUploader = new Dictionary<string,fileUpload>(); } public static fileQueue Instance() { lock (_lock) { if (_fileQueue == null) { _fileQueue = new fileQueue(); } } return _fileQueue; } public voID Add(fileInfo file) { _fileIndexs.Add(file.name,index); _files.Add(file.name,file); fileArea fileAerea = new fileArea(file); _fileAeas.Add(file.name,fileAerea); ++index; } public voID Remove(string filename) { _fileIndexs.Remove(filename); _files.Remove(filename); _fileAeas.Remove(filename); _fileUploader.Remove(filename); } public Dictionary<string,fileInfo> files { get { return _files; } set { _files = value; } } public Dictionary<string,fileArea> fileAreas { get { return _fileAeas; } set { _fileAeas = value; } } public Dictionary<string,fileUpload> fileUploader { get { return _fileUploader; } set { _fileUploader = value; } } public int GetfileIndex(string filename) { int i=-1; if (_fileIndexs.ContainsKey(filename)) { i = _fileIndexs[filename]; } return i; } public voID Clear() { string[] tempfilenames=new string[this.files.Count]; this.files.Keys.copyTo(tempfilenames,0); foreach (string filename in tempfilenames) { this.Remove(filename); } } }} fileArea用于构建每个文件的UI展示:
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.windows.Media.Imaging;using System.IO;using Cmj.MyWeb.MySilverlight.MyUserControl.button; namespace CmjUpload{ public class fileArea { private fileInfo _file = null; //private int _number = 0; private GrID _container = null; private TextBlock _name = null; private Image _thumnail = null; private Progressbar _progress = null; private TextBlock _size = null; private TextBlock _percent = null; private Cancel _cancel = null; private Pause _pause = null; private Play _continue = null; private Check _complete = null; public fileArea(fileInfo file) { _file = file; //_number = number; _container = new GrID(); _container.name = "fileArea_container_" + file.name; _container.ColumnDeFinitions.Add(new ColumnDeFinition() { WIDth = new GrIDLength(60)}); _container.ColumnDeFinitions.Add(new ColumnDeFinition()); _container.ColumnDeFinitions.Add(new ColumnDeFinition() { WIDth=new GrIDLength(60)}); _container.ColumnDeFinitions.Add(new ColumnDeFinition() { WIDth = new GrIDLength(60) }); _container.Height = 50; _thumnail = new Image(); _thumnail.name = "fileArea_thumnail_" + file.name; _thumnail.Height = 40; _thumnail.source = fileIcon.Instance().GetthumbnailImage(file); _thumnail.VerticalAlignment = VerticalAlignment.Bottom; _thumnail.HorizontalAlignment = HorizontalAlignment.Center; GrID.SetColumn(_thumnail,0); _progress = new Progressbar(); _progress.name = "fileArea_progress_" + file.name; _progress.Minimum = 0; _progress.Maximum = 100; _progress.Value = 0; _progress.Height = 20; _progress.VerticalAlignment = VerticalAlignment.Bottom; //_progress.HorizontalAlignment = HorizontalAlignment.Center; GrID.SetColumn(_progress,1); _name = new TextBlock(); _name.name = "fileArea_name_" + file.name; _name.Text = file.name; _name.VerticalAlignment = VerticalAlignment.Bottom; _name.HorizontalAlignment = HorizontalAlignment.left; _name.margin = new Thickness(10,2); GrID.SetColumn(_name,1); _percent = new TextBlock(); _percent.name = "fileArea_percent_" + file.name; _percent.VerticalAlignment = VerticalAlignment.Bottom; _percent.HorizontalAlignment = HorizontalAlignment.Right; _percent.margin = new Thickness(0,10,2); GrID.SetColumn(_percent,1); _size = new TextBlock(); _size.name = "fileArea_size_" + file.name; _size.VerticalAlignment = VerticalAlignment.Bottom; _size.HorizontalAlignment = HorizontalAlignment.Right; GrID.SetColumn(_size,2); _cancel = new Cancel(); _cancel.name = "fileArea_cancel_"+file.name; _cancel.WIDth = 15; _cancel.Height = 15; _cancel.VerticalAlignment = VerticalAlignment.Bottom; //_cancel.Click += new RoutedEventHandler(_cancel_Click); GrID.SetColumn(_cancel,3); _pause = new Pause(); _pause.name = "fileArea_pause_" + file.name; _pause.WIDth = 15; _pause.Height = 15; _pause.VerticalAlignment = VerticalAlignment.Bottom; _pause.Visibility = Visibility.Collapsed; GrID.SetColumn(_pause,3); _continue = new Play(); _continue.name = "fileArea_continue_" + file.name; _continue.WIDth = 15; _continue.Height = 15; _continue.VerticalAlignment = VerticalAlignment.Bottom; _continue.Visibility = Visibility.Collapsed; GrID.SetColumn(_continue,3); _complete = new Check(); _complete.name = "fileArea_complete_" + file.name; _complete.WIDth = 18; _complete.Height = 18; _complete.VerticalAlignment = VerticalAlignment.Bottom; _complete.Visibility = Visibility.Collapsed; GrID.SetColumn(_complete,3); _container.Children.Add(_thumnail); _container.Children.Add(_progress); _container.Children.Add(_size); _container.Children.Add(_name); _container.Children.Add(_percent); _container.Children.Add(_cancel); _container.Children.Add(_pause); _container.Children.Add(_continue); _container.Children.Add(_complete); } public GrID Container { get { return _container; } set { _container = value; } } public TextBlock name { get { return _name; } set { _name = value; } } public Image Thumnail { get { return _thumnail; } set { _thumnail = value; } } public Progressbar Progress { get { return _progress; } set { _progress = value; } } public TextBlock Size { get { return _size; } set { _size = value; } } public TextBlock Percent { get { return _percent; } set { _percent = value; } } public Cancel Cancel { get { return _cancel; } set { _cancel = value; } } public Pause Pause { get { return _pause; } set { _pause = value; } } public Play Continue { get { return _continue; } set { _continue = value; } } public Check Complete { get { return _complete; } set { _complete = value; } } }} ExposeInterface用于向客户端调用提供 *** 作接口:
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.Collections.Generic;using System.windows.browser; namespace CmjUpload.Config{ public class ExposeInterface { private static object _lock = new object(); private static ExposeInterface _exposeInterface = null; private string _fileTypes = string.Empty; private string _fileDialogFilter = string.Empty; private long _limitSize = 0; private int _limitCount = 0; private ExposeInterface() { } public static ExposeInterface Instance() { lock (_lock) { if (_exposeInterface == null) { _exposeInterface = new ExposeInterface(); } } return _exposeInterface; } [ScriptableMember] public string fileTypes //ex:*.jpg|*.gif { get { return _fileTypes; } set { _fileTypes = value; } } [ScriptableMember] public string fileDialogFilter { get { if (this._fileDialogFilter == string.Empty&&this._fileTypes!=string.Empty) { string[] types = this._fileTypes.Split('|'); string[] filters=new string[types.Length]; for(int i=0;i<types.Length;++i) { filters[i] = "("+types[i] +")|"+ types[i]; } _fileDialogFilter = string.Join("|",filters); } return _fileDialogFilter; } set { _fileDialogFilter = value; } } [ScriptableMember] public long limitSize//单位 MB { get { return _limitSize; } set { _limitSize = value; } } [ScriptableMember] public int limitCount { get { return _limitCount; } set { _limitCount = value; } } [ScriptableMember] public string RequestUrl { get; set; } public List<string> GetfileExtensions() { List<string> extensions = new List<string>(); string[] types = this._fileTypes.Split(new char[] { '|' },StringSplitoptions.RemoveEmptyEntrIEs); foreach(string type in types) { extensions.Add(type.Trimstart('*')); } return extensions; } }} CmjUpload用于提供给服务器端进行文件 *** 作,服务端只需要简单调用其Save方法就可以进行文件保存:
using System;using System.Collections.Generic;using System.Web;using System.IO; namespace CmjUpload.Web.Util{ public class CmjUpload { public static voID Save(string relationPath) { string filename = httpContext.Current.Request["filename"]; Save(relationPath,filename); } public static voID Save(string relationPath,string outputname) { string status = httpContext.Current.Request["status"]; if (status == "Start") { using (fileStream fs = file.Create(Path.Combine(relationPath,outputname))) { Savefile(httpContext.Current.Request.inputStream,fs); } } else if (status == "Uploading") { using (fileStream fs = file.Open(Path.Combine(relationPath,outputname),fileMode.Append)) { Savefile(httpContext.Current.Request.inputStream,fs); } } else if (status == "Completed") { httpContext.Current.Response.Write("{success:true}"); } } private static voID Savefile(Stream stream,fileStream fs) { byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = stream.Read(buffer,buffer.Length)) != 0) { fs.Write(buffer,bytesRead); } } }} OK,其他的代码就不再贴出了,看一下客户端如何使用吧。
为了方便使用客户端提供一个公用Js类CmjUpload.Js:
//注意要在控件加载完之后调用,建议放到插件onload事件中初始化(<param name="onLoad" value="pluginLoaded" />)var CmjUpload = function (options) { var uploader = null; if (options.hasOwnProperty("ID")) {//组件ID uploader = document.getElementByID(options.ID); } else { alert("Please configure the ID attribute before use CmjUpload component!"); return; } if (options.hasOwnProperty("requestUrl")) {//请求的url uploader.content.cmjUpload.RequestUrl = options.requestUrl; } else { alert("Please configure the requestUrl attribute before use CmjUpload component!"); return; } if (options.hasOwnProperty("fileTypes")) {//文件类型限制 uploader.content.cmjUpload.fileTypes = options.fileTypes; } if (options.hasOwnProperty("limitCount")) {//每批次上传的文件数 uploader.content.cmjUpload.limitCount = options.limitCount; } if (options.hasOwnProperty("limitSize")) {//单个文件大小限制 uploader.content.cmjUpload.limitSize = options.limitSize; }}CmjUpload.prototype.onBeforefileUpload = function () {//单个文件上传之前执行 } CmjUpload.prototype.onfileUploading = function () { //单个文件上传时执行 } CmjUpload.prototype.onfileUploaded = function () {//单个文件上传完毕执行 } CmjUpload.prototype.onBatchUploaded = function () {//整个批次的文件上传完毕执行 } 然后在页面添加上传组件(本地化语言在param中进行配置):
<object ID="cmjUpload1" data="data:application/x-silverlight-2," type="application/x-silverlight-2" wIDth="100%" height="100%"> <param name="source" value="ClIEntBin/CmjUpload.xap"/> <param name="onError" value="onSilverlightError" /> <param name="onLoad" value="pluginLoaded" /><!--注意这里,必须保证配置信息在页面加载之后执行--> <param name="culture" value="en-US" /><!--注意这里本地化配置--> <param name="uiculture" value="en-US" /><!--注意这里本地化配置--> <param name="background" value="white" /> <param name="minRuntimeVersion" value="4.0.50826.0" /> <param name="autoUpgrade" value="true" /> <a href="http://go.microsoft.com/fwlink/?linkID=149156&v=4.0.50826.0" > <img src="http://go.microsoft.com/fwlink/?linkID=161376" alt="Get Microsoft Silverlight" /> </a></object>
使用时在页面引用该类,进行ID和url等信息配置,具体的配置内容上面Js已经注释的很清楚,这里不再赘余。例如对页面做如下配置:
pluginLoaded=function(){ var upload = new CmjUpload({ ID: 'cmjUpload1',requestUrl: 'http://localhost:3407/Upload.aspx',fileTypes: '*.jpg|*.png|*.wmv|*.rar|*.iso',limitCount: 5,limitSize: 150 });} 后台文件执行文件保存 *** 作:
using System;using System.Collections.Generic;using System.linq;using System.Web;using System.Web.UI;using System.Web.UI.WebControls;using System.IO; namespace CmjUpload.Web{ public partial class Upload : System.Web.UI.Page { protected voID Page_Load(object sender,EventArgs e) { CmjUpload.Web.Util.CmjUpload.Save("f:\"); } }} 下面是使用效果:
类型限制
大小限制
数量限制
删除一个文件
上传中
上传暂停
完成上传
手动清空(即使不手动清空继续上传文件会自动清空,清空 *** 作主要用于上传暂停后不需要上传的清空)
下面看看本地化设置为英文后的效果
我们通过修改limitCount来看一下大文件传输,好几个G的文件同时传输也没有问题
OK,最后附上组件下载,使用方法上面说的也比较清楚了。关于组件源代码就不再提供下载了,相信读完这篇文章要实现并不难,真正需要的话可以给我留言或发邮件KenshinCui@hotmail.com。
组件下载
| 本作品采用知识共享署名 2.5 中国大陆许可协议进行许可,欢迎转载,演绎或用于商业目的。但转载请注明来自崔江涛(KenshinCui),并包含相关链接。 |
以上是内存溢出为你收集整理的Silverlight之文件上传组件全部内容,希望文章能够帮你解决Silverlight之文件上传组件所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)