Bonoscan.Components 0.15.0
Bonoscan.Components
A reusable Blazor document-scanner component. It opens the camera, finds the document in real time (Rust + WebAssembly edge detection — no OCR, no ML model), auto-captures when you hold it steady inside the frame, deskews it, and hands you the result. Also does single-image upload and torch/flashlight where supported.
Works in Blazor WebAssembly, Blazor Server (including prerendering), and .NET MAUI Blazor Hybrid — the same package, unchanged.
Install
dotnet add package Bonoscan.Components
That's all you need. The component's JavaScript, Web Worker, and .wasm ship
inside the package as static web assets and are served automatically from
_content/Bonoscan.Components/scanner/… — nothing to copy, no CDN, no build step.
The only dependency is the standard ASP.NET Core Blazor framework you already
have.
Use it
1. Register the service (in Program.cs, or MauiProgram.cs for Hybrid):
using Bonoscan.Components;
builder.Services.AddDocumentScanner(o =>
{
o.GuideAspect = 0.707; // A4 portrait guide; auto-rotates; null = no guide
o.FillThreshold = 0.60; // document must fill 60% of the guide before capture
o.CaptureWidth = 1240;
o.CaptureHeight = 1754; // PNG by default; set o.Mime = "image/jpeg" for JPEG
});
2. Drop in the component:
@using Bonoscan.Components
<DocumentScanner OnCaptured="OnCaptured" />
@code {
private void OnCaptured(ScannerResult r)
{
// r.ImageUrl is a short blob: URL, ready for <img src> or a download link.
// r.FromUpload is true if it came from the file picker rather than the camera.
// r.Mime is the encoded content type (e.g. "image/png").
}
}
The full-bleed camera, auto-capture, framing guide, torch, upload, and a result view with Scan again / Download are all built in.
Getting the image data (server / native, not just display)
ScannerResult.ImageUrl is a browser-only blob: URL — perfect for an <img>, but a
Blazor Server or MAUI host needs the actual bytes to store, upload, or OCR. Inject
DocumentScannerInterop (registered by AddDocumentScanner) and open the last captured
image as a stream — nothing is transferred until you ask:
@inject DocumentScannerInterop Scanner
@code {
private async Task OnCaptured(ScannerResult r)
{
await using var stream = await Scanner.OpenResultStreamAsync();
using var ms = new MemoryStream();
await stream.CopyToAsync(ms);
byte[] bytes = ms.ToArray(); // store / upload / OCR
// r.ImageUrl still works for display.
}
}
OpenResultStreamAsync streams via IJSStreamReference, so it isn't subject to SignalR's
per-message size cap and works the same on WebAssembly, Server, and MAUI — it is the one
canonical way to get the bytes. (GetResultMimeAsync() gives the content type.)
3. Make sure your host links the scoped-CSS bundle. The component's styles ride
along in your app's {AppName}.styles.css bundle — the Blazor templates already
include this line, but if you removed it, add it back:
<link href="YourApp.styles.css" rel="stylesheet" />
Useful parameters
Options (per-instance override of the DI defaults), Labels (a ScannerLabels
with all the static UI text), OnCaptured, OnError, OnConfirm, AutoStart,
EnableCamera, EnableUpload, ShowTorch, ShowResult (set false to render your
own result UI from OnCaptured), ShowDownload, ShowBrand, IdleContent (a custom
idle overlay), and Class. For programmatic control, grab a @ref and call
RescanAsync() / ResetAsync() / StopAsync().
Customize the built-in UI
Keep the ready-made scanner but reshape it — without forking it:
- Embed it inline. By default the scanner is a full-viewport overlay; set
Layout="ScannerLayout.Inline"to make it an in-flow box you size yourself (via the--scanner-width/--scanner-heightvariables or your own wrapper), e.g. inside a card or a column. - Replace individual pieces. Each chrome element is a
RenderFragment<IScannerHandle>slot, so you can swap just the part you want and keep the rest:BrandContent,StatusContent,GuidanceContent(the live hint pill),ResultContent(the whole result view), andResultActionsContent(just its buttons). The fragment's context is the live scan handle — readcontext.Stateand call its commands.
<DocumentScanner Layout="ScannerLayout.Inline" OnCaptured="OnCaptured">
<ResultActionsContent>
<button @onclick="() => context.RescanAsync()">Retake</button>
<button @onclick="Use">Use this scan</button>
</ResultActionsContent>
</DocumentScanner>
Build your own UI (headless)
When you want full control over the layout and flow, use the headless <ScannerView>
engine directly. It renders only the camera stage and runs the whole pipeline (detection,
auto-capture, upload, torch); you render everything else. It hands you an
IScannerHandle — as the slot's context and via @ref — exposing the live State
(Phase, Reason, Progress, Fill, ResultUrl, …) plus the commands StartAsync,
StopAsync, RescanAsync, ResetAsync, UploadAsync, ToggleTorchAsync,
GetCornersAsync (the live document quad, for drawing your own overlay), and
OpenResultStreamAsync.
<ScannerView AutoStart="true" OnCaptured="OnCaptured" OnStateChanged="OnStateChanged">
@if (!context.State.IsCaptured)
{
<div class="my-hint">@context.State.Reason</div>
<button @onclick="() => context.UploadAsync()">Upload instead</button>
}
</ScannerView>
OnCaptured / OnError / OnStateChanged keep you in the loop; the same DocumentScanner
above is itself just a styled skin over this engine.
Theme it (CSS variables)
The component is self-contained and themed entirely through CSS custom properties
with sensible fallbacks — set them in your app's global CSS (e.g. app.css):
:root {
--scanner-accent: #3b82f6; /* primary / lock-on colour */
--scanner-accent-ink: #ffffff; /* text on the accent button */
--scanner-amber: #f5b454; /* "align the frame" colour */
--scanner-text: #eef2f4;
--scanner-danger: #ff6b6b;
--scanner-bg: #000; /* letterbox backdrop */
--scanner-font: "Inter", system-ui, sans-serif;
--scanner-mono: "JetBrains Mono", ui-monospace, monospace;
}
Layout is overridable the same way (each falls back to the built-in value, so setting one
is optional): --scanner-radius-pill, --scanner-radius-control, --scanner-pad-edge,
--scanner-pad-top, --scanner-pad-bottom, --scanner-chrome-bg, --scanner-chrome-border,
--scanner-veil-bg, and (inline layout) --scanner-width / --scanner-height.
The live viewfinder drawn on the <canvas> (the detected-document quad, framing guide,
guidance banner, progress ring) themes from the same variables — --scanner-accent
(lock-on quad / ring / searching), --scanner-amber (un-framed quad), --scanner-text (hint
text), --scanner-font — plus two canvas-only ones: --scanner-guide (neutral framing guide
- ring track, default white) and
--scanner-overlay-scrim(guidance-banner bg + dim mask, default black). Set any of them and the on-camera overlay follows; unset, it keeps the default look. (The canvas reads these once when a scan starts.)
Hosting notes
- Camera needs a secure context (HTTPS or
localhost) in every host. - Blazor Server / Web App — fully supported, including prerendering. The
camera and WASM run in the browser; only tiny status strings cross SignalR, and
results are
blob:URLs (not megabyte data URLs), so they stay well under SignalR's message cap. To get the actual bytes server-side, useOpenResultStreamAsync(chunked viaIJSStreamReference, not subject to the per-message cap). Use any render mode (InteractiveServer,InteractiveWebAssembly, orInteractiveAuto). - .NET MAUI Blazor Hybrid — grant the platform camera permission yourself
(Android
CAMERAin the manifest + handle the WebViewOnPermissionRequest; iOSNSCameraUsageDescription). - One scanner instance is active at a time.
Notes
Classical computer vision, not a model — it shines on documents with a visible edge against a contrasting surface and won't find a near-invisible white-on-white page. Portrait phone cameras stream landscape, so you'll see letterbox bars (the whole document stays visible rather than being cropped).
Showing the top 20 packages that depend on Bonoscan.Components.
| Packages | Downloads |
|---|---|
|
Bonoscan.Components.Server
Blazor Server scan engine for Bonoscan.Components: an off-circuit HTTP
detect endpoint (DocAligner ONNX, model embedded in this package), a thin JS live loop
that keeps per-frame detection/tracking off the SignalR circuit, and a server-side C#
perspective warp at capture. Provides the IScannerEngine implementation the
ScannerView / DocumentUpload components consume on Blazor Server.
|
149 |
|
Praxxme.Components.Reactive
Reactive Praxxme Blazor components built on Fluxor for state-driven UIs (loading overlays, reactive list pages).
|
1 |
|
Praxxme.Components.Reactive
Reactive Praxxme Blazor components built on Fluxor for state-driven UIs (loading overlays, reactive list pages).
|
0 |
.NET 10.0
- Microsoft.AspNetCore.Components.Web (>= 10.0.0)
| Version | Downloads | Last updated |
|---|---|---|
| 0.31.0 | 197 | 06/04/2026 |
| 0.27.0 | 3 | 06/02/2026 |
| 0.26.0 | 3 | 06/02/2026 |
| 0.25.0 | 2 | 06/02/2026 |
| 0.24.0 | 2 | 06/02/2026 |
| 0.23.0 | 2 | 06/02/2026 |
| 0.22.0 | 4 | 05/31/2026 |
| 0.21.0 | 2 | 05/31/2026 |
| 0.20.0 | 3 | 05/31/2026 |
| 0.19.0 | 4 | 05/31/2026 |
| 0.18.0 | 2 | 05/31/2026 |
| 0.17.0 | 3 | 05/31/2026 |
| 0.16.0 | 2 | 05/31/2026 |
| 0.15.0 | 1 | 05/30/2026 |
| 0.14.0 | 2 | 05/30/2026 |
| 0.13.0 | 3 | 05/30/2026 |
| 0.12.0 | 5 | 05/30/2026 |
| 0.11.0 | 4 | 05/30/2026 |
| 0.10.0 | 2 | 05/29/2026 |
| 0.9.0 | 2 | 05/29/2026 |
| 0.8.0 | 1 | 05/29/2026 |
| 0.7.0 | 0 | 05/29/2026 |
| 0.6.0 | 2 | 05/29/2026 |
| 0.5.0 | 1 | 05/29/2026 |
| 0.4.0 | 2 | 05/29/2026 |
| 0.3.0 | 2 | 05/29/2026 |
| 0.2.0 | 2 | 05/29/2026 |
| 0.1.0 | 2 | 05/29/2026 |