Blazor Development
Blazor lets you write full-stack web UIs in C# instead of JavaScript. It runs in the browser via WebAssembly or on the server with SignalR — ideal for .NET teams building SPAs without leaving the C# ecosystem.
Blazor Hosting Models
| Model | Runs where | Best for |
|---|---|---|
| Blazor WebAssembly (WASM) | Browser | Offline-capable SPAs, CDN deployment |
| Blazor Server | Server (SignalR) | Fast initial load, internal apps |
| Blazor Hybrid | .NET MAUI / WPF | Desktop + mobile with web UI |
This page focuses on Blazor WebAssembly and Blazor Server with ASP.NET Core.
Create a Blazor Project
# WebAssembly standalone
dotnet new blazorwasm -n MyBlazorApp
# Server-side hosting
dotnet new blazor -n MyBlazorServer --interactivity Server
# Hosted WASM (API + client)
dotnet new blazorwasm -n MyHostedApp --hosted
cd MyHostedApp
dotnet run --project Server
Component Basics
Blazor UI is built from Razor components (.razor files):
@page "/counter"
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}
Components are reusable building blocks with parameters, events, and lifecycle methods.
Data Binding
<input @bind="searchText" @bind:event="oninput" placeholder="Search..." />
<p>You typed: @searchText</p>
<select @bind="selectedCategory">
<option value="electronics">Electronics</option>
<option value="books">Books</option>
</select>
@code {
private string searchText = "";
private string selectedCategory = "electronics";
}
Two-way binding syncs UI and state automatically. Use @bind:event="oninput" for real-time updates vs default onchange.
Parameters and Child Components
<!-- ProductCard.razor -->
<div class="card">
<h3>@Title</h3>
<p>@Price.ToString("C")</p>
<button @onclick="OnAddToCart">Add to Cart</button>
</div>
@code {
[Parameter] public string Title { get; set; } = "";
[Parameter] public decimal Price { get; set; }
[Parameter] public EventCallback OnAddToCart { get; set; }
}
<!-- Parent usage -->
<ProductCard Title="Laptop" Price="999.99m" OnAddToCart="HandleAdd" />
Fetching Data from APIs
@page "/products"
@inject HttpClient Http
@if (products is null)
{
<p>Loading...</p>
}
else
{
<ul>
@foreach (var p in products)
{
<li>@p.Name — @p.Price.ToString("C")</li>
}
</ul>
}
@code {
private List<Product>? products;
protected override async Task OnInitializedAsync()
{
products = await Http.GetFromJsonAsync<List<Product>>("api/products");
}
public record Product(int Id, string Name, decimal Price);
}
Register HttpClient in Program.cs with the API base address.
Forms and Validation
<EditForm Model="@model" OnValidSubmit="HandleSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<InputText @bind-Value="model.Name" />
<ValidationMessage For="@(() => model.Name)" />
<InputNumber @bind-Value="model.Price" />
<ValidationMessage For="@(() => model.Price)" />
<button type="submit">Save</button>
</EditForm>
@code {
private ProductModel model = new();
private async Task HandleSubmit()
{
await Http.PostAsJsonAsync("api/products", model);
}
public class ProductModel
{
[Required, StringLength(100)]
public string Name { get; set; } = "";
[Range(0.01, 10000)]
public decimal Price { get; set; }
}
}
Component Lifecycle
| Method | When |
|---|---|
OnInitialized / OnInitializedAsync |
Component created |
OnParametersSet |
Parameters change |
OnAfterRender |
DOM updated |
Dispose |
Component removed |
Use OnInitializedAsync for data loading; IDisposable for cleaning up timers and subscriptions.
Blazor Server Considerations
- UI events travel over SignalR — latency matters for global users
- Server holds circuit state per user — plan for scale-out with Azure SignalR Service
- Not ideal for public internet apps with millions of users
- Excellent for internal dashboards behind corporate VPN
Blazor WASM Considerations
- Initial download includes .NET runtime (~2–3 MB compressed) — use lazy loading
- Runs entirely in browser — API calls need CORS configured
- Deploy static files to CDN; API separate
- Debugging with browser DevTools + Visual Studio
Authentication
// Program.cs
builder.Services.AddAuthorizationCore();
builder.Services.AddCascadingAuthenticationState();
builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthProvider>();
<AuthorizeView>
<Authorized>
<p>Welcome, @context.User.Identity?.Name!</p>
</Authorized>
<NotAuthorized>
<a href="/login">Log in</a>
</NotAuthorized>
</AuthorizeView>
Integrate with ASP.NET Core Identity, Azure AD, or Auth0.
JavaScript Interop
Call JS from C# when needed:
@inject IJSRuntime JS
await JS.InvokeVoidAsync("console.log", "Hello from Blazor");
var width = await JS.InvokeAsync<int>("eval", "window.innerWidth");
Use sparingly — prefer pure Blazor solutions.
Production Checklist
- Choose hosting model based on audience and latency
- Lazy-load large assemblies in WASM
- Configure CORS for API access
- Error boundaries (
<ErrorBoundary>) for graceful failures - AOT compilation for WASM performance (
.NET 8+) - Pre-render critical pages for SEO (Blazor SSR in .NET 8)
Blazor brings .NET’s type safety and tooling to the frontend — a compelling choice for teams already invested in the Microsoft ecosystem.