Skip to content

Azure Functions Integration

Bridge library that lets Azure Functions handlers delegate to the same Voltimax.Messaging pipeline (middleware, error handling, MessageContext) used in ASP.NET Core hosts.

Package

text
Voltimax.Messaging.Functions    # Bridge library (references Voltimax.Messaging)

How It Works

Azure Functions owns the processor lifecycle via [ServiceBusTrigger]. The bridge library provides:

  1. AddMessagingFunctions() - registers the messaging pipeline, serializer, and middleware without starting any ServiceBusClient or ProcessorHost.
  2. IMessageHandler<T> - inject this into your Function class and delegate the trigger call to it. It wraps the Functions ServiceBusReceivedMessage + ServiceBusMessageActions into the library's MessageContext and runs the full pipeline.

Startup Configuration

Register handlers in Program.cs using AddMessagingFunctions(). This is the Functions equivalent of AddMessaging() - it accepts the same MessagingBuilder to map entities, attach handlers, middleware, and error handlers, but does not create any ServiceBusClient or processor infrastructure.

csharp
var builder = FunctionsApplication.CreateBuilder(args);

builder.Services.AddMessagingFunctions(messaging =>
{
    messaging
        .MapQueue<TelemetryFrame>("telemetry")
        .MapHandler(TelemetryFunctions.TelemetryFrameHandler)
        .MapErrorHandler(async (ex, ctx, ct) =>
            await ctx.DeadLetterAsync("ProcessingFailed", ex.Message, ct)
        );
});

builder.Build().Run();

NOTE

AddMessagingFunctions() does not register IMessageSender<T>. If you need to send messages from a Function, register the core AddMessaging() alongside or create a ServiceBusClient manually.

Function Definition

Function classes inject IMessageHandler<T> and forward the raw trigger types to it. The trigger must use AutoCompleteMessages = false because the pipeline handles message settlement through MessageContext.

csharp
public class TelemetryFunctions(IMessageHandler<TelemetryFrame> handler)
{
    [Function(nameof(StoreIncomingTelemetry))]
    public Task StoreIncomingTelemetry(
        [ServiceBusTrigger("telemetry", AutoCompleteMessages = false)]
        ServiceBusReceivedMessage message,
        ServiceBusMessageActions messageActions,
        CancellationToken cancellationToken
    ) => handler.HandleAsync(message, messageActions, cancellationToken);

    // Static handler delegate - registered in Program.cs via MapHandler()
    public static async Task TelemetryFrameHandler(
        TelemetryFrame frame,
        MessageContext ctx,
        IMetricsCommandRepository repo,
        CancellationToken ct)
    {
        await repo.IngestAsync(frame.GatewayId, frame, ct);
        await ctx.CompleteAsync(ct);
    }
}

Key points:

  • The trigger receives ServiceBusReceivedMessage and ServiceBusMessageActions (the native Functions types)
  • IMessageHandler<T>.HandleAsync() wraps these into a MessageContext via FunctionsMessageSettler and runs the full pipeline
  • The handler delegate uses the same parameter binding as the core library - message type, MessageContext, DI services, CancellationToken

IMessageHandler<T> Interface

csharp
public interface IMessageHandler<TMessage>
{
    Task HandleAsync(
        ServiceBusReceivedMessage message,
        ServiceBusMessageActions actions,
        CancellationToken ct = default);
}

One IMessageHandler<T> is registered per MapQueue<T>().MapHandler() / MapTopic<T>().MapSubscription() call. Internally it creates a FunctionsMessageSettler (adapting ServiceBusMessageActions to IMessageSettler) and feeds the message through MessagePipeline.

What's Shared vs. What's Different

CapabilityCore (AddMessaging)Functions (AddMessagingFunctions)
Handler delegates with DI binding
MessageContext (metadata, settlement, reply)
Middleware pipeline
Error handler chain
IMessageSender<T> for sending❌ Not registered
ProcessorHost / processor lifecycle✅ Managed internally❌ Functions runtime owns this
ServiceBusClient creation
Fluent handler options (concurrency, prefetch)❌ Use host.json / trigger attributes
IServiceBusTopology✅ via MessagingBuilder

Azure Service Bus Considerations

IMPORTANT

AutoCompleteMessages must be false on the trigger attribute. The handler pipeline calls CompleteAsync(), AbandonAsync(), or DeadLetterAsync() explicitly through MessageContext. If auto-complete is enabled, the Functions runtime will complete the message before (or in addition to) your handler, causing MessageLockLostException errors.

[!NOTE] Concurrency and prefetch are configured via host.json in Functions, not through the fluent API:

json
{
  "extensions": {
    "serviceBus": {
      "prefetchCount": 20,
      "maxConcurrentCalls": 10,
      "maxMessageBatchSize": 100,
      "autoCompleteMessages": false
    }
  }
}

[!NOTE] Session-enabled triggers use the attribute property:

csharp
[ServiceBusTrigger("session-queue", IsSessionsEnabled = true, AutoCompleteMessages = false)]

The queue must be created with RequiresSession = true in Azure - see Sessions.

Limitations

FeatureSupportedNotes
Single message handlersVia IMessageHandler<T>
Middleware pipelineRegistered via AddMessagingFunctions()
Error handler chainVia .MapErrorHandler()
Session handlersUse trigger attribute IsSessionsEnabled = true
Request/reply⚠️MessageContext.ReplyAsync works if ReplyTo is set, but IMessageSender<T>.RequestAsync is not available
IMessageSender<T>Not registered - Functions bridge is receive-only
Fluent concurrency/prefetch configUse host.json / trigger attributes
Batch handlers (MapBatchHandler)Use native Functions batch binding instead