Giter Club home page Giter Club logo

westwind.scripting's Introduction

Westwind CSharp Scripting

Dynamically compile and execute CSharp code at runtime

for .NET 4.52 and later

Get it from Nuget:

Install-Package Westwind.Scripting

(currently you need to use the -IncludePreRelease flag)

The small CSharpScripting class provides an easy way to compile and execute C# on the fly from source code at runtime using the .NET compiler services on full Framework .NET. You can use Roslyn compilation for the latest C# features, or classic C# 5 features.

This class makes is very easy to integrate simple scripting or text merging features into applications with minimal effort.

This library provides:

Execution Features

  • ExecuteCode() - Execute an arbitrary block of code
  • Evaluate() - Evaluate an expression from a code string
  • ExecuteMethod() - Execute one or more methods from source
  • CompileClass() - Generate a class instance from C# code

Supported features

  • Assembly Caching so not every execution generates a new assembly
  • Ability to compile entire classes and execute them
  • Automatic Assembly Cleanup at shutdown
  • Use Roslyn or Classic C# compiler interchangeably
  • Display errors and source and line numbers

Requires Roslyn Code Providers for your Project

If you want to use Roslyn compilation for the latest C# features you have to make sure you add the Microsoft.CodeDom.CompilerServices NuGet Package to your application's root project to provide the required compiler binaries for your application.

Note that this adds a sizable chunk of files to your application's output folder in the \roslyn folder. If you don't want this you can use the classic compiler, at the cost of not having access to C# 6+ features.

Usage

Using the CSharpScriptExecution class is very easy. It works with code passed as strings for either a Code block, expression, one or more methods or even as a full C# class that can be turned into an instance.

You can add Assembly References and Namespaces via the AddReferece() and AddNamespace() methods.

Script Execution is gated rather than throwing directly to provide

Executing a Code Snippet

A code snippet can be any block of .NET code that can be executed. You can pass any number of parameters to the snippets which are accessible via a parameters object array. You can optionally return a value by providing a return statement.

var script = new CSharpScriptExecution()
{
    SaveGeneratedCode = true,
    CompilerMode = ScriptCompilerModes.Roslyn
};
script.AddDefaultReferencesAndNamespaces();

//script.AddAssembly("Westwind.Utilities.dll");
//script.AddNamespace("Westwind.Utilities");

var code = $@"
// Check some C# 6+ lang features
var s = new {{ name = ""Rick""}}; // anonymous types
Console.WriteLine(s?.name);       // null propagation

// pick up and cast parameters
int num1 = (int) @0;   // same as parameters[0];
int num2 = (int) @1;   // same as parameters[1];

// string templates
var result = $""{{num1}} + {{num2}} = {{(num1 + num2)}}"";
Console.WriteLine(result);

return result;
";

string result = script.ExecuteCode(code,10,20) as string;

Console.WriteLine($"Result: {result}");
Console.WriteLine($"Error: {script.Error}");
Console.WriteLine(script.ErrorMessage);
Console.WriteLine(script.GeneratedClassCodeWithLineNumbers);

Assert.IsFalse(script.Error, script.ErrorMessage);
Assert.IsTrue(result.Contains(" = 30"));

Note that the return in your code snippet is optional - you can just run code without a result value.

Any parameters you pass in can be accessed either via parameters[0], parameters[1] etc. or using a simpler string representation of @0, @1.

Evaluating an expression

If you want to evaluate a single expression, there's a shortcut Evalute() method that works pretty much the same:

var script = new CSharpScriptExecution()
{
    SaveGeneratedCode = true,
};
script.AddDefaultReferencesAndNamespaces();

// Full syntax
//object result = script.Evaluate("(decimal) parameters[0] + (decimal) parameters[1]", 10M, 20M);

// Numbered parameter syntax is easier
object result = script.Evaluate("(decimal) @0 + (decimal) @1", 10M, 20M);

Console.WriteLine($"Result: {result}");
Console.WriteLine($"Error: {script.Error}");
Console.WriteLine(script.ErrorMessage);
Console.WriteLine(script.GeneratedClassCode);

Assert.IsFalse(script.Error, script.ErrorMessage);
Assert.IsTrue(result is decimal, script.ErrorMessage);

This method is a shortcut wrapper and simply wraps your code into a single line return {exp}; statement.

Executing a Method

ExecuteCode() and Evaluate() are shortcuts for the slightly lower level and more flexible ExecuteMethod() method which as the name implies allows you to specify a single or multiple methods. In fact you can provide an entire class body including properties, events and nested class definitions in the code passed in. This gives a lot of flexibility as you can properly type parameters and return types:

var script = new CSharpScriptExecution()
{
    SaveGeneratedCode = true,
    CompilerMode = ScriptCompilerModes.Roslyn,
    GeneratedClassName = "HelloWorldTestClass"
};
script.AddDefaultReferencesAndNamespaces();

string code = $@"
public string HelloWorld(string name)
{{
string result = $""Hello {{name}}. Time is: {{DateTime.Now}}."";
return result;
}}";

string result = script.ExecuteMethod(code,"HelloWorld","Rick") as string;

Console.WriteLine($"Result: {result}");
Console.WriteLine($"Error: {script.Error}");
Console.WriteLine(script.ErrorMessage);
Console.WriteLine(script.GeneratedClassCode);

Assert.IsFalse(script.Error);
Assert.IsTrue(result.Contains("Hello Rick"));

More than just a Single Method

Note that you can provide more than a method in the code block - you can provide an entire class body including additional methods, properties/fields, events and so on. Effectively you can build out an entire class this way. After intial execution you can access the ObjectInstance member and use either Reflection or dynamic to access the functionality on that class.

var script = new CSharpScriptExecution()
{
    SaveGeneratedCode = true,
    CompilerMode = ScriptCompilerModes.Roslyn
};
script.AddDefaultReferencesAndNamespaces();

// Class body with multiple methods and properties
string code = $@"
public string HelloWorld(string name)
{{
string result = $""Hello {{name}}. Time is: {{DateTime.Now}}."";
return result;
}}

public string GoodbyeName {{ get; set; }}

public string GoodbyeWorld()
{{
string result = $""Goodbye {{GoodbyeName}}. Time is: {{DateTime.Now}}."";
return result;
}}
";

string result = script.ExecuteMethod(code, "HelloWorld", "Rick") as string;

Console.WriteLine($"Result: {result}");
Console.WriteLine($"Error: {script.Error}");
Console.WriteLine(script.ErrorMessage);
Console.WriteLine(script.GeneratedClassCode);

Assert.IsFalse(script.Error);
Assert.IsTrue(result.Contains("Hello Rick"));

// You can pick up the ObjectInstance of the generated class
// Make dynamic for easier access
dynamic instance = script.ObjectInstance;

instance.GoodbyeName = "Markus";
result = instance.GoodbyeWorld();

Console.WriteLine($"Result: {result}");
Assert.IsTrue(result.Contains("Goodbye Markus"));

Compiling and Executing a Class

You can also compile an entire class and then get passed back a dynamic reference to that class so that you can explicitly use that object:

var script = new CSharpScriptExecution()
{
    SaveGeneratedCode = true,
    CompilerMode = ScriptCompilerModes.Roslyn
};
script.AddDefaultReferencesAndNamespaces();

var code = $@"
using System;

namespace MyApp
{{
    public class Math
    {{
        public string Add(int num1, int num2)
        {{
        // string templates
        var result = num1 + "" + "" + num2 + "" = "" + (num1 + num2);
        Console.WriteLine(result);
        
        return result;
        }}
        
        public string Multiply(int num1, int num2)
        {{
        // string templates
        var result = $""{{num1}}  *  {{num2}} = {{(num1 * num2)}}"";
        Console.WriteLine(result);
        
        return result;
        }}
        
    }}
}}
";

dynamic math = script.CompileClass(code);

Console.WriteLine(script.GeneratedClassCodeWithLineNumbers);

Assert.IsFalse(script.Error,script.ErrorMessage);
Assert.IsNotNull(math);

string addResult = math.Add(10, 20);
string multiResult = math.Multiply(3 , 7);


Assert.IsTrue(addResult.Contains(" = 30"));
Assert.IsTrue(multiResult.Contains(" = 21"));

Usage Notes

License

This library is published under MIT license terms.

Copyright © 2012-2019 Rick Strahl, West Wind Technologies

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Give back

If you find this library useful, consider making a small donation:

westwind.scripting's People

Contributors

rickstrahl avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.