Getting started
A package, one line in Program.cs and one tag in your layout. BevTools then inspects the running
app - its components, renders, state, forms, routes, requests, interop calls, errors, services and circuits -
with no change to the components themselves.
Install
Bit.BevTools works in any Blazor host. Bit.BevTools.Server adds the circuit
instrumentation of Blazor Server - add it to the server project of a Blazor Server or Interactive Auto app.
Bit.BevTools.Loader is only for opening BevTools on demand, below.
dotnet add package Bit.BevTools # any Blazor host dotnet add package Bit.BevTools.Server # + Blazor Server circuits dotnet add package Bit.BevTools.Loader # optional: open it on demand, lazy load it in WebAssembly
Register the services
In the server project, AddBitBevToolsServerServices registers everything plus the circuit
diagnostics; elsewhere AddBitBevToolsServices does. An Interactive Auto solution
calls both - the first in the server project, the second in the .Client project - so BevTools works
whichever render mode a page ends up in.
using Bit.BevTools; // Program.cs of the server project builder.Services.AddBitBevToolsServerServices(); // Program.cs of a WebAssembly (.Client) project builder.Services.AddBitBevToolsServices(o => o.Enabled = builder.HostEnvironment.IsDevelopment());
BevTools turns itself on only when the host environment is Development; set
options.Enabled to decide yourself. In a WebAssembly client pass the host environment explicitly, as
above, so Development-only activation does not depend on the server's environment variables. Disabled, it
registers no instrumentation at all.
WebAssembly: keep the metrics
The WebAssembly SDK compiles System.Diagnostics.Metrics out by default, and render-diff and
lifecycle timings come from those meters. Put them back in the WebAssembly project; without it the About panel
says that these timings are not available, and everything else still works.
<PropertyGroup> <MetricsSupport>true</MetricsSupport> </PropertyGroup>
Place the panel
Once, inside an interactive component - typically the layout. It renders nothing during prerendering, and it
loads its own script and stylesheet (static web assets of the package), so no <script> or
<link> tag is needed.
@* _Imports.razor *@ @using Bit.BevTools @* MainLayout.razor (or any interactive component) *@ <BevToolsPanel />
Open it
Press CtrlShiftD or click the β BevTools badge in the bottom corner; the panel
remembers its tab, dock, size and theme. CtrlK (or π Search / > command)
searches components and runs commands: >pause, >pick, >export,
>trace⦠The header, left to right:
- βΈ Pause freezes every buffer, so what you are reading is not pushed out by a busy app; errors are still collected. βΆ Resume says how many renders it skipped.
- live re-reads the open tab as data arrives; turned off, β» Refresh re-reads it on request.
- β Pick picks a component in the page; β§ opens the companion window; then the theme, the dock (bottom or right), the size, and β.
The panel's defaults are options too:
builder.Services.AddBitBevToolsServerServices(o =>
{
o.Ui.ToggleShortcut = "Ctrl+Shift+D"; // the default
o.Ui.Dock = BevToolsDock.Right; // or Bottom (the default)
o.Ui.Theme = BevToolsTheme.Auto; // Auto, Light or Dark
o.Ui.OpenByDefault = false;
o.Ui.ShowBadge = true; // false: only the companion window and the extension show it
o.Ui.EditorUrlTemplate = "vscode://file/{path}:{line}:{column}"; // what source links open
});The tabs at a glance
Each tab answers one kind of question; every scenario page of this site is built to ask some of them.
| Tab | What it answers | Try it on |
|---|---|---|
| Components | Which components exist, with their parameters, cascading values, state, services, handlers, lifecycle, source and render mode; why each render happened. β picks one in the page. | Dashboard, Counter |
| Profiler | Renders and time per component or per type, since a reset; each render batch as a cost tree or on a time axis; renders per page; comparison with an earlier export. | Products |
| Timeline | Every render, batch, event, navigation, state change, request, interop call, error and custom event, each linked to its cause and its effects. | Counter, Products |
| State | Registered state containers and the signed-in user: snapshots and a history of named changes with diffs. | Checkout, Routing |
| Forms | Every EditForm: its fields, what validates it, what shows its messages, each validation's outcome. | Checkout |
| Routing | The current page, template, route values and query; the route table; navigation history and guards; who is signed in. | Routing & sign-in |
| Network | Every IHttpClientFactory request with its trigger and sender; requests per endpoint; WebSocket and SignalR traffic. | Orders |
| JS Interop | .NET β JS calls per identifier and component, and JS β .NET calls. | JS interop |
| Errors | Exceptions, failed requests, JS errors, resources that did not load and CSP violations, with what preceded each; groups of the same failure. | Errors |
| Services | Every DI registration with its lifetime, dependencies and consumers; captive dependencies and cycles. | Dashboard |
| Diagnostics | Findings with evidence - render storms, request bursts, leaks, loops, forms that do not validate⦠- and the rules behind them. | every page |
| Runtime | Where each render mode runs, what prerendering did, the server process or WebAssembly runtime, the heap, and disposed components still alive. | Products, Circuit |
| Browser | Connectivity, visibility, the JS heap, page load, storage (redacted) and an accessibility check of the page. | JS interop, Checkout |
| About | What this session can and cannot observe and why, what BevTools itself costs, and the exports. | Share & automate |
| Circuit | Blazor Server only: the circuit's connection, inbound message time, reconnects and the process's other circuits. | Circuit |
Extensions add tabs after these - on this site, Store (see Extending). About also lists every piece of information as Available, Partially available, Requires instrumentation, Requires application integration, Requires runtime support, Not technically reliable or Not available, with the reason: what BevTools cannot observe, it says it cannot.
How much it reads
Structure, counts, timings, causes, requests and errors are recorded at every level;
options.ValueAccess decides how much of the app's own values BevTools reads.
| Level | Parameters, fields, state, claims | Default |
|---|---|---|
Full | Read and shown, member by member, redacted | Development, and every single-user host (WebAssembly, Blazor Hybrid) |
Shallow | Numbers, booleans, dates and harmless strings only; objects show their type and size | - |
None | Names and types only | Outside Development in a process that serves many users |
At every level, values are redacted where they are captured: by name (password, token,
authorization, apikey, secretβ¦), by [BevToolsSensitive], and by
shape - JSON Web Tokens, bearer tokens, connection strings, private keys, cloud keys. Configuration, options,
credentials, request and response bodies and WebSocket payloads are never read, nothing is sent anywhere, and
every export, trace and companion view shows exactly what the panel does. Runtime shows the
level in force; the dashboard has secrets to look for.
Open it on demand, even in production
With Bit.BevTools.Loader, an app ships BevTools without anyone downloading or running it until
they ask. The loader is small, trimmable and AOT compatible; until BevTools is attached it installs only hooks
that pass everything through. In Blazor WebAssembly, Bit.BevTools itself can then be lazy loaded.
<BlazorWebAssemblyLazyLoad Include="Bit.BevTools.wasm" /> <!-- the WebAssembly project --> builder.Services.AddBitBevToolsLoaderServices(o => o.RegisterAtStartup = builder.HostEnvironment.IsDevelopment()); @* MainLayout.razor, instead of <BevToolsPanel /> *@ <BevToolsLoaderPanel /> @* the app's own "Open dev tools" button *@ @inject BevToolsLoader BevToolsLoader @if (BevToolsLoader.CanOpen) { <button @onclick="BevToolsLoader.OpenAsync">Open dev tools</button> }
Opened later, BevTools reads the components that already exist and sees everything from then on; About lists
exactly what such a session can see. On Blazor Server one process serves many users, so outside Development
CanOpen stays false unless the app sets AllowInMultiUserHost together with
Authorize, a callback that decides who may open it - asked on every open, logged on every attach,
and with ValueAccess at None unless the app raises it.
builder.Services.AddBitBevToolsLoaderServices(o =>
{
o.AllowInMultiUserHost = true;
o.Authorize = async services =>
{
var state = await services.GetRequiredService<AuthenticationStateProvider>().GetAuthenticationStateAsync();
return state.User.IsInRole("Developer");
};
});How this site does it
This demo is a Blazor Web App in Interactive Auto, and it exists to show BevTools wherever it is deployed, so it
enables it explicitly instead of relying on the Development environment, and it registers an extension of its
own. The panel sits in Routes.razor, after the router, so it survives every navigation.
// Demo/Server/Program.cs (made-up data: values readable outside Development too) builder.Services.AddBitBevToolsServerServices(o => { o.Enabled = true; o.ValueAccess = BevValueAccess.Full; o.Extensions.Add(new DemoBevToolsExtension()); }); // Demo/Client/Program.cs builder.Services.AddBitBevToolsServices(options => { options.Enabled = true; options.Ui.OpenByDefault = false; options.Extensions.Add(new DemoBevToolsExtension()); });