使⽤Roslyn脚本化C#代码,C#动态脚本实现⽅案
【前⾔】
Roslyn 是微软公司开源的 .NET 编译器。
编译器⽀持 C# 和 Visual Basic 代码编译,并提供丰富的代码分析 API。
Roslyn不仅仅可以直接编译输出,难能可贵的就是上述描述中的开放了编译的API,使得代码脚本化成为了可能。
关于Roslyn,本⽂不做过多介绍,因为再介绍的丰满终究不及官⽅⽂档介绍的细腻,各位请移步官⽅说明地址:
众所周知,我们实现的Filter往往是写死的代码在项⽬⾥⾯的,⼀经发布,便不能随时改动,有过Paas平台开发经验的同僚更能体会到租户灵活配置个性化需求是⼀个难点。
那么,我们怎么能针对不同的业务逻辑灵活地在已经部署好地站点上制定不同地业务逻辑呢,让我们⼀起⾛进这个世界。
本⽂将通过⼀个⼩Demo的实现讲述如何使⽤Roslyn脚本化代码,以及如何采⽤脚本化的代码对⼀个⽹站接⼝实现脚本控制Before和After过滤器的功效。
Demo 源码地址:
⼀、熟悉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:
⼆、⼀个MVC Action Before/After Filter(Action执⾏前后过滤器)的Demo
⾸先说明项⽬背景及功能
1. 运⾏core mvc站点,点击菜单栏的进⼊Demo便得到下⾯界⾯
2. 我们定义了⼀个Action,按序号创建了100条记录⽤于数据演⽰
3. before 脚本的name参数是从url获取到的name参数,返回结果将作为100条Demo数据的“Name”字段Contains⽅法的参数相当于Linq
.Where(t=>t.Name.Contains(name));
4. after 脚本是将 Where 语句过滤后的结果集作为参数,然后执⾏完脚本中的代码后,返回结果展⽰在了下⾯的页⾯上
5. 可以简单理解为before是校验url参数的,after是⼆次处理结果数据的
6. 为了⽅便测试,我们的脚本都是从本地⽂件读写的
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}条测试数据" });
}
php如何运行代码return list;
}
}
}
下⾯我们来看看两处执⾏脚本的地⽅
⾸先是Before处理逻辑:
拼接了⼀个脚本(中间部分从⽂件读取),使⽤Roslyn API进⾏动态编译执⾏,然后将执⾏的结果返回
然后是After处理逻辑:
同样是拼接了⼀个脚本(中间部分从⽂件读取),使⽤Roslyn API进⾏动态编译执⾏,然后将执⾏的结果返回 在上述过程中还将多个命名空间引⼊,以便在After脚本中写Linq语法,否则会执⾏失败,出现异常
语法我们在上述章节都已经演⽰过了
实际我们的脚本:
before中直接忽略了参数返回了字符串“1”,然后我们Action代码⾸先过滤的数据就剩下Name字段包含“1”的
after中再次使⽤Where语法,过滤剩下数据中Name字段包含“3”的
那么,我们的结果中只剩下两条符合条件:
拓展⼀下
我们的测试到此本也结束了,但是为了我们测试脚本更加⽅便,我这⾥提供了⼀个微软刚出的⼯具,try.dot 不了解的同学可以参考之前博⽂熟悉⼀下 try.dot :
我们可以在测试站点上点“点我帮助你写脚本”的菜单:
然后进⼊try.dot的界⾯:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论