在 WebBrowser 的文档中从 JavaScript 调用 C# 代码

Posted

技术标签:

【中文标题】在 WebBrowser 的文档中从 JavaScript 调用 C# 代码【英文标题】:Invoke C# code from JavaScript in a Document in a WebBrowser 【发布时间】:2011-04-11 06:39:11 【问题描述】:

我有一个 C# WinForms 应用程序,其中有一个 WebBrowser 控件。我想在我的 C# 表单和嵌入式 Web 浏览器控件中的 javascript 之间执行双向通信。

我知道我可以使用 InvokeScript 调用 JavaScript 函数,但是如何从 Document 中的 JavaScript 调用 C# 代码?我想由于安全性,这并不容易,但是有可能,无论如何,无论如何? 这些 JavaScript 函数应该是用户函数,很像宏,它会在我自己编写的整个 C# 库的帮助下准确地告诉 WebBrowser 做什么。由于这是一个网络爬虫,JavaScript 是这些宏的完美语言,因为它几乎可以访问 html 文档中的元素。

【问题讨论】:

为什么这个问题我得到了一个减分? 您是在问如何从客户端代码调用服务器端方法吗?您需要为此使用 AJAX 调用。具体来说,您想要完成什么? 我正在制作一个网络爬虫,我希望能够从 html 文档中的 JavaScript 读取计算机中的文件。实际上,我想在我的程序(包含 WebBrowser 和 Document 的同一程序)中调用一堆方法,但假设我只想读取一个文件。 @jsoldi:请原谅我,但听起来你可能过度设计了这个。为什么它需要从本地机器读取 HTML 文件?您不能以简单的方式以表单形式提供 HTML 文件(上传或复制/粘贴到文本框中)吗? 我澄清了这个问题,希望没有人仍然认为您正在尝试与 Web 服务器上的 C# 代码进行通信。 【参考方案1】:

以下是我编写的一些扩展方法,用于帮助在 WebBrowser 对象和 C# 代码之间进行双向通信/调用:

using System;
using System.Threading;
using FluentSharp.Web35;
using FluentSharp.WinForms;
using FluentSharp.CoreLib;
using FluentSharp.CoreLib.API;

namespace FluentSharp.Watin

    public static class WatiN_IE_ExtensionMethods_Javascript
    

        public static object invokeScript(this WatiN_IE ie, string functionName)
        
            return ie.invokeScript(functionName, null);
        

        public static object invokeScript(this WatiN_IE ie, string functionName, params object[] parameters)
        
            //"[WatiN_IE] invokeScript '0' with parameters:1".info(functionName ,parameters.size());
            return ie.invokeScript(true, functionName, parameters);
           

        public static object invokeScript(this WatiN_IE ie, bool waitForExecutionComplete, string functionName, params object[] parameters)
        
            var sync = new AutoResetEvent(false);
            object responseValue = null;
            ie.WebBrowser.invokeOnThread(
                ()=>
                        var document = ie.WebBrowser.Document;
                        if (parameters.isNull())
                            responseValue = document.InvokeScript(functionName); 
                        else
                            responseValue = document.InvokeScript(functionName, parameters); 
                        sync.Set(); 
                );
            if (waitForExecutionComplete)
                sync.WaitOne();
            return responseValue;   
        

        public static object invokeEval(this WatiN_IE ie, string evalScript)
        
            var evalParam = "(function()  " + evalScript + ")();";
            //"[WatiN_IE] invokeEval evalParam: 0".debug(evalParam);
            return ie.invokeScript("eval", evalParam);   
        
        public static WatiN_IE.ToCSharp injectJavascriptFunctions(this WatiN_IE ie)
        
            return ie.injectJavascriptFunctions(false);
        

        public static WatiN_IE.ToCSharp injectJavascriptFunctions(this WatiN_IE ie, bool resetHooks)
        
            if (ie.WebBrowser.isNull())
                "in InjectJavascriptFunctions, ie.WebBrowser was null".error();
            else
            
                if (ie.WebBrowser.ObjectForScripting.isNull() || resetHooks)  
                
                    ie.WebBrowser.ObjectForScripting = new WatiN_IE.ToCSharp();

                    "Injecting Javascript Hooks * Functions for page: 0".debug(ie.url());
                    ie.eval("var o2Log = function(message)  window.external.write(message) ;");
                    ie.invokeScript("o2Log","Test from Javascript (via toCSharp(message) )");
                    ie.eval("$o2 = window.external");
                    "Injection complete (use o2Log(...) or $o2.write(...)  to talk back to O2".info();
                    return (ie.WebBrowser.ObjectForScripting as WatiN_IE.ToCSharp);
                
                else 
                
                    if((ie.WebBrowser.ObjectForScripting is WatiN_IE.ToCSharp))
                        return (ie.WebBrowser.ObjectForScripting as WatiN_IE.ToCSharp);
                    else
                        "in WatiN_IE injectJavascriptFunctions, unexpected type in ie.WebBrowser.ObjectForScripting: 0".error(ie.WebBrowser.ObjectForScripting.typeName());                   
                

            
            return null;
        

        public static object downloadAndExecJavascriptFile(this WatiN_IE ie, string url)
        
            "[WatiN_IE] downloadAndExecJavascriptFile: 0".info(url);
            var javascriptCode = url.uri().getHtml();
            if (javascriptCode.valid())
                ie.eval(javascriptCode);
            return ie;
        

        public static WatiN_IE injectJavascriptFunctions_onNavigate(this WatiN_IE ie)
        

            ie.onNavigate((url)=> ie.injectJavascriptFunctions());
            return ie;
        

        public static WatiN_IE setOnAjaxLog(this WatiN_IE ie, Action<string, string,string,string> onAjaxLog)
        
            (ie.WebBrowser.ObjectForScripting as WatiN_IE.ToCSharp).OnAjaxLog = onAjaxLog;
            return ie;
        

        public static WatiN_IE eval_ASync(this WatiN_IE ie, string script)
        
            O2Thread.mtaThread(()=> ie.eval(script));
            return ie;
        

        public static WatiN_IE eval(this WatiN_IE ie, string script)
        
            return ie.eval(script, true);
        

        public static WatiN_IE eval(this WatiN_IE ie, string script, bool waitForExecutionComplete)
        
            var executionThread = O2Thread.staThread(()=> ie.IE.RunScript(script));         
            if (waitForExecutionComplete)
                executionThread.Join();
            return ie;  
        

        public static WatiN_IE alert(this WatiN_IE ie, string alertScript)
        
            return ie.eval("alert(0);".format(alertScript));
        

        public static object getJsObject(this WatiN_IE ie)
        
            var toCSharpProxy = ie.injectJavascriptFunctions();
            if (toCSharpProxy.notNull())
                return toCSharpProxy.getJsObject();
            return null;        
        

        public static T getJsObject<T>(this WatiN_IE ie, string jsCommand)
        
            var jsObject = ie.getJsObject(jsCommand);
            if (jsObject is T)
                return (T)jsObject;
            return default(T);
        

        public static bool doesJsObjectExists(this WatiN_IE ie, string jsCommand)
        
            var toCSharpProxy = ie.injectJavascriptFunctions();
            if (toCSharpProxy.notNull())
            
                var command = "window.external.setJsObject(typeof(0))".format(jsCommand);
                ie.invokeEval(command);
                ie.remapInternalJsObject();             
                return toCSharpProxy.getJsObject().str()!="undefined";
            
            return false;
        

        public static object getJsVariable(this WatiN_IE ie, string jsCommand)
        
            return ie.getJsObject(jsCommand);
        

        public static object getJsObject(this WatiN_IE ie, string jsCommand)
        
            var toCSharpProxy = ie.injectJavascriptFunctions();
            if (toCSharpProxy.notNull())
            
                var command = "window.external.setJsObject(0)".format(jsCommand);
                ie.invokeEval(command);
                ie.remapInternalJsObject();             
                return toCSharpProxy.getJsObject();
            
            return null;
                       

        public static WatiN_IE remapInternalJsObject(this WatiN_IE ie)
               
            //"setting JS _jsObject variable to getJsObject()".info();
            ie.invokeEval("_jsObject = window.external.getJsObject()"); // creates JS variable to be used from JS
            return ie;
        

        public static WatiN_IE setJsObject(this WatiN_IE ie, object jsObject)
        
            var toCSharpProxy = ie.injectJavascriptFunctions();
            if (toCSharpProxy.notNull())            
            
                toCSharpProxy.setJsObject(jsObject);
                ie.remapInternalJsObject();
            
            return ie;
        

        public static object waitForJsObject(this WatiN_IE watinIe)
        
            return watinIe.waitForJsObject(500, 20);
        

        public static object waitForJsObject(this WatiN_IE watinIe, int sleepMiliseconds, int maxSleepTimes)
                           
            "[WatiN_IE][waitForJsObject] trying to find jsObject for 0 x 1 ms".info(maxSleepTimes, sleepMiliseconds);
            watinIe.setJsObject(null);
            for(var i = 0; i < maxSleepTimes ; i++)
            
                var jsObject = watinIe.getJsObject();
                if(jsObject.notNull())
                
                    "[watinIe][waitForJsObject] got value: 0 (n tries)".info(jsObject, i);
                    return jsObject;
                

                watinIe.sleep(500, false);
            
            "[WatiN_IE][waitForJsObject] didn't find jsObject after 0 sleeps of 1 ms".error(maxSleepTimes, sleepMiliseconds);
            return null;
        

        public static object waitForJsVariable(this WatiN_IE watinIe, string jsCommand)
        
            return watinIe.waitForJsVariable(jsCommand,  500, WatiN_IE_ExtensionMethods.WAITFORJSVARIABLE_MAXSLEEPTIMES);
        

        public static object waitForJsVariable(this WatiN_IE watinIe, string jsCommand, int sleepMiliseconds, int maxSleepTimes)
           
            "[WatiN_IE][waitForJsVariable] trying to find jsObject called '0' for 1 x 2 ms".info(jsCommand, maxSleepTimes, sleepMiliseconds);         
            watinIe.setJsObject(null);
            for(var i = 0; i < maxSleepTimes ; i++)
            
                if (watinIe.doesJsObjectExists(jsCommand))
                
                    var jsObject = watinIe.getJsObject(jsCommand);
                    "[watinIe][waitForJsVariable] got value: 0 (1 tries)".info(jsObject, i);
                    return jsObject;
                                   
                watinIe.sleep(500, false);
            
            "[WatiN_IE][waitForJsVariable] didn't find jsObject called '0' after 1 sleeps of 2 ms".error(jsCommand, maxSleepTimes, sleepMiliseconds);
            return null;
        

        public static WatiN_IE deleteJsVariable(this WatiN_IE watinIe, string jsVariable)
        
            var evalString = "try  delete " + jsVariable + "  catch(exception)  ";
            watinIe.eval(evalString);
            return watinIe;
        


    

【讨论】:

【参考方案2】:

您可能正在寻找http://msdn.microsoft.com/en-us/library/system.windows.forms.webbrowser.objectforscripting.aspx

WebBrowser.ObjectForScripting 允许您将 [ComVisible] .net 类的实例公开给在托管 Web 浏览器中运行的 javascript 代码。它在 javascript 中公开为 window.external

来自微软的优秀文章: How to: Implement Two-Way Communication Between DHTML Code and Client Application Code

【讨论】:

那篇文章远非优秀。例如,我很想知道是否可以将值返回到 javascript 代码中,但这一点都不清楚。【参考方案3】:

您需要做的是将 Web 浏览器控件上的 ObjectForScripting 属性设置为包含要从 JavaScript 调用的 C# 方法的对象。然后,您可以使用 window.external 从 JavaScript 访问该对象。唯一需要注意的是对象必须具有[ComVisibleAttribute(true)] 属性。我已经成功使用了几年。

这是一个带有文档和一个简单示例的页面:http://msdn.microsoft.com/en-us/library/a0746166.aspx

这是链接中的示例(我没有尝试过此代码):

using System;
using System.Windows.Forms;
using System.Security.Permissions;

[PermissionSet(SecurityAction.Demand, Name="FullTrust")]
[System.Runtime.InteropServices.ComVisibleAttribute(true)]
public class Form1 : Form

    private WebBrowser webBrowser1 = new WebBrowser();
    private Button button1 = new Button();

    [STAThread]
    public static void Main()
    
        Application.EnableVisualStyles();
        Application.Run(new Form1());
    

    public Form1()
    
        button1.Text = "call script code from client code";
        button1.Dock = DockStyle.Top;
        button1.Click += new EventHandler(button1_Click);
        webBrowser1.Dock = DockStyle.Fill;
        Controls.Add(webBrowser1);
        Controls.Add(button1);
        Load += new EventHandler(Form1_Load);
    

    private void Form1_Load(object sender, EventArgs e)
    
        webBrowser1.AllowWebBrowserDrop = false;
        webBrowser1.IsWebBrowserContextMenuEnabled = false;
        webBrowser1.WebBrowserShortcutsEnabled = false;
        webBrowser1.ObjectForScripting = this;
        // Uncomment the following line when you are finished debugging.
        //webBrowser1.ScriptErrorsSuppressed = true;

        webBrowser1.DocumentText =
            "<html><head><script>" +
            "function test(message)  alert(message); " +
            "</script></head><body><button " +
            "onclick=\"window.external.Test('called from script code')\">" +
            "call client code from script code</button>" +
            "</body></html>";
    

    public void Test(String message)
    
        MessageBox.Show(message, "client code");
    

    private void button1_Click(object sender, EventArgs e)
    
        webBrowser1.Document.InvokeScript("test",
            new String[]  "called from client code" );
    

【讨论】:

顺便说一句,我还使用这种方法在嵌入式 Web 控件中的 Silverlight 代码中的 C# 与托管它的 C# 表单之间进行通信。 您还应该注意从声明为 public 的 JavaScript 调用的 C# 方法。 您不应该使用 Form 对象或任何其他现有对象,而是使用一些专用对象来完成该作业,否则您可能会无意中允许 javascript 在对象上执行其他一些方法。 @NineBerry:是的,这是一个很好的观点。您需要一个专门用于生产代码的类。

以上是关于在 WebBrowser 的文档中从 JavaScript 调用 C# 代码的主要内容,如果未能解决你的问题,请参考以下文章

如何在 webBrowser 中从 HTML5 创建 .exe?

从 webbrowser 控件保存 pdf 文档

python 的 webbrowser 在 Windows 相对路径上启动 IE,而不是默认浏览器

导航后 C# WebBrowser 不同的 html 文档

如何检测 jQuery 是不是在 WinForm WebBrowser 控件中导航到的文档中?

使用 WebBrowser 访问 DOM