基于 Roslyn 實作一個簡單的條件決議引擎
Intro
最近在做一個勛章的服務,我們想定義一些勛章的獲取條件,滿足條件之后就給用戶頒發一個勛章,定義條件的時候會定義需要哪些引數,引數的型別,獲取勛章的時候會提供鎖需要的引數,有一些內置的引數,內置的引數決議器(ParamResolver),
最后基于 Roslyn 的 Script + 動態編譯功能實作了一個簡單的條件決議引擎,
Condition Eval Demo
條件決議示例:
[Fact]
public async Task EvalTest()
{
var condition = "x+y > 10";
var variables = JsonConvert.SerializeObject(new[]
{
new
{
Name = "x",
Type = "int"
},
new
{
Name = "y",
Type = "int"
},
});
var params1 = new Dictionary<string, object>()
{
{ "x", 2 },
{ "y", 3 }
};
Assert.False(await ScriptEngine.EvalAsync(condition, variables, params1));
var params1_1 = JsonConvert.SerializeObject(params1);
Assert.False(await ScriptEngine.EvalAsync(condition, variables, params1_1));
var params2 = new
{
x = 6,
y = 5
};
Assert.True(await ScriptEngine.EvalAsync(condition, variables, params2));
}
[Fact]
public async Task EvalStringTest()
{
var condition = "x > y.Length";
var variables = JsonConvert.SerializeObject(new[]
{
new
{
Name = "x",
Type = "int"
},
new
{
Name = "y",
Type = "string"
},
});
var params1 = new
{
x = 1,
y = "3"
};
Assert.False(await ScriptEngine.EvalAsync(condition, variables, params1));
var params2 = new
{
x = 6,
y = "5211"
};
Assert.True(await ScriptEngine.EvalAsync(condition, variables, params2));
}
[Fact]
public async Task EvalLinqTest()
{
var condition = "list.Any(x=>x>10)";
var variables = JsonConvert.SerializeObject(new[]
{
new
{
Name = "list",
Type = "List<int>"
}
});
var params1 = new
{
list = new List<int>()
{
1,2,3,4,5
}
};
Assert.False(await ScriptEngine.EvalAsync(condition, variables, params1));
var params2 = new
{
list = new List<int>()
{
1,2,3,4,5,10,12
}
};
Assert.True(await ScriptEngine.EvalAsync(condition, variables, params2));
}
實作原理
實作的方式是基于 Roslyn 實作的,核心實作是基于 Roslyn 的 Script 實作的,但是 Roslyn Script 的執行有一些限制,不支持匿名類物件的決議,因此還基于 Roslyn 運行時根據變數資訊來動態生成一個型別用于執行腳本決議
var result = await CSharpScript.EvaluateAsync<bool>("1 > 2");
運行時動態生成代碼在之前的 DbTool 專案中介紹過,介紹文章 基于 Roslyn 實作動態編譯
詳細實作細節可以參考代碼 https://github.com/WeihanLi/SamplesInPractice/tree/master/ScriptEngine
Memo
程式集加載在 framework 和 core 環境下的差異
實作的時候我們的專案有 dotnetcore 的,還有 netframework 的,這兩者加載 dll 的時候略有不同,實作的時候用了一個條件編譯,在 dotnet core 環境下和 dotnet framework 分開處理,在 dotnetcore 中使用 AssemblyLoadContext 來加載程式集
#if NETCOREAPP
var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(dllPath);
#else
var assembly = Assembly.LoadFile(dllPath);
#endif
程式集要保存到檔案
原本打算動態生成的程式集保存的一個 Stream 不保存檔案,但是實際測驗下來必須要保存到檔案才可以,所以在專案根目錄下創建了一個臨時目錄 temp 用來保存動態生成的程式集
Roslyn 動態生成的程式集管理
目前還是比較簡單的放在一個 temp 目錄下了,總覺得每一個型別生成一個程式集有些浪費,但是好像也沒辦法修改已有程式集,還沒找到比較好的解決方案,如果有好的處理方式,歡迎一起交流
More
Natasha 是一個基于 Roslyn 來實作動態編譯,能夠讓你更方便進行動態操作,有動態編譯相關需求的可以關注一下這個專案,后面也想用 Natasha 來優化前面提到的問題
基于roslyn的動態編譯庫,為您提供高效率、高性能、可追蹤的動態構建方案,兼容stanadard2.0, 只需原生C#語法不用Emit, 讓您的動態方法更加容易撰寫、跟蹤、維護
Reference
- https://github.com/WeihanLi/SamplesInPractice/tree/master/ScriptEngine
- https://github.com/dotnet/roslyn/wiki/Scripting-API-Samples
- https://github.com/dotnetcore/Natasha
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/54793.html
標籤:.NET Core
上一篇:nginx反向代理signalr
