谈谈文件上传
公司一个项目中要实现对于复杂逻辑绑定的多文件异步上传,由于页面级组件都是用GWT编写,外加java本身的原因导致的form冗余,对于复杂逻辑的上传明显心有余而力不足,于是想到了用第三方组件实现。
由于本人对AS实在没有任何兴趣flash便舍弃,对于这个问题的解决不由想到了2个方案,silverlight/ActiveX +webservice
由于Silverlight具有平台的可移植性,相对ActiveX而言我更倾向于前者。当然Silverlight也有自己的问题,由于Silverlight 2.0的部分API封锁,导致无法直接通过File得到文件流,只能通过自带的OpenFileDialog.File方法来得到,不由增加了原来和GWT的耦合性。
- using System;
- using System.Collections.Generic;
- using System.Windows;
- using System.Windows.Controls;
- using System.IO;
- using System.Windows.Browser;
- using System.Threading;
- using wondersgroup.Silverlight.FileWebService;
- namespace wondersgroup.Silverlight
- {
- public partial class Page : UserControl
- {
- private const string client_openDialoged_fuc = "client_openDialoged";
- private const string client_uploadcompleted_fuc = "client_uploadcompleted";
- private Thread t;
- private List<FileInfo> fis=new List<FileInfo>();
- private delegate void FileDelegate(string name);
- public Page()
- {
- InitializeComponent();
- HtmlPage.RegisterScriptableObject("Attribute", this);
- }
- [ScriptableMember]
- public void OpenFileDialog()
- {
- FileStream fs;
- OpenFileDialog openFileDialog1 = new OpenFileDialog();
- openFileDialog1.Filter = "All files (*.*)|*.*";
- if (openFileDialog1.ShowDialog() ==true)
- {
- FileInfo fi = openFileDialog1.File;
- if ((fs = fi.OpenRead()) != null)
- {
- fis.Add(fi);
- HtmlPage.Window.Invoke(client_openDialoged_fuc, fi.Name);
- }
- }
- }
- [ScriptableMember]
- public void RemoveFile(string filename)
- {
- foreach (FileInfo fi in fis)
- {
- if (fi.Name == filename)
- {
- fis.Remove(fi);
- return;
- }
- }
- }
- [ScriptableMember]
- public void Upload()
- {
- bd.Text = "上传中";
- foreach (FileInfo fi in fis)
- {
- t = new Thread(new ParameterizedThreadStart(UploadFile));
- t.Start(fi);
- }
- }
- private void client_UploadFileCompleted(object sender, UploadFileCompletedEventArgs e)
- {
- if (this.Dispatcher.CheckAccess()) UploadFileCompleted(e.Result);
- else this.Dispatcher.BeginInvoke(new FileDelegate(UploadFileCompleted), e.Result);
- }
- private void UploadFileCompleted(string r)
- {
- HtmlPage.Window.Invoke(client_uploadcompleted_fuc, r);
- bd.Text = r + "上传完成";
- }
- private void UploadFile(object f)
- {
- FileInfo fi = (FileInfo)f;
- FileStream fs = fi.OpenRead();
- int nBytes = (int)fs.Length;
- byte[] ByteArray = new byte[nBytes];
- fs.Read(ByteArray, 0, nBytes);
- fs.Close();
- FileWebServiceSoapClient client = new FileWebServiceSoapClient();
- //client.Endpoint.Address = new System.ServiceModel.EndpointAddress(" http://" + HtmlPage.Document.DocumentUri.Host + "/FileWebService.asmx");
- client.UploadFileAsync(ByteArray, fi.Name);
- client.UploadFileCompleted += new EventHandler<UploadFileCompletedEventArgs>(client_UploadFileCompleted);
- }
- }
- }
为了避免大量和GWT的关联,因此同过了一些小窍门绕开了无法通过File得到文件流的问题,[ScriptableMember]属性标记的是可供客户端JS(GWT)调用的方法。
- <p>
- <div id="content">
- </div>
- <input id="Button1" type="button" value="加入" onclick="return Button1_onclick()" /><input
- id="Button2" type="button" value="上传" onclick="return Button2_onclick()" /></p>
- <form id="form1" runat="server" style="height: 100%;">
- <asp:ScriptManager ID="ScriptManager1" runat="server">
- </asp:ScriptManager>
- <div style="height: 100%;">
- <asp:Silverlight ID="Xaml1" runat="server" Source="ClientBin/UploadFile.xap" MinimumVersion="2.0.31005.0"
- Width="300px" Height="32px" OnPluginLoaded="pluginloaded" />
- </div>
- <script language="javascript" type="text/javascript">
- <!CDATA[
- var silverlightjs;
- function pluginloaded() {
- silverlightjs = $get('Xaml1').content.Attribute;
- }
- function Button1_onclick() {
- silverlightjs.OpenFileDialog();
- }
- function Button2_onclick() {
- silverlightjs.Upload();
- }
- function client_openDialoged(name) {
- var node = document.createElement("<div onclick=remove_node('" + name + "') id='content_" + name + "'>");
- node.innerText = name;
- document.getElementById("content").appendChild(node);
- }
- function remove_node(name) {
- var node = document.getElementById("content_" + name);
- document.getElementById("content").removeChild(node);
- silverlightjs.RemoveFile(name);
- }
- function client_uploadcompleted(msg) {
- alert(msg);
- }
- ]]>
- </script>
通过客户端调用silverlight的silverlightjs.OpenFileDialog();来获取文件的File,silverlight将每次获取的Fileinfo放入List后执行HtmlPage.Window.Invoke(“client_openDialoged”, fi.Name);来调取客户端js的client_openDialoged方法,当然当客户端从文件列表中删除一个文件后也调用silverlight的RemoveFile方法从List删除,多线程上传完毕后通过调用客户端的方法client_uploadcompleted来做到callback。值得注意的是客户端javascript调用silverlight方法需要 var silverlightjs;function pluginloaded() { silverlightjs = $get('Xaml1').content.Attribute; } 来获取对象并在服务器端初始化时注册HtmlPage.RegisterScriptableObject("Attribute", this);
运用这个上传组件主要是问题出来耦合性上,即OpenFileDialog由自己弹出。
关于ActiveX,由于公司暂时没有可供签名的CA,必须要求IE浏览器更改安全级别,并且即使这样做也无法再vista+IE8下使用,因此实用性以及安全性降低。在多线程环境下,ActiveX有一个很麻烦的地方,在于javascript调用ActiveX方法容易,而反过来却十分困难,问题出在一个COM组件上。通常,ActiveX调用js需要借助ms的组件Microsoft.mshtml,以此来访问自身容器的DOM,方法很简单。引用之后
- //obj为客户端js中的window对象
- //execscriptname为客户端js的函数名
- //msg 为参数
- HTMLWindow2Class html = (HTMLWindow2Class)obj;
- html.execScript(execscriptname+"('" + msg + "');", "javascript");
但是,这个方法有一个很大的BUG----------它是线程不安全的!!!如果单纯的不安全,我可以用委托或则建立线程是运用ParameterizedThreadStart来绕过,但是它的问题是HTMLWindow2Class html = (HTMLWindow2Class)obj;只能执行在主线程中,任何其他线程哪怕是你把window对象作为参数给他也是无法调用的,而且会出现一个未知错误!!诡异。。。
好吧,既然如此,只能用流氓方法解决了,在主线程中执行
- using System;
- using System.Windows.Forms;
- using System.IO;
- using System.Threading;
- using mshtml;
- using wondersgroup.FileWs;
- using System.Runtime.InteropServices;
- using System.Security.Permissions;
- using System.Collections.Generic;
- namespace wondersgroup.ActiveX
- {
- [PermissionSet(SecurityAction.Assert, Name = "FullTrust")]
- [ClassInterface(ClassInterfaceType.AutoDual)]
- public class FileUpload : System.Windows.Forms.UserControl, IFileUpload
- {
- #region Initialization
- private string files;
- private string url= (new FileWebService()).Url;
- private List<string> msg = new List<string>();
- private List<Thread> threads = new List<Thread>();
- public FileUpload()
- {
- }
- protected override void Dispose(bool disposing)
- {
- foreach (Thread t in threads)
- {
- t.Abort();
- }
- base.Dispose();
- }
- #endregion
- #region Public
- public String FilePaths
- {
- get { return files; }
- set
- {
- files = value;
- }
- }
- public String Url
- {
- get { return url; }
- set
- {
- url = value;
- }
- }
- public void Upload(object window,string execscriptname)
- {
- string[] _files = files.Split('|');
- foreach (string s in _files)
- {
- Thread t = new Thread(new ParameterizedThreadStart(UploadFile));
- threads.Add(t);
- t.Start(s);
- }
- int _threadnum = _files.Length;
- string _temp = string.Empty;
- while (true && _threadnum != 0)
- {
- if (msg.Count != 0)
- {
- _temp = msg[msg.Count - 1];
- lock (msg)
- {
- msg.Remove(_temp);
- }
- UploadFileCompleted(window, _temp, execscriptname);
- _threadnum--;
- }
- Thread.Sleep(1000);
- }
- }
- #endregion
- #region Private
- private void UploadFile(object s)
- {
- string _file = s.ToString();
- if (File.Exists(_file))
- {
- FileInfo fi = new FileInfo(_file);
- FileStream fs = fi.OpenRead();
- int nBytes = (int)fs.Length;
- byte[] ByteArray = new byte[nBytes];
- fs.Read(ByteArray, 0, nBytes);
- fs.Close();
- FileWebService client = new FileWebService();
- string rt = client.UploadFile(ByteArray, fi.Name);
- lock (msg)
- {
- msg.Add(rt);
- }
- }
- }
- private void UploadFileCompleted(object obj, string msg,string execscriptname)
- {
- try
- {
- HTMLWindow2Class html = (HTMLWindow2Class)obj;
- html.execScript(execscriptname+"('" + msg + "');", "javascript");
- }
- catch (Exception e) { MessageBox.Show(e.Message, "ActiveX Error"); }
- }
- #endregion
- }
- }
接口
- using System;
- using System.Collections.Generic;
- using System.Text;
- using System.Runtime.InteropServices;
- namespace wondersgroup.ActiveX
- {
- [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
- public interface IFileUpload
- {
- /// <summary>
- /// Get or set the files' path with "|" that will upload
- /// eg:c:\aa.txt|d:\bb.txt
- /// </summary>
- String FilePaths { set; get; }
- /// <summary>
- /// Get or set the webservice url
- /// default is http://localhost:1707/FileWebService.asmx
- /// </summary>
- String Url { set; get; }
- /// <summary>
- /// Upload files set by FilePaths
- /// </summary>
- /// <param name="window">javascript window object</param>
- /// <param name="execscriptname">the js function to callback</param>
- void Upload(object window, string execscriptname);
- }
- }
客户端调用则相对比较简单
- <script>
- var obj = new ActiveXObject("wondersgroup.FileUpload");
- </script>
- <div id="content">
- </div>
- <input id="File1" type="file" /><input id="Button1" type="button" value="add to list"
- onclick="client_openDialoged(File1.value);" />
- <input type="submit" name="button" id="button" value="提交至ActiveX" onclick="post()" />
- <input type="button" value='上传' onclick='upload()'>
- <script>
- var i = 0;
- obj.Url = "http://localhost:1707/FileWebService.asmx";
- function client_openDialoged(name) {
- var node = document.createElement("<div onclick=remove_node('" + i + "') id='content_" + i + "'>");
- node.innerText = name;
- document.getElementById("content").appendChild(node);
- i++;
- }
- function post() {
- var _str = "";
- var temp = document.getElementById("content").childNodes;
- for (ii = 0; ii < temp.length; ii++) {
- _str += temp(ii).innerText + (ii == (temp.length - 1) ? "" : "|");
- }
- obj.FilePaths = _str;
- }
- function upload() {
- obj.Upload(window, "uploadSucc");
- }
- function remove_node(name) {
- var node = document.getElementById("content_" + name);
- document.getElementById("content").removeChild(node);
- }
- function uploadSucc(str) {
- alert("remote result: "+str);
- }
- </script>
唯一一点要注意的是手工使用regasm FileUpload.dll /tlb /codebase注册这个dll
- //webservice
- [WebMethod(Description = "Web 服务提供的方法,返回是否文件上载成功与否。")]
- public string UploadFile(byte[] fs, string filename)
- {
- try
- {
- MemoryStream m = new MemoryStream(fs);
- string relative=DateTime.Now.ToString("yyyy_MM_dd_mm_ss_") + filename;
- string path = Server.MapPath("uload") + "\\" + relative;
- FileStream f = new FileStream(path, FileMode.Create);
- m.WriteTo(f);
- m.Close();
- f.Close();
- f = null;
- m = null;
- return relative;
- }
- catch(Exception e)
- {
- return e.Message;
- }
- }
-----------------------------------------------------------------------------------------------------------------
| 戴佳顺 | msn:edwin19861218@hotmail.com | QQ:1961218 | Web:http://www.dumuzi.cn |