歡迎光臨
每天分享高質量文章

使用Roslyn指令碼化C#程式碼,C#動態指令碼實現方案

【前言】

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

首先說明專案背景及功能

  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
{    
///

/// 測試物體    ///

public class DemoModel
{        
public int ID { get; set; }        public int Age { get; set; }        public string Name { get; set; }        public string Desc { get; set; }        ///

/// 測試資料        ///
///
public static List GetDemoDatas()
{            
var list = new List();            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

原文地址:https://www.cnblogs.com/7tiny/p/10279349.html

贊(0)

分享創造快樂