Table of Contents

Scalar Consoles

Bielu.AspNetCore.AsyncApi integrates with Scalar to provide a modern, interactive documentation UI. Additionally, we provide companion packages that inject live, invocable consoles into the Scalar reference.

Basic Scalar Integration

Install the Scalar.AspNetCore package and point it at the AsyncAPI document generated by this library.

dotnet add package Scalar.AspNetCore
app.MapAsyncApi();
app.MapScalarApiReference(options =>
{
    options.AddAsyncApiDocument("v1", "My API", "/asyncapi/v1.json");
});

SignalR Console

The Bielu.AspNetCore.AsyncApi.Scalar.SignalR package adds a live console that allows you to connect to hubs, invoke client-to-server methods, and watch server-to-client events.

Installation

dotnet add package Bielu.AspNetCore.AsyncApi.Scalar.SignalR

Usage

app.MapScalarSignalRAssets(); // Serves the console bundle
app.MapScalarApiReference(options =>
{
    options.AddAsyncApiDocument("v1", "Chat", "/asyncapi/v1.json");
    options.WithSignalRClient(); // Injects the console into Scalar
});

gRPC Console

The Bielu.AspNetCore.AsyncApi.Scalar.Grpc package adds a console for invoking unary and server-streaming gRPC methods over gRPC-Web.

Installation

dotnet add package Bielu.AspNetCore.AsyncApi.Scalar.Grpc

Usage

app.UseGrpcWeb(new GrpcWebOptions { DefaultEnabled = true }); // From Grpc.AspNetCore.Web
app.MapGrpcService<GreeterService>();
app.MapScalarGrpcAssets(); // Serves the console bundle and descriptors
app.MapScalarApiReference(options =>
{
    options.AddAsyncApiDocument("v1", "Greeter", "/asyncapi/v1.json");
    options.WithGrpcClient(); // Injects the console into Scalar
});

Broker Console (Kafka)

The Bielu.AspNetCore.AsyncApi.Scalar.Broker package adds a console that publishes to and tails the message-broker channels your AsyncAPI bindings describe.

The SignalR and gRPC consoles talk to your app straight from the browser. This one cannot: no browser speaks a broker protocol. Instead the console drives an opt-in server-side bridge that this package mounts in your app, and a driver package supplies the client for the broker you use.

Important

Kafka is the only driver that ships today, as Bielu.AspNetCore.AsyncApi.Scalar.Broker.Kafka. The console already lists channels with mqtt and amqp bindings, but it only offers a connection whose protocol matches the channel, so those channels stay inert until a driver for them is registered. MQTT and AMQP drivers are planned; until then you can write your own.

Installation

dotnet add package Bielu.AspNetCore.AsyncApi.Scalar.Broker
dotnet add package Bielu.AspNetCore.AsyncApi.Scalar.Broker.Kafka

Usage

builder.Services.AddScalarBrokerBridge(broker =>
{
    broker.AddKafkaConnection("orders", "localhost:9092");
});

app.MapScalarBrokerAssets()          // Serves the console bundle and the proxy endpoints
   .RequireAuthorization("Admin");   // See "Securing the bridge" below

app.MapScalarApiReference(options =>
{
    options.AddAsyncApiDocument("v1", "Orders", "/asyncapi/v1.json");
    options.WithBrokerClient();      // Injects the console into Scalar
});

MapScalarBrokerAssets() mounts the bundle at {path}/plugin.js plus three proxy endpoints: GET {path}/connections, POST {path}/publish, and GET {path}/tail (Server-Sent Events).

Securing the bridge

Warning

The proxy can publish to your broker. Anyone who can reach it can write to your topics.

MapScalarBrokerAssets() returns a convention builder covering all three proxy endpoints, so a single RequireAuthorization(...) protects them together. The bundle itself is deliberately excluded — it is static JavaScript with no configuration or secrets in it, and it does nothing without the proxy.

If the endpoints carry no authorization metadata, the bridge:

  • allows the request in the Development environment, logging a warning once;
  • refuses it with 403 in every other environment, logging how to fix it.

Set AllowAnonymous when the endpoints are protected by something ASP.NET Core authorization cannot see, such as a network boundary:

builder.Services.AddScalarBrokerBridge(broker =>
{
    broker.AllowAnonymous = true;
    broker.AddKafkaConnection("orders", "localhost:9092");
});

Credentials entered in Scalar's auth panel travel with every proxy call — bearer tokens, HTTP Basic, and API keys in a header or a query parameter.

Tailing does not disturb your cluster

Each tail uses a throwaway consumer group starting at the newest offset, and never commits. Opening a console cannot move a real consumer group's committed position, and cannot replay a backlog into someone's browser.

The stream is read with fetch + ReadableStream rather than EventSource, because EventSource cannot send the Authorization header the proxy sits behind.

Connections are lazy

A connection's client is built on first use, so a broker that is unreachable at startup does not stop your application from starting — the console surfaces the error instead. Credentials never reach the browser: the console is told only a connection's name, protocol and a redacted display endpoint.

Writing a driver

Bielu.AspNetCore.AsyncApi.Scalar.Broker.Kafka is the reference implementation. A driver implements IBrokerBridge (PublishAsync + TailAsync) and registers connections with an extension method on ScalarBrokerBridgeOptions that calls AddConnection.

Note

There is no .Aspire companion for the broker console. The other consoles' Aspire packages load their plugin from a CDN into the Scalar container, which has no server-side bridge to talk to. Map the broker console in the app that owns the broker connection instead — as the Aspire mini-shop example does in its Order Service.

.NET Aspire Support

For .NET Aspire applications, use the .Aspire companion packages to enable these consoles in the Scalar resource.

dotnet add package Bielu.AspNetCore.AsyncApi.Scalar.SignalR.Aspire
dotnet add package Bielu.AspNetCore.AsyncApi.Scalar.Grpc.Aspire

In your AppHost:

var api = builder.AddProject<Projects.MyApi>("api");

builder.AddScalar("scalar")
    .WithSignalRClient()
    .WithGrpcClient();

Each console registers itself through Scalar's PluginUrls option, which loads the plugin as an ES module before the API Reference mounts. The Scalar container keeps its own bundle — only the plugin is added — so both consoles can be enabled on the same resource, and they stay compatible with whatever Scalar version the container ships.

By default each console loads the plugin module published alongside this package, pinned to an exact version on jsDelivr:

https://cdn.jsdelivr.net/npm/@bielu/scalar-signalr@<version>/dist/scalar-plugin.mjs

Pass your own URL to load the plugin from somewhere else — a private registry, or a copy you host yourself:

builder.AddScalar("scalar")
    .WithSignalRClient(pluginUrl: "https://cdn.example.com/scalar-signalr/scalar-plugin.mjs");

The URL must point at dist/scalar-plugin.mjs, the ES module whose default export is the plugin. The package's other build outputs are not interchangeable: dist/plugin.js is a <script> bundle that installs itself by hooking window.Scalar, and dist/standalone.js bundles Scalar itself.

By default the consoles discover AsyncAPI documents from the Scalar configuration they are rendered with. To name them explicitly:

builder.AddScalar("scalar")
    .WithGrpcClient(options => options.AddDocument("grpc", "https://api.example.com/asyncapi/grpc.json"));

Those URLs are resolved by the browser, so they must be reachable from the Scalar page's origin — not just from inside the Aspire network.