Leveraging AMQP Messaging with Solace Schema Registry: Serializing and Deserializing Messages in .NET

The SERDES Collection provides .NET libraries for efficient serialization and deserialization of structured message payloads over AMQP 1.0. These libraries enable applications to convert complex data structures into compact, transmittable formats and reconstruct them on receipt. SERDES transmits schema information through AMQP application properties rather than embedding it in the message payload, preserving schema identifiers during transmission and supporting both binary and text message formats. The examples on this page use AMQPNetLite, however the concepts apply to any AMQP 1.0 client library with appropriate adjustments.

Prerequisites

Before implementing SERDES with AMQP messaging, 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 AMQP-specific implementation details for .NET, including imports, AMQPNetLite configuration, and code examples for serializing and deserializing messages over AMQP 1.0.

Deploying with NuGet

To use the SERDES Collection with your AMQP applications, 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 NuGet dependencies for the AMQPNetLite implementation of AMQP. If you're using a different AMQP client library, you would need to include the appropriate dependencies for that implementation instead of or in addition to the AMQPNetLite client shown here.

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, and also brings in 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.

AMQP Client Dependencies (AMQPNetLite Example)

For AMQP messaging with SERDES using AMQPNetLite, you need to include the appropriate client library. This client provides AMQP 1.0 protocol implementation and is lightweight and easy to use. Other AMQP client libraries would have their own dependency requirements and configuration approaches.

Add the following package reference to your .NET project file:

<ItemGroup>
    <!-- AMQPNetLite Client -->
    <PackageReference Include="AMQPNetLite" Version="2.4.5" />
</ItemGroup>

For the latest version information, see the NuGet Gallery.

Required Using Statements

To use the SERDES Collection with AMQP messaging, include the following namespaces in your .NET application:

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Amqp;
using Amqp.Framing;
using Solace.SchemaRegistry.Serdes.JsonSchema;
using Solace.Serdes;

Using SERDES with AMQP Messaging

AMQP messaging provides a standards-based approach to message publishing and consumption that integrates with the SERDES Collection. When using SERDES with AMQP messaging, schema information is transmitted through AMQP application properties rather than being embedded in the message payload, enabling efficient schema resolution and cross-protocol compatibility with REST and Solace Messaging API applications.

General AMQP Concepts with SERDES

When implementing SERDES with any AMQP 1.0 client, there are several important protocol-level considerations:

  • Application Properties—SERDES headers are transmitted as AMQP application properties, which ensures schema identifiers and metadata are preserved during message transmission. This is a standard feature of the AMQP 1.0 protocol that all compliant clients support.
  • Message Type Support—AMQP messaging supports both binary and text message formats for SERDES operations. However, the SERDES deserializer only operates on binary payloads. Applications must extract or convert the message content into a binary format before passing it to the deserializer.
  • Cross-Protocol Compatibility—AMQP applications can seamlessly exchange messages with REST and Solace .NET API applications through the Solace broker, which automatically translates message formats and preserves SERDES headers across protocols. For more information on cross-protocol message translation, see AMQP.

AMQPNetLite Implementation Considerations

When using AMQPNetLite as your AMQP client implementation, there are additional specific considerations:

  • Application Properties Access—AMQPNetLite provides direct access to AMQP application properties through the Message.ApplicationProperties property, which is an ApplicationProperties object containing a Map property for accessing key-value pairs.
  • Message Body Handling—With AMQPNetLite, message bodies can be accessed as Data objects (which contain a Binary property with the byte array) or directly as byte arrays depending on how the message was constructed. The deserializer requires a byte array.
  • Async Support—AMQPNetLite supports asynchronous operations for sending and receiving messages. The SERDES libraries also provide async methods (SerializeAsync and DeserializeAsync) for non-blocking operations.

AMQP Connection Configuration (AMQPNetLite Example)

When implementing AMQP applications that use SERDES with AMQPNetLite, configure the connection, session, and sender/receiver links according to AMQPNetLite documentation. The key requirement for SERDES is ensuring that application properties containing schema information are properly handled during message transmission.

Example connection and sender link creation:

var connectionFactory = new ConnectionFactory();
var address = new Address($"amqp://{username}:{password}@{host}:{port}");
var connection = await connectionFactory.CreateAsync(address);
var session = new Session(connection);
var sender = new SenderLink(session, "json-schema-producer", $"topic://{topicName}");

// Use sender for SERDES operations (see examples below)

// Clean up resources when done
sender.Close();
session.Close();
connection.Close();

Use try/finally blocks to ensure AMQP resources are properly closed even if exceptions occur. Cleanup order should be: sender/receiver → session → connection (innermost to outermost).

Cross-Protocol Message Flow with SERDES

AMQP messaging integrates seamlessly with other protocols through SERDES. For detailed information about how AMQP messages flow to and from REST and Solace Messaging API consumers, including complete message flow examples, see Cross-Protocol Message Flow Examples.

Key AMQP-specific considerations for cross-protocol messaging with .NET:

  • AMQP uses native application properties for SERDES headers (no prefix required).
  • AMQPNetLite provides direct access via Message.ApplicationProperties property.
  • Message body can be Data objects (with Binary property) or direct byte arrays.
  • AMQP application properties are automatically mapped to HTTP headers (with solace-user-property- prefix) for REST consumers.
  • AMQP application properties are automatically mapped to SMF user properties for Solace Messaging API consumers.

Serializing and Deserializing Messages with JSON Schema over AMQP

The following examples demonstrate how to implement JSON Schema serialization and deserialization in AMQP messaging applications. These examples use AMQPNetLite as the AMQP client implementation, but the core concepts can be applied to other AMQP 1.0 client libraries with appropriate adjustments for their specific APIs.

Publishing JSON Schema Messages over AMQP (AMQPNetLite Example)

When publishing JSON Schema messages over AMQP using AMQPNetLite, create a JsonSchemaSerializer<T>, configure it with Schema Registry connection details (see Getting Started with SERDES), and serialize message payloads before sending them.

Both JsonSchemaSerializer<T> and JsonSchemaDeserializer<T> implement IDisposable and must be properly disposed when no longer needed. Always use a using statement or explicitly call Dispose() to ensure that resources are released and cached data is cleaned up.

The key SERDES integration point is setting schema metadata as AMQP application properties. Example serialization and message publishing:

// Create a User object to serialize
var user = new User
{
    Name = "John Doe",
    Id = "123",
    Email = "support@solace.com"
};

// Serialize - this populates the headers dictionary with schema information
Dictionary<string, object> headers = new Dictionary<string, object>();
byte[] payloadBytes = await serializer.SerializeAsync(topicName, user, headers);

// Create AMQP message with serialized payload
var message = new Message(payloadBytes)
{
    Properties = new Properties() { ContentType = "application/json", Subject = topicName }
};

// CRITICAL: Set SERDES headers as AMQP application properties
var applicationProperties = new ApplicationProperties();
foreach (var entry in headers)
{
    applicationProperties[entry.Key] = entry.Value;
}
message.ApplicationProperties = applicationProperties;

await sender.SendAsync(message);

For a complete list of JSON Schema SERDES properties and methods, see the .NET JSON SERDES API Reference.

Consuming JSON Schema Messages over AMQP (AMQPNetLite Example)

When consuming JSON Schema messages over AMQP using AMQPNetLite, create a JsonSchemaDeserializer<T> configured with Schema Registry connection details (see Getting Started with SERDES). The key SERDES integration points are extracting headers from AMQP application properties and handling different message body types.

Exception Types

When deserializing messages, the DeserializeAsync method can throw the following exceptions:

  • JsonSchemaValidationException (from Solace.Serdes namespace)—Thrown when the message payload does not conform to the JSON Schema retrieved from the registry. This indicates a schema validation failure.
  • SerializationException (from Solace.Serdes namespace)—Thrown when the message payload cannot be decoded or deserialized, such as when the payload is malformed or the schema cannot be resolved from the registry.

Example SERDES header extraction and deserialization:

var message = await receiver.ReceiveAsync(TimeSpan.FromSeconds(1));
if (message != null)
{
    try
    {
        // Extract payload - handle both Data objects and byte arrays
        byte[] payloadBytes = message.Body is Data data ? data.Binary :
                              message.Body is byte[] bytes ? bytes : null;
        if (payloadBytes == null)
        {
            receiver.Reject(message);
            continue;
        }

        // CRITICAL: Extract SERDES headers from AMQP application properties
        Dictionary<string, object> headers = new Dictionary<string, object>();
        if (message.ApplicationProperties != null)
        {
            foreach (var key in message.ApplicationProperties.Map.Keys)
            {
                headers[key.ToString()] = message.ApplicationProperties.Map[key];
            }
        }

        // Deserialize using JSON Schema validation
        User user = await deserializer.DeserializeAsync(topicName, payloadBytes, headers);
        Console.WriteLine($"Got message: Name={user.Name}, Id={user.Id}, Email={user.Email}");
        receiver.Accept(message);
    }
    catch (JsonSchemaValidationException ve)
    {
        Console.WriteLine($"Validation error: {ve.Message}");
        receiver.Reject(message);
    }
    catch (SerializationException ex)
    {
        Console.WriteLine($"Decoding error: {ex.Message}");
        receiver.Reject(message);
    }
}

For a complete list of JSON Schema SERDES properties and methods, see the .NET JSON SERDES API Reference.

Preventing Head-of-Line Blocking in AMQP Consumers

When consuming messages with AMQP and SERDES, it's important to properly acknowledge or reject messages to prevent head-of-line blocking. With AMQPNetLite, you use receiver.Accept(message) for successfully processed messages and receiver.Reject(message) for messages that cannot be processed.

Key considerations for message settlement:

  • Accept Successfully Processed Messages—Call receiver.Accept(message) after successfully deserializing and processing a message to acknowledge it.
  • Reject Invalid Messages—Call receiver.Reject(message) for messages with validation errors, deserialization failures, or unexpected body types. This prevents the consumer from blocking on messages it cannot process.
  • Handle Exceptions Appropriately—Catch both JsonSchemaValidationException (for schema validation errors) and SerializationException (for deserialization errors) and reject the message in both cases.
  • Continuous Receive Loop—Implement a continuous receive loop with appropriate timeout handling to process messages as they arrive.

Example message settlement pattern:

while (!exitRequested)
{
    try
    {
        // Receive a message with a timeout
        var message = await receiver.ReceiveAsync(TimeSpan.FromSeconds(1));

        if (message != null)
        {
            try
            {
                // Extract payload and deserialize
                // ...
                User user = await deserializer.DeserializeAsync(topicName, payloadBytes, headers);

                // Process the message
                Console.WriteLine($"Got message: Name={user.Name}, Id={user.Id}");

                // Accept the message to acknowledge receipt
                receiver.Accept(message);
            }
            catch (JsonSchemaValidationException ve)
            {
                Console.WriteLine($"Validation error: {ve.Message}");

                // Reject the message due to validation error
                receiver.Reject(message);
            }
            catch (SerializationException ex)
            {
                Console.WriteLine($"Decoding error: {ex.Message}");

                // Reject the message due to decoding error
                receiver.Reject(message);
            }
        }
    }
    catch (Exception ex) when (!(ex is TaskCanceledException))
    {
        Console.WriteLine($"Error receiving message: {ex.Message}");
        // Continue with the next message
    }
}

Important considerations for message settlement:

  • If you neither accept nor reject a message, it remains unacknowledged and may block subsequent messages from being delivered (head-of-line blocking).
  • For messages that fail validation or deserialization, rejecting them allows the consumer to continue processing subsequent messages.
  • Consider implementing a dead-letter queue or logging mechanism for rejected messages to track problematic payloads.
  • The exception filter when (!(ex is TaskCanceledException)) prevents logging normal cancellation events while still handling other exceptions.

Troubleshooting Cross-Protocol SERDES

When working with mixed AMQP, REST, and Solace .NET API environments using SERDES, you may encounter specific issues related to message type handling, application properties, and cross-protocol compatibility. This section provides guidance for common troubleshooting scenarios, with a focus on AMQPNetLite implementation but including general AMQP considerations as well.

Common Issues and Solutions

  • General AMQP Considerations—Ensure your AMQP client properly handles application properties, as these are used to transmit schema information. Different AMQP clients may have different APIs for setting and retrieving these properties.
  • Missing SERDES Headers—With AMQPNetLite, verify that SERDES headers are being set as AMQP application properties using the ApplicationProperties class and that headers are being extracted properly by iterating through the ApplicationProperties.Map. With other AMQP clients, ensure you're properly setting and retrieving application properties according to that client's API.
  • Cross-Protocol Header Mapping Issues—Ensure that AMQP application properties are being mapped correctly to other protocol headers:
    • For AMQP to REST: Application properties should appear as HTTP headers with solace-user-property- prefix.
    • For AMQP to Solace .NET API: Application properties should appear as SMF user properties.
  • Message Body Type Issues—Ensure that your AMQP consumers can handle both Data objects (with Binary property) and direct byte array message bodies, as cross-protocol scenarios may result in different message body types depending on the publishing protocol and content type.
  • Async/Await Patterns—Ensure that you're properly using async/await with the SERDES async methods (SerializeAsync and DeserializeAsync) to avoid blocking the thread and potential deadlocks.
  • Exception Handling—Make sure to catch both JsonSchemaValidationException (for schema validation errors) and SerializationException (for general serialization/deserialization errors) when working with JSON Schema SERDES.