Blazor
Blazor is a free and open-source web framework developed by Microsoft as part of ASP.NET Core, enabling developers to build interactive web user interfaces (UIs) using C# and .NET instead of JavaScript, with support for reusable components, data binding, and integration with HTML and CSS.[1] It originated as an experimental project in 2018, achieved official preview status in April 2019, and saw its server-side hosting model (Blazor Server) released with .NET Core 3.0 on September 23, 2019, while the client-side WebAssembly model (Blazor WebAssembly) became generally available in May 2020.[2][3] Blazor supports multiple hosting models to suit different application needs: Blazor Server uses SignalR for real-time UI updates from the server, providing low latency for intranet apps but requiring a persistent connection; Blazor WebAssembly runs .NET code directly in the browser via WebAssembly, allowing offline capabilities and reduced server load; and Blazor Hybrid integrates web technologies into native desktop or mobile apps using frameworks like .NET MAUI or WPF.[4] With the release of .NET 8 in 2023, Blazor evolved into a unified full-stack framework called Blazor Web Apps, combining server-side rendering for initial loads with optional client-side interactivity, enhancing performance and SEO.[5] Key features include component-based architecture for modular UI development, built-in support for forms, validation, and state management, as well as seamless JavaScript interop for leveraging existing libraries.[6] The framework runs on supported platforms including Windows, Linux, and macOS, and extends to web, mobile, and desktop via integrations like .NET MAUI, making it versatile for full-stack .NET development. Blazor's ecosystem includes official project templates in Visual Studio and VS Code, along with third-party components from vendors like Telerik and DevExpress, and open-source libraries for rapid prototyping.[1] As of .NET 10 in 2025, ongoing enhancements focus on improved reconnection handling, JavaScript interoperability, and state persistence, positioning Blazor as Microsoft's preferred UI framework for web applications.[7]History
Origins and Development
Blazor originated as a personal project by Steve Sanderson, a Microsoft engineer, who first demonstrated a prototype at the NDC Oslo conference in June 2017. The project aimed to enable developers to build interactive web user interfaces using C# and the .NET ecosystem directly in the browser, bypassing the need for JavaScript for client-side logic. Sanderson's initial vision was to leverage WebAssembly, an emerging web standard, to run .NET code client-side, allowing full-stack development with a single language while reusing existing .NET libraries and tools. In February 2018, the ASP.NET team at Microsoft formally adopted Blazor as an experimental open-source project under their organization, marking its transition from a solo effort to a team-backed initiative. Key contributors included Sanderson, along with ASP.NET team members such as Daniel Roth and David Fowler, who expanded the framework's scope to include both client-side and server-side rendering models. The primary goal was to empower .NET developers to create single-page applications (SPAs) without plugins or JavaScript dependencies, integrating seamlessly with the broader .NET ecosystem for shared code across client and server. At Microsoft Connect(); 2018 in December, Blazor was highlighted as a core component of the .NET Core 3 Preview 1 announcement, signaling Microsoft's commitment to its maturation.[8][9] Blazor's early development faced significant hurdles due to the nascent state of WebAssembly, which was still maturing in 2018 with limited browser support initially confined to Chrome and Firefox, requiring polyfills or fallbacks for broader compatibility. Bundle size was another concern, as the initial .NET runtime for WebAssembly resulted in larger downloads compared to traditional JavaScript frameworks, impacting initial load times on slower connections. Despite these challenges, the project progressed rapidly, culminating in its elevation from experimental status to an official .NET component in April 2019 with the release of .NET Core 3.0 previews, introducing Blazor Server for real-time server-side rendering and early WebAssembly support for client-side execution.[2][10]Key Releases and Milestones
Blazor's development has progressed through annual .NET releases, each introducing significant enhancements to its hosting models, performance, and developer experience. The framework achieved a major milestone with the release of .NET 5 in November 2020, which delivered stable support for Blazor WebAssembly, allowing production deployment of client-side applications running .NET code in the browser via WebAssembly. This version also introduced preview support for ahead-of-time (AOT) compilation in experimental builds, alongside improved hosting capabilities such as enhanced trimming for smaller app sizes and better integration with ASP.NET Core hosted scenarios.[11] In November 2021, .NET 6 solidified Blazor WebAssembly as fully production-ready with substantial performance optimizations, including up to 2-3x faster startup times compared to prior previews, and introduced .NET Hot Reload for Blazor projects. Hot Reload enabled developers to modify C# code, Razor markup, and CSS in running applications without restarting the server or browser, streamlining iteration in both Blazor Server and WebAssembly modes. These updates were complemented by AOT compilation previews now more accessible for testing, further reducing runtime overhead.[12][13] .NET 7, released in November 2022, focused on refining Blazor's rendering capabilities with enhanced streaming rendering support in Blazor Server, enabling partial UI updates over SignalR connections for more responsive applications, and introduced global interactivity options to simplify component event handling across scopes. Additional improvements included better Hot Reload support for adding new types and methods in Blazor WebAssembly, along with new data binding modifiers like@bind:after for post-binding logic. These changes prioritized smoother development workflows and reduced latency in interactive scenarios.[14]
The .NET 8 release in November 2023 marked a unification effort with the new Blazor Web App template, supporting multiple render modes—static server-side rendering (SSR), interactive server-side rendering, and interactive WebAssembly—within a single project for flexible full-stack development. Streaming rendering saw major advancements, allowing components to render progressively as data loads, improving perceived performance for data-heavy UIs. This version also enhanced form handling with built-in model binding and validation directly in components, reducing reliance on custom JavaScript.[15]
With .NET 9 in November 2024, Blazor received refinements to the QuickGrid component, including new parameters like OverscanCount for optimized virtualized rendering of large datasets, and improved form handling with support for range inputs in InputNumber<TValue>, enabling native slider controls with full validation integration. These updates emphasized efficiency in data display and user input scenarios without external libraries.[16]
The most recent .NET 10, released in November 2025, introduced inline boot configuration by embedding blazor.boot.[json](/page/JSON) directly into dotnet.[js](/page/JS++) for simpler deployment, and state persistence via the [PersistentState] attribute in PersistentComponentState for declarative handling of user data across sessions. Enhanced JavaScript interop added methods like InvokeConstructorAsync for dynamic object creation from .NET, while circuit state persistence maintained Blazor Server sessions during temporary disconnects, such as network issues. Integration of passkey support via WebAuthn/FIDO2 was added to authentication templates, bolstering security. A key milestone was the Blazor United initiative, emphasizing unified full-stack .NET development across all render modes to streamline hybrid server-client architectures.[17]
Fundamentals
Components and Razor Syntax
Blazor components serve as the fundamental building blocks of Blazor applications, encapsulating reusable portions of the user interface along with their associated processing logic. These components are defined using Razor syntax, which seamlessly integrates HTML markup, C# code, and CSS styles within a single file, enabling developers to create self-contained, nestable UI elements that can be composed to form complex applications.[18] The structure of a Blazor component is typically housed in a.razor file, where it inherits from the ComponentBase class to gain access to core functionality such as rendering and lifecycle management. Key elements include the @page directive, which defines routing for the component by associating it with a URL path, such as @page "/counter" to make it accessible via navigation. The component's UI is primarily composed of HTML markup, which can include Razor expressions like @ to embed dynamic C# values or logic directly into the markup. C# code is placed within an @code block (or multi-line @{ ... } for more complex logic), housing fields, properties, methods, and event handlers that manage the component's state and behavior.[18][18][18]
Component parameters facilitate data passing from parent to child components, declared as public properties annotated with the [Parameter] attribute, allowing customization without tight coupling. For two-way data binding, the @bind directive attribute synchronizes values between the component's properties and UI elements, such as form inputs, ensuring updates propagate bidirectionally.[18][18]
Blazor components follow a defined lifecycle to handle initialization, updates, and rendering. The OnInitializedAsync method executes once after the initial parameters are set, ideal for setup tasks like loading data that do not depend on parameter changes. OnParametersSetAsync runs after parameters are updated, including on the first render and subsequent parent rerenders, making it suitable for reacting to input changes. OnAfterRenderAsync invokes after the component renders to the UI, providing a firstRender boolean to distinguish initial renders, and is commonly used for post-render operations like focusing elements. These asynchronous methods allow for non-blocking operations, with synchronous counterparts available for simpler scenarios.[19][19][19]
A representative example is the Counter component, which demonstrates basic structure, state management, and event handling:
This component uses therazor@page "/counter" <h1>Counter</h1> <p role="status">Current count: @currentCount</p> <button class="btn btn-primary" @onclick="IncrementCount">Click me</button> @code { private int currentCount = 0; private void IncrementCount() { currentCount++; } }@page "/counter" <h1>Counter</h1> <p role="status">Current count: @currentCount</p> <button class="btn btn-primary" @onclick="IncrementCount">Click me</button> @code { private int currentCount = 0; private void IncrementCount() { currentCount++; } }
@page directive for routing, HTML for display, an @onclick event bound to a method in the @code block, and Razor syntax to interpolate the currentCount value.[18]
During rendering, Blazor compiles the component into an in-memory representation known as a render tree, which combines the DOM structure and CSS information; the framework then efficiently updates the actual DOM by diffing the new render tree against the previous one, minimizing unnecessary manipulations. Various hosting models execute these components to produce the final output in the browser.[18][20]
To manage dependencies and inheritance, Blazor provides Razor directives such as @using to import namespaces (often globally in _Imports.razor), @inject for dependency injection of services into fields or constructor parameters, and @inherits to specify a custom base class beyond ComponentBase. These directives enhance modularity and reusability without cluttering the component file.[18][18][18]
Data Binding and Event Handling
Blazor supports one-way data binding to display data from component properties or fields directly in the rendered markup using Razor expressions prefixed with@. For instance, in a component, <p>Current count: @currentCount</p> renders the value of the currentCount property and updates automatically when the property changes during a component re-render.[21]
Two-way data binding enables synchronization between UI elements and component state, allowing user input to update properties and vice versa. This is achieved with the @bind directive on supported HTML elements like inputs, where <input @bind="name" /> binds the input's value attribute to the name property and the onchange event to update it. Developers can customize the binding by specifying the value attribute with @bind:get and the event with @bind:event, such as @bind-value="name" @bind:event="oninput" for immediate updates on keystrokes, or use @bind:set to control the setter invocation.[21]
Event handling in Blazor uses Razor directives like @onclick to attach C# methods to DOM events, triggering component re-renders upon completion. For example, <button @onclick="Increment">Click me</button> calls the Increment method, defined as private void Increment() => currentCount++;, which can pass event arguments like MouseEventArgs for details such as coordinates: @onclick:preventDefault="true" @onclick="HandleClick". Events support asynchronous operations via methods returning Task, ensuring UI responsiveness during delays, as in private async Task IncrementAsync() { await Task.Delay(500); currentCount++; }. The EventCallback<T> type facilitates async invocation and parameter passing, preventing unnecessary re-renders if the parent component does not update.[22]
Custom events allow child components to notify parents of actions, promoting reusable parent-child communication without direct references. A child component exposes an EventCallback<T> property, such as [Parameter] public EventCallback<int> OnIncrement { get; set; }, and invokes it with await OnIncrement.InvokeAsync(currentCount); in response to internal events. The parent then binds to this callback, e.g., <ChildComponent OnIncrement="ParentIncrement" />, where ParentIncrement handles the passed value. This pattern supports generic types for flexible data passing, like EventCallback<MouseEventArgs>.[22]
Form handling in Blazor leverages the EditForm component for structured data entry and validation, binding to a model object via the Model parameter: <EditForm Model="@starship">. It integrates with EditContext for custom validation logic and supports input components like InputText that inherit from InputBase<TValue> for type-safe binding, e.g., <InputText @bind-Value="starship.Identifier" />. Validation uses DataAnnotations attributes on the model, such as [Required] or [StringLength(3)], automatically checked on form submission or field changes; custom validators can extend ValidationMessage or implement IValidator. Errors are displayed with <ValidationSummary /> or <ValidationMessage For="@(() => starship.Identifier)" />, providing user feedback without manual state management.[23]
In .NET 10, Blazor's form validation was enhanced to support nested validation for complex objects and collections using source generators for efficiency and AOT compatibility. Developers apply the [ValidatableType] attribute to the root model class, e.g., [ValidatableType] public class Order { public Customer Customer { get; set; } public List<OrderItem> Items { get; set; } }, enabling recursive validation of nested properties like Customer.FullName with DataAnnotations. This requires calling builder.Services.AddValidation(); in Program.cs and defining models in separate C# files to support source generation, improving performance over reflection-based approaches in prior versions.[24]
Hosting Models
Blazor Web App with Render Modes
The Blazor Web App template, introduced in .NET 8, provides a unified project structure that supports multiple render modes within a single application, enabling developers to mix static server-side rendering (SSR) with interactive components rendered on the server or client side. This model allows for flexible hosting, where components can be prerendered on the server for fast initial loads and SEO benefits, then transition to interactive modes as needed, without requiring separate projects for different hosting scenarios.[25] Static server-side rendering (SSR) in Blazor Web Apps pre-renders HTML on the server without any client-side interactivity, producing lightweight, non-interactive pages that load quickly and are crawlable by search engines. Components or pages using this mode do not require the@rendermode directive or explicitly set it to @rendermode:Static, resulting in pure HTML output served directly to the browser. This approach is ideal for content-heavy pages where user interaction is minimal or handled via traditional forms.[26]
Interactive Server rendering enables real-time UI updates by executing components on the server and streaming changes to the client via a SignalR WebSocket connection, supporting full interactivity with low initial payload. Developers specify this mode using @rendermode InteractiveServer on components or pages, which establishes a persistent circuit for event handling and state management. Prerendering is enabled by default to show initial content while the connection initializes, and enhanced navigation preserves state during client-side routing.[26]
Interactive WebAssembly rendering shifts execution to the client-side .NET runtime in the browser, downloading the necessary assemblies for independent operation after an optional server prerender. This mode is set with @rendermode InteractiveWebAssembly and benefits from ahead-of-time (AOT) compilation in .NET 8 and later, which improves startup time and runtime performance by compiling C# to WebAssembly ahead of deployment. It suits applications requiring offline capabilities or reduced server load.[26]
Interactive Auto mode combines server and WebAssembly rendering by starting with Interactive Server for immediate interactivity, then seamlessly switching to WebAssembly once the client runtime loads, optimizing for both fast startup and long-term efficiency. Configured via @rendermode InteractiveAuto, it can be applied globally in the application's Program.cs or per-component, with the transition handled automatically to maintain user experience. Location-based variants allow specifying render modes for specific routes.[26]
Prerendering serves as an optional initial server-side render for interactive modes like WebAssembly and Auto, generating static HTML that hydrates into an interactive component on the client, thereby enhancing perceived load times and search engine optimization. Developers can disable it for specific components using custom render mode instances, such as new InteractiveWebAssemblyRenderMode(prerender: false).[26]
In .NET 10, Blazor Web Apps gain the ReconnectModal component in the default project template, which provides a customizable UI for handling SignalR disconnects in Interactive Server mode, including states like "retrying" and events for tracking reconnection progress. Additionally, enhanced prerendered state persistence uses the [PersistentState] attribute to declaratively retain component and service state across mode transitions and navigations, preventing UI flicker and data loss without manual intervention.[27][28]
Blazor WebAssembly Standalone
Blazor WebAssembly Standalone is a hosting model that enables the execution of .NET applications entirely within the browser, leveraging WebAssembly to run C# code without any server-side processing after the initial download.[20] In this model, the .NET runtime is downloaded to the client browser, where it interprets and executes the application's assemblies, allowing for full client-side interactivity similar to traditional single-page applications (SPAs) built with JavaScript frameworks.[20] This approach differs from server-involved models by eliminating ongoing server communication for UI updates, focusing instead on static asset delivery and local computation.[25] The execution process begins with the browser downloading the .NET runtime and the application's assemblies via WebAssembly, typically resulting in a bundle size of approximately 2-5 MB depending on optimizations like the IL Trimmer, which removes unused code.[29] Once loaded, the runtime handles component rendering, event processing, and state management directly in the browser's JavaScript sandbox, using JavaScript interop to manipulate the DOM and access browser features.[20] App bundles include the runtime, framework assemblies, and custom code, all compressed for efficient transfer and cached by the browser to support subsequent visits.[29] Deployment for Blazor WebAssembly Standalone involves publishing the application as static files, which can be hosted on any web server, content delivery network (CDN), or static hosting service like Azure Static Web Apps, without requiring a dedicated server for runtime execution post-download.[30] After the initial asset download, the application operates independently, making it suitable for scenarios where low server overhead is prioritized.[30] Key features include support for offline functionality through service workers, which cache essential assets like the runtime and assemblies, enabling the app to load and function without an internet connection after the first visit.[31] Additionally, Blazor WebAssembly Standalone apps can be configured as Progressive Web Apps (PWAs) by adding a web app manifest, allowing installation on devices and providing app-like experiences with offline capabilities.[31] In .NET 10, several enhancements improve the standalone model's efficiency, including client-side fingerprinting for JavaScript modules enables better caching and faster reloads by generating unique hashes for assets, while the new ResourcePreloader component facilitates proactive loading of critical resources to minimize perceived load times.[17] Hot Reload is now enabled by default in Debug mode, allowing developers to iterate quickly without full recompilation.[32] Despite these advancements, limitations persist, such as the initial load time required to download and initialize the runtime, which can impact user experience on slower networks or devices.[33] Direct access to certain browser APIs is restricted, necessitating JavaScript interop for operations like file handling or geolocation, which introduces minor performance costs compared to native JavaScript access.[34] The boot process is initiated by thedotnet.js script, which loads the WebAssembly runtime and configures the application environment before executing the entry point.[35] In .NET 10, boot configuration via boot.json is inlined directly into the HTML for streamlined startup, reducing the need for separate file fetches.[17]
Blazor Hybrid
Blazor Hybrid enables the development of interactive web user interfaces using .NET and Razor components within native client applications, leveraging an embedded Web View for rendering without relying on WebAssembly. This approach combines web technologies with native frameworks, allowing developers to build cross-platform applications that run directly on devices.[36] Supported platforms include desktop environments such as Windows via WPF and Windows Forms, as well as cross-platform desktop and mobile through .NET MAUI, which targets Windows, macOS, iOS, and Android. These applications gain full access to native device APIs, such as sensors, file systems, and hardware features, through standard .NET interfaces.[36][37] Setup involves integrating theBlazorWebView or MauiBlazorWebView control into the native app's user interface, which hosts the embedded browser and loads Razor components from an assembly or local files. Developers configure the Web View using events like BlazorWebViewInitializing for setup and BlazorWebViewInitialized for post-initialization tasks, enabling seamless rendering of components within the native shell.[36][38]
Key advantages include significant code sharing between web and native projects, as Razor components and .NET libraries can be reused across platforms without platform-specific UI code. This reduces development effort while maintaining high performance through native execution and direct hardware integration.[25][39]
In .NET 10, Blazor Hybrid benefits from improved state persistence across the application lifecycle using the declarative [PersistentState] attribute, which simplifies managing component and service state during scenarios like prerendering or app suspension. Additionally, OwningComponentBase now implements IAsyncDisposable, providing DisposeAsync and DisposeAsyncCore methods for asynchronous resource cleanup, enhancing reliability in long-running native apps.[17][40]
Common use cases encompass extending Progressive Web Apps (PWAs) into native distributions for better device integration or constructing full native applications where C# handles UI logic alongside native features, such as in enterprise mobile tools or desktop productivity software.[36][31]
Advanced Features
JavaScript Interoperability
Blazor provides JavaScript interoperability (JS interop) to enable seamless integration between C# code and JavaScript, allowing applications to leverage browser APIs and existing JavaScript libraries while maintaining a primarily .NET-based development model. This bidirectional communication is essential for tasks such as DOM manipulation, accessing device features, or incorporating third-party scripts that are not yet natively supported in Blazor. The interop mechanism relies on theMicrosoft.JSInterop namespace and is available in both Blazor WebAssembly and Blazor Server hosting models, though with differences in latency and threading considerations.[34]
To invoke JavaScript functions from .NET code, developers inject the IJSRuntime interface, which provides asynchronous methods for calling JS. The primary method is InvokeAsync<T>, where T specifies the expected return type, enabling the execution of global or scoped JS functions with JSON-serializable arguments. For instance, to display a browser alert, code might use await JSRuntime.InvokeAsync<string>("alert", "Hello from Blazor!");, which passes the string argument to the native alert function and awaits its completion without a return value. A void-returning variant, InvokeVoidAsync, is used when no result is needed, such as triggering a non-returning JS operation. These calls are asynchronous to accommodate the non-blocking nature of web applications and must reference JS functions relative to the global scope or via imported modules.[41][42]
Conversely, JavaScript can invoke .NET methods marked with the [JSInvokable] attribute, facilitating callbacks from browser events or scripts to Blazor components. These methods must be public and can be static or instance-based; static methods are called directly via DotNet.invokeMethodAsync('AssemblyName', 'MethodName'), while instance methods require creating a DotNetObjectReference to maintain a strong reference and prevent garbage collection. For example, a C# method [JSInvokable] public static async Task<string> GetDataAsync(string input) could be invoked from JS as DotNet.invokeMethodAsync('MyApp', 'GetDataAsync', 'inputValue').then(result => console.log(result));, returning the processed string asynchronously. Proper disposal of DotNetObjectReference instances is crucial to avoid memory leaks in long-running applications.[43][44]
In .NET 10, Blazor's JS interop was enhanced with new methods on IJSRuntime to simplify object manipulation without repeated function invocations. InvokeConstructorAsync allows creating instances of JS objects by calling their constructors, returning an IJSObjectReference for further interaction, such as await JSRuntime.InvokeConstructorAsync("[Date](/page/Date)", DateTime.UtcNow);. Property access is streamlined via GetValueAsync<T> to retrieve values, e.g., await JSRuntime.GetValueAsync<string>("someObject.[property](/page/Property)");, and SetValueAsync to update them, like await JSRuntime.SetValueAsync("someObject.[property](/page/Property)", "newValue");. Synchronous variants are available through IJSInProcessRuntime for in-process scenarios, and improved handling of byte arrays reduces overhead by avoiding unnecessary Base64 encoding in high-performance transfers. These additions promote more efficient, object-oriented interop patterns.[17][45]
Data marshaling between .NET and JavaScript relies on automatic JSON serialization using System.Text.Json, which handles primitives, arrays, and objects with public properties and default constructors. Complex types are serialized to JSON strings for transmission, with member names camel-cased by default; deserialization supports flexible casing via JsonElement. For custom types or performance-critical scenarios, developers can implement JsonConverter classes with the [JsonConverter] attribute to override serialization logic, such as for non-standard formats or to optimize large payloads. Byte arrays benefit from direct interop to bypass JSON overhead, enhancing efficiency for binary data.[34][46]
Best practices emphasize minimizing interop calls to optimize performance, as each invocation incurs serialization and context-switching overhead. Developers should batch operations where possible, use C# wrappers around common JS APIs like localStorage to abstract repetitive calls, and prefer JS modules for isolated, reusable scripts. For example, a wrapper class might encapsulate storage operations: public async Task SetLocalStorage(string key, string value) => await JSRuntime.InvokeVoidAsync("localStorage.setItem", key, value);. Avoiding synchronous interop in WebAssembly contexts prevents UI blocking, and profiling tools should be used to identify high-frequency calls for refactoring.[47]
Limitations include the single-threaded execution model in Blazor WebAssembly, where JS interop must be asynchronous to avoid halting the UI thread during browser API access. In server-side Blazor, interop calls traverse the SignalR connection, introducing network latency and size limits on payloads (typically 4 KB for reliable transmission). All interop requires proper error handling for JS exceptions via JSException, and prerendering scenarios restrict calls until the interactive circuit is established. These constraints necessitate careful design to balance .NET purity with necessary JS integration.[34][47]
State Management and Persistence
Blazor provides several built-in mechanisms for managing application state across its hosting models, enabling developers to share data between components and services while handling lifecycle events such as rendering and reconnection. These techniques ensure that user interactions remain consistent without relying on external frameworks for basic scenarios.[48] One fundamental approach is the use of theCascadingValue component, which allows data to flow down the component hierarchy from an ancestor to multiple descendant components without explicit parameter passing. This is particularly useful for providing shared context, such as theme settings or authentication state, to deeply nested UI elements. For instance, a root-level CascadingValue can supply a service instance or value that child components access via the [CascadingParameter] attribute.[49]
Dependency injection (DI) complements this by facilitating state management through services with defined lifetimes: Scoped services maintain state for the duration of a user session or circuit in Blazor Server, while Singleton services preserve state across the application's lifetime, suitable for global caches or configurations. Services are registered in the DI container during app startup, and components inject them to access or mutate shared state, promoting loose coupling and testability.[50]
For persistence, Blazor offers the IPersistentComponentState service, which enables hydration of component state after prerendering, preventing data loss when transitioning from static server-rendered HTML to interactive client-side execution. Developers inject this service into components and use methods like PersistAsJson to store serializable state, which is then restored on the client via a hidden form field or similar mechanism during the initial render. Browser-based storage, such as localStorage or sessionStorage, can be accessed via JavaScript interop for longer-term persistence in WebAssembly apps, where state would otherwise reside solely in memory and be lost on page refresh.[28]
Introduced in .NET 10, the [PersistentState] attribute provides a declarative way to mark fields or properties in components and services for automatic persistence using the PersistentComponentState service, simplifying state management without manual serialization calls. This attribute supports options like AllowUpdates for controlling restoration behavior during navigation and RestoreBehavior for customizing hydration logic. Developers can extend serialization through custom implementations of IPersistentComponentStateSerializer, allowing support for complex types beyond JSON. Additionally, .NET 10 enhances circuit state persistence in Blazor Server by retaining user session data during SignalR disconnects, browser tab throttling, app pauses, or network interruptions, using in-memory caching (default retention: 2 hours for up to 1,000 circuits) or distributed options like HybridCache (default: 8 hours). This ensures seamless reconnection without full state recreation, improving resilience in interactive server scenarios.[17][51]
For more complex applications, patterns inspired by Flux or Redux can be implemented using libraries like Fluxor, a zero-boilerplate state management solution that enforces unidirectional data flow through actions, reducers, and a centralized store. Fluxor integrates seamlessly with Blazor's component model, dispatching actions to update immutable state and notifying subscribers for re-renders, which is ideal for handling asynchronous operations and debugging large-scale apps. In Blazor Server specifically, server-side state is inherently managed within the SignalR circuit, where component instances and scoped services hold the hierarchy and render output until disconnection, at which point .NET 10's persistence features mitigate loss.[52]
In prerendering scenarios for Blazor WebAssembly, state computed on the server during initial render is transferred to the client to avoid redundant API calls or computations upon hydration. The IPersistentComponentState service facilitates this by serializing state on the server and deserializing it on the client, ensuring continuity in hybrid hosting models where static and interactive renders coexist.[28]
Performance and Security
Optimization Techniques
Blazor applications can be optimized across hosting models to reduce load times and enhance runtime efficiency, particularly in WebAssembly scenarios where initial download size and JIT compilation impact user experience. Key techniques focus on minimizing bundle sizes, improving rendering efficiency, and leveraging caching and compression. These strategies are applicable to Blazor WebAssembly standalone apps and hybrid models, while server-side optimizations emphasize circuit management.[33] Bundle trimming, also known as IL trimming, removes unused code from assemblies during publishing, significantly reducing the download size of Blazor WebAssembly apps. To enable it, set<PublishTrimmed>true</PublishTrimmed> in the project's .csproj file and publish with dotnet publish -c Release. This process analyzes code usage to eliminate unnecessary IL, though it requires careful testing to avoid trimming essential reflection-dependent code.[29][53]
Ahead-of-time (AOT) compilation transforms .NET IL code into native WebAssembly binaries before runtime, eliminating JIT overhead and improving execution speed for compute-intensive operations in Blazor WebAssembly apps. Enable AOT by adding <RunAOTCompilation>true</RunAOTCompilation> to the .csproj file, available since .NET 7, and publish in Release mode. While it boosts runtime performance, it doubles the initial app size due to native code generation, making it suitable for apps where startup time is less critical than ongoing interactivity.[54]
In .NET 10, new diagnostic counters provide detailed observability into Blazor app performance, tracking metrics for component lifecycle events (such as rendering and disposal), navigation flows, and event handling. These counters integrate with OpenTelemetry for exporting traces and metrics to tools like Application Insights, enabling proactive identification of bottlenecks in WebAssembly and server-side circuits. Developers can collect data using dotnet-counters or browser dev tools, with examples showing reduced navigation latency through targeted optimizations.[27]
Caching mechanisms in Blazor optimize static asset delivery by fingerprinting files with SHA-256 hashes in blazor.boot.json, allowing browsers to cache .NET runtime, assemblies, and app bundles while validating integrity on load. Preloading is facilitated via HTTP Link headers for critical resources like JavaScript and locale data, reducing subsequent fetches. The BlazorCacheBootResources property, which previously controlled boot resource caching, is removed in .NET 10 in favor of standard browser caching behaviors.[55]
Rendering optimizations prevent unnecessary UI updates, preserving efficiency in interactive apps. Overriding the ShouldRender method in ComponentBase allows components to skip re-rendering if state changes are irrelevant, such as ignoring parameter updates that don't affect output. For data-heavy tables, the QuickGrid component employs virtualization to render only visible rows, supporting large datasets (e.g., thousands of items) with scrolling, which can significantly improve performance compared to full rendering.[56][57]
WebAssembly-specific techniques further enhance Blazor's client-side performance. Brotli compression, applied statically during publish at the highest level, reduces file sizes more effectively than Gzip for .NET assemblies—while servers automatically serve compressed variants based on client headers. Lazy loading of assemblies defers downloading non-essential modules until route activation, cutting initial payload by loading only core runtime and boot resources first, configurable via assembly mapping in blazor.boot.json.[30][58]
Security Considerations
Blazor applications leverage ASP.NET Core's authentication mechanisms, primarily integrating with ASP.NET Core Identity to manage user accounts, including registration, login, and profile management.[59] This integration is particularly seamless in Blazor Web Apps, where scaffolding provides UI components for identity operations, and authentication state is propagated via theAuthenticationStateProvider.[59] For external providers, Blazor supports OpenID Connect (OIDC) and Microsoft Entra ID, with sample implementations demonstrating secure token handling and user consent flows in the Blazor Web App template.[60][61]
Authorization in Blazor relies on components like CascadingAuthenticationState, which cascades the current authentication state to child components, and the AuthorizeView component, which conditionally renders content based on user roles or policies.[59] The [Authorize] attribute can be applied to Razor components or pages to enforce access control, ensuring that sensitive operations are restricted server-side where possible, as client-side checks in WebAssembly are considered untrusted.[59] In .NET 10, these features are enhanced with updated security samples in Blazor Web Apps, including role-based access examples.[27]
Recent updates in .NET 10 introduce passkey support for passwordless authentication in Blazor Web Apps, utilizing the Web Authentication API (WebAuthn) integrated with ASP.NET Core Identity.[62] Developers configure this by updating the Identity schema to version 3 and adding endpoints for passkey creation and assertion, with UI components like PasskeySubmit handling browser prompts for registration and sign-in.[62] Additionally, .NET 10 improves form validation through unified APIs in the Microsoft.Extensions.Validation package, enabling stronger prevention of injection attacks via attributes like [Required] and custom validators on Blazor forms.[63] Security samples now include JWT handling in minimal APIs, demonstrating token validation and issuance for API endpoints consumed by Blazor components.[27]
Common threats in Blazor include SignalR connection hijacking in interactive server-side rendering modes, where unauthorized access to circuits could lead to data leakage or manipulation.[64] Mitigation involves enforcing authentication handshakes during connection establishment and avoiding the reflection of user-controlled content in compressed responses to prevent side-channel attacks like CRIME or BREACH.[64] Cross-site scripting (XSS) risks arise from JavaScript interop, where unsanitized inputs could execute malicious code; developers should validate all interop parameters, prefer AddContent over AddMarkupContent for rendering, and implement Content Security Policies (CSP) to restrict script sources.[64] In static server-side rendering, prerendered HTML may expose sensitive data or app structure, necessitating careful authorization checks and avoidance of embedding confidential information.[65] For Blazor Hybrid apps, threats from untrusted Web View content require sandboxing iframes and prohibiting unsafe JavaScript practices like eval.[66]
Best practices for Blazor security emphasize validating all inputs using DataAnnotations attributes in models to guard against injection and other exploits.[65] Applications must enforce HTTPS to protect data in transit, and developers should avoid storing or exposing sensitive data—such as credentials or tokens—in client-accessible locations like prerendered HTML or local storage.[65] Cross-site request forgery (CSRF) is addressed through built-in antiforgery tokens, automatically included in EditForm components via the AntiforgeryToken element, which generates hidden fields validated on submission.[59][67] For state persistence across sessions, secure storage mechanisms should be used to prevent unauthorized access to cached authentication data.[59]
Ecosystem and Tools
Development Tools
Blazor development relies on a suite of official Microsoft tools integrated into the .NET ecosystem to streamline the creation, debugging, and testing of applications. Visual Studio serves as the primary integrated development environment (IDE), offering comprehensive support through Blazor-specific project templates that scaffold complete applications, including components, routing, and layout structures.[68] It provides full IntelliSense functionality for Razor syntax, enabling autocompletion, error highlighting, and navigation within .razor files that blend HTML, CSS, and C# code.[20] For developers preferring a lightweight editor, Visual Studio Code (VS Code) supports Blazor via the official C# extension, which delivers similar IntelliSense, syntax highlighting, and task running capabilities, though with less integrated project management than Visual Studio.[68] Command-line interface (CLI) tools from the .NET SDK further enhance workflow efficiency by automating project setup and iterative development. Thedotnet new blazor command generates a new Blazor Web App project, configurable for interactive render modes, including options for WebAssembly or server-side hosting.[68] For rapid iteration, dotnet watch enables hot reload, automatically rebuilding and refreshing the application upon file changes without losing state, supporting modifications to C#, Razor, CSS, and HTML.[68] In .NET 9 and later, WebAssembly-specific hot reload is enabled by default in Debug configuration via the WasmEnableHotReload MSBuild property set to true, allowing seamless updates during browser-based development.[69] Additionally, .NET 10 introduces WasmBundlerFriendlyBootConfig, an MSBuild property that, when set to true during publish, produces JavaScript bundler-compatible output for Blazor WebAssembly apps, facilitating integration with modern build pipelines like Webpack or Vite.[17]
Debugging tools are tightly integrated to support both client-side and server-side Blazor models. For Blazor WebAssembly, browser developer tools—such as those in Chrome DevTools or Edge Inspector—allow inspection of .NET code, setting breakpoints, and stepping through execution directly in the browser console after enabling source maps.[69] Visual Studio extends this with full debugger attachment for Blazor Server apps, enabling server-side breakpoints, variable inspection, and call stack analysis within the IDE, akin to traditional ASP.NET debugging.[69]
Testing frameworks recommended by Microsoft focus on component isolation and end-to-end validation. The bUnit library, a community-driven but officially endorsed tool, facilitates unit and integration testing of Razor components by rendering them in a test host, mocking dependencies, and asserting rendered output or invoked services.[70] For end-to-end (E2E) testing, Playwright for .NET provides cross-browser automation, simulating user interactions across Blazor apps hosted in various environments while verifying full application behavior.[70]