Roslyn 是微软公司开源的 .NET 编译器。javascript
编译器支持 C# 和 Visual Basic 代码编译,并提供丰富的代码分析 API。html
Roslyn不单单能够直接编译输出,难能难得的就是上述描述中的开放了编译的API,使得代码脚本化成为了可能。java
关于Roslyn,本文不作过多介绍,由于再介绍的丰满终究不及官方文档介绍的细腻,各位请移步官方说明地址:https://github.com/dotnet/roslyn/wikigit
众所周知,咱们实现的Filter每每是写死的代码在项目里面的,一经发布,便不能随时改动,有过Paas平台开发经验的同僚更能体会到租户灵活配置个性化需求是一个难点。github
那么,咱们怎么能针对不一样的业务逻辑灵活地在已经部署好地站点上制定不一样地业务逻辑呢,让咱们一块儿走进这个世界。mvc
本文将经过一个小Demo的实现讲述如何使用Roslyn脚本化代码,以及如何采用脚本化的代码对一个网站接口实现脚本控制Before和After过滤器的功效。工具
Demo 源码地址:https://github.com/sevenTiny/Demo.CSharpScript单元测试
按顺序引入下面三个Nuget包测试
Microsoft.CodeAnalysis.CSharp 网站
Microsoft.CodeAnalysis.Scripting
Microsoft.CodeAnalysis.CSharp.Scripting
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咱们能够从官方介绍文档中轻松获得
官方WIKI:https://github.com/dotnet/roslyn/wiki/Scripting-API-Samples
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 :http://www.javashuo.com/article/p-zexeclcl-ed.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