Skip to content

adelehtamaskani/JsScriptTaskExecutor.Net

Repository files navigation

JsScriptTaskExecuter.Net

JsScriptTaskExecuter.Net is a .NET library powered by the Microsoft ClearScript V8 engine to safely execute JavaScript scripts, with strongly-typed inputs/outputs, timeout control, and dynamic library injection. It is particularly useful for executing script tasks in BPMN or workflow engines where dynamic logic is required.

✨ Features

  • Execute JavaScript using Microsoft ClearScript V8 engine
  • Strongly-typed input/output variable handling (primitive, object, list types)
  • Inject reusable JavaScript libraries at runtime
  • Timeout control to safely prevent long-running or stuck scripts
  • Type-safe conversions between JS and .NET types
  • Fully unit-tested core engine (xUnit)
  • MVC sample app for script task CRUD and live execution

πŸ“¦ NuGet Package Installation

Install via NuGet:

dotnet add package JsScriptTaskExecutor.Net

Or using the NuGet Package Manager:

PM> Install-Package JsScriptTaskExecutor.Net

πŸ›  Quick Start (Package Usage)

  1. Register the service in your Program.cs:
using ScriptTaskExecutor.Engine;

var builder = WebApplication.CreateBuilder(args);

// REQUIRED: Add logging services before adding the script executor in Console Apps
services.AddLogging(loggingBuilder => 
{
    loggingBuilder.AddConsole();
    loggingBuilder.SetMinimumLevel(LogLevel.Information);
});

// Then add the executor
// Configure ScriptTaskExecutor with path to JS libraries
builder.Services.AddScriptTaskExecutor(librariesPath: "path/to/your/js/libraries");

var app = builder.Build();

This sets up the IScriptTaskExecutor service for injection into controllers or other services.

  1. Basic Usage Example
using ScriptTaskExecutor.Engine.DataTypes;
using ScriptTaskExecutor.Engine.Executors;
using ScriptTaskExecutor.Engine.Models;

public class ScriptService
{
    private readonly IScriptTaskExecutor _executor;

    public ScriptService(IScriptTaskExecutor executor)
    {
        _executor = executor;
    }

    public async Task RunScript()
    {
        var context = new ScriptExecutionContext
        {
            Script = "result = input1 + input2;",
            Inputs = new Dictionary<string, (DataTypesEnum Type, object? DefaultValue)>
            {
                { "input1", (DataTypesEnum.Int32, 5) },
                { "input2", (DataTypesEnum.Int32, 10) }
            },
            Outputs = new Dictionary<string, DataTypesEnum>
            {
                { "result", DataTypesEnum.Int32 }
            },
            TimeoutMs = 5000
        };

        var outputs = await _executor.ExecuteScript(context);
        Console.WriteLine($"Result: {outputs["result"]}"); // Output: 15
    }
}

⚠️ Important β€” Declaring Output Variables in JavaScript

Any JavaScript variable you expect to retrieve as an output variable must be declared with var.

In JavaScript, let and const are block-scoped and are not attached to the global object, so they will not be accessible from the .NET side after script execution.

Why this is required

When the V8 engine executes your script, only variables declared with var at the top level are added as properties of the global object. Variables declared with let or const live in the script’s internal scope and are not visible outside the script.

βœ… Example (works)

var result = inputValue * 2;

❌ Example (will not work)

let result = inputValue * 2;
const result = inputValue * 2;

πŸ’‘ If you must use let or const

Explicitly assign the value to the global object:

let result = inputValue * 2;
globalThis.result = result; // Now accessible from C#

πŸ— Repository Structure

The repository contains three main projects:

ScriptTaskExecuter.Net/
β”œβ”€β”€ ScriptTaskExecutor.Engine/            # Core library (NuGet package source)
β”‚   β”œβ”€β”€ DataTypes/                        # Type system implementation
β”‚   β”œβ”€β”€ Executors/                        # Core execution logic
β”‚       β”œβ”€β”€ IScriptTaskExecutor.cs
β”‚       β”œβ”€β”€ ScriptResultConverter.cs
β”‚       └── ScriptTaskExecutor.cs
β”‚   └── Models/                           # Data models
β”œβ”€β”€ ScriptTaskExecutor.Net/               # MVC sample application
β”‚   β”œβ”€β”€ Controllers/                      # Web controllers
β”‚   β”œβ”€β”€ Models/                           # Domain entities
β”‚   β”œβ”€β”€ Services/                         # Business logic
β”‚   └── Views/                            # UI components
β”œβ”€β”€ ScriptTaskExecutor.Net.Test/          # Unit tests
β”‚   └── ScriptTaskExecutorTest.cs         # Core engine tests
β”‚   

Key Components:

  • ScriptTaskExecutor.Engine: Core execution engine with V8 integration
  • ScriptTaskExecutor.Net: Sample MVC app with CRUD operations
  • ScriptTaskExecutor.Net.Test: Comprehensive Unit tests

How ScriptTaskExecutor.Engine Works

The core logic of this Repository resides in the ScriptTaskExecutor.Engine class library, specifically within the Executors/ScriptTaskExecutor.cs file. This class is responsible for safely executing JavaScript code using the V8 engine with:

  • Initializing input variables (primitive, object, list)

  • Loading JS libraries before execution

  • Running the script with timeout

  • Extracting and validating output variables

Supported DataTypesEnum

The class library supports strongly-typed variables through the DataTypesEnum, which includes:

  • String
  • Number (supports .NET numeric types like Int32, Int64, Double, etc.)
  • Boolean
  • Object
  • List

Use the helper methods DataTypeHelper.GetSystemType() and ScriptResultConverter.ToNativeObject() to ensure correct type conversion between JavaScript and .NET.

var systemType = DataTypeHelper.GetSystemType(DataTypesEnum.Int32);
var convertedValue = ScriptResultConverter.ToNativeObject(jsValue, DataTypesEnum.Int32);

Script Execution Method The core method ExecuteScript accepts a ScriptExecutionContext and returns a dictionary of output variables:

public Task<Dictionary<string, object?>> ExecuteScript(ScriptExecutionContext context)

The ScriptExecutionContext defines:

public class ScriptExecutionContext
{
    public string Script { get; set; }   // JavaScript code to execute
    public Dictionary<string, (DataTypesEnum Type, object? DefaultValue)> Inputs { get; set; }  // Dictionary of input variables with their types and default values
    public Dictionary<string, DataTypesEnum> Outputs { get; set; }  // Dictionary of expected output variable names and their types
    public List<string>? Libraries { get; set; }   // List of JavaScript library filenames to inject
    public int TimeoutMs { get; set; } = 30;    // Maximum execution time in milliseconds (default: 30ms)
}

** JS Library Injection** Place JavaScript libraries in your configured libraries path and reference them by filename:

var context = new ScriptExecutionContext
{
    Libraries = new List<string> { "mathUtils.js", "validation.js" },
    // ... other properties
};

MVC Sample App

The repository includes a fully-functional ASP.NET Core MVC application that demonstrates practical usage of the JsScriptTaskExecutor.Net library. This web-based interface provides:

  • Complete CRUD Operations: Create, read, update, and delete script tasks

  • Variable Management: Configure input and output variables for each script

  • Live Execution: Execute scripts and view results directly in the browser

  • Visual Interface: User-friendly UI for managing script workflows

Domain Entities Implementation The MVC application uses the following domain entities to structure its data:

public class ScriptTask
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Statement { get; set; }
    public ICollection<ScriptTaskVariable> ScriptTaskVariables { get; set; }
}

public class ScriptTaskVariable
{
    public int Id { get; set; }
    public int ScriptTaskId { get; set; }
    public ScriptTask ScriptTask { get; set; }
    public string Name { get; set; }
    public string? DefaultValue { get; set; }
    public DataTypesEnum Type { get; set; }
    public bool Input { get; set; }
    public bool Output { get; set; }
}

These entities form the foundation of the application's data model, enabling persistent storage of script tasks and their associated variables in a database through Entity Framework Core. The MVC app serves as both a practical example and a testing environment for the core library functionality.

πŸ§ͺ Comprehensive Unit Testing

The project includes extensive unit tests using xUnit and Moq to ensure reliability and correctness across all functionality. The test suite covers:

Test Categories

  • Input/Output Mapping: Validates proper variable passing between .NET and JavaScript

  • Type Validation & Conversion: Ensures correct type handling across all supported data types

  • Timeout Enforcement: Verifies script execution respects timeout constraints

  • Library Injection: Tests external JavaScript library loading and usage

  • Error Handling: Confirms proper exception handling for various error scenarios

Test Data Structure The MoqData class provides comprehensive test cases through a data-driven approach:

public class MoqData : IEnumerable<object[]>
{
    public IEnumerator<object[]> GetEnumerator()
    {
        // 13 diverse test cases covering:
        // - Basic arithmetic operations
        // - String manipulation and concatenation
        // - Boolean logic and comparisons
        // - Date parsing and manipulation
        // - Array/List operations and transformations
        // - Complex object handling
        // - External library integration (dayjs, lodash)
        // - JSON serialization/deserialization
        yield return new object[] { /* Test case data */ };
        // ... additional test cases
    }
}

Key Test Examples

  • Simple Arithmetic: Validates basic mathematical operations

  • String Concatenation: Tests string manipulation and formatting

  • Boolean Logic: Verifies comparison operations and boolean results

  • Date Handling: Tests JavaScript Date object functionality

  • Array Operations: Validates list/array manipulation and access

  • Object Processing: Tests complex object handling and property access

  • Library Integration: Verifies external library loading (dayjs, lodash)

  • JSON Operations: Tests JSON serialization/deserialization scenarios

Test Execution

The main test class ScriptTaskExecutorTest includes:

[Theory]
[ClassData(typeof(MoqData))]
public async Task ExecuteScript_ShouldCorrectlyMapInputToOutput(ScriptTaskExecutorDto data)
{
    // Comprehensive test validating input→output mapping with library support
}

[Fact]
public void ExecuteScript_ShouldThrowTimout_WhenScriptTakesTooLong()
{
    // Timeout validation using infinite loop script
}

[Fact]
public async Task ExecuteScript_ShouldThrow_WhenOutputTypeDoesNotMatchExpected()
{
    // Type mismatch exception handling test
}

Test Infrastructure

  • Solution Path Resolution: Automatically locates library files relative to solution root

  • Deep Equality Comparison: Custom comparers for complex object validation

  • Mock Dependencies: Isolated testing with mocked logger and settings

  • Comprehensive Coverage: Tests edge cases, error conditions, and success scenarios

πŸš€ Getting Started with the Repository

1. Clone and Setup

git clone https://github.com/adelehtamaskani/JsScriptTaskExecuter.Net.git
cd JsScriptTaskExecuter.Net
dotnet restore

2. Database Setup (MVC Sample)

dotnet ef database update --project ScriptTaskExecutor.Net

3. Run the Sample Application

dotnet run --project ScriptTaskExecutor.Net

4. Run tests

dotnet test

Technologies Used

  • .NET 8

  • Microsoft.ClearScript.V8

  • ASP.NET Core MVC

  • Entity Framework Core

  • xUnit

πŸ“„ License

This project is licensed under the MIT License.

🀝 Contributions

Feel free to open issues or submit pull requests. Suggestions and improvements are always welcome!

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages