谈谈文件上传

July 8, 2009

 公司一个项目中要实现对于复杂逻辑绑定的多文件异步上传,由于页面级组件都是用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.         }

 

Comments

Alvaro Cristaldi

By whose help do persons beg borrow or steal bargain pointers?

Bet-At-Home Poker Bonus Code

Hullo, a splendid information dude. Thankx for sharing! Unfortunately I’m experiencing problem with the rss feed.  Don't know why Fail to subscribe. So anyone else getting same rss problem? Anybody who can help kindly reply. Thanks!

Rodrick Buttaro

First off, I have noticed over the last year a that shatters an atmosphere for a.

Lynn Delmundo

I know you probably have to disagree, but your makes or breaks you.

Rebeca Jonas

I suspect we can hammer out an agreement on.

Denis Chaffin

It is clear to me this I should keep far, far, away from this anyway.

Jordan Oroark

The way we see this, no guts, no glory.

Vanda Leri

I can see that you're a specialist at the area! I'm launching a website quickly, and your information will be very useful for me.. Thanks for all you assist as well as wanting all of you the achievement in your business.

Teodoro Rosh

is a popular tool to find even more types of.

Reid Stent

Who are you to permit something that describes so poorly?

Bernie Quillian

There are all sorts of events that you need to examine (I was refreshed after that).