使用Roslyn脚本化C#代码,C#动态脚本实现方案
來源:https://www.cnblogs.com/7tiny/p/10279349.html
【前言】
Roslyn 是微軟公司開源的 .NET 編譯器。
編譯器支持 C# 和 Visual Basic 代碼編譯,并提供豐富的代碼分析 API。
Roslyn不僅僅可以直接編譯輸出,難能可貴的就是上述描述中的開放了編譯的API,使得代碼腳本化成為了可能。
關(guān)于Roslyn,本文不做過多介紹,因?yàn)樵俳榻B的豐滿終究不及官方文檔介紹的細(xì)膩,各位請移步官方說明地址:https://github.com/dotnet/roslyn/wiki
眾所周知,我們實(shí)現(xiàn)的Filter往往是寫死的代碼在項(xiàng)目里面的,一經(jīng)發(fā)布,便不能隨時改動,有過Paas平臺開發(fā)經(jīng)驗(yàn)的同僚更能體會到租戶靈活配置個性化需求是一個難點(diǎn)。
那么,我們怎么能針對不同的業(yè)務(wù)邏輯靈活地在已經(jīng)部署好地站點(diǎn)上制定不同地業(yè)務(wù)邏輯呢,讓我們一起走進(jìn)這個世界。
本文將通過一個小Demo的實(shí)現(xiàn)講述如何使用Roslyn腳本化代碼,以及如何采用腳本化的代碼對一個網(wǎng)站接口實(shí)現(xiàn)腳本控制Before和After過濾器的功效。
Demo 源碼地址:https://github.com/sevenTiny/Demo.CSharpScript
一、熟悉Roslyn API
首先項(xiàng)目中引入微軟腳本API相關(guān)的Nuget包
按順序引入下面三個Nuget包
Microsoft.CodeAnalysis.CSharp
Microsoft.CodeAnalysis.Scripting
Microsoft.CodeAnalysis.CSharp.Scripting
了解API
1.我們寫一個Run腳本的Demo:
[Fact] [Trait("desc", "調(diào)用動態(tài)創(chuàng)建的腳本方法")]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方法分別執(zhí)行了兩段腳本,最終的結(jié)果輸出了:
"Hello Roslyn!"2.我們再寫一個腳本調(diào)用已存在的類的Demo:
首先我們定義一個類型:
public class TestClass { ? ?public string arg1 { get; set; } ? ?public string GetString(){ ? ? ? ?return "hello world!";} ? ?public string DealString(string a){ ? ? ? ?return a;} }
然后寫腳本執(zhí)行該類型里面的DealString方法(帶參數(shù)和返回值的)
[Trait("desc", "使用類的實(shí)例調(diào)用類的帶參數(shù)的方法,并獲取返回值")] [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 方法傳遞參數(shù),參數(shù)名必須要和參數(shù)類型的字段名稱一直才可以識別
ScriptOptions.Default.WithReferences 明確程序集要引用的類型,類似于引用一個dll
ScriptOptions.Default.WithImports 明確代碼中引用的類型,類似于using
globalsType: typeof(TestClass) 指定了傳遞參數(shù)需要用到的類型(API不支持隱式的參數(shù),只能定義一個明確類型傳遞參數(shù))
script.Compile(); 方法將腳本編譯并保存到內(nèi)存中,待調(diào)用
script.RunAsync(new TestClass { arg1 = x }).Result.ReturnValue 調(diào)用腳本并傳遞參數(shù)獲取返回值,x=“123”,單元測試傳遞的參數(shù)
然后我們便得到了“123”的返回值
更多API
更多的API我們可以從官方介紹文檔中輕松得到
官方WIKI:https://github.com/dotnet/roslyn/wiki/Scripting-API-Samples
二、一個MVC Action Before/After Filter(Action執(zhí)行前后過濾器)的Demo
首先說明項(xiàng)目背景及功能
運(yùn)行.netcore mvc站點(diǎn),點(diǎn)擊菜單欄的進(jìn)入Demo便得到下面界面
我們定義了一個Action,按序號創(chuàng)建了100條記錄用于數(shù)據(jù)演示
before 腳本的name參數(shù)是從url獲取到的name參數(shù),返回結(jié)果將作為100條Demo數(shù)據(jù)的“Name”字段Contains方法的參數(shù) 相當(dāng)于Linq .Where(t=>t.Name.Contains(name));
after 腳本是將 Where 語句過濾后的結(jié)果集作為參數(shù),然后執(zhí)行完腳本中的代碼后,返回結(jié)果展示在了下面的頁面上
可以簡單理解為before是校驗(yàn)url參數(shù)的,after是二次處理結(jié)果數(shù)據(jù)的
為了方便測試,我們的腳本都是從本地文件讀寫的
Demo的管道形式的數(shù)據(jù)流如下:
Demo界面:
? 我們從代碼中可以看到上述描述的數(shù)據(jù)流程:
ss是執(zhí)行文件保存的Before腳本后的結(jié)果
然后我們把他當(dāng)作校驗(yàn)Name的參數(shù)
result是data數(shù)據(jù)執(zhí)行After腳本之后的結(jié)果,然后我們將最終的結(jié)果返回到界面
測試Demo的提供:
using System.Collections.Generic;namespace Demo.CSharpScript.Models { ? ?/// <summary>/// 測試實(shí)體 ? ?/// </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>/// 測試數(shù)據(jù) ? ? ? ?/// </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}條測試數(shù)據(jù)" });} ? ? ? ? ? ?return list;}} }
下面我們來看看兩處執(zhí)行腳本的地方
首先是Before處理邏輯:
拼接了一個腳本(中間部分從文件讀取),使用Roslyn API進(jìn)行動態(tài)編譯執(zhí)行,然后將執(zhí)行的結(jié)果返回
然后是After處理邏輯:
同樣是拼接了一個腳本(中間部分從文件讀取),使用Roslyn API進(jìn)行動態(tài)編譯執(zhí)行,然后將執(zhí)行的結(jié)果返回
在上述過程中還將多個命名空間引入,以便在After腳本中寫Linq語法,否則會執(zhí)行失敗,出現(xiàn)異常
語法我們在上述章節(jié)都已經(jīng)演示過了
實(shí)際我們的腳本:
before中直接忽略了參數(shù)返回了字符串“1”,然后我們Action代碼首先過濾的數(shù)據(jù)就剩下Name字段包含“1”的
after中再次使用Where語法,過濾剩下數(shù)據(jù)中Name字段包含“3”的
那么,我們的結(jié)果中只剩下兩條符合條件:
拓展一下
我們的測試到此本也結(jié)束了,但是為了我們測試腳本更加方便,我這里提供了一個微軟剛出的工具,try.dot.net
不了解的同學(xué)可以參考之前博文熟悉一下 try.dot.net :https://www.cnblogs.com/7tiny/p/10277600.html
我們可以在測試站點(diǎn)上點(diǎn)“點(diǎn)我?guī)椭銓懩_本”的菜單:
然后進(jìn)入try.dot.net的界面:
在這里我們可以使用智能提示編寫腳本,寫完后粘貼回測試的頁面,避免文本框?qū)懘a出現(xiàn)錯誤
三、開拓視野
我們今天用的是 Roslyn,事實(shí)上,微軟有很多類庫可以幫助我們執(zhí)行動態(tài)腳本代碼,例如:
CodeDom(動態(tài)生成或編譯代碼)
ClearScript(執(zhí)行javascript腳本和CSharp代碼) https://microsoft.github.io/ClearScript/Examples/Examples.html
PhpNet(執(zhí)行Php代碼)
JavaDynamicCompiler(執(zhí)行Java代碼)
IronPython
...
有興趣可以去搜索拓展一下!謝謝~
最后,本文Demo 源碼地址:https://github.com/sevenTiny/Demo.CSharpScript
原文地址:https://www.cnblogs.com/7tiny/p/10279349.html
.NET社區(qū)新聞,深度好文,歡迎訪問公眾號文章匯總 http://www.csharpkit.com
點(diǎn)個贊,讓我在心里記住你???
總結(jié)
以上是生活随笔為你收集整理的使用Roslyn脚本化C#代码,C#动态脚本实现方案的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: try.dot.net 的正确使用姿势
- 下一篇: 浅谈c#垃圾回收机制(GC)