Roslyn编译器-C#动态脚本实现方案
【前言】
Roslyn 是微软公司开源的 .NET 编译器。
编译器支持 C# 和 Visual Basic 代码编译,并提供丰富的代码分析 API。
Roslyn不仅仅可以直接编译输出,难能可贵的就是上述描述中的开放了编译的API,使得代码脚本化成为了可能。
关于Roslyn,本文不做过多介绍,因为再介绍的丰满终究不及官方文档介绍的细腻,各位请移步官方说明地址:https://github.com/dotnet/roslyn/wiki

众所周知,我们实现的Filter往往是写死的代码在项目里面的,一经发布,便不能随时改动,有过Paas平台开发经验的同僚更能体会到租户灵活配置个性化需求是一个难点。
那么,我们怎么能针对不同的业务逻辑灵活地在已经部署好地站点上制定不同地业务逻辑呢,让我们一起走进这个世界。
本文将通过一个小Demo的实现讲述如何使用Roslyn脚本化代码,以及如何采用脚本化的代码对一个网站接口实现脚本控制Before和After过滤器的功效。
Demo 源码地址:https://github.com/sevenTiny/Demo.CSharpScript
一、熟悉Roslyn API
首先项目中引入微软脚本API相关的Nuget包
按顺序引入下面三个Nuget包
Microsoft.CodeAnalysis.CSharp
Microsoft.CodeAnalysis.Scripting
Microsoft.CodeAnalysis.CSharp.Scripting
了解API
1.我们写一个Run脚本的Demo:

[Fact]
[Trait("desc", "调用动态创建的脚本方法")]
public void CallScriptFromText()
{
string code1 = @"
public class ScriptedClass
{
public string HelloWorld { get; set; }
public ScriptedClass()
{
HelloWorld = ""Hello Roslyn!"";
}
}"; var script = CSharpScript.RunAsync(code1).Result; var result = script.ContinueWithAsync<string>("new ScriptedClass().HelloWorld").Result; Assert.Equal("Hello Roslyn!", result.ReturnValue);
}

Demo中,我们用字符串定义了一个类,并在其中写了小段逻辑,通过Run方法和ContinueWityAsync方法分别执行了两段脚本,最终的结果输出了:
"Hello Roslyn!"
2.我们再写一个脚本调用已存在的类的Demo:
首先我们定义一个类型:

public class TestClass
{
public string arg1 { get; set; } public string GetString()
{
return "hello world!";
} public string DealString(string a)
{
return a;
}
}

然后写脚本执行该类型里面的DealString方法(带参数和返回值的)

[Trait("desc", "使用类的实例调用类的带参数的方法,并获取返回值")]
[Theory]
[InlineData("123")]
public void CallScriptFromAssemblyWithArgument(string x)
{
var script = CSharpScript.Create<string>("return new TestClass().DealString(arg1);",
ScriptOptions.Default
.WithReferences(typeof(TestClass).Assembly)
.WithImports("Test.Standard.DynamicScript"), globalsType: typeof(TestClass));
script.Compile();
var result = script.RunAsync(new TestClass { arg1 = x }).Result.ReturnValue;
Assert.Equal(x, result.ToString());
}

RunAsync 方法传递参数,参数名必须要和参数类型的字段名称一直才可以识别
ScriptOptions.Default.WithReferences 明确程序集要引用的类型,类似于引用一个dll
ScriptOptions.Default.WithImports 明确代码中引用的类型,类似于using
globalsType: typeof(TestClass) 指定了传递参数需要用到的类型(API不支持隐式的参数,只能定义一个明确类型传递参数)
script.Compile(); 方法将脚本编译并保存到内存中,待调用
script.RunAsync(new TestClass { arg1 = x }).Result.ReturnValue 调用脚本并传递参数获取返回值,x=“123”,单元测试传递的参数
然后我们便得到了“123”的返回值
更多API
更多的API我们可以从官方介绍文档中轻松得到
官方WIKI:https://github.com/dotnet/roslyn/wiki/Scripting-API-Samples

二、一个MVC Action Before/After Filter(Action执行前后过滤器)的Demo
首先说明项目背景及功能
- 运行.netcore mvc站点,点击菜单栏的进入Demo便得到下面界面
- 我们定义了一个Action,按序号创建了100条记录用于数据演示
- before 脚本的name参数是从url获取到的name参数,返回结果将作为100条Demo数据的“Name”字段Contains方法的参数 相当于Linq .Where(t=>t.Name.Contains(name));
- after 脚本是将 Where 语句过滤后的结果集作为参数,然后执行完脚本中的代码后,返回结果展示在了下面的页面上
- 可以简单理解为before是校验url参数的,after是二次处理结果数据的
- 为了方便测试,我们的脚本都是从本地文件读写的
Demo的管道形式的数据流如下:

Demo界面:

我们从代码中可以看到上述描述的数据流程:

ss是执行文件保存的Before脚本后的结果
然后我们把他当作校验Name的参数
result是data数据执行After脚本之后的结果,然后我们将最终的结果返回到界面
测试Demo的提供:

using System.Collections.Generic; namespace Demo.CSharpScript.Models
{
/// <summary>
/// 测试实体
/// </summary>
public class DemoModel
{
public int ID { get; set; }
public int Age { get; set; }
public string Name { get; set; }
public string Desc { get; set; } /// <summary>
/// 测试数据
/// </summary>
/// <returns></returns>
public static List<DemoModel> GetDemoDatas()
{
var list = new List<DemoModel>();
for (int i = 0; i < 100; i++)
{
list.Add(new DemoModel { ID = i, Age = i, Name = $"7tiny_{i}", Desc = $"第{i}条测试数据" });
}
return list;
}
}
}

下面我们来看看两处执行脚本的地方
首先是Before处理逻辑:

拼接了一个脚本(中间部分从文件读取),使用Roslyn API进行动态编译执行,然后将执行的结果返回
然后是After处理逻辑:

同样是拼接了一个脚本(中间部分从文件读取),使用Roslyn API进行动态编译执行,然后将执行的结果返回
在上述过程中还将多个命名空间引入,以便在After脚本中写Linq语法,否则会执行失败,出现异常
语法我们在上述章节都已经演示过了
实际我们的脚本:

before中直接忽略了参数返回了字符串“1”,然后我们Action代码首先过滤的数据就剩下Name字段包含“1”的
after中再次使用Where语法,过滤剩下数据中Name字段包含“3”的
那么,我们的结果中只剩下两条符合条件:

拓展一下

我们的测试到此本也结束了,但是为了我们测试脚本更加方便,我这里提供了一个微软刚出的工具,try.dot.net
不了解的同学可以参考之前博文熟悉一下 try.dot.net :https://www.cnblogs.com/7tiny/p/10277600.html
我们可以在测试站点上点“点我帮助你写脚本”的菜单:

然后进入try.dot.net的界面:

在这里我们可以使用智能提示编写脚本,写完后粘贴回测试的页面,避免文本框写代码出现错误
三、开拓视野
我们今天用的是 Roslyn,事实上,微软有很多类库可以帮助我们执行动态脚本代码,例如:
CodeDom(动态生成或编译代码)
ClearScript(执行javascript脚本和CSharp代码) https://microsoft.github.io/ClearScript/Examples/Examples.html
PhpNet(执行Php代码)
JavaDynamicCompiler(执行Java代码)
IronPython
...
有兴趣可以去搜索拓展一下!谢谢~
最后,本文Demo 源码地址:https://github.com/sevenTiny/Demo.CSharpScript
Roslyn编译器-C#动态脚本实现方案的更多相关文章
- 使用Roslyn脚本化C#代码,C#动态脚本实现方案
[前言] Roslyn 是微软公司开源的 .NET 编译器. 编译器支持 C# 和 Visual Basic 代码编译,并提供丰富的代码分析 API. Roslyn不仅仅可以直接编译输出,难能可贵的就 ...
- Roslyn 编译器Api妙用:动态生成类并实现接口
在上一篇文章中有讲到使用反射手写IL代码动态生成类并实现接口. 反射的妙用:C#通过反射动态生成类型继承接口并实现 有位网友推荐使用 Roslyn 去脚本化动态生成,今天这篇文章就主要讲怎么使用 Ro ...
- 基于 OpenResty 的动态服务路由方案
2019 年 5 月 11 日,OpenResty 社区联合又拍云,举办 OpenResty × Open Talk 全国巡回沙龙武汉站,又拍云首席布道师在活动上做了< 基于 OpenResty ...
- PHP加Nginx实现动态裁剪图片方案
许久以前写过一篇也是关于高性能PHP图片动态裁剪方案的文章,那文章使用的是nginx Cache和rewrite实现的,当然再加上CDN,那个方案存在一个问题就是图片并没有实际生成,而是以二进制的形式 ...
- Fortify漏洞之Dynamic Code Evaluation: Code Injection(动态脚本注入)和 Password Management: Hardcoded Password(密码硬编码)
继续对Fortify的漏洞进行总结,本篇主要针对 Dynamic Code Evaluation: Code Injection(动态脚本注入) 和 Password Management: Har ...
- [.NET大牛之路 006] 了解 Roslyn 编译器
.NET大牛之路 • 王亮@精致码农 • 2021.07.09 维基百科对编译器的解释是:编译器是一种程序,它将某种编程语言编写的源代码(原始语言)转换成另一种编程语言(目标语言).编译是从源代码(通 ...
- 【开源】.Net 动态脚本引擎NScript
开源地址: https://git.oschina.net/chejiangyi/NScript 开源QQ群: .net 开源基础服务 238543768 .Net 动态脚本引擎 NScript ...
- JavaScript 动态脚本
动态脚本,指的是在页面加载时不存在,但将来的某一个时刻通过修改DOM动态添加的脚本. <script type="text/javascript"> function ...
- DOM动态脚本和动态样式
动态脚本 [定义] 在页面加载时不存在,但将来的某一时刻通过修改DOM动态添加的脚本. [方式] [1]插入外部文件方式 var script = document.createElement(&qu ...
随机推荐
- CentOS下的Autoconf和AutoMake(完善篇) 3
在<实践篇>之后,由于需求不断修正,所以这篇是针对<实践篇>的一些完善.(以后内容会不定期增加完善) 1.不想链接到math的动态库,想连接到静态库①使用命令ldd ./mys ...
- XAML中格式化日期
要求被格式化数据的类型是DateTime StringFormat='yyyy-MM-dd' StringFormat={}{0:yyyy-MM-dd}
- 名称随id的变化而变化
$("#user_id").change(function () { var uid = $(this).val(); if (uid == '') { $("#user ...
- kubectl管理多个k8s集群
#把每个k8s集群的json配置文件放到/root/.kube/目录下,改为不同名字,通过--kubeconfig实现不同集群操作 kubectl --kubeconfig=/root/.kube/m ...
- (转载)-关于sg函数的理解
最近学习了nim博弈,但是始终无法理解sg函数为什么sg[S]=mex(sg[S'] | S->S'),看到一篇博文解释的不错,截取了需要的几章节. 四.Sprague-Grundy数的提出 我 ...
- POJ-2251 Dungeon Master (BFS模板题)
You are trapped in a 3D dungeon and need to find the quickest way out! The dungeon is composed of un ...
- HttpServletRequest解决中文乱码的问题
HTTP请求有get和post,这两中方式解决中文乱码的方式如下: 1.Post方式请求 //这句话是设置post请求体的编码为utf-8 request.setCharacterEncoding(& ...
- CMD模拟http请求
搭建环境 前提是在win7中开启telnet服务 开启方法请参考:http://jingyan.baidu.com/article/870c6fc3cd6fa9b03fe4bee4.html 打开Te ...
- Intel DAAL AI加速 ——传统决策树和随机森林
# file: dt_cls_dense_batch.py #===================================================================== ...
- JavaScript学习总结(九)——Javascript面向(基于)对象编程
一.澄清概念 1.JS中"基于对象=面向对象" 2.JS中没有类(Class),但是它取了一个新的名字叫“原型对象”,因此"类=原型对象" 二.类(原型对象)和 ...