FlowMessage
The FlowMessage is the primary data structure for passing information between modules in a flow. It's a dynamic object that allows you to set and retrieve properties at runtime without needing to define a fixed schema.
Key Concepts
- Dynamic Structure: FlowMessages can hold any property structure, making them flexible for various data scenarios
- Type Safety: While dynamic, FlowMessage provides type-safe methods for getting and setting values
- Immutable Properties: Internally uses immutable dictionaries for thread-safe operations
- JSON Serialization: Seamlessly converts to and from JSON
A FlowMessage is what you receive in the MessageReceived method on your module, and what you pass when calling the Next method.
Creating FlowMessages
There are several ways to create and populate a FlowMessage.
Using Index Operator
You can use the index operator to set properties:
var message = new FlowMessage();
message["name"] = "Steven";
message["age"] = 42;
message["isActive"] = true;
Using Type-Safe Set Method
For better type safety and control, use the Set<T> method:
var message = new FlowMessage();
message.Set("name", "Steven");
message.Set("age", 42);
message.Set("isActive", true);
Using dynamic Syntax
You can also create a FlowMessage as a dynamic and add properties using dot notation:
dynamic message = new FlowMessage();
message.name = "Steven";
message.age = 42;
message.isActive = true;
Working with Nested Properties
FlowMessage supports hierarchical data structures using dot notation. This allows you to work with complex, nested objects efficiently.
Reading Nested Properties
When you receive a message with nested structure like this:
{
"data": {
"sensor": {
"id": "abc123",
"celsius": 34.5
}
}
}
You can access nested values directly using dot notation:
// Using Get<T> method (recommended)
var celsius = message.Get<double>("data.sensor.celsius");
// Using GetValue<T> method (alternative)
var sensorId = message.GetValue<string>("data.sensor.id");
// Using index operator (less type-safe)
var id = (string) message["data.sensor.id"];
Writing Nested Properties
You can set nested properties, and FlowMessage will automatically create the intermediate objects:
// Set a new property in the nested structure
message.Set("data.sensor.fahrenheit", 94.1);
After these operations, your message structure will be:
{
"data": {
"sensor": {
"id": "abc123",
"celsius": 34.5,
"fahrenheit": 94.1 }
}
}
Working with Arrays
FlowMessage also supports array indexing in paths:
// Set array elements
message.Set("sensors[0].temperature", 23.5);
message.Set("sensors[1].temperature", 24.2);
// Get array elements
var temp = message.Get<double>("sensors[0].temperature");
Checking for Properties
Before accessing properties, it's often useful to check if they exist. FlowMessage provides the Has method for this purpose.
Basic Property Check
Check if a property exists at any level:
// Check if a simple property exists
if (message.Has("age"))
{
Console.WriteLine("The message has an 'age' property");
}
// Check nested properties
if (message.Has("data.sensor.temperature"))
{
var temp = message.Get<double>("data.sensor.temperature");
}
Type-Safe Property Check
You can also verify that a property exists and has a specific type using Has<T>:
// Check if 'age' exists and is an integer
if (message.Has<int>("age"))
{
var age = message.Get<int>("age");
Console.WriteLine($"Age is: {age}");
}
// Check if a nested property exists with the correct type
if (message.Has<string>("data.sensor.id"))
{
var sensorId = message.Get<string>("data.sensor.id");
}
Tip
Using Has<T> is safer than Has alone because it ensures the property can be converted to the expected type before you attempt to retrieve it.
Removing Properties
You can remove properties from a FlowMessage at any level:
// Remove a top-level property
message.Remove("age");
// Remove a nested property
message.Remove("data.sensor.humidity");
// The Remove method returns the FlowMessage for method chaining
message.Remove("prop1").Remove("prop2").Set("prop3", "value");
Cloning FlowMessages
When you need an independent copy of a FlowMessage, use the Clone method:
var original = new FlowMessage();
original.Set("name", "John");
original.Set("age", 30);
// Create a deep copy
var copy = original.Clone();
// Modifying the copy doesn't affect the original
copy.Set("age", 31);
Console.WriteLine(original.Get<int>("age")); // Still 30
Console.WriteLine(copy.Get<int>("age")); // Now 31
Working with JSON
Parsing JSON into FlowMessage
// Parse JSON string
var json = "{\"name\":\"Alice\",\"age\":25}";
var message = FlowMessage.Parse(json);
// Safe parsing with TryParse
if (FlowMessage.TryParse(json, out IFlowMessage result))
{
Console.WriteLine("Successfully parsed");
}
// Parse JSON arrays or complex structures
var arrayJson = "[{\"id\":1},{\"id\":2}]";
var message2 = FlowMessage.Parse("items", arrayJson);
Converting FlowMessage to JSON
var message = new FlowMessage();
message.Set("name", "Bob");
message.Set("age", 35);
// Convert to JSON string
string json = message.ToJSON();
// or simply
string json2 = message.ToString();
Converting Objects to FlowMessage
You can convert any object to a FlowMessage:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
var person = new Person { Name = "Charlie", Age = 40 };
// Convert to FlowMessage with extenstion method
var message = FlowMessageExtensions.ToFlowMessage(person);
// Or just assigning the person object to a property
message.Set("person", person);
// Now you can access properties dynamically
var name = message.Get<string>("Name");
var age = message.Get<int>("Age");
var personFromMessage = message.Get<Person>("person");
Using Templates
FlowMessage supports template syntax for dynamic value substitution:
var message = new FlowMessage();
message.Set("firstName", "John");
message.Set("lastName", "Doe");
// Use template to combine values
var result = message.GetByTemplate("Hello {firstName} {lastName}!");
// Result: "Hello John Doe!"
// Templates work with nested properties too
message.Set("user.name", "Alice");
var greeting = message.GetByTemplate("Welcome {user.name}");
Error Handling
FlowMessage provides built-in properties for tracking success/error states:
// Mark a message as successful
message.SetSuccess();
// Mark a message as failed with an error message
message.SetError("Connection timeout");
// Check the status
if (message.IsError)
{
Console.WriteLine($"Error: {message.ErrorMessage}");
}
if (message.IsSuccess)
{
Console.WriteLine("Operation completed successfully");
}
Best Practices
- Use type-safe methods: Prefer
Get<T>()andSet<T>()over dynamic access for better type safety - Check before accessing: Always use
Has()orHas<T>()before retrieving values to avoid exceptions - Clone when needed: If you need to modify a message while preserving the original, use
Clone() - Use templates wisely: Templates are powerful but add processing overhead—use them when dynamic substitution is necessary
- Handle nested structures: Take advantage of dot notation for cleaner code when working with complex objects
Common Patterns
Safely Getting a Value with Default
// Get a value or return a default if it doesn't exist
var age = message.Has<int>("age")
? message.Get<int>("age")
: 0;
Transforming Data Between Modules
protected override void MessageReceived(IFlowMessage message)
{
// Read input
if (message.Has<double>("celsius"))
{
var celsius = message.Get<double>("celsius");
// Transform
var fahrenheit = (celsius * 9 / 5) + 32;
// Write output
message.Set("fahrenheit", fahrenheit);
// Pass to next module
this.Next(message);
}
}
Validating Required Properties
private bool ValidateMessage(IFlowMessage message)
{
var requiredProperties = new[] { "id", "timestamp", "value" };
foreach (var prop in requiredProperties)
{
if (!message.Has(prop))
{
message.SetError($"Missing required property: {prop}");
return false;
}
}
return true;
}