Westwind.Scripting
Dynamically compile and execute C# Code and Expressions at runtime. Also includes a light weight script templating engine using Handlebar style C# syntax.
Install / Use
/learn @RickStrahl/Westwind.ScriptingREADME
Westwind C# Scripting and Templating
Dynamically compile and execute C# code at runtime, as well as a light weight, Handlebars style C# template script engine
This small library provides an easy way to compile and execute C# code from source code provided at runtime. It uses Roslyn to provide compilation services for string based code via the CSharpScriptExecution class.
There's also a lightweight, self contained C# script template engine via the ScriptParser class that can evaluate expressions and structured C# statements using Handlebars-like ({{ expression }} and {{% code-block }}) script templates. Unlike other script engines, this engine uses plain C# syntax for expressions and code block execution.
Get it from Nuget:
dotnet add package Westwind.Scripting
It supports the following targets:
v2.0 and later
- .NET 10 (net10.0), .NET 8.0 (net8.0)
for .NET Framework and .NET Standard support you can use v1:
v1.x
- .NET 10 (net10.0), .NET 8.0 (net8.0)
- .NET 4.72
- .NET Standard 2.0
For more detailed information and a discussion of the concepts and code that runs this library, you can also check out this introductory blog post:
Features
- Easy C# code compilation and execution for:
- Code blocks (generic execution)
- Full methods (method header/result value)
- Expressions (evaluate expressions)
- Full classes (compile and load)
- Async execution support
- Caching of already compiled code
- Ability to compile entire classes and load, execute them
- Error Handling
- Intercept compilation and execution errors
- Detailed compiler error messages
- Access to compiled output w/ line numbers
- Roslyn Warmup
- Template Scripting Engine using Handlebars-like C# syntax
- Script Execution from strings
- Script Execution from files
- Support for Partials, Layout and Sections
- Script Evaluator
- Runtime Expression Evaluation
- Non-compiled, Reflection based evaluation of
{{ expressions }}in strings
CSharpScriptExecution: C# Runtime Compilation and Execution
Runtime code compilation and execution is handled via the CSharpScriptExecution class.
ExecuteCode()- Execute an arbitrary block of code. Pass parameters, return a valueEvaluate()- Evaluate a single expression from a code string and returns a valueExecuteMethod()- Provide a complete method signature and call from codeCompileClass()- Generate a class instance from C# code
There are also async versions of the Execute and Evaluate methods:
ExecuteMethodAsync()ExecuteCodeAsync()EvaluateAsync()
All method also have additional generic return type overloads.
Additionally you can also compile self-contained classes:
CompileClass()CompileClassToType()CompileAssembly()
These CompileXXX() methods provide compilation only without execution and create an instance, type or assembly respectively. You can cache these in your application for later re-use and much faster execution.
Use these methods if you need to repeatedly execute the same code and when performance is important as using re-used cached instances is an order of magnitude faster than using the ExecuteXXX() methods repeatedly.
ScriptParser: C# Template Script Expansion
Script Templating using a Handlebars like syntax that can expand C# expressions and C# structured code in text templates that produce transformed text output, can be achieved using the ScriptParser class.
Methods:
ExecuteScript()ExecuteScriptAsync()ParseScriptToCode()
Script parser expansion syntax used is:
{{ expression }}{{% openBlock }}other text or expressions or code blocks{{% endblock }}
Important: Large Runtime Dependencies on Roslyn Libraries
Please be aware that this library has a dependency on
Microsoft.CodeAnalysiswhich contains the Roslyn compiler components used by this component. This dependency incurs a 10+mb runtime dependency and a host of support files that are added to your project output.
Quick Start Examples
To get you going quickly here are a few simple examples that demonstrate functionality. I recommend you read the more detailed instructions below but these examples give you a quick idea on how this library works.
Execute generic C# code with Parameters and Result Value
var script = new CSharpScriptExecution() { SaveGeneratedCode = true };
script.AddDefaultReferencesAndNamespaces();
var code = $@"
// pick up and cast parameters
int num1 = (int) @0; // same as parameters[0];
int num2 = (int) @1; // same as parameters[1];
var result = $""{{num1}} + {{num2}} = {{(num1 + num2)}}"";
Console.WriteLine(result); // just for kicks in a test
return result;
";
// should return a string: (`"10 + 20 = 30"`)
string result = script.ExecuteCode<string>(code,10,20);
if (script.Error)
{
Console.WriteLine($"Error: {script.ErrorMessage}");
Console.WriteLine(script.GeneratedClassCodeWithLineNumbers);
return
}
Execute Async Code with a strongly typed Model
var script = new CSharpScriptExecution() { SaveGeneratedCode = true };
script.AddDefaultReferencesAndNamespaces();
// have to add references so compiler can resolve
script.AddAssembly(typeof(ScriptTest));
script.AddNamespace("Westwind.Scripting.Test");
var model = new ScriptTest() { Message = "Hello World " };
var code = @"
// To Demonstrate Async support
await Task.Delay(10); // test async
string result = Model.Message + "" "" + DateTime.Now.ToString();
return result;
";
// Use generic version to specify result and model types
string execResult = await script.ExecuteCodeAsync<string, ScriptTest>(code, model);
Note that you can forego the strongly typed model by using the non-generic
ExecuteCodeAsync()orExecuteCode()methods which usedynamicinstead of the strong type. This allows the compiler to resolveModelwithout explicitly having to add the reference.
Evaluate a single expression
var script = new CSharpScriptExecution();
script.AddDefaultReferencesAndNamespaces();
// Numbered parameter syntax is easier
var result = script.Evaluate<decimal>("(decimal) @0 + (decimal) @1", 10M, 20M);
Assert.IsFalse(script.Error, script.ErrorMessage );
Template Script Parsing
var model = new TestModel {Name = "rick", DateTime = DateTime.Now.AddDays(-10)};
string script = @"
Hello World. Date is: {{ Model.DateTime.ToString(""d"") }}!
{{% for(int x=1; x<3; x++) {
}}
{{ x }}. Hello World {{Model.Name}}
{{% } }}
And we're done with this!
";
Console.WriteLine(script);
// Optional - build customized script engine
// so we can add custom
var scriptParser = new ScriptParser();
// add dependencies
scriptParser.AddAssembly(typeof(ScriptParserTests));
scriptParser.AddNamespace("Westwind.Scripting.Test");
// Execute
string result = scriptParser.ExecuteScript(script, model);
Console.WriteLine(result);
Console.WriteLine(scriptParser.ScriptEngine.GeneratedClassCodeWithLineNumbers);
Assert.IsNotNull(result, scriptParser.ScriptEngine.ErrorMessage);
which produces:
Hello World. Date is: 5/22/2022!
1. Hello World rick
1. Hello World rick
And we're done with this!
Usage
Using the CSharpScriptExecution class is very easy to use. It works with code passed as strings for either a block of code, an expression, one or more methods or even as a full C# class that can be turned into an instance.
There are methods for:
- Generic code execution
- Complete method execution
- Expression Evaluation
- In-memory class compilation and loading
Important: Large Roslyn Dependencies
If you choose to use this library, please realize that you will pull in a very large dependency on the
Microsoft.CodeAnalysisRoslyn libraries which accounts for 10+mb of runtime files that have to be distributed with your application.
Setting up for Compilation: CSharpScriptExecution
Compiling code is easy - setting up for compilation and ensuring that all your dependencies are available is a little more complicated and also depends on whether you're using full .NET Framework or .NET Core or .NET Standard.
In order to compile code the compiler requires that all dependencies are referenced both for assemblies that need to be compiled against as well as any namespaces that you plan to access in your code and don't want to explicitly mention.
Adding Assemblies and Namespaces
There are a number of methods that help with this:
AddDefaultReferencesAndNamespaces()AddLoadedReferences()AddAssembly()AddAssemblies()AddNamespace()AddNamespaces()
Initial setup for code execution then looks like this:
var script = new CSharpScriptExecution()
{
SaveGeneratedCode = true // useful for errors and trouble shooting
};
// load a default set of assemblies that provides common base class functionality
script.AddDefaultReferencesAndNamespaces();
// Add any additional dependencies
script.AddAssembly(typeof(MyApplication)); // by type
script.AddAssembly("Westwind.Utilitiies.dll"); // by assembly file
// Add additional namespaces you might use in your code snippets
script.AddNamespace("MyApplication");
script.AddNamespace("MyApplication.Utilities");
script.
Related Skills
node-connect
338.0kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
83.4kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
338.0kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
commit-push-pr
83.4kCommit, push, and open a PR
