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
Voltimax.Messaging.Functions # Bridge library (references Voltimax.Messaging)How It Works
Azure Functions owns the processor lifecycle via [ServiceBusTrigger]. The bridge library provides:
AddMessagingFunctions()- registers the messaging pipeline, serializer, and middleware without starting anyServiceBusClientorProcessorHost.IMessageHandler<T>- inject this into your Function class and delegate the trigger call to it. It wraps the FunctionsServiceBusReceivedMessage+ServiceBusMessageActionsinto the library'sMessageContextand 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.
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.
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
ServiceBusReceivedMessageandServiceBusMessageActions(the native Functions types) IMessageHandler<T>.HandleAsync()wraps these into aMessageContextviaFunctionsMessageSettlerand 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
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
| Capability | Core (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:
{
"extensions": {
"serviceBus": {
"prefetchCount": 20,
"maxConcurrentCalls": 10,
"maxMessageBatchSize": 100,
"autoCompleteMessages": false
}
}
}[!NOTE] Session-enabled triggers use the attribute property:
[ServiceBusTrigger("session-queue", IsSessionsEnabled = true, AutoCompleteMessages = false)]The queue must be created with RequiresSession = true in Azure - see Sessions.
Limitations
| Feature | Supported | Notes |
|---|---|---|
| Single message handlers | ✅ | Via IMessageHandler<T> |
| Middleware pipeline | ✅ | Registered via AddMessagingFunctions() |
| Error handler chain | ✅ | Via .MapErrorHandler() |
| Session handlers | ✅ | Use 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 config | ❌ | Use host.json / trigger attributes |
Batch handlers (MapBatchHandler) | ❌ | Use native Functions batch binding instead |