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

You can use REST messaging with the SERDES Collection to handle structured message payloads efficiently over HTTP. The SERDES Collection provides .NET libraries for efficient serialization and deserialization of structured message payloads, allowing applications to convert complex data structures into a compact, transmittable format when publishing messages and to reconstruct them when consuming messages. Schema information is transmitted through HTTP headers rather than being embedded in the message payload, enabling efficient schema resolution and cross-protocol compatibility. The .NET SERDES libraries currently support JSON Schema serialization.

Prerequisites

Before implementing SERDES with REST 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, Avro and JSON Schema-specific settings)
  • Cross-protocol message translation

This page focuses on REST-specific implementation details for .NET, including imports, HTTP client configuration, and code examples for serializing and deserializing JSON Schema messages over HTTP.

Deploying with NuGet

To use the SERDES Collection with your REST 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 you how to configure your .NET project to include the necessary dependencies for schema registry integration:

JSON Schema Serializer Dependencies

The JSON Schema serializer 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.

Required Using Statements

To use the JSON Schema SERDES with REST messaging, include the following namespaces in your .NET application:

using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Solace.SchemaRegistry.Serdes.JsonSchema;
using Solace.SchemaRegistry.Serdes.Core;

Using SERDES with REST Messaging

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

General REST Concepts with SERDES

When implementing SERDES with REST messaging, there are several important considerations:

  • Header Format—REST messaging requires string-based headers, making the SchemaIdString header format optimal for interoperability. For more information, see SERDES Header Configuration. This ensures schema identifiers are transmitted as human-readable strings in HTTP headers.
  • Content-Type and Message Type Handling—The HTTP Content-Type header determines how Solace interprets the message payload and the resulting message type for Solace .NET API consumers:
    • Use application/octet-stream for binary content that results in BytesMessage objects
    • Use application/json and other text-based Content-Types for content that results in TextMessage objects
    • The .NET SERDES deserializer operates on binary payloads regardless of Content-Type
  • User Properties—SERDES headers are transmitted as Solace user properties using the Solace-User-Property- prefix in HTTP headers (case-insensitive).
  • REST Delivery Point Configuration—REST consumer applications require proper configuration of a REST Delivery Point (RDP) on the Solace broker to receive messages. The RDP must be configured with the correct POST request target that matches your consumer application's endpoint. For detailed instructions, refer to Configuring REST Delivery Points.

.NET HttpClient and HttpListener Implementation Considerations

When using .NET's HttpClient for publishing and HttpListener for consuming REST messages with SERDES, there are additional specific considerations:

  • HttpClient Best Practices—Use a single HttpClient instance throughout your application lifetime to avoid socket exhaustion. The HttpClient class is thread-safe and designed for reuse.
  • Header Naming—When adding SERDES headers to HTTP requests, use the Solace-User-Property- prefix. Header names are case-insensitive in HTTP, but the prefix should match the broker's expected format.
  • HttpListener Platform Support—The HttpListener class is available on Windows and provides a lightweight HTTP server for receiving messages. On Linux/macOS, consider using ASP.NET Core's Kestrel server for cross-platform compatibility.
  • Async Support—Both HttpClient and HttpListener support asynchronous operations. The SERDES libraries also provide async methods (SerializeAsync and DeserializeAsync) for non-blocking operations.
  • IDisposable Pattern—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.

Cross-Protocol Message Flow with SERDES

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

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

  • REST uses the Solace-User-Property- prefix for SERDES headers in HTTP requests and responses.
  • Configure serializers with SchemaIdString format for optimal REST compatibility.
  • REST consumers require a configured REST Delivery Point (RDP) on the broker.
  • The Content-Type header determines message type: application/octet-stream → BytesMessage, application/json → TextMessage.

REST Consumer Application Parameters

When implementing REST consumer applications that use SERDES, you need to configure several key parameters to ensure proper message reception and deserialization:

  • Post Request Target—The endpoint path that your consumer application listens on (for example, /my-rest-endpoint). This must match the "POST Request Target" configured in your broker's REST Delivery Point (RDP).
  • Port—The port number for your local HTTP server (for example, 8080). This determines where your consumer application will listen for incoming HTTP requests from the broker.
  • HTTP Topic Header Key—(Optional) The name of a custom HTTP header that contains the message's topic information. This is only needed if your RDP is configured to add topic information to a specific header (for example, X-Solace-Topic).

Example consumer application startup with these parameters:

// Consumer listening on port 8080, endpoint /my-rest-endpoint, with topic in X-Solace-Topic header
string postRequestTarget = "/my-rest-endpoint";
int port = 8080;
string topicHeaderKey = "X-Solace-Topic"; // Optional

// Start HTTP listener with these parameters
using (var listener = new HttpListener())
{
    listener.Prefixes.Add($"http://+:{port}{postRequestTarget}/");
    listener.Start();
    // Handle incoming requests...
}

Serializing and Deserializing Messages with JSON Schema over REST

The following examples demonstrate how to implement JSON Schema serialization and deserialization in REST messaging applications using .NET's HttpClient for publishing and HttpListener for consuming.

Publishing JSON Schema Messages over REST (.NET Example)

When publishing JSON Schema messages over REST, you create a JsonSchemaSerializer<T>, configure it with Schema Registry connection details (see Getting Started with SERDES), and serialize message payloads before sending them via HTTP POST requests.

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.

Key configuration for JSON Schema REST publishing:

  • Configure SchemaIdString for optimal REST compatibility
  • Set appropriate Content-Type (BINARY or JSON)
  • Format SERDES headers as Solace user properties with Solace-User-Property- prefix

Example configuration for a JSON Schema REST publisher:

// Create and configure the JSON Schema serializer
using (var serializer = new JsonSchemaSerializer<User>())
{
    var config = new Dictionary<string, object>
    {
        { JsonSchemaPropertyKeys.RegistryUrl, "http://localhost:8081/apis/registry/v3" },
        { JsonSchemaPropertyKeys.AuthUsername, "sr-readonly" },
        { JsonSchemaPropertyKeys.AuthPassword, "roPassword" },
        // Use SchemaIdString for REST compatibility (string-based headers)
        { SerdePropertyKeys.SchemaHeaderIdentifiers, SchemaHeaderId.SchemaIdString }
    };
    serializer.Configure(config);

    // Use serializer for publishing...
}

For information about configuring SERDES properties, see Getting Started with SERDES and JSON Schema-Specific Configuration.

Example serialization and HTTP request:

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

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

// Create HTTP request
string url = $"http://{brokerHost}:{port}/TOPIC/{topicName}";
var request = new HttpRequestMessage(HttpMethod.Post, url);

// Add SERDES headers with Solace-User-Property- prefix
foreach (var kvp in headers)
{
    string headerName = $"Solace-User-Property-{kvp.Key}";
    string headerValue = kvp.Value?.ToString() ?? string.Empty;
    request.Headers.Add(headerName, headerValue);
}

// Set Content-Type for binary or JSON payload
request.Content = new ByteArrayContent(payload);
if (contentType == "JSON")
{
    request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
}
else
{
    request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
}

// Send the HTTP request
HttpResponseMessage response = await httpClient.SendAsync(request);
Console.WriteLine($"Published message with status: {response.StatusCode}");

For a complete example, see RestJsonSchemaPublisher.cs on the Solace Samples Repository.

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

Consuming JSON Schema Messages over REST (.NET Example)

When consuming JSON Schema messages over REST, you create an HTTP server using HttpListener that receives POST requests, extracts SERDES headers from the HTTP headers, and uses a JsonSchemaDeserializer<T> to reconstruct the original message payload.

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.

Key aspects of JSON Schema REST consumption:

  • Extract SERDES headers from HTTP headers with Solace-User-Property- prefix (case-insensitive).
  • Handle type conversion for schema IDs (string vs. long) using regex pattern matching.
  • Use appropriate HTTP status codes for different error conditions.

Example SERDES header extraction:

// Extracts SERDES headers and handles both string and typed values (e.g., "123 ; type=int64")
private static Dictionary<string, object> ExtractSerdesHeaders(NameValueCollection headers)
{
    const string solaceUserPropertyPrefix = "solace-user-property-";
    var serdesHeaders = new Dictionary<string, object>();

    // Regex pattern to extract value and optional type: "<value> [; type=<type>]"
    var typePattern = new Regex(@"(.*?)(?:\s*;\s*type=(\S+))?$");

    foreach (string key in headers.AllKeys)
    {
        if (key == null) continue;

        // Only process Solace user property headers (case-insensitive)
        if (key.StartsWith(solaceUserPropertyPrefix, StringComparison.OrdinalIgnoreCase))
        {
            string httpValue = headers[key];
            Match matcher = typePattern.Match(httpValue);

            if (matcher.Success)
            {
                string valueAsString = matcher.Groups[1].Value.Trim();
                string type = matcher.Groups[2].Success ? matcher.Groups[2].Value : null;

                object value;
                if (type != null && "int64".Equals(type.Trim(), StringComparison.OrdinalIgnoreCase))
                {
                    // Convert to long for int64 type
                    value = long.TryParse(valueAsString, out long longValue) ? longValue : valueAsString;
                }
                else
                {
                    // Default to string for SchemaIdString and other properties
                    value = valueAsString;
                }

                // Remove the solace-user-property- prefix to get original key
                string propertyKey = key.Substring(solaceUserPropertyPrefix.Length).ToLowerInvariant();
                serdesHeaders[propertyKey] = value;
            }
        }
    }

    return serdesHeaders;
}

Example deserialization and error handling:

// Extract SERDES headers from HTTP request
var serdesHeaders = ExtractSerdesHeaders(context.Request.Headers);

// Read message payload
byte[] messagePayload;
using (var ms = new MemoryStream())
{
    await context.Request.InputStream.CopyToAsync(ms);
    messagePayload = ms.ToArray();
}

// Get topic from optional header if configured
string requestTopic = "";
if (!string.IsNullOrEmpty(httpTopicHeaderKey))
{
    requestTopic = context.Request.Headers[httpTopicHeaderKey] ?? "";
}

try
{
    // Deserialize the message
    User user = await deserializer.DeserializeAsync(requestTopic, messagePayload, serdesHeaders);
    Console.WriteLine($"Received SERDES message: {user}");

    // Send success response
    await SendResponseAsync(context, 200, Array.Empty<byte>());
}
catch (JsonSchemaValidationException ve)
{
    // Schema validation failed - return 422 Unprocessable Entity
    Console.WriteLine($"Schema validation error: {ve.Message}");
    await SendResponseAsync(context, 422, Encoding.UTF8.GetBytes(ve.Message));
}
catch (SerializationException se)
{
    // General serialization/deserialization error - return 400 Bad Request
    Console.WriteLine($"Serialization error: {se.Message}");
    await SendResponseAsync(context, 400, Encoding.UTF8.GetBytes(se.Message));
}
catch (Exception e)
{
    // Other unexpected errors - return 500 Internal Server Error
    Console.WriteLine($"Unexpected error: {e.Message}");
    await SendResponseAsync(context, 500, Encoding.UTF8.GetBytes("An internal server error occurred."));
}

For a complete example, see RestJsonSchemaConsumer.cs on the Solace Samples Repository.

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

Troubleshooting Cross-Protocol SERDES

When working with mixed REST, AMQP, and Solace .NET API environments using SERDES, you may encounter specific issues related to message type handling, HTTP headers, and cross-protocol compatibility. This section provides guidance for common troubleshooting scenarios.

Common Issues and Solutions

  • Missing SERDES Headers—Verify that SERDES headers are being set with the Solace-User-Property- prefix (case-insensitive). Check that headers are being extracted properly from HttpListenerContext.Request.Headers on the consumer side.
  • Header Case Sensitivity—While HTTP headers are case-insensitive, ensure consistent casing when adding and extracting headers. The .NET samples use case-insensitive comparison (StringComparison.OrdinalIgnoreCase) when checking for the Solace-User-Property- prefix.
  • Type Conversion Issues—When extracting SERDES headers, handle type suffixes properly (e.g., "; type=int64"). Use regex pattern matching to parse header values and convert to appropriate .NET types (string, long, etc.).
  • Content-Type Mismatch Issues—Ensure that REST publishers use appropriate Content-Type headers:
    • Use application/json for JSON Schema messages that should be delivered as a TextMessage.
    • Use application/octet-stream for binary messages that should be delivered as a BytesMessage.
    • Verify that consumers can handle the expected message type.