C# 代码中调用 Javascript 代码段以提高应用程序的配置灵活性(使用 Javascript .NET 与 Jint)

2018-07-30 09:51

 

简单的 key/value 配置参数有可能满足不了软件需求,有可能需要使用略微复杂的 Javascript 代码段,来进行一些逻辑判断。此文章通过调用 Javascript.NET 与 Jint,以实现

 

 

一般来说,我们需要在开发应用软件的配置文件中,添加一些参数,用于后续用户根据实际情况,自行调整。

配置参数,可以放在配置文件中、环境变量中、或数据库表中(如果使用了数据库的话)。通常,配置数据,以 key/value 的形式。

有时候,这种 key/value 的形式,不足以满足用户需求。比如,系统中有个定时删除临时文件的 job ,我们希望在客户工厂的生产交接班期间及员工吃饭时间,比如客户工厂生产交接班时间为 5:30 - 6:00 , 23:00-23:30, 中途吃饭时间为 11:00, 4:00。

也许,可以用正则表达式,来实现以上的功能。但实际情况一调查,我们发现,客户用户懂正则表达式的基本没有,我们自己公司软件开发人员懂正则表达式的也很少。如果做成正则表达式方式,后续代码交接之后,能不能维护/修改,也很难说。

这样,我们找到了"以 Javascript 的代码段,进行判断,作为配置参数值",这样可以完美地解决我们的问题。Javascript 基本语法简单,客户用户也可自行更改。

 

对于可在 C# 代码中使用的 Javascript 引擎,我们找到了两个: Javascript .NET 与 Jint。前者依赖于 Goolge V8 引擎,运行时需要 Microsoft C Runtime Libraries, 后者则是纯 C# 代码组件。

为同时测试这两种,我们先进行代码抽象:

Javascript 代码,可能无 package/namespace ,可能无 function ,只是一段代码。但无论如何,调用前赋值、调用程序、调用后获取需要的数值,这个基本逻辑,是不会变的。

a. 基础类定义如下:


using System.Collections.Generic;

namespace xxxx
{
    public interface IJavascriptEngine
    {
        /// 
        /// 执行一段 Javascript 代码,传入一些参数,得到一些数值
        /// 
        /// 
        /// 
        /// 传入时,只填key, 保留 value为空;返回时,填写value
        void Execute(string strJavascriptCode, Dictionary inputParameters, Dictionary outputNameValues);
    }
}
		

 

b. Javascript .NET 实现以上接口的代码如下:


using System.Collections.Generic;
 
 namespace dispatch_service.srv
 {
    public class JsNetJavascriptEngineSrv : IJavascriptEngine
    {
        public virtual void Execute(string strJavascriptCode, Dictionary inputParameters, Dictionary outputNameValues)
        {
            using (Noesis.Javascript.JavascriptContext context = new Noesis.Javascript.JavascriptContext())
            {
                //step 1, 初始化各个变量值
                foreach (KeyValuePair pair in inputParameters)
                {
                    context.SetParameter(pair.Key, pair.Value);
                }

                //step 2, 执行 Javascript 代码,可能是多个函数,或无函数的代码段
                context.Run(strJavascriptCode);

                //step 3, 读取所需的变量值,暂存到 nonNullKeyValues 变量中。
                Dictionary nonNullKeyValues = new Dictionary();
                foreach (KeyValuePair pair in outputNameValues)
                {
                    object value = context.GetParameter(pair.Key);
                    if (value != null)
                    {
                        nonNullKeyValues[pair.Key] = value;
                    }
                }

                //step 4,将暂存的变量值,通过 outputNameValues 返回。
                foreach (KeyValuePair pair in nonNullKeyValues)
                {
                    outputNameValues[pair.Key] = pair.Value;
                }
            }
        }
    }
    
}
		

 

c. Jint 实现此接口的代码如下:


using System.Collections.Generic;
using Jint;

namespace xxxx
{
    public class JintJavascriptEngineSrv : IJavascriptEngine
    {
        public virtual void Execute(string strJavascriptCode, Dictionary inputParameters, Dictionary outputNameValues)
        {
            Engine en = new Engine();
           
            //step 1, 初始化各个变量值
            foreach (KeyValuePair pair in inputParameters)
            {
                en.SetValue(pair.Key, pair.Value);
            }
               
            //step 2, 执行 Javascript 代码,可能是多个函数,或无函数的代码段
            en.Execute(strJavascriptCode);

            //step 3, 读取所需的变量值,暂存到 nonNullKeyValues 变量中。
            Dictionary nonNullKeyValues = new Dictionary();
            foreach (KeyValuePair pair in outputNameValues)
            {
                Jint.Native.JsValue value = en.GetValue(pair.Key);
                if (value != null)
                {
                    nonNullKeyValues[pair.Key] = value.ToObject();
                }
            }

            //step 4,将暂存的变量值,通过 outputNameValues 返回。
            foreach (KeyValuePair pair in nonNullKeyValues)
            {
                outputNameValues[pair.Key] = pair.Value;
            }

        }
    }
}
		

 

d. 最后,调用代码里,可以自由切换以上两种 Javascript 引擎:


                Dictionary inputParameters = new Dictionary();
                //给 inputParameters 填充数值,此处无需填充。

                Dictionary outputNameValues = new Dictionary();
                //给 outputNameValues 填充 key 值,此处需得到 canRunNow 变量数值。
                outputNameValues["canRunNow"] = null;

                //IJavascriptEngine eng = new JsNetJavascriptEngineSrv();
                IJavascriptEngine eng = new JintJavascriptEngineSrv();

                eng.Execute(jsStr, inputParameters, outputNameValues);
                object objValue = outputNameValues["canRunNow"];
                System.Nullable bValue =(System.Nullable) objValue;
                if (bValue != null && bValue.Value)
                {
                    needRunNow = true;
                }
		

 

e. 附上作为配置参数值的 Javascript 代码段:


var nowTime=new Date(); var canRunNow = false; var nowHour = nowTime.getHours();  var nowMin = nowTime.getMinutes();  if ( nowHour == 22 && nowMin == 0 ) {canRunNow = true;}		
		

或:


var nowTime=new Date(); var canRunNow = false; var nowMin = nowTime.getMinutes(); var nowSec = nowTime.getSeconds(); if ( nowSec % 3 == 0 ) {canRunNow = true;}
		

 

这样配置就很灵活了。

当然,这里的 Javascript 代码段 , 作为配置参数 (key/value 中的 value),我们把它的多个代码写成一行。其实,不写成一行,也是可行的。

 

 

欢迎转载,转载请注明出处: https://www.zheguisoft.com/staff_blogs/jacklondon_chen/2018, 及 https://www.cnblogs.com/jacklondon/p/call_javascript_in_csharp_code_using_javascript_dot_net_and_jint.html