FlowNodeBase
Base class for all custom nodes. Inherit from this class to create your own node types.
Overview
FlowNodeBase provides the foundation for custom nodes, handling socket management, lifecycle events, and serialization.
Properties
| Property | Type | Description |
|---|---|---|
| Id | string | Unique node identifier (auto-generated) |
| X | double | X coordinate position |
| Y | double | Y coordinate position |
| Graph | FlowGraph | Reference to parent graph |
| Sockets | IReadOnlyList | All sockets (input + output) |
| InputSockets | IReadOnlyDictionary<string, FlowSocket> | Input sockets dictionary |
| OutputSockets | IReadOnlyDictionary<string, FlowSocket> | Output sockets dictionary |
| DomElement | FlowNode | DOM element reference |
| NodeKind | NodeKind | Node type (Regular or Group) |
| IsRendered | bool | Whether node UI has rendered |
Abstract Methods
ExecuteAsync
Main execution logic for the node. Must be implemented.
Signature: abstract ValueTask ExecuteAsync(FlowExecutionContext context)
public override async ValueTask ExecuteAsync(FlowExecutionContext context)
{
var inputA = context.GetInputSocketData<float>("A");
var inputB = context.GetInputSocketData<float>("B");
var result = inputA + inputB;
context.SetOutputSocketData("Result", result);
await ValueTask.CompletedTask;
}
Virtual Methods
BeforeGraphExecutionAsync
Called before graph execution starts. Use this to reset state.
Signature: virtual ValueTask BeforeGraphExecutionAsync()
public override ValueTask BeforeGraphExecutionAsync()
{
// Reset cached values
_cachedResult = 0;
return ValueTask.CompletedTask;
}
OnRenderedAsync
Called when the node UI is first rendered.
Signature: virtual ValueTask OnRenderedAsync()
public override async ValueTask OnRenderedAsync()
{
await base.OnRenderedAsync();
// Initialize UI-dependent state
}
Public Methods
GetSocketByName
Gets a socket by name and type.
Signature: FlowSocket? GetSocketByName(string name, SocketType type)
var socket = node.GetSocketByName("InputA", SocketType.Input);
AddSocket
Adds a socket to the node (typically called automatically).
Signature: void AddSocket(FlowSocket flowSocket, bool overridePreviousName = false)
node.AddSocket(newSocket, overridePreviousName: false);
GetSerializableObjectAsync
Gets serializable representation of the node.
Signature: async ValueTask<NodeProperties> GetSerializableObjectAsync()
var props = await node.GetSerializableObjectAsync();
Node Lifecycle
Understanding the node lifecycle is important for managing state correctly:
- Creation: Node instance is created via
graph.CreateNodeAsync<T>() - Initialization:
OnInitialized()is called (Blazor lifecycle) - First Render:
OnRenderedAsync()is called when UI renders - Before Execution:
BeforeGraphExecutionAsync()is called before graph execution - Execution:
ExecuteAsync(context)is called during graph execution - Disposal:
Dispose()is called when node is removed
public partial class MyNode : FlowNodeBase
{
protected override void OnInitialized()
{
base.OnInitialized();
// Component initialization
}
public override async ValueTask OnRenderedAsync()
{
await base.OnRenderedAsync();
// UI is now ready
}
public override ValueTask BeforeGraphExecutionAsync()
{
// Reset state before execution
return ValueTask.CompletedTask;
}
public override async ValueTask ExecuteAsync(FlowExecutionContext context)
{
// Main logic
await ValueTask.CompletedTask;
}
public void Dispose()
{
// Cleanup resources
}
}
Creating a Custom Node
MyNode.razor.cs
using FlowState.Components;
using FlowState.Attributes;
using FlowState.Models.Execution;
namespace MyNamespace;
[FlowNodeMetadata(
Title = "My Custom Node",
Category = "Custom",
Icon = "⚡",
Description = "Does something awesome"
)]
public partial class MyNode : FlowNodeBase
{
private float _cachedValue = 0;
public override ValueTask BeforeGraphExecutionAsync()
{
_cachedValue = 0;
return ValueTask.CompletedTask;
}
public override async ValueTask ExecuteAsync(FlowExecutionContext context)
{
var input = context.GetInputSocketData<float>("Input");
var result = input * 2;
_cachedValue = result;
context.SetOutputSocketData("Output", result);
await ValueTask.CompletedTask;
}
}
MyNode.razor
@using FlowState.Components
@using FlowState.Models
@inherits FlowNodeBase
<FlowNode>
<div class="title">⚡ My Custom Node</div>
<div class="body">
<FlowSocket Name="Input" Type="SocketType.Input" T="typeof(float)"/>
<FlowSocket Name="Output" Type="SocketType.Output" T="typeof(float)"/>
@if (_cachedValue > 0)
{
<div class="result">Result: @_cachedValue</div>
}
</div>
</FlowNode>
Accessing Node Data
From Within the Node
public override async ValueTask ExecuteAsync(FlowExecutionContext context)
{
// Access node properties
var myId = this.Id;
var myPosition = (this.X, this.Y);
var myGraph = this.Graph;
// Access sockets
var inputSocket = this.GetSocketByName("Input", SocketType.Input);
var allInputs = this.InputSockets;
var allOutputs = this.OutputSockets;
await ValueTask.CompletedTask;
}
From Outside the Node
var node = graph.GetNodeById("node-id-123");
if (node != null)
{
Console.WriteLine($"Node at ({node.X}, {node.Y})");
Console.WriteLine($"Has {node.Sockets.Count} sockets");
foreach (var socket in node.InputSockets.Values)
{
Console.WriteLine($"Input: {socket.Name}");
}
}
Parameter Serialization
All [Parameter] properties are automatically serialized:
[FlowNodeMetadata(Title = "Config Node")]
public partial class ConfigNode : FlowNodeBase
{
[Parameter]
public string ConfigValue { get; set; } = "default";
[Parameter]
public int Count { get; set; } = 10;
// These will be saved/loaded automatically
public override async ValueTask ExecuteAsync(FlowExecutionContext context)
{
// Use ConfigValue and Count
await ValueTask.CompletedTask;
}
}
<FlowNode>
<div class="title">⚙️ Config Node</div>
<div class="body">
<input class="input-box" @bind="ConfigValue" />
<input class="input-box" type="number" @bind="Count" />
</div>
</FlowNode>
See Also
- Custom Nodes - Complete guide to creating custom nodes
- FlowSocket - Socket component
- FlowExecutionContext - Execution context reference
- FlowGroupNodeBase - Group node base class