記Outlook插件與Web頁面交互的各種坑 (含c# HttpWebRequest 連接https 的完美解決方法)


1) 方案一,  使用Web Service  基礎功能沒問題, 只是在連接https (ssh) 網站時, 需要針對https進行開發 (即http 和https 生成兩套接口, 不太容易統一 ).   后來改為使用web頁面交互(實質是.ashx) 結果也遇到了不少問題.

 
2) 方案二, 使用  HttpWebRequest     HttpWebRequest  這東西get數據很容易, POST卻很麻煩, 最終代碼如下:
 
 1 public class WebApiClient
 2     {
 3         public string Url { get; set; }
 4         private CookieContainer Cookies = new CookieContainer();
 5          
 6           
 7         public static string Get( string url )
 8         {
 9             //this code can fix https after .net 4.6
10             if (url.StartsWith("https", StringComparison.CurrentCultureIgnoreCase))
11             {
12                 System.Net.ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(CheckValidationResult);
13             }
14             System.Net.HttpWebRequest request = (System.Net.HttpWebRequest)WebRequest.Create(url);
15             request.Method = "GET";
16             request.ContentType = "application/x-www-form-urlencoded";
17              
18             System.Net.HttpWebResponse response = (System.Net.HttpWebResponse)request.GetResponse();
19             System.IO.Stream s = response.GetResponseStream();
20             using (StreamReader reader = new StreamReader(s))
21             { 
22                 string strValue = reader.ReadToEnd();
23                 return strValue;
24             }
25         }
26         private static bool CheckValidationResult(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors)
27         {
28             return true;  
29         }
30         public Result<DataTable> CallWebApi(string action, string json )
31         {
32             return CallWebApi(action, json, null, null);
33         }
34         public Result<DataTable> CallWebApi(string action, string json, string fileName, byte[] fileData)
35         {
36             string responseContent;
37             var memStream = new MemoryStream();
38             var webRequest = (HttpWebRequest)WebRequest.Create(Url); 
39             var boundary = "---------------" + DateTime.Now.Ticks.ToString("x"); 
40             var beginBoundary = Encoding.ASCII.GetBytes("--" + boundary + "\r\n");
41             //var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read); 
42             var endBoundary = Encoding.ASCII.GetBytes("--" + boundary + "--\r\n"); 
43             webRequest.Method = "POST"; 
44             //webRequest.Timeout = timeOut;
45             webRequest.ContentType = "multipart/form-data; boundary=" + boundary;
46             if (!string.IsNullOrEmpty(fileName))
47             {
48                 const string filePartHeader =
49                "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"\r\n" +
50                 "Content-Type: application/octet-stream\r\n\r\n";
51                 var header = string.Format(filePartHeader, "file1", fileName);
52                 var headerbytes = Encoding.UTF8.GetBytes(header);
53                 memStream.Write(beginBoundary, 0, beginBoundary.Length);
54                 memStream.Write(headerbytes, 0, headerbytes.Length);
55                 memStream.Write(fileData, 0, fileData.Length);
56             }
57             
58             
59             var stringKeyHeader = "\r\n--" + boundary +
60                                    "\r\nContent-Disposition: form-data; name=\"{0}\"" +
61                                    "\r\n\r\n{1}\r\n";
62             var bytes = Encoding.UTF8.GetBytes(string.Format(stringKeyHeader, "action", action));
63             memStream.Write(bytes, 0, bytes.Length);
64             bytes = Encoding.UTF8.GetBytes(string.Format(stringKeyHeader, "json", json));
65             memStream.Write(bytes, 0, bytes.Length);
66              
67             memStream.Write(endBoundary, 0, endBoundary.Length);
68             webRequest.ContentLength = memStream.Length;
69             var requestStream = webRequest.GetRequestStream();
70             memStream.Position = 0;
71             var tempBuffer = new byte[memStream.Length];
72             memStream.Read(tempBuffer, 0, tempBuffer.Length);
73             memStream.Close();
74             requestStream.Write(tempBuffer, 0, tempBuffer.Length);
75             requestStream.Close();
76             var httpWebResponse = (HttpWebResponse)webRequest.GetResponse();
77             using (var httpStreamReader = new StreamReader(httpWebResponse.GetResponseStream(),
78                                                             Encoding.GetEncoding("utf-8")))
79             {
80                 responseContent = httpStreamReader.ReadToEnd();
81             } 
82             httpWebResponse.Close();
83             webRequest.Abort();
84             try
85             {
86                 return JsonConvert.DeserializeObject<Result<DataTable>>(responseContent);
87             }
88             catch (Exception ex)
89             {
90                 throw new ApplicationException("Parse server result error: " + responseContent, ex);
91             } 
92         }
93          
94     }
View Code


 

POST數據的麻煩, 封裝一下可以忍了, 但是連接HTTPS的網站時, 有的網站可以, 有的網站會出錯: System.Net.WebException: The underlying connection was closed: An unexpected error occurred on a send. ---> System.IO.IOException: Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host. ---> System.Net.Sockets.SocketException: An existing connection was forcibly closed by the remote host
   at System.Net.Sockets.Socket.Receive(Byte[] buffer, Int32 offset, Int32 size, SocketFlags socketFlags)
   at System.Net.Sockets.NetworkStream.Read(Byte[] buffer, Int32 offset, Int32 size)
   --- End of inner exception stack trace ---
   at System.Net.Sockets.NetworkStream.Read(Byte[] buffer, Int32 offset, Int32 size)
   at System.Net.FixedSizeReader.ReadPacket(Byte[] buffer, Int32 offset, Int32 count)
   at System.Net.Security.SslState.StartReceiveBlob(Byte[] buffer, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslState.ForceAuthentication(Boolean receiveFirst, Byte[] buffer, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslState.ProcessAuthentication(LazyAsyncResult lazyResult)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Net.TlsStream.ProcessAuthentication(LazyAsyncResult result)
   at System.Net.TlsStream.Write(Byte[] buffer, Int32 offset, Int32 size)
   at System.Net.ConnectStream.WriteHeaders(Boolean async)
   --- End of inner exception stack trace ---
   at System.Net.HttpWebRequest.GetResponse()
 
如果是普通的桌面程序 , 在.net 4.6以上版本時, Create  HttpWebRequest 對象前添加
  1. if(url.StartsWith("https",StringComparison.CurrentCultureIgnoreCase))
  2. {
  3. System.Net.ServicePointManager.ServerCertificateValidationCallback=newRemoteCertificateValidationCallback(CheckValidationResult);
  4. }
  1. privatestatic bool CheckValidationResult(object sender, X509Certificate certificate, X509Chain chain,SslPolicyErrors errors)
  2. {
  3. returntrue;
  4. }
 
有的web服務器要求較新的HTTPS的安裝協議, 這種情況下.net4.0以下的版本不支持, 因此要升級到.net4.6以上, 並且在創建HttpWebRequest對象前加入如下設置代碼:
  1. ServicePointManager.SecurityProtocol=SecurityProtocolType.Tls|SecurityProtocolType.Tls11|SecurityProtocolType.Tls12|SecurityProtocolType.Ssl3;
這樣就可以最大程度的兼容各種安全協議了. 有的時候發現我們的程序挑.net framework的版本, 大都是由這個原因引起的.
解決了HttpWebRequest連接HTTPS的問題之后, 該方案成為了功能最強的方案(重要是能較好的支持文件的上傳)
 
方案三 使用WebBrowser控件 .
 
初步試了一下, get https的頁面沒有問題, post 的略有麻煩, 網上查到的方法是截獲post事件進行處理:

WebBrowser 其實是對 ActiveX 控件 SHDocVw 的封裝,而這個SHDocVw的很多底層調用WebBrowser控件並沒有提供實現,我們需要直接操作 SHDoceVw 控件來實現這些高級調用。操作方法如下:
1、在 windows/system32 目錄下找到 shdocvw.dll 這個動態庫,將其添加到引用中

2、在 Form1_Load 中添加如下語句

SHDocVw.WebBrowser wb = (SHDocVw.WebBrowser)webBrowser1.ActiveXInstance; 
wb.BeforeNavigate2 += new DWebBrowserEvents2_BeforeNavigate2EventHandler(WebBrowser_BeforeNavigate2);

3、添加如下成員函數

private void WebBrowser_BeforeNavigate2(object pDisp, ref object URL, ref object Flags, 
ref object TargetFrameName, ref object PostData, ref object Headers, ref bool Cancel)
{
string postDataText = System.Text.Encoding.ASCII.GetString(PostData as byte[]);
}


完成上述3步后,你post 數據時, 就會響應 BeforeNavigate2 事件,postDataText 中就是你post的數據。你也可以修改PostData,對這些數據進行轉換或加密。


感覺會比較麻煩, 其實還有一種方法,就是利用WebBrowser的DocumentText屬性注入腳本(執行腳本), 然后利用Ajax的方式和web服務器進行交互. 之后利用JS和C#互操作來完成相應的功能.
互操作的C#類上需要加上這個設置:
  1. [System.Runtime.InteropServices.ComVisibleAttribute(true)]
並且運行此代碼:
  1. webBrowser1.ObjectForScripting=this;
C#里調用JS方法:
  1. webBrowser1.Document.InvokeScript("jsFunction",newstring[]{‘ssss’});
 
JS里調用C#方法:
  1. window.external.CSharpFunction(‘呵呵’);

這個方法也有些麻煩的地方, 而且不支持文件的上傳.

 






免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM