Skip to content

WhizbangCoreOptions

Verified by tests

WhizbangCoreOptionsTests, TagOptionsTests, ServiceCollectionExtensionsTests — library CI run #37346231411 (2026-10-05)

WhizbangCoreOptions is the central configuration class for Whizbang. Pass a configuration lambda to AddWhizbang() to customize behavior.

Basic Usage

Basic Configuration

services.AddWhizbang(options => {
  // Configure tag processing
  options.Tags.UseHook<NotificationTagAttribute, MyNotificationHook>();

  // Change processing mode
  options.TagProcessingMode = TagProcessingMode.AsLifecycleStage;

  // Disable tag processing entirely
  options.EnableTagProcessing = false;
});

Properties

Property Type Default Purpose
EnableTagProcessing bool true Master switch for message tag processing
TagProcessingMode TagProcessingMode AfterReceptorCompletion When tag hooks run in the pipeline
Tags TagOptions — Tag hook registration (see below)
Tracing TracingOptions — Handler/message tracing configuration (see Tracing)
Services ServiceRegistrationOptions — Auto-registration behavior for discovered Lenses/Perspectives (see ServiceRegistrationOptions)
AutoRegisterAspNetHosting bool true Automatically fold in AddWhizbangAspNet() when the Whizbang.Hosting.AspNet assembly is loaded; set false to call it yourself
DefaultQueryScope QueryScope QueryScope.Tenant Default scope filtering for ILensQuery<TModel>.DefaultScope queries
ShowBanner bool true Print the ASCII art banner on startup (the version log line always prints)
ImmediateDetachedChainWarningThreshold int 10 Warn when ImmediateDetached dispatch chains reach a multiple of this depth (no hard limit)
EmptyStreamIdPolicy EmptyStreamIdPolicy Reject How rows with stream_id = Guid.Empty are handled (see Empty Stream ID Policy)

EnableTagProcessing

Property Type Default
EnableTagProcessing bool true

Controls whether message tag processing is enabled. Set to false to disable all tag hook invocations.

Disable Tag Processing

services.AddWhizbang(options => {
  options.EnableTagProcessing = false; // No hooks will fire
});

TagProcessingMode

Property Type Default
TagProcessingMode TagProcessingMode AfterReceptorCompletion

Controls when tag hooks are executed in the message processing pipeline.

AfterReceptorCompletion (Default)

Tags are processed immediately after receptor completion, before lifecycle stages:

Message → Receptor → Cascade Events → TAG PROCESSING → Lifecycle Stages

AsLifecycleStage

Tags are processed during lifecycle invocation. Use this when hooks depend on lifecycle receptors completing first:

Message → Receptor → Cascade Events → Lifecycle Stages → TAG PROCESSING

Lifecycle Stage Mode

services.AddWhizbang(options => {
  options.TagProcessingMode = TagProcessingMode.AsLifecycleStage;
});

Tags

Property Type
Tags TagOptions

Access to tag-specific configuration options. See TagOptions below.

TagOptions

TagOptions configures message tag hook registration and behavior.

Property Type Default Purpose
HookRegistrations IReadOnlyList<TagHookRegistration> empty The registered hook configurations
PayloadSizeWarningThresholdBytes int? 8192 Log a warning when a built tag payload exceeds this size (raw JSON text length); null disables
PayloadSizeErrorThresholdBytes int? null Throw InvalidOperationException instead of dispatching when the payload exceeds this size (evaluated before hooks run); null disables

UseHook<TAttribute, THook>

Register a hook for a specific tag attribute type:

Register Tag Hook

services.AddWhizbang(options => {
  options.Tags.UseHook<SignalTagAttribute, SignalRNotificationHook<NotificationHub>>();
});

With Priority

Control execution order with priority (lower values execute first). The default priority is -100 (fires first):

Hook Priority

services.AddWhizbang(options => {
  options.Tags.UseHook<SignalTagAttribute, ValidationHook>(priority: -100);    // First (default)
  options.Tags.UseHook<SignalTagAttribute, NotificationHook>(priority: 0);     // After
  options.Tags.UseHook<SignalTagAttribute, AuditHook>(priority: 500);          // Last
});

With Lifecycle Stage

UseHook also accepts an optional fireAt parameter to restrict a hook to a specific LifecycleStage. When fireAt is null (the default), the hook fires at all stages:

Stage-Restricted Hook

services.AddWhizbang(options => {
  options.Tags.UseHook<SignalTagAttribute, NotificationHook>(
    fireAt: LifecycleStage.PostPerspectiveInline);
});

UseUniversalHook<THook>

Register a hook that fires for all tagged messages regardless of attribute type. The hook must implement IMessageTagHook<MessageTagAttribute>:

Universal Hook

services.AddWhizbang(options => {
  // This hook fires for every tagged message
  options.Tags.UseUniversalHook<LoggingHook>();

  // With priority
  options.Tags.UseUniversalHook<MetricsHook>(priority: 1000);
});

Complete Configuration Example

Complete Configuration

services.AddWhizbang(options => {
  // Enable tag processing (default is true)
  options.EnableTagProcessing = true;

  // Process tags after receptor completion (default)
  options.TagProcessingMode = TagProcessingMode.AfterReceptorCompletion;

  // Register built-in tag hooks
  // SignalRNotificationHook<THub> ships in Whizbang.SignalR;
  // OpenTelemetrySpanHook / OpenTelemetryMetricHook ship in Whizbang.Observability
  options.Tags.UseHook<SignalTagAttribute, SignalRNotificationHook<NotificationHub>>();
  options.Tags.UseHook<TelemetryTagAttribute, OpenTelemetrySpanHook>();
  options.Tags.UseHook<MetricTagAttribute, OpenTelemetryMetricHook>();

  // Register custom tag hooks (user-defined MessageTagAttribute subclasses)
  options.Tags.UseHook<AuditEventAttribute, AuditLogHook>();
  options.Tags.UseHook<SlackNotificationAttribute, SlackHook>();

  // Register hooks with priority (default is -100, lower fires first)
  options.Tags.UseHook<SignalTagAttribute, ValidationHook>(priority: -100);
  options.Tags.UseHook<SignalTagAttribute, EnrichmentHook>(priority: -50);

  // Universal hook for logging all tagged messages
  options.Tags.UseUniversalHook<TagLoggingHook>(priority: int.MaxValue);

  // Payload size guards
  options.Tags.PayloadSizeWarningThresholdBytes = 8192;  // default
  options.Tags.PayloadSizeErrorThresholdBytes = 65536;   // default: null (disabled)
});

DI Registration

When you call AddWhizbang(options => ...):

  1. WhizbangCoreOptions is registered as Singleton (first call wins — TryAddSingleton)
  2. TagOptions is registered as Singleton
  3. All hooks are registered as Scoped (via TryAddScoped)
  4. IMessageTagProcessor is registered as Singleton with IServiceScopeFactory
  5. Generated service registration callbacks are invoked automatically — discovered Lenses and Perspectives are registered without an explicit AddAllWhizbangServices() call (see ServiceRegistrationExtensions)

AddWhizbang() returns a WhizbangBuilder for chaining storage configuration (e.g., .WithEFCore<MyDbContext>().WithDriver.Postgres).

Multiple AddWhizbang Calls

AddWhizbang() can be called multiple times safely. The first call wins for option values; tag hook registrations from all calls are merged (duplicate attribute/hook pairs are skipped). This lets different parts of your startup code register hooks independently.

Hook Lifetime

Hooks are Scoped services, meaning:

  • ✅ Hooks can inject scoped services (DbContext, IHttpContextAccessor)
  • ✅ Multiple hooks in the same processing call share the same scope
  • ✅ Each message dispatch gets a fresh scope
  • ✅ Scope is disposed after processing completes

Hook with Scoped Dependencies

public class AuditHook : IMessageTagHook<AuditEventAttribute> {
  private readonly MyDbContext _dbContext; // Scoped - works!
  private readonly IHttpContextAccessor _httpContext; // Scoped - works!

  public AuditHook(MyDbContext dbContext, IHttpContextAccessor httpContext) {
    _dbContext = dbContext;
    _httpContext = httpContext;
  }

  public async ValueTask<JsonElement?> OnTaggedMessageAsync(
      TagContext<AuditEventAttribute> context,
      CancellationToken ct) {
    // Use scoped services safely
    var userId = _httpContext.HttpContext?.User?.Identity?.Name;
    _dbContext.AuditLogs.Add(new AuditLog { ... });
    await _dbContext.SaveChangesAsync(ct);
    return null;
  }
}

Backward Compatibility

The parameterless AddWhizbang() overload still works:

Parameterless Overload

// This still works - uses default options
services.AddWhizbang();

// Equivalent to:
services.AddWhizbang(options => { });

See Also

ValidateRegistrations

bool, default true.

Validates at startup that every registered service can have its constructor satisfied, and throws naming both the missing service and the type that wanted it. The check reads service descriptors and resolves nothing, so it constructs no services and fires no factory side effects.

Set to false for a partial composition that deliberately registers only a subset, such as a test fixture exercising one worker in isolation.

See Registration Validation.