Blazor Mastery Guide

 

🚀 Blazor Mastery Guide

Comprehensive Q&A with Production-Ready Code Examples

.NET 8 | Blazor Server | Blazor WebAssembly | Blazor Hybrid
Q1: How Blazor is different from ASP.NET MVC and ASP.NET Razor Pages?

Answer: Blazor represents a paradigm shift from traditional server-rendered frameworks. Here's the key difference:

  • MVC/Razor Pages: Server-side rendering only. Each interaction requires a full HTTP request/response cycle with page reloads.
  • Blazor: Enables true SPA experiences using C# instead of JavaScript. Components render interactively with real-time UI updates without full page reloads.
📁 Example: Blazor Component vs Razor Page
// Traditional Razor Page (Server-side only)
@page "/counter"
<h3>Counter: @count</h3>
<button @onsubmit="Increment">Click</button>
@code {
    private int count = 0;
    public IActionResult OnPostIncrement() {
        count++; // Requires full postback
        return Page();
    }
}

// Blazor Component (Interactive SPA)
@page "/counter"
<h3>Counter: @count</h3>
<button @onclick="Increment">Click</button>
@code {
    private int count = 0;
    private void Increment() {
        count++; // Real-time update, no page reload!
    }
}
Q2: What are the benefits of Blazor?

Answer: Blazor offers compelling advantages for .NET teams:

  • ✅ Full-stack C# development (share models, validation, business logic)
  • ✅ No JavaScript required (though optional for advanced scenarios)
  • ✅ True component-based architecture with reusability
  • ✅ Real-time WebSocket support out-of-the-box (Blazor Server)
  • ✅ Progressive Web App (PWA) support for offline scenarios
  • ✅ .NET ecosystem access (NuGet, EF Core, Azure SDK)
💡 Pro Tip: Blazor Server offers instant load times and full .NET access, while Blazor WebAssembly provides offline capability and reduced server load.
Q3: What are the rendering modes in Blazor?

Answer: .NET 8 introduced unified rendering modes:

  • Static Server-Side Rendering (SSR): Traditional server rendering, no interactivity
  • Interactive Server: Real-time SignalR connection, runs on server
  • Interactive WebAssembly: Runs entirely in browser via WebAssembly
  • Interactive Auto: Starts with Server, switches to WebAssembly after download
📁 Program.cs - Configuring Rendering Modes
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents()    // Enable Server interactivity
    .AddInteractiveWebAssemblyComponents(); // Enable WASM interactivity

var app = builder.Build();
app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode()
    .AddInteractiveWebAssemblyRenderMode();
Q13: How does child-parent relationship maintain in Blazor component?

Answer: Blazor maintains parent-child communication through parameters and callbacks.

📁 ParentComponent.razor
@* Parent Component *@
<h3>Parent Component</h3>
<p>Message from child: @childMessage</p>

<ChildComponent 
    Title="Hello Child!" 
    OnNotifyParent="HandleChildNotification" />

@code {
    private string childMessage = string.Empty;
    
    private void HandleChildNotification(string message) {
        childMessage = message;
    }
}
📁 ChildComponent.razor
@* Child Component *@
<div class="card">
    <h4>@Title</h4>
    <button @onclick="() => OnNotifyParent.Invoke(\"Button clicked!\")">
        Notify Parent
    </button>
</div>

@code {
    [Parameter] public string Title { get; set; } = string.Empty;
    [Parameter] public EventCallback<string> OnNotifyParent { get; set; }
}
Q16: How to communicate between non-parent-child components (siblings)?

Answer: Use a state container service as a shared communication hub.

📁 StateContainer.cs
public class StateContainer {
    private string? _sharedData;
    public event Action? OnStateChange;
    
    public string? SharedData {
        get => _sharedData;
        set {
            _sharedData = value;
            NotifyStateChanged();
        }
    }
    
    private void NotifyStateChanged() => OnStateChange?.Invoke();
}

// In Program.cs
builder.Services.AddSingleton<StateContainer>();
📁 Sibling Components using StateContainer
// SenderComponent.razor
@inject StateContainer State
<button @onclick="() => State.SharedData = DateTime.Now.ToString()">
    Update Shared Data
</button>

// ReceiverComponent.razor
@implements IDisposable
@inject StateContainer State
<p>Received: @State.SharedData</p>

@code {
    protected override void OnInitialized() => State.OnStateChange += StateHasChanged;
    public void Dispose() => State.OnStateChange -= StateHasChanged;
}
Q36: How to implement a state container service for sharing data across unrelated components?

Answer: Create an observable state container with event notification pattern.

public class ShoppingCartState {
    private readonly List<CartItem> _items = new();
    public IReadOnlyList<CartItem> Items => _items;
    public event Action? OnCartChanged;
    
    public void AddItem(CartItem item) {
        _items.Add(item);
        OnCartChanged?.Invoke();
    }
    
    public void RemoveItem(int id) {
        _items.RemoveAll(i => i.Id == id);
        OnCartChanged?.Invoke();
    }
}

// Usage in any component
@inject ShoppingCartState Cart
@implements IDisposable

protected override void OnInitialized() => Cart.OnCartChanged += StateHasChanged;
public void Dispose() => Cart.OnCartChanged -= StateHasChanged;
Q37: How to persist user state across browser refreshes in Blazor WebAssembly?

Answer: Use ProtectedBrowserStorage for persistent client-side storage.

@inject ProtectedLocalStorage LocalStorage
@implements IDisposable

@code {
    private UserPreferences _preferences = new();
    
    protected override async Task OnInitializedAsync() {
        try {
            var result = await LocalStorage.GetAsync<UserPreferences>("userPrefs");
            if (result.Success) _preferences = result.Value;
        } catch (InvalidOperationException) {
            // Handle privacy mode or unavailable storage
        }
    }
    
    private async Task SavePreferences() {
        await LocalStorage.SetAsync("userPrefs", _preferences);
    }
    
    private async Task ClearPreferences() {
        await LocalStorage.DeleteAsync("userPrefs");
    }
}
Q42: How to prevent unnecessary re-renders of child components?

Answer: Override ShouldRender() and implement custom render logic.

@code {
    private ExpensiveData _data = new();
    
    protected override bool ShouldRender() {
        // Only re-render when data has actually changed
        return _data.HasChanged;
    }
    
    // For immutable parameters, use @inherit .NET's equality comparison
    [Parameter] public List<string> Items { get; set; } = new();
    
    // Better: Use immutable collections or implement IEquatable
}
📁 Optimized List Rendering with @key
@* Without @key - entire list re-renders *@
@foreach (var item in items) {
    <ChildComponent Item="item" />
}

@* With @key - only changed items re-render *@
@foreach (var item in items) {
    <ChildComponent @key="item.Id" Item="item" />
}
Q45: How to implement virtualized scrolling for large lists?

Answer: Use Virtualize component for handling thousands of items.

@* Virtualize efficiently loads only visible items *@
<Virtualize ItemsProvider="LoadCustomers" Context="customer">
    <div class="customer-item">
        <strong>@customer.Name</strong> - @customer.Email
    </div>
</Virtualize>

@code {
    private async ValueTask<ItemsProviderResult<Customer>> LoadCustomers(
        ItemsProviderRequest request) {
        
        var customers = await db.Customers
            .Skip(request.StartIndex)
            .Take(request.Count)
            .ToListAsync();
        
        var totalCount = await db.Customers.CountAsync();
        
        return new ItemsProviderResult<Customer>(customers, totalCount);
    }
}
Q49: How to call a JavaScript function from a Blazor component and pass complex .NET objects?

Answer: Use IJSRuntime with JSON serialization for complex objects.

📁 Component.razor
@inject IJSRuntime JS

<button @onclick="ShowChart">Display Analytics Chart</button>

@code {
    private async Task ShowChart() {
        var data = new ChartData {
            Labels = new[] { "Jan", "Feb", "Mar" },
            Values = new[] { 10, 25, 15 },
            Colors = new[] { "#ff0000", "#00ff00", "#0000ff" }
        };
        
        await JS.InvokeVoidAsync("window.createChart", data);
    }
}

// wwwroot/js/site.js
window.createChart = (data) => {
    const ctx = document.getElementById('myChart');
    new Chart(ctx, {
        type: 'bar',
        data: {
            labels: data.labels,
            datasets: [{
                data: data.values,
                backgroundColor: data.colors
            }]
        }
    });
};
Q50: How to call a .NET method from JavaScript in Blazor?

Answer: Use DotNetReference to expose .NET methods to JavaScript.

@inject IJSRuntime JS
@implements IAsyncDisposable

<div id="map" style="height: 400px;"></div>

@code {
    private DotNetObjectReference<MapComponent>? _dotNetRef;
    
    protected override async Task OnAfterRenderAsync(bool firstRender) {
        if (firstRender) {
            _dotNetRef = DotNetObjectReference.Create(this);
            await JS.InvokeVoidAsync("initMap", _dotNetRef);
        }
    }
    
    [JSInvokable]
    public void OnMapClick(double lat, double lng) {
        Console.WriteLine($"Clicked at: {lat}, {lng}");
        // Update UI or perform logic
    }
    
    async ValueTask IAsyncDisposable.DisposeAsync() {
        _dotNetRef?.Dispose();
    }
}
Q29: How to implement custom validation attributes for Blazor forms?

Answer: Create custom ValidationAttribute and implement IClientModelValidator.

public class FutureDateAttribute : ValidationAttribute, IClientModelValidator {
    protected override ValidationResult? IsValid(object? value, ValidationContext context) {
        if (value is DateTime date && date <= DateTime.Now) {
            return new ValidationResult("Date must be in the future");
        }
        return ValidationResult.Success;
    }
    
    public void AddValidation(ClientModelValidationContext context) {
        context.Attributes.Add("data-val", "true");
        context.Attributes.Add("data-val-futuredate", ErrorMessage);
        context.Attributes.Add("data-val-futuredate-min", DateTime.Now.ToString("yyyy-MM-dd"));
    }
}

// Usage in model
public class EventModel {
    [FutureDate(ErrorMessage = "Event date must be in the future")]
    public DateTime EventDate { get; set; }
}
Q34: How to debounce input validation?

Answer: Implement debounce pattern with Timer and StateHasChanged.

<input @bind="searchTerm" @bind:event="oninput" @oninput="OnSearchInput" />

@code {
    private string _searchTerm = string.Empty;
    private System.Timers.Timer? _debounceTimer;
    
    private void OnSearchInput(ChangeEventArgs e) {
        _debounceTimer?.Dispose();
        _debounceTimer = new System.Timers.Timer(500);
        _debounceTimer.Elapsed += async (sender, args) => {
            await InvokeAsync(async () => {
                await PerformSearch();
                StateHasChanged();
            });
            _debounceTimer?.Dispose();
        };
        _debounceTimer.Start();
    }
    
    private async Task PerformSearch() {
        // API call or search logic
        results = await searchService.SearchAsync(_searchTerm);
    }
}
Q56: How to implement route constraints in Blazor?

Answer: Use built-in route constraints in @page directive.

@page "/products/{id:int}"
@page "/products/{category}/{id:int:min(1)}"
@page "/users/{username:regex(^[a-zA-Z0-9]+$)}"

<h3>Product ID: @ProductId</h3>
<h3>Category: @Category</h3>

@code {
    [Parameter] public int ProductId { get; set; }
    [Parameter] public string? Category { get; set; }
    
    protected override async Task OnParametersSetAsync() {
        // Load data based on route parameters
        var product = await ProductService.GetByIdAsync(ProductId);
    }
}
Q62: How to control component authorization in Blazor?

Answer: Use AuthorizeView component or [Authorize] attribute.

@* Declarative authorization *@
<AuthorizeView Roles="Admin">
    <Authorized>
        <button @onclick="DeleteData">Delete All Data</button>
    </Authorized>
    <NotAuthorized>
        <p>You need administrator privileges.</p>
    </NotAuthorized>
</AuthorizeView>

@* Policy-based authorization *@
<AuthorizeView Policy="RequireElevatedRights">
    <Authorized>
        <AdminPanel />
    </Authorized>
</AuthorizeView>

@* Attribute-based at component level *@
@attribute [Authorize(Roles = "Admin,Manager")]

@code {
    [CascadingParameter]
    private Task<AuthenticationState>? AuthState { get; set; }
    
    private async Task CheckUserClaims() {
        var authState = await AuthState!;
        var user = authState.User;
        
        if (user.HasClaim(c => c.Type == "Permission" && c.Value == "Delete")) {
            // Show delete button
        }
    }
}
Q73: How to create a global error boundary component?

Answer: Use ErrorBoundary component in .NET 8 or implement custom error handling.

@* App.razor - Global error boundary *@
<ErrorBoundary>
    <ChildContent>
        <Router ... />
    </ChildContent>
    <ErrorContent>
        <div class="alert alert-danger">
            <h5>Something went wrong</h5>
            <p>@context.Exception.Message</p>
            <button @onclick="context.Recover">Recover</button>
        </div>
    </ErrorContent>
</ErrorBoundary>

// Custom error handling service
public class GlobalExceptionHandler : IErrorBoundaryLogger {
    private readonly ILogger<GlobalExceptionHandler> _logger;
    
    public GlobalExceptionHandler(ILogger<GlobalExceptionHandler> logger) {
        _logger = logger;
    }
    
    public ValueTask LogErrorAsync(Exception exception) {
        _logger.LogError(exception, "Unhandled exception in component");
        // Send to Application Insights, Sentry, etc.
        return ValueTask.CompletedTask;
    }
}
Q91: How to mock IJSRuntime for unit testing a component that calls JavaScript?

Answer: Use bUnit with custom mock implementation.

// Test using bUnit
[Fact]
public void Component_Calls_JavaScript_On_Click() {
    // Arrange
    var jsRuntimeMock = new Mock<IJSRuntime>();
    var cut = RenderComponent<MyComponent>(services => 
        services.AddSingleton(jsRuntimeMock.Object));
    
    // Act
    cut.Find("button").Click();
    
    // Assert
    jsRuntimeMock.Verify(x => 
        x.InvokeVoidAsync("myJsFunction", It.IsAny<object[]>(), It.IsAny<CancellationToken>()),
        Times.Once);
}

// Custom JS mock for bUnit
public class TestJSRuntime : IJSRuntime {
    public List<(string Identifier, object?[] Args)> Invocations { get; } = new();
    
    public ValueTask<TValue> InvokeAsync<TValue>(string identifier, object?[]? args, 
        CancellationToken cancellationToken = default) {
        Invocations.Add((identifier, args ?? Array.Empty<object?>()));
        return new ValueTask<TValue>((TValue)(object)null!);
    }
}
Q97: How to build a Blazor app using CI/CD in GitHub Actions?

Answer: Create GitHub Actions workflow for automated build and test.

📁 .github/workflows/blazor-ci.yml
name: Blazor CI/CD

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build-and-test:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Setup .NET
      uses: actions/setup-dotnet@v3
      with:
        dotnet-version: 8.0.x
    
    - name: Restore dependencies
      run: dotnet restore
    
    - name: Build
      run: dotnet build --no-restore --configuration Release
    
    - name: Run Unit Tests
      run: dotnet test --no-build --configuration Release --verbosity normal
    
    - name: Run Playwright Tests
      run: |
        dotnet tool install --global Microsoft.Playwright.CLI
        playwright install
        dotnet test --filter "Category=E2E"
    
    - name: Publish Blazor WASM
      run: dotnet publish YourBlazorApp.csproj -c Release -o release
    
    - name: Deploy to Azure Static Web Apps
      uses: Azure/static-web-apps-deploy@v1
      with:
        azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_TOKEN }}
        repo_token: ${{ secrets.GITHUB_TOKEN }}
        action: "upload"
        app_location: "release/wwwroot"
📖 Expert Summary: This comprehensive guide covers the essential patterns, practices, and production-ready code for building modern Blazor applications. Remember that Blazor has evolved significantly with .NET 8's unified rendering model, offering unprecedented flexibility in how you build web applications with C#.

© 2024 Blazor Mastery Guide | Written by .NET Blazor Expert

Comprehensive answers with production-ready code examples for real-world Blazor development

Post a Comment

Previous Post Next Post