【前言】

  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("")]
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

  首先说明项目背景及功能

  1. 运行.netcore 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 = ; i < ; 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#代码,C#动态脚本实现方案的更多相关文章

  1. Roslyn编译器-C#动态脚本实现方案

    [前言] Roslyn 是微软公司开源的 .NET 编译器. 编译器支持 C# 和 Visual Basic 代码编译,并提供丰富的代码分析 API. Roslyn不仅仅可以直接编译输出,难能可贵的就 ...

  2. Roslyn 编译器Api妙用:动态生成类并实现接口

    在上一篇文章中有讲到使用反射手写IL代码动态生成类并实现接口. 反射的妙用:C#通过反射动态生成类型继承接口并实现 有位网友推荐使用 Roslyn 去脚本化动态生成,今天这篇文章就主要讲怎么使用 Ro ...

  3. 浏览器环境下JavaScript脚本加载与执行探析之动态脚本与Ajax脚本注入

    在<浏览器环境下JavaScript脚本加载与执行探析之defer与async特性>中,我们研究了延迟脚本(defer)和异步脚本(async)的执行时机.浏览器支持情况.浏览器bug以及 ...

  4. JavaScript 客户端JavaScript之脚本化HTTP(通过XMLHttpRequest)

    XMLHttpRequest对象的设计目的是为了处理由普通文本或XML组成的响应:但是,一个响应也可能是另外一种类型,如果用户代理(UA)支持这种内容类型的话.   大多数浏览的客户端JavaScri ...

  5. JavaScript 客户端JavaScript之 脚本化文档

    客户端JavaScript的存在把静态HTML转变为交互式的Web应用程序,脚本化Web页面的内容正是JavaScript存在的理由.   一个文档对象模型或者说DOM就是一个API,它定义了如何访问 ...

  6. Javascript学习8 - 脚本化文档(Document对象)

    原文:Javascript学习8 - 脚本化文档(Document对象) 每个Web浏览器窗口(或帧)显示一个HTML文档,表示这个窗口的Window对象有一个document属性,它引用了一个Doc ...

  7. Java 脚本化编程指南

    Java 脚本化编程指南 Java脚本化API为谁准备? 脚本语言的一些有用的特性是: 方便:大多数脚本语言都是动态类型的.您通常可以创建新的变量,而不声明变量类型,并且您可以重用变量来存储不同类型的 ...

  8. JavaScript权威指南--脚本化CSS

    知识要点 客户端javascript程序员对CSS感兴趣的是因为样式可以通过脚本编程.脚本化css启用了一系列有趣的视觉效果.例如:可以创建动画让文档从右侧“滑入”.创造这些效果的javascript ...

  9. JavaScript权威指南--脚本化文档

    知识要点 脚本化web页面内容是javascript的核心目标. 第13章和14章解释了每一个web浏览器窗口.标签也和框架由一个window对象所示.每个window对象有一个document对象, ...

随机推荐

  1. Nginx的正向代理与反向代理详解

    正向代理和反向代理的概念 代理服务(Proxy),通常也称为正向代理服务. 如果把局域网外Internet想象成一个巨大的资源库,那么资源就分布到了Internet的各个点上,局域网内的客户端要访问这 ...

  2. spring学习(五) ———— 整合web项目(SSM)

    一.SSM框架整合 1.1.整合思路 从底层整合起,也就是先整合mybatis与spring,然后在编写springmvc. 1.2.开发需求 查询商品列表(从数据库中查询) 1.3.创建web工程 ...

  3. Go语言系列文章

    这个系列写的不是很好,未来重构. Go基础系列 Go基础 Go基础 1.Go简介 2.Go数据结构struct 3.构建Go程序 4.import导包和初始化阶段 5.array 6.Slice详解 ...

  4. 2018.12/17 function 的闭包

    1.闭包:函数在调用的时候会形成一个私有的作用域,对内部变量起到保护的作用,这就是闭包. 2.变量销毁: 1.人为销毁  var a=12; a=null 2.自然销毁  函数调用完成之后 浏览器会自 ...

  5. 前端导出excel数据-jsonToExcel

    咳咳,好久没有写博了... 在工作中遇到了纯前端,将数据导出为excel文件.正文开始: 第一步 安装依赖: npm i xlsx 第二步 写导出函数: import XLSX from 'xlsx' ...

  6. Windows编译OpenCV4Android解决undefined reference to std错误

    注意OpenCV 4.0.1 解决了这个问题请直接下载OpenCV 4.0.1 但是OpenCV 4.0.1作为模块导入Android Studio会有找不到R.styleable的问题 OpenCV ...

  7. vulnhub writeup - 持续更新

    目录 wakanda: 1 0. Description 1. flag1.txt 2. flag2.txt 3. flag3.txt Finished Tips Basic Pentesting: ...

  8. Linux中逻辑卷的快照与还原

    有关逻辑卷的其他操作,请看: Linux中对逻辑卷的建立 Linux中对逻辑卷进行扩容与缩小 Linux中对逻辑卷的移除 LVM还有快照的功能,类似windows的系统还原点.其特点: 1.快照卷的容 ...

  9. Integer a= 127 与 Integer b = 128相关

    Integer a = 127; Integer b = 127; Integer c = 128; Integer d = 128; a == b 与 c == d 的比较结果是什么? a == b ...

  10. 委托学习总结(一)浅谈对C#委托理解

    初入社会,对于我这个初级程序员来说要学的东西实在太多了,公司最近在做一个winform框架开发的桌面应用程序,众所周知,winform也好,webform也好,里面随处可见的事件驱动,有事件,当然也少 ...