谈谈文件上传

 公司一个项目中要实现对于复杂逻辑绑定的多文件异步上传,由于页面级组件都是用GWT编写,外加java本身的原因导致的form冗余,对于复杂逻辑的上传明显心有余而力不足,于是想到了用第三方组件实现。
 由于本人对AS实在没有任何兴趣flash便舍弃,对于这个问题的解决不由想到了2个方案,silverlight/ActiveX +webservice
 由于Silverlight具有平台的可移植性,相对ActiveX而言我更倾向于前者。当然Silverlight也有自己的问题,由于Silverlight 2.0的部分API封锁,导致无法直接通过File得到文件流,只能通过自带的OpenFileDialog.File方法来得到,不由增加了原来和GWT的耦合性。

 
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Windows;
  4. using System.Windows.Controls;
  5. using System.IO;
  6. using System.Windows.Browser;
  7. using System.Threading;
  8. using wondersgroup.Silverlight.FileWebService;
  9.  
  10. namespace wondersgroup.Silverlight
  11. {
  12.     public partial class Page : UserControl
  13.     {
  14.         private const string client_openDialoged_fuc = "client_openDialoged";
  15.         private const string client_uploadcompleted_fuc = "client_uploadcompleted";
  16.  
  17.         private  Thread t;
  18.         private List<FileInfo> fis=new List<FileInfo>();
  19.         private delegate void FileDelegate(string name);
  20.         public Page()
  21.         {
  22.             InitializeComponent();
  23.             HtmlPage.RegisterScriptableObject("Attribute"this);
  24.         }
  25.         [ScriptableMember]
  26.         public void OpenFileDialog()
  27.         {
  28.             FileStream fs;
  29.             OpenFileDialog openFileDialog1 = new OpenFileDialog();
  30.             openFileDialog1.Filter = "All files (*.*)|*.*";
  31.             if (openFileDialog1.ShowDialog() ==true)
  32.             {
  33.  
  34.                     FileInfo fi = openFileDialog1.File;
  35.                     if ((fs = fi.OpenRead()) != null)
  36.                     {
  37.                         fis.Add(fi); 
  38.                         HtmlPage.Window.Invoke(client_openDialoged_fuc, fi.Name);
  39.                     }
  40.             }
  41.         }
  42.         [ScriptableMember]
  43.         public void RemoveFile(string filename)
  44.         {
  45.             foreach (FileInfo fi in fis)
  46.             {
  47.                 if (fi.Name == filename)
  48.                 {
  49.                     fis.Remove(fi);
  50.                     return;
  51.                 }
  52.             }
  53.         }
  54.  
  55.         [ScriptableMember]
  56.         public void Upload()
  57.         {
  58.             bd.Text = "上传中";
  59.             foreach (FileInfo fi in fis)
  60.             {
  61.                 t = new Thread(new ParameterizedThreadStart(UploadFile));
  62.                 t.Start(fi);
  63.             }
  64.         }
  65.         private void client_UploadFileCompleted(object sender, UploadFileCompletedEventArgs e)
  66.         {
  67.             if (this.Dispatcher.CheckAccess()) UploadFileCompleted(e.Result);
  68.             else this.Dispatcher.BeginInvoke(new FileDelegate(UploadFileCompleted), e.Result);
  69.         }
  70.         private void UploadFileCompleted(string r)
  71.         {
  72.             HtmlPage.Window.Invoke(client_uploadcompleted_fuc, r);
  73.             bd.Text = r + "上传完成";
  74.         }
  75.  
  76.         private void UploadFile(object f)
  77.         {
  78.             FileInfo fi = (FileInfo)f;
  79.             FileStream fs = fi.OpenRead();
  80.             int nBytes = (int)fs.Length;
  81.             byte[] ByteArray = new byte[nBytes];
  82.             fs.Read(ByteArray, 0, nBytes);
  83.             fs.Close();
  84.             FileWebServiceSoapClient client = new FileWebServiceSoapClient();
  85.             //client.Endpoint.Address = new System.ServiceModel.EndpointAddress(" http://" + HtmlPage.Document.DocumentUri.Host + "/FileWebService.asmx");
  86.             client.UploadFileAsync(ByteArray, fi.Name);
  87.             client.UploadFileCompleted += new EventHandler<UploadFileCompletedEventArgs>(client_UploadFileCompleted);
  88.         }
  89.  
  90.  
  91.     }
  92. }

为了避免大量和GWT的关联,因此同过了一些小窍门绕开了无法通过File得到文件流的问题,[ScriptableMember]属性标记的是可供客户端JS(GWT)调用的方法。

 
  1. <p>
  2.       <div id="content">
  3.       </div>
  4.       <input id="Button1" type="button" value="加入" onclick="return Button1_onclick()" /><input
  5.           id="Button2" type="button" value="上传" onclick="return Button2_onclick()" /></p>
  6.   <form id="form1" runat="server" style="height: 100%;">
  7.   <asp:ScriptManager ID="ScriptManager1" runat="server">
  8.   </asp:ScriptManager>
  9.   <div style="height: 100%;">
  10.       <asp:Silverlight ID="Xaml1" runat="server" Source="ClientBin/UploadFile.xap" MinimumVersion="2.0.31005.0"
  11.           Width="300px" Height="32px" OnPluginLoaded="pluginloaded" />
  12.   </div>
  13.  
  14. <script language="javascript" type="text/javascript">
  15.  <!CDATA[
  16.       var silverlightjs;
  17.       function pluginloaded() {
  18.           silverlightjs = $get('Xaml1').content.Attribute;
  19.       }
  20.       function Button1_onclick() {
  21.           silverlightjs.OpenFileDialog();
  22.       }
  23.       function Button2_onclick() {
  24.           silverlightjs.Upload();
  25.       }
  26.       function client_openDialoged(name) {
  27.           var node = document.createElement("<div onclick=remove_node('" + name + "') id='content_" + name + "'>");
  28.           node.innerText = name;
  29.           document.getElementById("content").appendChild(node);
  30.       }
  31.       function remove_node(name) {
  32.           var node = document.getElementById("content_" + name);
  33.           document.getElementById("content").removeChild(node);
  34.           silverlightjs.RemoveFile(name);
  35.       }
  36.       function client_uploadcompleted(msg) {
  37.           alert(msg);
  38.       }
  39.  ]]>
  40.   </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,方法很简单。引用之后

 
  1. //obj为客户端js中的window对象
  2. //execscriptname为客户端js的函数名
  3. //msg 为参数
  4. HTMLWindow2Class html = (HTMLWindow2Class)obj;
  5. html.execScript(execscriptname+"('" + msg + "');""javascript");

但是,这个方法有一个很大的BUG----------它是线程不安全的!!!如果单纯的不安全,我可以用委托或则建立线程是运用ParameterizedThreadStart来绕过,但是它的问题是HTMLWindow2Class html = (HTMLWindow2Class)obj;只能执行在主线程中,任何其他线程哪怕是你把window对象作为参数给他也是无法调用的,而且会出现一个未知错误!!诡异。。。

好吧,既然如此,只能用流氓方法解决了,在主线程中执行

 
  1. using System;
  2. using System.Windows.Forms;
  3. using System.IO;
  4. using System.Threading;
  5. using mshtml;
  6. using wondersgroup.FileWs;
  7. using System.Runtime.InteropServices;
  8. using System.Security.Permissions;
  9. using System.Collections.Generic;
  10. namespace wondersgroup.ActiveX
  11. {
  12.  
  13.     [PermissionSet(SecurityAction.Assert, Name = "FullTrust")]
  14.     [ClassInterface(ClassInterfaceType.AutoDual)]
  15.     public class FileUpload : System.Windows.Forms.UserControl, IFileUpload
  16.     {
  17.         #region Initialization
  18.  
  19.         private string files;
  20.         private string url= (new FileWebService()).Url;
  21.         private List<string> msg = new List<string>();
  22.         private List<Thread> threads = new List<Thread>();
  23.         public FileUpload()
  24.         {
  25.  
  26.         }
  27.         protected override void Dispose(bool disposing)
  28.         {
  29.             foreach (Thread t in threads)
  30.             {
  31.                 t.Abort();
  32.             }
  33.             base.Dispose();
  34.         }
  35.  
  36.  
  37.         #endregion
  38.         #region Public
  39.         public String FilePaths
  40.         {
  41.             get { return files; }
  42.             set
  43.             {
  44.                 files = value;
  45.             }
  46.         }
  47.         public String Url
  48.         {
  49.             get { return url; }
  50.             set
  51.             {
  52.                 url = value;
  53.             }
  54.         }
  55.  
  56.         public void Upload(object window,string execscriptname)
  57.         {
  58.  
  59.             string[] _files = files.Split('|');
  60.             foreach (string s in _files)
  61.             {
  62.                 Thread t = new Thread(new ParameterizedThreadStart(UploadFile));
  63.                 threads.Add(t);
  64.                 t.Start(s);
  65.             }
  66.             int _threadnum = _files.Length;
  67.             string _temp = string.Empty;
  68.             while (true && _threadnum != 0)
  69.             {
  70.                 if (msg.Count != 0)
  71.                 {
  72.                     _temp = msg[msg.Count - 1];
  73.                     lock (msg)
  74.                     {
  75.                         msg.Remove(_temp);
  76.                     }
  77.                     UploadFileCompleted(window, _temp, execscriptname);
  78.                     _threadnum--;
  79.                 }
  80.  
  81.                 Thread.Sleep(1000);
  82.             }
  83.         }
  84.         #endregion
  85.  
  86.         #region Private
  87.         private void UploadFile(object s)
  88.         {
  89.             string _file = s.ToString();
  90.             if (File.Exists(_file))
  91.             {
  92.                 FileInfo fi = new FileInfo(_file);
  93.                 FileStream fs = fi.OpenRead();
  94.                 int nBytes = (int)fs.Length;
  95.                 byte[] ByteArray = new byte[nBytes];
  96.                 fs.Read(ByteArray, 0, nBytes);
  97.                 fs.Close();
  98.                 FileWebService client = new FileWebService();
  99.                 string rt = client.UploadFile(ByteArray, fi.Name);
  100.                 lock (msg)
  101.                 {
  102.                     msg.Add(rt);
  103.                 }
  104.             }
  105.         }
  106.  
  107.         private void UploadFileCompleted(object obj, string msg,string execscriptname)
  108.         {
  109.             try
  110.             {
  111.                 HTMLWindow2Class html = (HTMLWindow2Class)obj;
  112.                 html.execScript(execscriptname+"('" + msg + "');""javascript");
  113.             }
  114.             catch (Exception e) { MessageBox.Show(e.Message, "ActiveX Error"); }
  115.         }
  116.         #endregion
  117.  
  118.  
  119.  
  120.     }
  121. }

接口

 
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using System.Runtime.InteropServices;
  5.  
  6. namespace wondersgroup.ActiveX
  7. {
  8.     [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
  9.     public interface IFileUpload
  10.     {
  11.         /// <summary>
  12.         /// Get or set the files' path with "|" that will upload
  13.         /// eg:c:\aa.txt|d:\bb.txt
  14.         /// </summary>
  15.          String FilePaths { setget; }
  16.  
  17.         /// <summary>
  18.         /// Get or set the webservice url
  19.         /// default is http://localhost:1707/FileWebService.asmx
  20.         /// </summary>
  21.          String Url { setget; }
  22.  
  23.         /// <summary>
  24.          /// Upload files set by FilePaths
  25.         /// </summary>
  26.         /// <param name="window">javascript window object</param>
  27.         /// <param name="execscriptname">the js function to callback</param>
  28.          void Upload(object window, string execscriptname);
  29.     }
  30.    
  31. }

客户端调用则相对比较简单

 
  1. <script>
  2.         var obj = new ActiveXObject("wondersgroup.FileUpload");
  3.     </script>
  4.  
  5.     <div id="content">
  6.     </div>
  7.     <input id="File1" type="file" /><input id="Button1" type="button" value="add to list"
  8.         onclick="client_openDialoged(File1.value);" />
  9.     <input type="submit" name="button" id="button" value="提交至ActiveX" onclick="post()" />
  10.     <input type="button"  value='上传' onclick='upload()'>
  11.     <script>
  12.         var i = 0;
  13.         obj.Url = "http://localhost:1707/FileWebService.asmx";
  14.         function client_openDialoged(name) {
  15.             var node = document.createElement("<div onclick=remove_node('" + i + "') id='content_" + i + "'>");
  16.             node.innerText = name;
  17.             document.getElementById("content").appendChild(node);
  18.             i++;
  19.         }
  20.         function post() {
  21.             var _str = "";
  22.             var temp = document.getElementById("content").childNodes;
  23.             for (ii = 0; ii < temp.length; ii++) {
  24.                 _str += temp(ii).innerText + (ii == (temp.length - 1) ? "" : "|");
  25.             }
  26.             obj.FilePaths = _str;
  27.         }
  28.         function upload() {
  29.             obj.Upload(window, "uploadSucc");
  30.         }
  31.         function remove_node(name) {
  32.             var node = document.getElementById("content_" + name);
  33.             document.getElementById("content").removeChild(node);
  34.         }
  35.         function uploadSucc(str) {
  36.             alert("remote result: "+str);
  37.         }
  38.     </script>

唯一一点要注意的是手工使用regasm FileUpload.dll /tlb /codebase注册这个dll

 
  1. //webservice
  2.         [WebMethod(Description = "Web 服务提供的方法,返回是否文件上载成功与否。")]
  3.         public string UploadFile(byte[] fs, string filename)
  4.         {
  5.             try
  6.             {
  7.                 MemoryStream m = new MemoryStream(fs);
  8.                 string relative=DateTime.Now.ToString("yyyy_MM_dd_mm_ss_") + filename;
  9.                 string path = Server.MapPath("uload") + "\\" + relative;
  10.                 FileStream f = new FileStream(path, FileMode.Create);
  11.                 m.WriteTo(f);
  12.                 m.Close();
  13.                 f.Close();
  14.                 f = null;
  15.                 m = null;
  16.                 return relative;
  17.             }
  18.             catch(Exception e)
  19.             {
  20.                 return e.Message;
  21.             }
  22.         }

 

posted @ 2009-07-08 15:43  Edwin Tai  阅读(2803)  评论(13编辑  收藏  举报