Skip to main content

Configuration

Dispatch uses the standard .NET configuration patterns with fluent builders for service registration.

Before You Start

  • .NET 8.0+ (or .NET 9/10 for latest features)
  • Install the required packages:
    dotnet add package Excalibur.Dispatch
    dotnet add package Excalibur.Dispatch.Abstractions
  • Familiarity with .NET configuration and dependency injection

Basic Configuration

Service Registration

Register Dispatch in your Program.cs:

var builder = WebApplication.CreateBuilder(args);

// Auto-discover handlers from an assembly (recommended)
builder.Services.AddDispatch(dispatch =>
{
dispatch.AddHandlersFromAssembly(typeof(Program).Assembly);
});

var app = builder.Build();

Configuration Flow

flowchart TD
A[Program.cs] --> B[AddDispatch]
B --> C[IDispatchBuilder]
C --> D[Handler Registration]
C --> E[Middleware Configuration]
C --> F[Pipeline Profiles]
C --> G[Transport Setup]

Using the Dispatch Builder

The main configuration method uses a fluent builder for comprehensive setup:

// Register handlers from assembly with configuration
builder.Services.AddDispatch(dispatch =>
{
// Handler registration
dispatch.AddHandlersFromAssembly(typeof(Program).Assembly);

// Global middleware
dispatch.UseMiddleware<LoggingMiddleware>();
dispatch.UseMiddleware<ValidationMiddleware>();

// Options configuration
dispatch.ConfigureOptions<DispatchOptions>(options =>
{
options.DefaultTimeout = TimeSpan.FromSeconds(30);
});
});

// Serialization is registered separately via DI
builder.Services.AddMemoryPackInternalSerialization();
// Or: builder.Services.AddMessagePackSerialization();
// Or: builder.Services.AddJsonSerialization();

Handler Registration

// Single assembly (recommended)
builder.Services.AddDispatch(dispatch =>
{
dispatch.AddHandlersFromAssembly(typeof(Program).Assembly);
});

// Multiple assemblies
builder.Services.AddDispatch(dispatch =>
{
dispatch.AddHandlersFromAssembly(typeof(OrderHandler).Assembly);
dispatch.AddHandlersFromAssembly(typeof(PaymentHandler).Assembly);
});

Middleware Configuration

builder.Services.AddDispatch(dispatch =>
{
dispatch.AddHandlersFromAssembly(typeof(Program).Assembly);

// Add global middleware (applies to all pipelines)
dispatch.UseMiddleware<LoggingMiddleware>();
dispatch.UseMiddleware<ValidationMiddleware>();
dispatch.UseMiddleware<AuthorizationMiddleware>();

// Or configure a named pipeline with specific middleware
dispatch.ConfigurePipeline("Events", pipeline =>
{
pipeline.ForMessageKinds(MessageKinds.All);
});
});
Automatic Default Pipeline

When you use UseMiddleware<T>() without explicitly calling ConfigurePipeline(), Dispatch automatically creates a "Default" pipeline containing your global middleware. This means you don't need to configure pipelines for simple scenarios—your middleware is applied to all messages automatically.

Options Configuration

builder.Services.AddDispatch(dispatch =>
{
dispatch.AddHandlersFromAssembly(typeof(Program).Assembly);

// Configure dispatch options
dispatch.ConfigureOptions<DispatchOptions>(options =>
{
options.DefaultTimeout = TimeSpan.FromSeconds(30);
});
});

Ultra-Local Performance Options

Configure direct-local/ultra-local behavior on DispatchOptions.CrossCutting.Performance:

builder.Services.AddDispatch(dispatch =>
{
dispatch.AddHandlersFromAssembly(typeof(Program).Assembly);
dispatch.ConfigureOptions<DispatchOptions>(options =>
{
options.CrossCutting.Performance.DirectLocalContextInitialization =
DirectLocalContextInitializationProfile.Lean; // default

options.CrossCutting.Performance.EmitDirectLocalResultMetadata = false; // default
});
});

Use DirectLocalContextInitializationProfile.Full when you need eager full-context initialization on direct-local paths.

See Ultra-Local Dispatch for dispatch semantics and fallback behavior.

Profile detail:

ProfileDirect-local initialization behavior
Lean (default)Sets Message, correlation/causation (when needed), and skips eager MessageType population
FullSame as Lean, plus eager MessageType initialization when missing

Cross-Cutting Concerns

Configure observability, resilience, caching, and security through the builder:

builder.Services.AddDispatch(dispatch =>
{
dispatch.AddHandlersFromAssembly(typeof(Program).Assembly);

// Observability (tracing, metrics, context flow)
dispatch.AddObservability();

// Resilience (retry, circuit breaker, timeout)
dispatch.AddResilience(res => res.DefaultRetryCount = 3);

// Caching
dispatch.AddCaching();

// Security (requires IConfiguration for reflection-based scanning)
dispatch.AddSecurity(builder.Configuration);
});
Short names on the builder

When called on IDispatchBuilder, the Dispatch prefix is dropped since it's redundant. For example, services.AddDispatchObservability() becomes dispatch.AddObservability().

Configuration from appsettings.json

Bind configuration from your settings file. DispatchOptions supports nested options for cross-cutting concerns:

appsettings.json
{
"Dispatch": {
"DefaultTimeout": "00:00:30",
"EnableMetrics": true,
"Security": {
"EnableEncryption": false,
"EnableValidation": true
},
"Observability": {
"Enabled": true,
"EnableTracing": true,
"EnableMetrics": true
},
"Resilience": {
"DefaultRetryCount": 3,
"EnableCircuitBreaker": false
},
"Caching": {
"Enabled": false,
"DefaultExpiration": "00:05:00"
}
}
}
// Program.cs
builder.Services.AddDispatch(dispatch =>
{
dispatch.AddHandlersFromAssembly(typeof(Program).Assembly);
});

// Bind all Dispatch options from appsettings (including nested options)
builder.Services.Configure<DispatchOptions>(
builder.Configuration.GetSection("Dispatch"));

Transport Configuration

Configure transports through the AddDispatch() builder using Use{Transport}() methods:

Single Transport

builder.Services.AddDispatch(dispatch =>
{
dispatch.AddHandlersFromAssembly(typeof(Program).Assembly);

// Configure transport through the builder (recommended)
dispatch.UseRabbitMQ(rmq => rmq.HostName("localhost"));
});

All five transports follow the same pattern:

builder.Services.AddDispatch(dispatch =>
{
dispatch.UseKafka(kafka => kafka.BootstrapServers("localhost:9092"));
dispatch.UseAzureServiceBus(asb => asb.ConnectionString("..."));
dispatch.UseAwsSqs(sqs => sqs.Region("us-east-1"));
dispatch.UseGooglePubSub(pubsub => pubsub.ProjectId("my-project"));
dispatch.UseRabbitMQ(rmq => rmq.HostName("localhost"));
});
Standalone methods still work

You can also register transports directly on IServiceCollection if preferred:

builder.Services.AddKafkaTransport(options => { /* ... */ });

The builder Use{Transport}() methods are thin wrappers that delegate to these standalone methods.

See Transports for transport-specific setup guides.

Named Transports

Register multiple instances of the same transport with different names:

builder.Services.AddDispatch(dispatch =>
{
dispatch.UseKafka(kafka => kafka.BootstrapServers("localhost:9092"));
dispatch.UseKafka("analytics", kafka => kafka.BootstrapServers("analytics:9092"));
});

Multi-Transport Routing

builder.Services.AddDispatch(dispatch =>
{
dispatch.AddHandlersFromAssembly(typeof(Program).Assembly);

// Transports
dispatch.UseKafka(kafka => kafka.BootstrapServers("localhost:9092"));
dispatch.UseRabbitMQ(rmq => rmq.HostName("localhost"));

// Configure routing
dispatch.UseRouting(routing =>
{
routing.Transport
.Route<OrderCreatedEvent>().To("kafka")
.Route<PaymentProcessedEvent>().To("rabbitmq")
.Default("rabbitmq");
});
});

Environment-Specific Configuration

Development vs Production

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDispatch(dispatch =>
{
dispatch.AddHandlersFromAssembly(typeof(Program).Assembly);

if (builder.Environment.IsDevelopment())
{
// Development: add debug middleware
dispatch.UseMiddleware<DebugMiddleware>();
}
});

// Environment-specific transport
if (builder.Environment.IsDevelopment())
{
// Use in-memory transport for local development
builder.Services.AddMessageBus();
}
else
{
builder.Services.AddKafkaTransport(options =>
{
options.BootstrapServers = builder.Configuration["Kafka:Servers"];
});
}

Using IConfiguration

builder.Services.AddDispatch(dispatch =>
{
dispatch.AddHandlersFromAssembly(typeof(Program).Assembly);
});

// Bind from configuration
builder.Services.AddOptions<DispatchOptions>()
.Bind(builder.Configuration.GetSection("Dispatch"))
.ValidateDataAnnotations()
.ValidateOnStart();

Health Checks

Add health checks for Dispatch components:

builder.Services.AddHealthChecks()
.AddTransportHealthChecks();

var app = builder.Build();
app.MapHealthChecks("/health");

Observability Configuration

OpenTelemetry Integration

builder.Services.AddOpenTelemetry()
.WithTracing(tracing =>
{
tracing.AddSource("Excalibur.Dispatch.Observability");
tracing.AddOtlpExporter();
})
.WithMetrics(metrics =>
{
metrics.AddDispatchMetrics();
metrics.AddOtlpExporter();
});

Logging Configuration

builder.Logging.AddConfiguration(builder.Configuration.GetSection("Logging"));

// In appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Excalibur.Dispatch": "Debug",
"Excalibur.Dispatch.Pipeline": "Trace"
}
}
}

Configuration Validation

Validate configuration at startup:

builder.Services.AddOptions<DispatchOptions>()
.Bind(builder.Configuration.GetSection("Dispatch"))
.Validate(options =>
{
if (options.DefaultTimeout <= TimeSpan.Zero)
return false;
return true;
}, "DefaultTimeout must be positive")
.ValidateOnStart();

Builder Pattern Reference

IDispatchBuilder — Core Methods

MethodPurposeExample
ConfigurePipeline()Named pipeline setupdispatch.ConfigurePipeline("default", p => ...)
RegisterProfile()Pipeline profiledispatch.RegisterProfile(new MyProfile())
AddBinding()Transport bindingdispatch.AddBinding(b => ...)
UseMiddleware<T>()Global middlewaredispatch.UseMiddleware<LoggingMiddleware>()
ConfigureOptions<T>()Options configurationdispatch.ConfigureOptions<DispatchOptions>(o => ...)

IDispatchBuilder — Transport Extensions (Use prefix)

MethodPackageExample
UseRabbitMQ()Excalibur.Dispatch.Transport.RabbitMQdispatch.UseRabbitMQ(rmq => ...)
UseKafka()Excalibur.Dispatch.Transport.Kafkadispatch.UseKafka(kafka => ...)
UseAwsSqs()Excalibur.Dispatch.Transport.AwsSqsdispatch.UseAwsSqs(sqs => ...)
UseAzureServiceBus()Excalibur.Dispatch.Transport.AzureServiceBusdispatch.UseAzureServiceBus(asb => ...)
UseGooglePubSub()Excalibur.Dispatch.Transport.GooglePubSubdispatch.UseGooglePubSub(pubsub => ...)

All transport methods support named overloads: dispatch.UseKafka("analytics", kafka => ...).

IDispatchBuilder — Cross-Cutting Extensions (Add prefix)

MethodPackageExample
AddObservability()Excalibur.Dispatch.Observabilitydispatch.AddObservability(obs => ...)
AddResilience()Excalibur.Dispatch.Resilience.Pollydispatch.AddResilience(res => ...)
AddCaching()Excalibur.Dispatch.Cachingdispatch.AddCaching()
AddSecurity()Excalibur.Dispatch.Securitydispatch.AddSecurity(configuration)

Standalone IServiceCollection Methods

These standalone methods remain available for consumers who prefer direct registration:

MethodPurposePackage
AddDispatch()Core Dispatch servicesExcalibur.Dispatch
AddRabbitMQTransport()RabbitMQ transportExcalibur.Dispatch.Transport.RabbitMQ
AddKafkaTransport()Kafka transportExcalibur.Dispatch.Transport.Kafka
AddAwsSqsTransport()AWS SQS transportExcalibur.Dispatch.Transport.AwsSqs
AddAzureServiceBusTransport()Azure Service BusExcalibur.Dispatch.Transport.AzureServiceBus
AddGooglePubSubTransport()Google Pub/SubExcalibur.Dispatch.Transport.GooglePubSub
AddDispatchObservability()ObservabilityExcalibur.Dispatch.Observability
AddDispatchResilience()Resilience (Polly)Excalibur.Dispatch.Resilience.Polly
AddDispatchCaching()CachingExcalibur.Dispatch.Caching
AddDispatchSecurity()SecurityExcalibur.Dispatch.Security
AddMemoryPackInternalSerialization()MemoryPack serializationExcalibur.Dispatch.Serialization.MemoryPack
AddMessagePackSerialization()MessagePack serializationExcalibur.Dispatch.Serialization.MessagePack

Common Configuration Patterns

Minimal API

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDispatch(dispatch =>
{
dispatch.AddHandlersFromAssembly(typeof(Program).Assembly);
});
var app = builder.Build();

app.MapPost("/orders", async (CreateOrderAction action, IDispatcher dispatcher, CancellationToken ct) =>
await dispatcher.DispatchAsync(action, ct));

app.Run();
var builder = WebApplication.CreateBuilder(args);

// Unified Dispatch registration — transports + cross-cutting through the builder
builder.Services.AddDispatch(dispatch =>
{
dispatch.AddHandlersFromAssembly(typeof(Program).Assembly);

// Transports (Use prefix)
dispatch.UseKafka(kafka => kafka.BootstrapServers(builder.Configuration["Kafka:Servers"]));
dispatch.UseRabbitMQ(rmq => rmq.HostName("localhost"));

// Cross-cutting concerns (Add prefix)
dispatch.AddObservability();
dispatch.AddResilience(res => res.DefaultRetryCount = 3);
dispatch.AddCaching();
dispatch.AddSecurity(builder.Configuration);

// Global middleware
dispatch.UseMiddleware<LoggingMiddleware>();
dispatch.UseMiddleware<ValidationMiddleware>();
dispatch.UseMiddleware<AuthorizationMiddleware>();

// Options
dispatch.ConfigureOptions<DispatchOptions>(options =>
{
options.DefaultTimeout = TimeSpan.FromSeconds(30);
});

// Multi-transport routing
dispatch.UseRouting(routing =>
{
routing.Transport
.Route<OrderCreatedEvent>().To("kafka")
.Default("rabbitmq");
});
});

// Serialization
builder.Services.AddMemoryPackInternalSerialization();

// Health checks
builder.Services.AddHealthChecks()
.AddTransportHealthChecks();

// OpenTelemetry
builder.Services.AddOpenTelemetry()
.WithTracing(t => t.AddSource("Excalibur.Dispatch.Observability"))
.WithMetrics(m => m.AddDispatchMetrics());

var app = builder.Build();
app.MapHealthChecks("/health");
app.Run();

Combined with Excalibur

When using Excalibur subsystems alongside Dispatch, call AddDispatch for transport and pipeline configuration, then AddExcalibur for domain infrastructure. AddExcalibur registers Dispatch primitives with defaults, so both orderings are safe:

// Dispatch — transports, middleware, pipelines
builder.Services.AddDispatch(dispatch =>
{
dispatch.AddHandlersFromAssembly(typeof(Program).Assembly);
dispatch.UseKafka(kafka => kafka.BootstrapServers("localhost:9092"));
dispatch.AddObservability();
dispatch.ConfigurePipeline("default", p => p.UseValidation());
});

// Excalibur — event sourcing, outbox, sagas
builder.Services.AddExcalibur(excalibur =>
{
excalibur
.AddEventSourcing(es => es.UseEventStore<SqlServerEventStore>())
.AddOutbox(outbox => outbox.UseSqlServer(connectionString))
.AddSagas();
});

What's Next

See Also

  • Built-in Middleware — Pre-built middleware for logging, validation, authorization, and more
  • Getting Started — Step-by-step guide to setting up your first Dispatch project
  • Pipeline Profiles — Define reusable pipeline configurations for different message types
  • Actions and Handlers — Define actions and register handlers that configuration wires together