Serializing and Deserializing Messages in the Solace .NET API
You can use the Solace .NET API with the SERDES Collection to handle structured message payloads efficiently. SERDES, which stands for Serialization and Deserialization, allows applications to convert complex data structures into a compact, transmittable format when publishing messages and to reconstruct them when consuming messages. This is particularly useful when integrating with schema registries. The SERDES Collection refers to the set of serializer and deserializer libraries, such as Solace JSON Schema SERDES for .NET, that you can use to integrate applications with Solace Schema Registry for schema-based message serialization and deserialization.
- Prerequisites
- Deploying with NuGet
- Serializing and Deserializing Messages with JSON Schema
- Advanced JSON Schema SERDES Features
- Handling SERDES Failures to Avoid Head-of-Line Blocking
Prerequisites
Before implementing SERDES with the Solace .NET API, you should understand the language-agnostic concepts and configuration options covered in Serialization and Deserialization with Solace Schema Registry. Key topics include:
- Connecting to Schema Registry (authentication and security)
- Choosing schema resolution strategies (Destination ID, Topic ID, Topic ID with Profiles, Record ID)
- Optimizing performance (caching and lookup options)
- Advanced configuration (auto-registration, header formats, JSON Schema-specific settings)
- Cross-protocol message translation
This page focuses on Solace .NET API-specific implementation details, including .NET imports and code examples for serializing and deserializing messages.
Deploying with NuGet
To use the SERDES Collection with the Solace .NET API, you need to include the appropriate NuGet packages in your project. The SERDES libraries are distributed as separate packages that you can include based on your serialization format requirements.
The following sections show you how to configure your .NET project to include the necessary dependencies for schema registry integration:
JSON Schema SERDES Dependencies
The JSON Schema SERDES provides a specific implementation for JSON Schema serialization and deserialization. This dependency includes the JsonSchemaSerializer<T>, JsonSchemaDeserializer<T>, and JSON Schema-specific configuration properties. It also includes the required common SERDES components used across all schema formats.
Add the following package reference to your .NET project file:
<ItemGroup>
<PackageReference Include="Solace.SchemaRegistry.Serdes.JsonSchema" Version="1.0.0" />
</ItemGroup>
For the latest version information and additional details about the JSON Schema SERDES package, see the NuGet Gallery.
Generic SERDES Dependencies
The Generic SERDES provides simple string serialization and deserialization capabilities with no Solace Schema Registry interaction. This package includes the StringSerializer and StringDeserializer classes for basic string message handling.
Add the following package reference to your .NET project file:
<ItemGroup>
<PackageReference Include="Solace.Serdes" Version="1.0.0" />
</ItemGroup>
For the latest version information and additional details about the Generic SERDES package, see the NuGet Gallery.
Using Generic SERDES
To use Generic SERDES, include these namespaces:
using SolaceSystems.Solclient.Messaging; using Solace.Serdes;
The example below shows how to use StringSerializer and StringDeserializer with the Solace .NET API:
// Create serializer and deserializer - no configuration needed
var serializer = new StringSerializer();
var deserializer = new StringDeserializer();
// Serialize and send a message
using (var message = ContextFactory.Instance.CreateMessage())
{
message.Destination = topic;
// Serialize the string to the message
message.Serialize(serializer, "Hello World");
session.Send(message);
}
// In your message event handler, deserialize the string
private static void HandleMessage(object source, MessageEventArgs args, IDeserializer<string> deserializer)
{
// Deserialize the received message
string receivedText = args.Message.Deserialize(deserializer);
Console.WriteLine("Received: " + receivedText);
}
For a complete example, see HelloWorldSolaceDotNetJsonSchemaSerde.cs on the Solace Samples Repository.
Serializing and Deserializing Messages with JSON Schema
The following sections show you how to use JSON Schema SERDES with the Solace .NET API to serialize and deserialize messages with schema validation.
Required Using Statements
To use JSON Schema SERDES with the Solace .NET API, include the following namespaces in your C# files:
using System; using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Nodes; using System.Threading; using SolaceSystems.Solclient.Messaging; using SolaceSystems.Solclient.Messaging.Serialization; using Solace.SchemaRegistry.Serdes.JsonSchema; using Solace.Serdes;
Configuration and Setup
Before using JSON Schema SERDES, you need to initialize the Solace .NET API context and create a session. For complete setup details, see Creating Contexts and Creating Client Sessions.
SERDES Configuration
After creating your context and session, configure the SERDES serializer and deserializer with Schema Registry connection details:
// Configure SERDES properties
var config = new Dictionary<string, object>();
config[JsonSchemaPropertyKeys.RegistryUrl] = "http://localhost:8081/apis/registry/v3";
config[JsonSchemaPropertyKeys.AuthUsername] = "myUsername";
config[JsonSchemaPropertyKeys.AuthPassword] = "myPassword";
config[JsonSchemaPropertyKeys.ValidateSchema] = true;
// Create and configure serializer and deserializer
using (var serializer = new JsonSchemaSerializer<JsonNode>())
using (var deserializer = new JsonSchemaDeserializer<JsonNode>())
{
serializer.Configure(config);
deserializer.Configure(config);
// Serializer and deserializer are now ready to use
}
Both JsonSchemaSerializer<T> and JsonSchemaDeserializer<T> establish connections to the schema registry that may prevent application exit if not properly disposed. Always use using statements or explicitly call Dispose() when finished to properly clean up resources and close connections.
For detailed information about all available configuration properties, see Getting Started with SERDES and JSON Schema-Specific Configuration.
After you configure your serializer and deserializer objects, you can use them to process messages. The following sections explain how to serialize and deserialize messages with the Solace .NET API:
- Serializing and Sending JSON Schema Messages Using the Solace .NET API
- Receiving and Deserializing JSON Schema Messages Using the Solace .NET API
Serializing and Sending JSON Schema Messages Using the Solace .NET API
When you are serializing and sending messages using the Solace .NET API, first create a JsonNode or POCO (Plain Old CLR Object) that conforms to your JSON schema, then serialize and send the message. Because the Solace .NET API uses synchronous extension methods, you must wrap the async serializer with a synchronous adapter using the AsSyncOverAsync() method.
The example below shows you how to create a simple JSON object, convert it to binary format with the Serialize() extension method, and then publish the message:
// Wrap async serializer with synchronous adapter for use with Solace's synchronous extension methods
var syncSerializer = serializer.AsSyncOverAsync();
// Create the topic destination
ITopic topic = ContextFactory.Instance.CreateTopic("solace/samples/json");
// Create a JsonNode object
var user = new JsonObject
{
["name"] = "John Doe",
["id"] = "123",
["email"] = "support@solace.com"
};
// Serialize and send the message
// Note: 'session' is your active ISession instance, 'syncSerializer' is the synchronous wrapper
using (var message = ContextFactory.Instance.CreateMessage())
{
message.Destination = topic;
// Serialize the JsonNode to the message using JSON Schema serialization
// This validates the data against the schema and embeds schema metadata in the message header
message.Serialize(syncSerializer, user);
// Send the message
ReturnCode returnCode = session.Send(message);
if (returnCode == ReturnCode.SOLCLIENT_OK)
{
Console.WriteLine("Message sent successfully.");
}
}
The Serialize() extension method not only converts the data to binary format but also sets important schema information as Solace Message Format (SMF) user properties in the message. These properties include the schema identifier and other metadata needed for proper deserialization. The schema information is stored in the SMF user properties, not in the payload itself, which is important for interoperability with other messaging protocols.
Asynchronous Serialization
For async workflows, use the SerializeAsync method. This method returns the serialized payload and populates a headers dictionary with schema metadata that must be set as message user properties:
var headers = new Dictionary<string, object>();
byte[] payloadBytes = await serializer.SerializeAsync("solace/samples/json", user, headers);
// Set payload and headers on message (see API Reference for IMapContainer details)
message.BinaryAttachment = payloadBytes;
// Populate user property map from headers dictionary
For a complete example, see HelloWorldCSCSMPJsonSchemaSerde.cs on the Solace Samples Repository.
For a complete list of JSON Schema SERDES properties and methods, see the .NET JSON SERDES API Reference.
Receiving and Deserializing JSON Schema Messages Using the Solace .NET API
When you are consuming JSON Schema serialized messages with the Solace .NET API, you configure a message event handler to receive and deserialize messages. The Solace .NET API provides both synchronous and asynchronous patterns for message handling.
Receive and Deserialize a Message with a Synchronous Message Event Handler
The Solace .NET API message event handlers are invoked synchronously. When using SERDES with synchronous message handlers, you must wrap the async serializer/deserializer with a synchronous adapter using the AsSyncOverAsync() method. This method creates a synchronous wrapper that blocks on async operations, making them compatible with the synchronous callbacks in the Solace .NET API.
The example below shows the key SERDES integration pattern:
// Wrap async deserializer with synchronous adapter
var syncDeserializer = deserializer.AsSyncOverAsync();
// Create session with message event handler (see Creating Client Sessions for session setup)
using (ISession session = context.CreateSession(sessionProps,
(source, msgArgs) => HandleMessage(source, msgArgs, syncDeserializer), null))
{
session.Connect();
session.Subscribe(topic, true);
// Wait for messages
}
// Message event handler
private static void HandleMessage(object source, MessageEventArgs args, IDeserializer<JsonNode> deserializer)
{
try
{
// Deserialize the message payload using JSON Schema validation
JsonNode jsonNode = args.Message.Deserialize(deserializer);
Console.WriteLine("Got a JsonNode: {0}", jsonNode.ToJsonString());
}
catch (SerializationException ex)
{
// Handle deserialization failures to avoid head-of-line blocking
Console.WriteLine("Deserialization exception: {0}", ex.Message);
}
}
For a complete example, see HelloWorldCSCSMPJsonSchemaSerde.cs on the Solace Samples Repository.
Asynchronous Deserialization
For async workflows outside of the synchronous message event handler context, use the DeserializeAsync method directly. See the API Reference for method signatures and the samples repository for complete examples.
Deserializing to POCOs
In addition to deserializing to JsonNode, you can deserialize directly to strongly-typed POCOs (Plain Old CLR Objects) by specifying the target type when creating the deserializer:
// Create and configure JSON Schema deserializer for User POCO
using (var deserializer = new JsonSchemaDeserializer<User>())
{
deserializer.Configure(config);
var syncDeserializer = deserializer.AsSyncOverAsync();
// Use in message event handler
// (session creation details omitted - see Creating Client Sessions)
}
// Message event handler that deserializes to User POCO
private static void HandleMessage(object source, MessageEventArgs args, IDeserializer<User> deserializer)
{
try
{
User user = args.Message.Deserialize(deserializer);
Console.WriteLine("Got message: Name={0}, Id={1}, Email={2}",
user.Name, user.Id, user.Email);
}
catch (SerializationException ex)
{
Console.WriteLine("Deserialization exception: {0}", ex.Message);
}
}
For a complete example, see JsonSchemaDeserializeConsumerToPoco.cs on the Solace Samples Repository.
For a complete list of JSON Schema SERDES properties and methods, see the .NET JSON SERDES API Reference.
Advanced JSON Schema SERDES Features
The JSON Schema SERDES libraries provide additional features for advanced scenarios:
Validation Error Handler
Both JsonSchemaSerializer<T> and JsonSchemaDeserializer<T> support a ValidationErrorHandler configuration property that allows you to customize how JSON Schema validation errors are handled. This provides an alternative to exception-based error handling and is useful for logging, monitoring, or implementing custom validation logic.
The validation error handler is configured as a function delegate that receives validation error details and can either suppress the exception (by returning null) or return a custom exception to be thrown.
// Create configuration dictionary
var config = new Dictionary<string, object>();
config[JsonSchemaPropertyKeys.RegistryUrl] = "http://localhost:8081/apis/registry/v3";
config[JsonSchemaPropertyKeys.AuthUsername] = "myUsername";
config[JsonSchemaPropertyKeys.AuthPassword] = "myPassword";
// Configure a validation error handler
config[JsonSchemaPropertyKeys.ValidationErrorHandler] = new Func<JsonValidationErrorArgs, JsonSchemaValidationException>(args =>
{
// Access validation error details
// args.Errors contains the validation errors as a JsonArray
// args.JsonObject contains the raw JSON bytes that failed validation (ReadOnlyMemory<byte>)
// args.Schema contains the schema as raw bytes (ReadOnlyMemory<byte>)
// args.Metadata contains deserialization metadata
// Log the validation errors
Console.WriteLine("Validation error: {0}", args.Errors.ToJsonString());
// Return null to suppress the exception and allow processing to continue
// OR return a custom JsonSchemaValidationException to throw
return null;
});
// Create and configure JSON Schema deserializer
using (var deserializer = new JsonSchemaDeserializer<JsonNode>())
{
deserializer.Configure(config);
// Use the deserializer as normal
}
Type Property Configuration
When deserializing to POCOs, the JSON Schema SERDES supports a TypeProperty configuration that allows the JSON Schema itself to specify which concrete type should be used for deserialization. This is useful in polymorphic scenarios where different message types share a base class or interface.
The TypeProperty setting specifies the name of a custom property in your JSON Schema that contains the fully qualified .NET type name. When the deserializer encounters this property, it uses reflection to load and instantiate the specified type.
Example configuration:
// Configure deserializer with TypeProperty
var config = new Dictionary<string, object>();
config[JsonSchemaPropertyKeys.RegistryUrl] = "http://localhost:8081/apis/registry/v3";
config[JsonSchemaPropertyKeys.AuthUsername] = "myUsername";
config[JsonSchemaPropertyKeys.AuthPassword] = "myPassword";
// Specify the custom property name in your JSON Schema that contains the .NET type name
config[JsonSchemaPropertyKeys.TypeProperty] = "customDotnetType";
using (var deserializer = new JsonSchemaDeserializer<JsonNode>())
{
deserializer.Configure(config);
// Use the deserializer as normal
}
Important considerations when using type properties:
- The type specified in the schema must be assignable to the generic type parameter
Tof the deserializer - The type must be available in the application's loaded assemblies
- If the type property is invalid or the type cannot be loaded, a
SerializationExceptionis thrown
Handling SERDES Failures to Avoid Head-of-Line Blocking
When consuming messages with SERDES, it is critical to handle deserialization failures properly to avoid head-of-line blocking. Head-of-line blocking occurs when a message that cannot be deserialized blocks all subsequent messages in the queue, preventing your application from processing valid messages.
Understanding Head-of-Line Blocking
Head-of-line blocking can occur when:
- A message fails schema validation (the payload does not match the expected schema)
- A schema mismatch occurs (the schema ID in the message header does not match an available schema)
- The message payload is corrupted or malformed
- An exception is thrown during deserialization and not properly handled
If your application does not catch and handle these exceptions, the consumer may stop processing messages, causing all subsequent valid messages to remain unprocessed in the queue.
Best Practices for Exception Handling
All the code examples on this page demonstrate proper exception handling by catching SerializationException in message event handlers. This pattern ensures that deserialization failures do not stop message processing.
Exception Types
When working with JSON Schema SERDES, you may encounter the following exception types:
JsonSchemaValidationException—Thrown when the payload fails JSON Schema validation (for example, missing required fields, incorrect data types, or constraint violations)SerializationException—Thrown when serialization or deserialization fails for reasons other than validation (for example, malformed JSON, type conversion errors, or schema resolution failures)OperationCanceledException—Thrown when an async operation is canceled via a cancellation token
For most applications, catching SerializationException (which is the base class for JsonValidationException) is sufficient. However, you can catch validation errors separately if you need to handle them differently:
private static void HandleMessage(object source, MessageEventArgs args, IDeserializer<JsonNode> deserializer)
{
try
{
// Deserialize the message payload
JsonNode jsonNode = args.Message.Deserialize(deserializer);
// Process the deserialized message
Console.WriteLine("Got a JsonNode: {0}", jsonNode.ToJsonString());
}
catch (JsonSchemaValidationException ve)
{
// Handle JSON Schema validation errors specifically
// This occurs when the payload structure doesn't match the schema
Console.WriteLine("Validation error: {0}", ve.Message);
// For example, log validation errors differently or send to a validation error queue
}
catch (SerializationException ex)
{
// Handle other serialization errors (malformed JSON, schema resolution failures, etc.)
Console.WriteLine("Deserialization exception: {0}", ex.Message);
// Log the error with relevant details for troubleshooting
// Consider publishing to a dead message queue (DMQ) or error topic for later analysis
}
}
Handling Persistent Message Failures
When consuming persistent messages (Guaranteed Messaging) using flows, you must explicitly settle failed messages to prevent head-of-line blocking. The example below shows proper exception handling with message settlement:
// Message event handler for persistent messaging with flows
private static void HandlePersistentMessage(object source, MessageEventArgs args,
IDeserializer<JsonNode> deserializer, IFlow flow)
{
try
{
// Deserialize the message payload
JsonNode jsonNode = args.Message.Deserialize(deserializer);
// Process the deserialized message
Console.WriteLine("Got a JsonNode: {0}", jsonNode.ToJsonString());
// Acknowledge successful processing (for client acknowledgment mode)
flow.Ack(args.Message.ADMessageId);
}
catch (JsonSchemaValidationException ve)
{
// Handle JSON Schema validation errors specifically
Console.WriteLine("Validation error: {0}", ve.Message);
// CRITICAL: Settle the message with REJECTED outcome to prevent redelivery
flow.Settle(args.Message.ADMessageId, MessageOutcome.Rejected);
}
catch (SerializationException ex)
{
// Handle other serialization errors
Console.WriteLine("Deserialization exception: {0}", ex.Message);
// CRITICAL: Settle the message with REJECTED outcome to prevent redelivery
// This prevents the failed message from blocking subsequent messages in the queue
flow.Settle(args.Message.ADMessageId, MessageOutcome.Rejected);
// Log the error and consider publishing to a dead message queue (DMQ)
// for later analysis and manual intervention
}
}
For more information about flow creation and message acknowledgment modes, see Creating Flows and Acknowledging Messages Received by Clients.