Blazor Coding Standards
The full reference for building UIs in Blazor: components, parameters, state, lifecycle, render modes, security, forms, JS interop, and performance. Blazor shares ideas with React but has its own model, and an important security difference between Server and WebAssembly. Builds on .NET / C# Coding Standards and the same web-security rules used everywhere; pairs with the HTML, CSS, and React standards.
We use Blazor (for example the intranet and internal tooling) with the modern component model and render modes, in C# and Razor with no TypeScript. Many ideas match React Coding Standards: small composable components, one-way data flow, immutability. But Blazor's lifecycle, parameter rules, render modes, and the Server-versus-WASM trust boundary each need care.
The most important security point: in Blazor WebAssembly, all C# runs in the browser and the user can see and edit it. So it is never a trust boundary. Server-side authorization and validation are required whatever the render mode (see Web & Frontend Security, Authentication & Authorization).
Components & structure
- DoBuild small, focused components. Use code-behind (
.razor.cs) for non-trivial logic and scoped CSS (.razor.css) for styles. - DoKeep components presentational. Put business logic and data access in injected services, not in
@codeblocks or markup (see Separation of Concerns, Inversion of Control). - DoFollow .NET naming and the C# standards in
@code: PascalCase components, one main component per file (see .NET / C# Coding Standards). - DoCompose with child components and
RenderFragmentor templated components instead of huge components or duplicated markup (see Frontend Architecture). - AvoidHuge components that mix markup, state, business rules, and data access.
Parameters & data flow
- DoPass data in with
[Parameter]and send changes up withEventCallback. This is one-way data flow, like React. - DoTreat parameters as read-only inputs owned by the parent. Do not mutate them; raise an
EventCallbackto request a change. - DoUse
[EditorRequired]for required parameters. Use cascading parameters sparingly, only for genuinely cross-cutting values. - DoGive items in a loop a stable
@keyso the diff renders correctly.
State, rendering & lifecycle
- DoDo one-time setup in
OnInitialized{Async}and parameter-dependent work inOnParametersSet{Async}. Avoid heavy work inOnAfterRenderloops. - DoCall
StateHasChanged()only when needed (for example after an async update triggered outside a normal UI event), and run updates on the right context. - DoKeep components async-friendly: await async work and never block the UI thread (see .NET / C# Coding Standards).
- DoImplement
IDisposableorIAsyncDisposableto clean up timers, subscriptions, and event handlers (see Performance & Resource Use). - AvoidRender loops caused by changing state during render or after-render without a guard.
Render modes & hosting
- DoChoose the render mode on purpose (Server, WebAssembly, static/streaming, auto) based on the component's needs, and understand the trade-offs: latency, offline use, scale, and what is exposed.
- DoFor Blazor Server, remember each circuit holds state and a live connection. Limit per-circuit memory, handle concurrency, and plan for reconnection (see Concurrency).
- DoFor WebAssembly, keep the download small (trimming, lazy-loading assemblies) and remember the start-up cost (see Frontend Performance).
Security
- AlwaysEnforce all authorization and validation on the server. In WebAssembly the C# runs in the browser and the user can see and edit it, so it is never a trust boundary (see Web & Frontend Security).
- DoValidate input on the server even when you use built-in form validation. Authorize data access in injected server-side services (see Trust Boundaries, Authentication & Authorization).
- DoUse
<AuthorizeView>or[Authorize]to improve the UI, but never as the only gate. The server endpoint or service is the real one. - NeverSend secrets, connection strings, or other users' data into a WebAssembly component. Anything in the WASM app is downloaded and readable by the user (see Secrets at Rest & in Transit).
// Blazor WebAssembly component
[Parameter] public Order Order { get; set; }
void Approve() {
if (currentUser.IsAdmin) { Order.Status = "Approved"; api.Save(Order); }
}
The IsAdmin check runs in the browser, where the user can bypass it. The API seems to trust the call, and the component mutates a parameter it does not own. Authorization must be on the server, and parameters are read-only.
void Approve() => OnApprove.InvokeAsync(Order.Id); // ask parent or service
// server service: [Authorize(Roles="Admin")] ApproveAsync(id) — the real gate,
// re-checks tenant and state, audits the decision
The component just requests the action. The server-side service is where authorization, validation, and audit actually happen. This is safe whatever the render mode.
Forms, rendering & JS interop
- DoUse
EditFormwith a model and validation, show validation messages clearly, and keep forms accessible (labels, keyboard) (see Accessibility, HTML & Markup Standards). - DoRender untrusted content as text; Razor encodes by default. Use
MarkupStringonly on content you have sanitised, because it skips encoding. - DoUse JS interop (
IJSRuntime) sparingly. Never pass unsanitised data into JS that writes the DOM, and dispose JS object references. - DoVirtualise long lists (
<Virtualize>) and do not render large trees when you do not need to (see Frontend Performance). - NeverPass untrusted or user content to
MarkupStringor into DOM-writing JS interop without sanitising it. That is an XSS hole (see Web & Frontend Security).
Errors & testing
- DoUse error boundaries for component-level failures and handle async errors, so one component failing does not blank the whole page (see Error Handling).
- DoTest components (for example with bUnit) for behaviour and rendering, and test the injected services as normal .NET code (see Test Code Standards).
Self-review checklist
- AskIs authorization and validation enforced on the server, whatever the render mode?
- AskFor WASM, is any secret or any other user's data sent to the browser?
- AskAre parameters read-only, are lifecycle and state used correctly (no render loops), and are resources disposed?
- AskIs untrusted content rendered as text, with MarkupString and JS interop used only on sanitised data?