SoftwareDriven.Blockly.Code 1.8.0

dotnet add package SoftwareDriven.Blockly.Code --version 1.8.0
                    
NuGet\Install-Package SoftwareDriven.Blockly.Code -Version 1.8.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="SoftwareDriven.Blockly.Code" Version="1.8.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="SoftwareDriven.Blockly.Code" Version="1.8.0" />
                    
Directory.Packages.props
<PackageReference Include="SoftwareDriven.Blockly.Code" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add SoftwareDriven.Blockly.Code --version 1.8.0
                    
#r "nuget: SoftwareDriven.Blockly.Code, 1.8.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package SoftwareDriven.Blockly.Code@1.8.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=SoftwareDriven.Blockly.Code&version=1.8.0
                    
Install as a Cake Addin
#tool nuget:?package=SoftwareDriven.Blockly.Code&version=1.8.0
                    
Install as a Cake Tool

SoftwareDriven.Blockly.Code

A Blockly based code engine for .NET.

Blockly is a JavaScript library for building visual programming editors. This library takes the Blockly XML produced by such an editor and executes it server-side in C# — Blockly's own JavaScript code generators are not used. The XML is parsed into a tree of Statement objects that a custom interpreter runs.

The package contains no UI. To embed the editor itself into a Blazor application, use SoftwareDriven.Blockly.Blazor.

Installation

dotnet add package SoftwareDriven.Blockly.Code

Target framework: net10.0.

Quick start

using SoftwareDriven.Blockly.Code;
using SoftwareDriven.Blockly.Code.Parser;
using SoftwareDriven.Blockly.Code.Statements;

// 1. Parse the Blockly XML into an executable program.
var reader = new BlocklyXmlReader();
var control = reader.ReadFile("Program.xml", new BlockControlConfig()
{
    OutputTopics = BlockOutputTopic.TextPrint | BlockOutputTopic.Errors,
    DefaultOutput = BlockDefaultOutput.Console,
});

if (control == null)
    return; // The file could not be read.

// 2. Run it.
var result = control.Run(new RunContext() { Caller = "MyApp" });

// 3. Evaluate the result and read back the variables.
if (result.Type == ExecutionResultType.Error)
    Console.WriteLine(result.Message);

var answer = control.GetVariableByName("answer");
Console.WriteLine(answer?.Value);

ReadStream(Stream, BlockControlConfig?) is the equivalent overload for XML coming from a database or from the Blazor editor's Export():

using var stream = new MemoryStream(Encoding.UTF8.GetBytes(workspaceXml));
var control = reader.ReadStream(stream, config);

Writing the program back to XML — e.g. to feed it into the editor again:

var writer = new BlocklyXmlWriter();
writer.WriteFile("Program.xml", control);

using var output = new MemoryStream();
writer.WriteStream(output, control);
var xml = Encoding.UTF8.GetString(output.ToArray());

Configuration

BlockControlConfig is passed to the reader and is available afterwards as control.Config.

Property Description
Name A name for the program, used in the log output.
OutputTopics Flags selecting what is written to the output: Errors, TextPrint, LogExecutionLogic, LogSetVariable, MemberAccess.
DefaultOutput Where the output goes: Debug, Console or Logger.
Logger The ILogger used when DefaultOutput is Logger.
MaxStackDepth Guard against runaway recursion (default 1000).
MaxLoopCycles Guard against endless loops (default 1000).
FormattedJson Indents the JSON values produced by the engine.
BlockRepository The IBlockRepository providing custom blocks.
TypeRegistry The IBlockTypeRegistry describing custom object types.
HttpClient The client used by the API call blocks.

Keep the MaxStackDepth / MaxLoopCycles guards — they prevent a faulty user program from taking down the host process.

Values and variables

Values are stringly typed. A TypedValue holds a string? Value plus a string? Type naming a KnownType (Boolean, String, Number, Array, Colour, DateTime, TimeSpan, Dictionary, Object) or a registered custom type. BlockVariable adds a name to that pair.

Numbers and dates are always parsed and formatted with CultureInfo.InvariantCulture.

// Provide input before the run ...
control.CreateOrSetVariable("input", "42", KnownType.Number);

control.Run(new RunContext());

// ... and read the output afterwards.
foreach (var variable in control.Variables)
    Console.WriteLine($"{variable.Name} = {variable.Value} ({variable.Type})");

Further members of interest on BlockControl: Main (the top level function), Functions, GetFunctionByName(), GetVariableById(), SetVariableByName() and Copy() to run the same program several times in parallel.

Custom object types

The object members used by the GetMember / SetMember / MemberAccess blocks are resolved through an IBlockTypeRegistry. Types can be declared explicitly, taken from a .NET type by reflection, or deserialized from JSON.

var registry = new BlockTypeRegistry();

// The built-in types (Boolean, String, Number, ...).
registry.AddKnownTypes();

// A hand written type.
registry.GetOrAddType("Customer", "Customer")
        .AddMember("Name", nameof(KnownType.String))
        .AddMember("Orders", nameof(KnownType.Array));

// A type derived from a .NET class by reflection.
registry.GetOrAddReflectedType<Order>();

config.TypeRegistry = registry;

A registry can also be loaded from JSON and layered with others:

var fromJson = JsonSerializer.Deserialize<BlockTypeRegistry>(json);

var composite = new CompositeTypeRegistry();
composite.Registries.Add(registry);
composite.Registries.Add(fromJson!);

Values of registered types are JSON documents. The registry offers helpers to work with them without leaving the string world:

var value = registry.CreateValueOfRegisteredType("Customer")?.ToString();

registry.SetNestedValue("Customer", "Name", value, "\"Doe\"", out value);
registry.AppendNestedValue("Customer", "Orders", value!, "{ \"Id\": 1 }", out value);
registry.GetElementsOfNestedArray("Customer", "Orders", value, "Order", out var orders);

Custom blocks

All standard Blockly blocks are handled by the engine itself. An IBlockRepository only has to deal with custom block types; returning null from CreateStatementFromBlock defers to the built-in handling.

public class MyRepository : IBlockRepository
{
    public string? BlocksJson => myBlockDefinitionsJson; // Blockly block JSON for the editor.

    public Toolbox Toolbox { get; } = new();

    public Statement? CreateStatementFromBlock(BlockControl control, XmlNode node,
        string id, string type, BlocklyXmlReader reader)
    {
        if (type != "my_block")
            return null; // Not ours - let the engine handle it.

        return new MyStatement()
        {
            ID = id,
            Input = reader.TryCreateValueStatementFromBlock(control, node.ChildNodes.Cast<XmlNode>(), "INPUT"),
        };
    }

    public string? CreateBlockFromStatement(BlockControl control, XmlDocument doc,
        XmlElement node, Statement statement, BlocklyXmlWriter writer)
    {
        if (statement is not MyStatement my)
            return null;

        writer.CreateValue(control, doc, node, my.Input, "INPUT");
        return "my_block";
    }
}

The matching statement derives from Statement, or from ValueStatement if it yields a value. Execute is called repeatedly: return ExecutionResult.Next(child) to have a child evaluated first and ExecutionResult.Done() when finished. On the following call continueFromChildId names the child that has just completed.

public class MyStatement : ValueStatement
{
    public ValueStatement? Input { get; set; }

    public override IEnumerable<Statement> Children
    {
        get { if (Input != null) yield return Input; }
    }

    public override ExecutionResult Execute(BlockControl control, RunContext context, string continueFromChildId)
    {
        if (string.IsNullOrWhiteSpace(continueFromChildId) && (Input != null))
            return ExecutionResult.Next(Input); // Evaluate the input first.

        Result = new TypedValue()
        {
            Value = Input?.Result?.Value?.ToUpper(),
            Type = nameof(KnownType.String),
        };

        return ExecutionResult.Done();
    }

    public override Statement Copy() => new MyStatement() { ID = ID, Input = Input?.CopyV() };
}

Use CompositeRepository to combine several repositories and DefaultRepository to serve blocks.json / toolbox.json from a directory while deferring all execution to the built-in blocks:

var repository = new CompositeRepository();

var defaultRepository = new DefaultRepository();
defaultRepository.Init("Content"); // Reads Content/blocks.json and Content/toolbox.json.

repository.Repositories.Add(defaultRepository);
repository.Repositories.Add(new MyRepository());

config.BlockRepository = repository;

Toolbox

The Editor namespace models the Blockly toolbox as C# objects, so the same definition can be handed to the editor and kept under source control. ToolboxFactory provides the built-in categories as extension methods.

using SoftwareDriven.Blockly.Code.Editor;

var toolbox = new Toolbox();
toolbox.AddLogicCategory()
       .AddLoopsCategory()
       .AddMathCategory()
       .AddTextCategory()
       .AddSeparator()
       .AddVariablesCategory()
       .AddFunctionsCategory()
       .AddCategory("My blocks", category =>
       {
           category.Colour = "#00FFFF";
           category.Contents = [new Block("my_block")];
       });

toolbox.HideCategory(ToolboxFactory.ApiCategoryName);
toolbox.DisableBlock("text_print");

Suspending and resuming a run

Suspend/resume is a first-class feature: a statement may return ExecutionResult.Wait() when it depends on something external. The engine then keeps its exact position in the program (StatementIdStack, LastChildId, LastItemState), so the BlockControl can be persisted and the run continued later from exactly that block.

var result = control.Run(new RunContext());

if (result.Type == ExecutionResultType.Wait)
{
    // Persist the control (including StatementIdStack / LastChildId / LastItemState),
    // wait for the external event, then continue where the program stopped.
    control.Run(new RunContext());
}

A statement that waits should implement the pair SerializeStateAtWait() / DeserializeStateAtContinue() so that its own loop counters and intermediate values survive the interruption.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on SoftwareDriven.Blockly.Code:

Package Downloads
SoftwareDriven.Blockly.Blazor

SoftwareDriven.Blockly.Blazor is a blazor razor component with an embedded blockly editor. Blockly is a JavaScript library for building visual programming editors. https://developers.google.com/blockly

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.8.0 25 8/25/2026
1.7.8 640 4/7/2025
1.7.7 449 3/6/2025
1.7.6 380 3/6/2025
1.7.4 384 3/5/2025
1.7.3 403 3/3/2025
1.7.1 348 12/5/2024
1.7.0 365 9/26/2024
1.6.2 410 3/6/2024
1.6.1 340 1/23/2024
1.6.0 341 1/22/2024
1.5.2 413 12/21/2023
1.5.1 306 12/21/2023
1.4.0 289 11/27/2023
1.2.4 901 2/14/2022
1.2.3 641 2/14/2022
Loading failed