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