【前言】
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