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
<PackageReference Include="SoftwareDriven.Blockly.Code" Version="1.8.0" />
<PackageVersion Include="SoftwareDriven.Blockly.Code" Version="1.8.0" />
<PackageReference Include="SoftwareDriven.Blockly.Code" />
paket add SoftwareDriven.Blockly.Code --version 1.8.0
#r "nuget: SoftwareDriven.Blockly.Code, 1.8.0"
#:package SoftwareDriven.Blockly.Code@1.8.0
#addin nuget:?package=SoftwareDriven.Blockly.Code&version=1.8.0
#tool nuget:?package=SoftwareDriven.Blockly.Code&version=1.8.0
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 | Versions 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. |
-
net10.0
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.11)
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 | 59 | 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 | 307 | 12/21/2023 |
| 1.4.0 | 289 | 11/27/2023 |
| 1.2.4 | 901 | 2/14/2022 |
| 1.2.3 | 641 | 2/14/2022 |