2012-05-18 22 views
3

我編寫了一個非常小的網站bot在C#中使用默認的WebBrowser控件。事實上,幾乎所有事情都按照它應該工作的方式工作,但我似乎在自動化的最後一步遇到問題。WebBrowserControl:UnauthorizedAccessException當訪問幀的屬性

該網站是使用多個iframe構建的。這是沒有太大的一個大問題,因爲我只是用

webBrowser1.Document.Window.Frames[0].Document.GetElementById("element").InvokeMember("click"); 

然而,這當IFRAME的來源正在於比實際的網站不同的域名託管不起作用訪問這些框架和它們的元素。當我搜索互聯網尋找問題的答案時,我偶然發現了一篇MSDN文章中提到的這個特定問題,他們指的是針對跨站點腳本的安全措施,這可能是導致此錯誤的原因。

我真的找不到一種禁用此功能的方式,所以我繼續前進並決定重新編碼一切,以使用geckofx-12而不是默認(基於IE)的Web瀏覽器控件,但我遇到了類似的問題。 ..

我的問題是:有什麼辦法可以繞過這種煩人的行爲?我並不關心安全問題,也不在乎是否正在使用geckofx或默認的Web瀏覽器控件,我只想以編程方式訪問正在託管在不同域上的站點的元素,而無需運行到UnauthorizedAccessException 。

我很想得到那裏的專家的建議。

回答

1

我還沒試過,但changing the document domain顯然有效。

使用geckofx 12看起來這可能是由nsIDOMHTMLDocument.SetDomainAttribute辦妥(GeckoDocument.Domain沒有制定者,但你可以很容易地將其添加)

IE。如果您更改文檔的域以匹配您可能能夠訪問的子框架。

+2

不幸的是,當嘗試使用這種方法時,我得到了一個COM異常。然而,我通過獲取帶有webBrowser1.Document.GetElementsByTagName(「iframe」)的iframe並通過((Gecko.DOM.GeckoIFrameElement)frames [0])訪問其內容文檔來完成工作。ContentDocument完美地工作。不過,我將答案標記爲解決方案,因爲無論如何都沒有其他答案。 – beta

6

您無法訪問來自不同域的幀。這是一項安全功能。這裏是它的一個小黑客:

public class CrossFrameIE 
{ 
    // Returns null in case of failure. 
    public static IHTMLDocument2 GetDocumentFromWindow(IHTMLWindow2 htmlWindow) 
    { 
     if (htmlWindow == null) 
     { 
      return null; 
     } 

     // First try the usual way to get the document. 
     try 
     { 
      IHTMLDocument2 doc = htmlWindow.document;     

      return doc; 
     } 
     catch (COMException comEx) 
     { 
      // I think COMException won't be ever fired but just to be sure ... 
      if (comEx.ErrorCode != E_ACCESSDENIED) 
      { 
       return null; 
      } 
     } 
     catch (System.UnauthorizedAccessException) 
     { 
     } 
     catch 
     { 
      // Any other error. 
      return null; 
     } 

     // At this point the error was E_ACCESSDENIED because the frame contains a document from another domain. 
     // IE tries to prevent a cross frame scripting security issue. 
     try 
     { 
      // Convert IHTMLWindow2 to IWebBrowser2 using IServiceProvider. 
      IServiceProvider sp = (IServiceProvider)htmlWindow; 

      // Use IServiceProvider.QueryService to get IWebBrowser2 object. 
      Object brws = null; 
      sp.QueryService(ref IID_IWebBrowserApp, ref IID_IWebBrowser2, out brws); 

      // Get the document from IWebBrowser2. 
      IWebBrowser2 browser = (IWebBrowser2)(brws); 

      return (IHTMLDocument2)browser.Document; 
     } 
     catch 
     { 
     } 

     return null; 
    } 

    private const int E_ACCESSDENIED = unchecked((int)0x80070005L); 
    private static Guid IID_IWebBrowserApp = new Guid("0002DF05-0000-0000-C000-000000000046"); 
    private static Guid IID_IWebBrowser2 = new Guid("D30C1661-CDAF-11D0-8A3E-00C04FC9E26E"); 
} 

// This is the COM IServiceProvider interface, not System.IServiceProvider .Net interface! 
[ComImport(), ComVisible(true), Guid("6D5140C1-7436-11CE-8034-00AA006009FA"), 
InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] 
public interface IServiceProvider 
{ 
    [return: MarshalAs(UnmanagedType.I4)] 
    [PreserveSig] 
    int QueryService(ref Guid guidService, ref Guid riid, [MarshalAs(UnmanagedType.Interface)] out object ppvObject); 
} 
+1

什麼是* IWebBrowser2 *? – Kiquenet

+0

如果我可以不止一次地投票,我會做到這一點。完美的作品。 – DoronG

+0

@Kiquenet顯然它來自SHDocVw:https://stackoverflow.com/questions/20845140/add-reference-shdocvw-in-c-sharp-project-using-visual-c-sharp-2010-express –

1

我更新,丹尼爾波格丹略微發佈使用擴展方法,給你調用它的一種方式,而不必去到MSHTML命名空間中的黑客:

using mshtml; 
using SHDocVw; 
using System; 
using System.Reflection; 
using System.Runtime.InteropServices; 
using System.Windows.Forms; 

namespace TradeAutomation 
{ 
    public static class CrossFrameIE 
    { 
     private static FieldInfo ShimManager = typeof(HtmlWindow).GetField("shimManager", BindingFlags.NonPublic | BindingFlags.Instance); 
     private static ConstructorInfo HtmlDocumentCtor = typeof(HtmlDocument).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance)[0]; 

     public static HtmlDocument GetDocument(this HtmlWindow window) 
     { 
      var rawDocument = (window.DomWindow as IHTMLWindow2).GetDocumentFromWindow(); 

      var shimManager = ShimManager.GetValue(window); 

      var htmlDocument = HtmlDocumentCtor 
       .Invoke(new[] { shimManager, rawDocument }) as HtmlDocument; 

      return htmlDocument; 
     } 


     // Returns null in case of failure. 
     public static IHTMLDocument2 GetDocumentFromWindow(this IHTMLWindow2 htmlWindow) 
     { 
      if (htmlWindow == null) 
      { 
       return null; 
      } 

      // First try the usual way to get the document. 
      try 
      { 
       IHTMLDocument2 doc = htmlWindow.document; 

       return doc; 
      } 
      catch (COMException comEx) 
      { 
       // I think COMException won't be ever fired but just to be sure ... 
       if (comEx.ErrorCode != E_ACCESSDENIED) 
       { 
        return null; 
       } 
      } 
      catch (System.UnauthorizedAccessException) 
      { 
      } 
      catch 
      { 
       // Any other error. 
       return null; 
      } 

      // At this point the error was E_ACCESSDENIED because the frame contains a document from another domain. 
      // IE tries to prevent a cross frame scripting security issue. 
      try 
      { 
       // Convert IHTMLWindow2 to IWebBrowser2 using IServiceProvider. 
       IServiceProvider sp = (IServiceProvider)htmlWindow; 

       // Use IServiceProvider.QueryService to get IWebBrowser2 object. 
       Object brws = null; 
       sp.QueryService(ref IID_IWebBrowserApp, ref IID_IWebBrowser2, out brws); 

       // Get the document from IWebBrowser2. 
       IWebBrowser2 browser = (IWebBrowser2)(brws); 

       return (IHTMLDocument2)browser.Document; 
      } 
      catch 
      { 
      } 

      return null; 
     } 

     private const int E_ACCESSDENIED = unchecked((int)0x80070005L); 
     private static Guid IID_IWebBrowserApp = new Guid("0002DF05-0000-0000-C000-000000000046"); 
     private static Guid IID_IWebBrowser2 = new Guid("D30C1661-CDAF-11D0-8A3E-00C04FC9E26E"); 
    } 

    // This is the COM IServiceProvider interface, not System.IServiceProvider .Net interface! 
    [ComImport(), ComVisible(true), Guid("6D5140C1-7436-11CE-8034-00AA006009FA"), 
    InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] 
    public interface IServiceProvider 
    { 
     [return: MarshalAs(UnmanagedType.I4)] 
     [PreserveSig] 
     int QueryService(ref Guid guidService, ref Guid riid, [MarshalAs(UnmanagedType.Interface)] out object ppvObject); 
    } 
} 

用法:

webBrowser1.Document.Window.Frames["main"].GetDocument(); 

正如我在評論上面提到的,你還需要添加一個引用SHDOCVW。你可以在這裏找到方向:Add reference 'SHDocVw' in C# project using Visual C# 2010 Express