zoukankan      html  css  js  c++  java
  • C# / .NET Core 调用javascript方法(适用于Windows/Linux平台)

    使用背景

    最近在使用c#(dotnetcore)编写一些爬虫进行实践,在模拟网站请求的时候,往往在请求参数里含有一个根据请求内容实时生成的token,通过对前端js文件的调用,找到了用来生成token的js方法,但是将js代码翻译成c#代码有点太费劲费时,于是想要找到一个这样的框架,可以直接从c#调用js的方法并返回值。

    尝试的框架

    我前前后后尝试了好几个框架,大致分为两类1、浏览器内核/无头浏览器,2、js引擎/框架。主要区别是一个是模拟浏览器去请求一个完整的网页,另外的则是单单去计算调用一个纯粹的js方法,我更倾向于后者。

    浏览器内核/无头浏览器

    js引擎/框架

    最后根据项目需求选择了JavaScriptEngineSwitcher.ChakraCore,因为支持在linux平台运行,在Windows上运行的时候需要额外引用JavaScriptEngineSwitcher.ChakraCore.Native.win-x64,在linux上运行时需要额外引用JavaScriptEngineSwitcher.ChakraCore.Native.linux-x64,这两个包可以同时引用。

    PuppeteerSharp时puppeteer的c#版本,由于我在使用时好像发现他在运行时需要额外下载内容,且下载失败,故没有做仔细研究便弃用。

    Microsoft.AspNetCore.NodeServices,功能满足需求,可以在linux上运行,但由于在使用Systemd运行应用时无法调用Nodejs,且暂未找到问题原因,且方法接口等已经标注过时,考虑以后的维护升级考虑,只作备选方案。

    Microsoft.ClearScript.V8功能满足需求,但是不满足在linux上运行,故不选择。

    代码示例
    注:以下代码实例均是使用目标框架 .Net Core 3.1,先在Windows平台测试,后在Linux下测试,使用到的框架应该也均有 .Net Framework 的版本支持,但对于此点并没有做验证。测试做的并不完全,如有疏漏或错误欢迎指正。

    Microsoft.AspNetCore.NodeServices

    先决条件:Nodejs环境,设置环境变量NODE_PATH,脚本需要按照Nodejs的模块导出的格式将方法写成导出的形式。

    c#

    using Microsoft.AspNetCore.NodeServices;
    using Microsoft.Extensions.DependencyInjection;
    
    namespace NodeServicesDemo
    {
        public class Demo
        {
            [Obsolete]
            private readonly INodeServices _nodeServices;
            private IServiceCollection _nodeServiceCollections = new ServiceCollection();
    
            [Obsolete]
            public XieChengScrapyService(IServiceProvider services)
            {
                _nodeServiceCollections.AddNodeServices(options => 
                {
                    options.NodeInstanceOutputLogger = loggerFactory.CreateLogger("nodeservices");
                    options.ProjectPath = Environment.CurrentDirectory; // 设置项目为挡墙项目目录
                });
                var sp = _nodeServiceCollections.BuildServiceProvider();
                _nodeServices = sp.GetRequiredService<INodeServices>();
            }
         
    public async void Execute()
    {
    var result = await _nodeServices.InvokeAsync<string>("./Scripts/demo", input);  // 要调用的模块的相对路径,参数
    } } }

    demo.js(Nodejs模块)

    function m(t) {
        return p(v(t))
    }
    
    module.exports = function (callback, t){
        var output = m(t);
        callback(null, output);
    }

    Microsoft.ClearScript

    引入Nuget包,Microsoft.ClearScript

    demo.js(原生javascript)

    function m(t, e, r) {
        p(v(t))
    }

    注:除了NodeServices中,其他要调用的js文件均为以此为示例,后面不再复述。

    引入Nuget包,Microsoft.ClearScript

    using Microsoft.ClearScript.JavaScript;
    using Microsoft.ClearScript.V8;

    初始化

    using (var engine = new V8ScriptEngine())
    {
        engine.DocumentSettings.AccessFlags = Microsoft.ClearScript.DocumentAccessFlags.EnableFileLoading;
        engine.DefaultAccess = Microsoft.ClearScript.ScriptAccess.Full; // 这两行是为了允许加载js文件
        // do something
    }

    调用脚本有多种方案。

    方案一:调用engine.ComplieDocument方法直接加载js文件,然后调用engine.Execute将引入的脚本执行一遍,这样后面就可以调用js方法,m就是js的方法名,调用格式与js相同。

    V8Script script = engine.CompileDocument(ScriptFilePath);   // 载入并编译js文件, 然后Execute, 就可以直接调用。
    engine.Execute(script);
    var result = engine.Script.m("SHAURCOnewayduew&^%5d54nc'KH");  

    方案二:将要导入的js方法的代码读出来,然后执行一遍,再调用要执行的js方法

    string scriptContent = string.Empty;
    using(FileStream fs = new FileStream(ScriptFilePath, FileMode.Open, FileAccess.Read))
    {
        using(StreamReader sr = new StreamReader(fs))
        {
            scriptContent = sr.ReadToEnd().Replace("
    ", "");
        }
    }
    engine.Execute(scriptContent);  // 取得脚本里的所有内容,Execute一下,然后,调用engine.Script.func(x,y)执行一下。
    
    var result = engine.Script.m("SHAURCOnewayduew&^%5d54nc'KH");

    直接调用执行调用的方法的js代码也是可以的

    string scriptContent = string.Empty;
    using(FileStream fs = new FileStream(ScriptFilePath, FileMode.Open, FileAccess.Read))
    {
        using(StreamReader sr = new StreamReader(fs))
        {
            scriptContent = sr.ReadToEnd().Replace("
    ", "");
        }
    }
    scriptContent += "m("SHAURCOnewayduew&^%5d54nc'KH");";  // 在js代码的结尾加上执行的代码
    
    engine.Execute(scriptContent);  // 取得脚本里的所有内容,Execute一下,然后,调用engine.Script.func(x,y)执行一下。
    
    var result = engine.Script.m("SHAURCOnewayduew&^%5d54nc'KH");

    特殊情况,调用js全局方法,就是调用js的默认的那些方法

    var result = engine.Invoke("encodeURIComponent", "SHAURCOnewayduew&^%5d54nc'KH"); //只能调用全局方法,如encodeURIComponent

    JavaScriptEngineSwitcher.ChakraCore

    引入Nuget包,JavaScriptEngineSwitcher.ChakraCore,JavaScriptEngineSwitcher.ChakraCore.Native.win-x64,JavaScriptEngineSwitcher.ChakraCore.Native.linux-x64

    using JavaScriptEngineSwitcher.ChakraCore;
    using JavaScriptEngineSwitcher.Core;

    使用,同样是先把js文件执行一遍,然后再去调用要使用的方法。

    string ScriptPath = Path.Combine(Directory.GetCurrentDirectory(), "Scripts", "demo.js");
    var switcher = JsEngineSwitcher.Current;
    switcher.EngineFactories.Add(new ChakraCoreJsEngineFactory());
    switcher.DefaultEngineName = ChakraCoreJsEngine.EngineName;
    IJsEngine engine = JsEngineSwitcher.Current.CreateDefaultEngine();
    engine.ExecuteFile(ScriptPath, Encoding.UTF8);
    string result = engine.CallFunction<string>("m", "SHAURCOnewayduew&^%5d54nc'KH");

    参考资料:

    webmote-org/netcore-javascript

    microsoft/ChakraCore -- github

    Microsoft/ClearScript -- V8ScriptEngine Class

    koopla/NodeServices -- github

     

  • 相关阅读:
    element-UI树形table父子级全选
    VUE父组件调用子组件方法
    elementUI-radio(单选框)label数据类型问题
    微信小程序下载wod,exc,pdf,并显示进度条
    微信小程序js跳转到外部页面
    微信使用e-char图表采坑
    微信登录授权
    外部二维码进入小程序
    js将对象属性作为参数传递
    vscode 个人配置
  • 原文地址:https://www.cnblogs.com/WattWang/p/csharpjs.html
Copyright © 2011-2022 走看看