AppSurface Search
API Reference

Streams

Type

RazorWireStreamAdmissionController

Source

Controls admission for live RazorWire stream subscriptions in the current application process.

Remarks

The controller is the endpoint-side guardrail that sits before IRazorWireStreamHub.Subscribe(string, RazorWireStreamSubscribeOptions). It validates decoded channel names, enforces the configured live-channel and live-subscription limits from RazorWireStreamOptions, and returns a disposable lease for accepted requests.

Use this controller for bounded in-process SSE fanout. It is not a distributed quota system, an IP/user fairness mechanism, or a replacement for ASP.NET Core rate limiting, reverse-proxy limits, SignalR, or managed pub/sub. Each accepted live SSE request consumes one subscription slot until the lease is disposed.

State changes are synchronized by an internal lock. Callers must dispose accepted leases exactly once the endpoint is done with the matching hub subscription; the lease itself is idempotent, but skipping disposal leaves admission capacity reserved for the lifetime of the process. Rejection metrics are low-cardinality diagnostics tagged only by rejection reason.

Method

TryAcquire

RazorWireStreamAdmissionResult TryAcquire(string channel) Source

Attempts to reserve live stream capacity for a decoded channel name.

Parameters

  • channelThe decoded channel name from the route value or tag helper input.

Returns

An accepted result with a disposable lease when the channel is valid and all configured limits allow the subscription; otherwise, a rejected result that identifies the first failed validation or capacity check.

Remarks

This method validates channel grammar before capacity checks. Accepted callers must subscribe to the hub only after this method succeeds and must dispose the returned lease after unsubscribing. Failed results record the rejection metric and do not reserve capacity.

Method

RejectPreAuthorizationValidation

RazorWireStreamAdmissionResult RejectPreAuthorizationValidation(string channel, RazorWireStreamAdmissionRejectionReason reason) Source

Records a pre-authorization channel validation rejection without reserving live stream capacity.

Parameters

  • channelThe decoded channel name that failed route-level validation.
  • reasonThe validation rejection reason. Only RazorWireStreamAdmissionRejectionReason.InvalidChannelName and RazorWireStreamAdmissionRejectionReason.ChannelNameTooLong are valid here.

Returns

A rejected result that includes the current live-count snapshot and records the rejection metric.

Remarks

RazorWire validates malformed channels before resolving custom authorizers so bad input returns 400 without invoking host authorization code. Use this method for that non-acquiring path so diagnostics and metrics stay consistent with capacity rejections. Do not use it for authorization denials or capacity failures.

Property

Snapshot

RazorWireStreamAdmissionSnapshot Snapshot { get; } Source

Gets the current in-process live subscription and live channel counts.

Remarks

The snapshot is moment-in-time diagnostic state. It is useful for development responses, logs, and tests, but it should not be treated as a stable concurrency token because another request can change the counts immediately after the snapshot is read.

Type

RazorWireStreamAdmissionLease

Source

Represents one accepted live stream admission reservation.

Remarks

Dispose the lease after the corresponding hub subscription is removed. Disposal is idempotent so nested cleanup paths can call it safely, but the endpoint should still keep lease lifetime aligned with the hub reader lifetime.

Method

Dispose

void Dispose() Source

Releases the reserved subscription slot and, when this was the final subscriber, the live channel slot.

Type

RazorWireStreamAdmissionResult

Source

Describes the outcome of a RazorWire stream admission attempt.

Parameters

  • AcceptedWhether live capacity was reserved.
  • LeaseThe lease that must be disposed for accepted results; null for rejections.
  • RejectionReasonThe reason admission failed, or null for accepted results.
  • ChannelLengthThe decoded channel-name length reported for rejected requests.
  • CurrentThe count or length that triggered the rejection.
  • SnapshotThe live-count snapshot captured when the result was created.

Remarks

Consumers should branch on Accepted. Do not assume Lease is present unless admission was accepted, and do not expose raw channel names in logs or diagnostics when building responses from rejected results.

Method

Accept

RazorWireStreamAdmissionResult Accept(RazorWireStreamAdmissionController.RazorWireStreamAdmissionLease lease, RazorWireStreamAdmissionSnapshot snapshot) Source

Creates an accepted result for a reserved subscription.

Parameters

  • leaseThe live capacity lease owned by the accepted subscription.
  • snapshotThe live-count snapshot after accepting the subscription.

Returns

An accepted admission result.

Method

Rejected

RazorWireStreamAdmissionResult Rejected(RazorWireStreamAdmissionRejectionReason reason, int channelLength, int current, RazorWireStreamAdmissionSnapshot snapshot) Source

Creates a rejected result without reserving capacity.

Parameters

  • reasonThe validation or capacity reason admission failed.
  • channelLengthThe decoded channel-name length.
  • currentThe count or length compared against the configured limit.
  • snapshotThe live-count snapshot captured at rejection time.

Returns

A rejected admission result.

Type

RazorWireStreamAdmissionSnapshot

Source

Captures live stream admission counts at one point in time.

Parameters

  • LiveSubscriptionsThe number of live SSE subscriptions currently admitted in this process.
  • LiveChannelsThe number of decoded channel names with at least one live admitted subscription.

Remarks

Snapshots are diagnostic values for logs, development responses, and tests. They are not durable counters and should not be used for distributed fairness or cross-node capacity planning.

Enum

RazorWireStreamAdmissionRejectionReason

Source

Identifies why a RazorWire stream admission request was rejected.

Remarks

Validation reasons map to HTTP 400. Capacity reasons map to HTTP 429. Authorization denials are handled separately and continue to map to HTTP 403.

Type

RazorWireStreamPublishOptions

Source

Options that control how a RazorWire stream message is published.

Remarks

Replay is disabled by default so existing channels remain live-only. Enable Replay only for idempotent state snapshots or bounded progress events that are safe for late subscribers to receive after publish. The in-memory hub retains at most 25 messages per replay channel and prunes inactive replay channels when more than 256 replay channels are retained.

Property

Replay

bool Replay { get; init; } Source

Gets a value indicating whether this message should be retained in the bounded replay buffer for its channel. Retained messages survive live subscriber disconnects until normal replay pruning removes them.

Type

RazorWireStreamSubscribeOptions

Source

Options that control how a RazorWire stream subscription is created.

Remarks

Replay is opt-in per subscription. A replaying subscriber first receives the channel's retained messages, then receives live messages like any other subscriber. The in-memory hub bounds replay storage and drops the oldest retained messages when the channel exceeds its replay capacity of 25 messages, and prunes inactive replay channels when more than 256 replay channels are retained.

Property

Replay

bool Replay { get; init; } Source

Gets a value indicating whether retained channel messages should be delivered before live messages. A replay subscription to a channel with no retained messages does not create a durable replay buffer.

Type

IRazorWireStreamHub

Source

Defines the contract for a message hub that supports pub/sub operations over named channels.

Remarks

Implementations may keep live subscriber state and replay retention state separately. Live subscriptions should be released when readers disconnect, while opt-in replay buffers may outlive live subscribers until the implementation's bounded retention policy prunes them.

Method

PublishAsync

2 overloads
ValueTask PublishAsync(string channel, string message) Source

Publishes a trusted stream message to the specified channel.

Parameters

  • channelThe name of the channel to publish the message to.
  • messageThe trusted Turbo Stream payload to publish. The hub transports this string as-is; it does not encode or sanitize template content.

Returns

A ValueTask that completes when the publish operation has finished.

Remarks

Prefer publishing output from Bridge.RazorWireStreamBuilder so plain-text values are encoded before the message reaches the hub. If composing a raw message, encode user-supplied values before publishing.

ValueTask PublishAsync(string channel, string message, RazorWireStreamPublishOptions? options) Source

Publishes a trusted stream message to the specified channel with explicit delivery options.

Parameters

  • channelThe name of the channel to publish the message to.
  • messageThe trusted Turbo Stream payload to publish. The hub transports this string as-is; it does not encode or sanitize template content.
  • optionsOptional publish behavior. The default interface implementation is a compatibility fallback and ignores this value by delegating to PublishAsync(string, string). Implementations that support replay retention or other optioned semantics must override this overload.

Returns

A ValueTask that completes when the publish operation has finished.

Remarks

Pitfall: callers should not expect RazorWireStreamPublishOptions to be honored unless the concrete IRazorWireStreamHub implementation overrides this member. The hub is also a raw transport boundary: use Bridge.RazorWireStreamBuilder or encode user-supplied values before publishing raw stream strings.

Method

Subscribe

2 overloads
ChannelReader<string> Subscribe(string channel) Source

Subscribes to a named message channel and provides a reader for incoming messages.

Parameters

  • channelThe name of the channel to subscribe to.

Returns

A ChannelReader{String} that yields messages published to the specified channel; the reader completes when the subscription is removed or the hub shuts down.

ChannelReader<string> Subscribe(string channel, RazorWireStreamSubscribeOptions? options) Source

Subscribes to a named message channel with explicit subscription options.

Parameters

  • channelThe name of the channel to subscribe to.
  • optionsOptional subscription behavior. The default interface implementation is a compatibility fallback and ignores this value by delegating to Subscribe(string).

Returns

A ChannelReader{String} for the specified channel. Replay semantics are available only when the concrete implementation overrides this overload.

Remarks

Pitfall: RazorWireStreamSubscribeOptions.Replay is an opt-in contract between caller and hub implementation; the interface fallback remains live-only for backward compatibility. Replay should be reserved for idempotent retained state, not one-shot commands or sensitive payloads.

Method

Unsubscribe

void Unsubscribe(string channel, ChannelReader<string> reader) Source

Unsubscribes the specified reader from the named channel so it no longer receives messages from that channel.

Parameters

  • channelThe name of the channel to unsubscribe from. Normal callers should pass the same channel used for subscription. Endpoint cleanup paths usually have that value available; passing a different value can be surprising on implementations that do not keep their own subscription ownership map.
  • readerThe ChannelReader<string> instance to detach from the channel.
Type

IRazorWireStreamAuthorizationFilter

Source

Provides an optional pre-authorizer for RazorWire stream subscriptions before the active stream authorizer runs.

Remarks

Register filters when a package or module owns a reserved channel and must enforce a channel-specific gate that normal host IRazorWireStreamAuthorizer replacement should not bypass. Return null when the filter does not apply to the requested channel. Return an allowed result to let later filters and the active stream authorizer continue, or return a denial/setup-failure result to stop the subscription before the stream opens.

Method

AuthorizeAsync

ValueTask<AppSurfaceAuthResult?> AuthorizeAsync(RazorWireStreamAuthorizationContext context) Source

Evaluates a pre-authorization gate for the requested stream subscription.

Parameters

  • contextThe current stream authorization context.

Returns

null when the filter does not apply; otherwise an AppSurface auth result whose denial stops the subscription and whose allowed outcome lets normal stream authorization continue.

Type

RazorWireBoolChannelAuthorizerAdapter

Source

Bridges legacy bool channel authorizers into the result-bearing stream authorization contract.

Remarks

The adapter resolves IRazorWireChannelAuthorizer from the current request services on every authorization call. This preserves legacy before/after AddRazorWire registration behavior and avoids capturing scoped or transient authorizers in a singleton adapter.

Method

AuthorizeWithResolvedAuthorizerAsync

ValueTask<(AppSurfaceAuthResult Result, string AuthorizerType)> AuthorizeWithResolvedAuthorizerAsync(RazorWireStreamAuthorizationContext context, IRazorWireChannelAuthorizer? channelAuthorizer = null) Source

Authorizes a stream by mapping the legacy channel authorizer's boolean decision to an auth result.

Parameters

  • contextThe current stream authorization context.
  • channelAuthorizerOptional same-request-scope authorizer override. Endpoint code uses this after resolving the legacy authorizer once so exception paths can still report the concrete legacy authorizer type; tests may also pass a resolved instance to avoid exercising dependency injection.

Returns

The mapped result and the resolved legacy authorizer type. true maps to AppSurfaceAuthResult.Allowed; false maps to AppSurfaceAuthResult.Challenge for anonymous callers and AppSurfaceAuthResult.Forbidden for authenticated callers.

Remarks

Use channelAuthorizer only when it was resolved from the same HttpContext.RequestServices scope represented by context. Supplying a cached scoped or transient legacy authorizer from another request can break host lifetime expectations.

Method

ResolveChannelAuthorizer

IRazorWireChannelAuthorizer ResolveChannelAuthorizer(RazorWireStreamAuthorizationContext context) Source

Resolves the effective legacy channel authorizer from the current request service scope.

Parameters

  • contextThe stream authorization context whose HttpContext.RequestServices scope is active.

Returns

The request-scoped IRazorWireChannelAuthorizer used for the current subscription attempt.

Remarks

This method intentionally resolves per request instead of capturing the channel authorizer in the singleton adapter. That preserves scoped and transient legacy authorizers and keeps before/after AddRazorWire registration behavior compatible.

Method

GetAuthorizerType

string GetAuthorizerType(IRazorWireChannelAuthorizer channelAuthorizer) Source

Returns the diagnostic type name for a resolved legacy channel authorizer.

Parameters

  • channelAuthorizerThe legacy authorizer resolved for the current request.

Returns

The authorizer's full type name when available, otherwise its simple type name. The returned value is safe for low-cardinality diagnostics and avoids exception messages, channel names, users, and claims.

Type

RazorWireStreamAuthorizationResponseMapper

Source

Maps passive stream authorization results to safe pre-SSE HTTP responses and log fields.

Type

InMemoryRazorWireStreamHub

Source

Provides an in-memory implementation of IRazorWireStreamHub using Channel{T} for message distribution.

Remarks

Live subscriber state is tracked separately from replay state. Empty live channels are removed after the last subscriber disconnects or after publish-time pruning finds stale writers, while replay buffers remain available until the bounded replay retention policy removes them. The hub keeps up to 25 retained messages per replay channel and prunes inactive replay channels when more than 256 replay channels are retained.

Method

PublishAsync

ValueTask PublishAsync(string channel, string message) Source

Publishes a message to all subscribers of the specified channel. Any subscribers that are closed or unable to accept the message are removed during the process.

Parameters

  • channelThe name of the channel to publish to.
  • messageThe message payload to deliver to subscribers.

Returns

`ValueTask.CompletedTask` on success, or a faulted `ValueTask` containing the exception if publishing failed.

Method

Subscribe

ChannelReader<string> Subscribe(string channel) Source

Subscribes to a named channel and returns a reader that receives messages published to that channel. The subscription uses an in-memory bounded buffer with capacity 100 that drops the oldest messages when full.

Parameters

  • channelThe name of the channel to subscribe to.

Returns

A ChannelReader{String} that yields messages published to the specified channel until the subscription is removed or the writer is completed.

Method

Unsubscribe

void Unsubscribe(string channel, ChannelReader<string> reader) Source

Unregisters a subscriber from the specified channel and completes its associated writer.

Parameters

  • channelThe channel name supplied by the caller. The in-memory hub removes the reader from its recorded channel so endpoint cleanup remains correct even if this value is stale, but application code should still pass the same channel used for subscription for clarity and compatibility with other implementations.
  • readerThe subscriber's ChannelReader{String}; its paired writer will be completed and removed from channel tracking.
Method

GetDiagnostics

InMemoryRazorWireStreamHubDiagnostics GetDiagnostics() Source

Gets internal stream state counts used by regression tests to verify cleanup behavior.

Returns

A snapshot of live and replay tracking counts.

Method

TryCompleteSubscriberWriterForTesting

bool TryCompleteSubscriberWriterForTesting(ChannelReader<string> reader) Source

Completes the writer for an active subscription without removing the subscription maps.

Parameters

  • readerThe reader whose paired writer should be completed.

Returns

true when the reader belongs to an active subscription; otherwise, false.

Remarks

This internal test seam simulates a writer closed by channel infrastructure so publish-time pruning can be verified through the same cleanup path that handles stale live subscribers.

Property

AfterLiveChannelRetiredForTesting

Action? AfterLiveChannelRetiredForTesting { get; set; } Source

Gets or sets an internal test hook that runs after an empty live channel state is retired and before it is removed from the live channel map.

Remarks

Production code leaves this unset. Regression tests use it to force subscribe/cleanup interleavings that would otherwise depend on timing.

Property

BeforeReplayPruneCandidateForTesting

Action<string>? BeforeReplayPruneCandidateForTesting { get; set; } Source

Gets or sets an internal test hook that runs before a replay prune candidate takes its channel stripe lock.

Remarks

Production code leaves this unset. Regression tests use it to force publish/prune interleavings that would otherwise depend on timing.

Type

InMemoryRazorWireStreamHubDiagnostics

Source

Represents a snapshot of internal stream tracking counts used by the RazorWire test suite.

Parameters

  • LiveChannelCountThe number of live channel state entries currently retained.
  • LiveSubscriberCountThe number of active live subscriber writers currently retained.
  • SubscriptionCountThe number of reader-owned subscription records currently retained.
  • ReplayChannelCountThe number of channels with retained replay messages.
Type

IRazorWireChannelAuthorizer

Source

Defines the contract for authorizing subscription requests to RazorWire channels.

Method

CanSubscribeAsync

ValueTask<bool> CanSubscribeAsync(HttpContext context, string channel) Source

Determines whether the current HTTP request is permitted to subscribe to the specified channel.

Parameters

  • contextThe current HTTP context for the subscription request.
  • channelThe name of the channel to subscribe to.

Returns

true if subscription is permitted, false otherwise.

Type

DenyAllRazorWireChannelAuthorizer

Source

Provides a built-in implementation of IRazorWireChannelAuthorizer that denies all subscriptions.

Remarks

This is RazorWire's safe default when no app-specific authorizer is registered. Keep ForgeTrust.RazorWire.RazorWireStreamAuthorizationMode.DenyAll for production defaults, set ForgeTrust.RazorWire.RazorWireStreamOptions.AuthorizationMode to ForgeTrust.RazorWire.RazorWireStreamAuthorizationMode.AllowAll only for public/demo streams, and register a custom IRazorWireStreamAuthorizer for user, tenant, or workflow-specific channels. Use this bool authorizer contract when simple legacy allow/deny compatibility is enough.

Method

CanSubscribeAsync

ValueTask<bool> CanSubscribeAsync(HttpContext context, string channel) Source

Determines whether the request represented by the context may subscribe to the specified channel.

Parameters

  • contextThe HTTP context of the requesting client.
  • channelThe name of the channel to subscribe to.

Returns

false for every request.

Type

AllowAllRazorWireChannelAuthorizer

Source

Provides a built-in implementation of IRazorWireChannelAuthorizer that permits all subscriptions.

Remarks

This authorizer is intended for public, demo, or local-development streams only. Do not use it for channels that include user-specific, tenant-specific, workflow-specific, or otherwise sensitive data.

Method

CanSubscribeAsync

ValueTask<bool> CanSubscribeAsync(HttpContext context, string channel) Source

Determines whether the request represented by the context may subscribe to the specified channel.

Parameters

  • contextThe HTTP context of the requesting client.
  • channelThe name of the channel to subscribe to.

Returns

true for every request.

Remarks

Prefer a custom IRazorWireStreamAuthorizer for production streams that depend on HttpContext.User or other request state. Use IRazorWireChannelAuthorizer when a simple legacy allow/deny decision is enough.

Type

IRazorWireStreamAuthorizer

Source

Defines the result-bearing contract for authorizing RazorWire stream subscription requests.

Remarks

Implement this interface when a stream needs to distinguish unauthenticated, forbidden, stale-session, unsafe-navigation, or host setup-failure outcomes. RazorWire consumes the passive AppSurfaceAuthResult before opening Server-Sent Events; it does not challenge, forbid, redirect, mutate cookies, evaluate host policies, or echo app-supplied auth messages.

Method

AuthorizeAsync

ValueTask<AppSurfaceAuthResult> AuthorizeAsync(RazorWireStreamAuthorizationContext context) Source

Authorizes the current HTTP request for the requested RazorWire stream channel.

Parameters

  • contextThe stream authorization context for this subscription request.

Returns

A non-null AppSurfaceAuthResult describing whether the subscription may proceed. RazorWire defensively treats a null result from a misbehaving implementation as a setup failure.

Type

RazorWireStreamAuthorizationContext

Source

Carries request and configuration context for a RazorWire stream authorization decision.

Remarks

ConfiguredAuthorizationMode is diagnostic context from RazorWireOptions.Streams. It is not proof of the effective decision when the host registers a custom IRazorWireStreamAuthorizer or legacy IRazorWireChannelAuthorizer.

Property

HttpContext

HttpContext HttpContext { get; } Source

Gets the current HTTP request context.

Property

Channel

string Channel { get; } Source

Gets the validated stream channel name requested by the client.

Property

ConfiguredAuthorizationMode

RazorWireStreamAuthorizationMode ConfiguredAuthorizationMode { get; } Source

Gets the configured stream authorization mode from RazorWireOptions.Streams.

Property

RequestAborted

CancellationToken RequestAborted { get; } Source

Gets the request-aborted cancellation token for the stream subscription request.