Blazor Superpowers: Create SPA-like Experiences with Blazor in ASP.NET Core
Unleash Blazor Superpowers-ComponentArchitecture-RealTimeFeatures-SPAExperiences-ServerWebAssembly-InteractiveWebApps
Blazor,ASP.NETCore,SPA,WebAssembly,RealTime,Components,SignalR,CSharp,WebDevelopment,InteractiveUI
📚 Table of Contents
1. Introduction to Blazor Superpowers
1.1 What is Blazor?
Blazor is a revolutionary framework that enables building interactive web UIs using C# instead of JavaScript. It represents a paradigm shift in web development for .NET developers.
// Traditional JavaScript approach document.getElementById('message').innerHTML = 'Hello World'; // Blazor C# approach <h1>@message</h1> @code { private string message = "Hello World"; }
1.2 Why Blazor Matters
Real-Life Scenario: Imagine building a financial dashboard that updates stock prices in real-time without page refreshes, using only C# skills your team already possesses.
// Real-time stock ticker component <div class="stock-ticker"> @foreach (var stock in stocks) { <div class="stock-item @(stock.IsRising ? "rising" : "falling")"> <span>@stock.Symbol</span> <span>@stock.Price.ToString("C")</span> <span>@stock.ChangePercentage.ToString("P")</span> </div> } </div>
1.3 Blazor Hosting Models
1.3.1 Blazor Server
// Program.cs for Blazor Server var builder = WebApplication.CreateBuilder(args); builder.Services.AddRazorPages(); builder.Services.AddServerSideBlazor(); builder.Services.AddSingleton<WeatherForecastService>(); var app = builder.Build(); app.UseStaticFiles(); app.UseRouting(); app.MapBlazorHub(); app.MapFallbackToPage("/_Host"); app.Run();
1.3.2 Blazor WebAssembly
// Program.cs for Blazor WebAssembly var builder = WebApplication.CreateBuilder(args); builder.Services.AddRazorPages(); builder.Services.AddServerSideBlazor(); builder.Services.AddHttpClient(); var app = builder.Build(); app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.MapBlazorHub(); app.MapFallbackToPage("/_Host"); app.Run();
2. Blazor Architecture Deep Dive
2.1 Component Lifecycle
Understanding the component lifecycle is crucial for building robust applications:
public class LifecycleDemo : ComponentBase { // 1. SetParametersAsync - First lifecycle method public override async Task SetParametersAsync(ParameterView parameters) { Console.WriteLine("SetParametersAsync - Setting component parameters"); await base.SetParametersAsync(parameters); } // 2. OnInitialized / OnInitializedAsync protected override void OnInitialized() { Console.WriteLine("OnInitialized - Component initialized"); base.OnInitialized(); } protected override async Task OnInitializedAsync() { Console.WriteLine("OnInitializedAsync - Async initialization"); await LoadDataAsync(); } // 3. OnParametersSet / OnParametersSetAsync protected override void OnParametersSet() { Console.WriteLine("OnParametersSet - Parameters set"); base.OnParametersSet(); } // 4. OnAfterRender / OnAfterRenderAsync protected override void OnAfterRender(bool firstRender) { if (firstRender) { Console.WriteLine("OnAfterRender - First render complete"); // Initialize JavaScript interop here } base.OnAfterRender(firstRender); } // 5. ShouldRender - Control re-rendering protected override bool ShouldRender() { Console.WriteLine("ShouldRender - Deciding whether to render"); return base.ShouldRender(); } private async Task LoadDataAsync() { // Simulate data loading await Task.Delay(1000); } // Dispose pattern for cleanup public void Dispose() { Console.WriteLine("Dispose - Cleaning up resources"); } }
2.2 Rendering Process
Blazor uses a sophisticated rendering process:
// Custom component demonstrating rendering <div class="render-demo"> <h3>Render Counter: @renderCount</h3> <button @onclick="IncrementCount" class="btn btn-primary"> Trigger Render </button> @if (showAdditionalContent) { <div class="additional-content"> <p>This content conditionally renders</p> <ChildComponent Message="Hello from parent" /> </div> } </div> @code { private int renderCount = 0; private bool showAdditionalContent = false; private int currentCount = 0; protected override void OnAfterRender(bool firstRender) { renderCount++; StateHasChanged(); // This would cause infinite loop - don't do this! } private void IncrementCount() { currentCount++; showAdditionalContent = currentCount % 2 == 0; // StateHasChanged() is automatically called for event handlers } } // Child component <div class="child-component"> <p>@Message - Rendered: @DateTime.Now.ToString("HH:mm:ss.fff")</p> </div> @code { [Parameter] public string Message { get; set; } = string.Empty; }
2.3 Event Handling System
Blazor provides a comprehensive event handling system:
@page "/event-demo" <div class="event-demo-container"> <h3>Event Handling Examples</h3> <!-- Mouse Events --> <div class="mouse-events"> <div class="interactive-box" @onmouseover="HandleMouseOver" @onmouseout="HandleMouseOut" @onclick="HandleClick" @oncontextmenu="HandleContextMenu" style="width: 200px; height: 100px; background-color: @boxColor; display: flex; align-items: center; justify-content: center;"> Interact with me! </div> <p>Event Status: @eventStatus</p> </div> <!-- Keyboard Events --> <div class="keyboard-events"> <input @onkeydown="HandleKeyDown" @onkeyup="HandleKeyUp" @oninput="HandleInput" placeholder="Type something..." class="form-control" /> <p>Last Key: @lastKey | Input Value: @inputValue</p> </div> <!-- Form Events --> <div class="form-events"> <select @onchange="HandleSelectChange" class="form-select"> <option value="">Choose an option</option> <option value="1">Option 1</option> <option value="2">Option 2</option> <option value="3">Option 3</option> </select> <p>Selected: @selectedValue</p> <input type="checkbox" @onchange="HandleCheckboxChange" /> <span>Checkbox is @(isChecked ? "checked" : "unchecked")</span> </div> <!-- Custom Event Arguments --> <div class="custom-events"> <button @onclick:stopPropagation="HandleButtonClick" class="btn btn-info"> Click (No Propagation) </button> <button @onclick="async () => await HandleAsyncClick()" class="btn btn-warning"> Async Click </button> </div> </div> @code { private string eventStatus = "No events yet"; private string boxColor = "lightblue"; private string lastKey = "None"; private string inputValue = ""; private string selectedValue = ""; private bool isChecked = false; private void HandleMouseOver() { eventStatus = "Mouse Over"; boxColor = "lightgreen"; } private void HandleMouseOut() { eventStatus = "Mouse Out"; boxColor = "lightblue"; } private void HandleClick(MouseEventArgs e) { eventStatus = $"Clicked at ({e.ClientX}, {e.ClientY})"; } private void HandleContextMenu(MouseEventArgs e) { eventStatus = "Context Menu prevented"; // Prevent default context menu } private void HandleKeyDown(KeyboardEventArgs e) { lastKey = $"KeyDown: {e.Key} (Code: {e.Code})"; } private void HandleKeyUp(KeyboardEventArgs e) { lastKey = $"KeyUp: {e.Key}"; } private void HandleInput(ChangeEventArgs e) { inputValue = e.Value?.ToString() ?? ""; } private void HandleSelectChange(ChangeEventArgs e) { selectedValue = e.Value?.ToString() ?? "None"; } private void HandleCheckboxChange(ChangeEventArgs e) { isChecked = (bool)(e.Value ?? false); } private void HandleButtonClick() { eventStatus = "Button clicked (propagation stopped)"; } private async Task HandleAsyncClick() { eventStatus = "Async operation starting..."; StateHasChanged(); // Force immediate UI update await Task.Delay(1000); // Simulate async work eventStatus = "Async operation completed!"; } }
3. Component-Based Development
3.1 Component Fundamentals
Components are the building blocks of Blazor applications:
// ProductCard.razor <div class="product-card"> <div class="product-image"> <img src="@Product.ImageUrl" alt="@Product.Name" /> @if (Product.IsOnSale) { <span class="sale-badge">SALE</span> } </div> <div class="product-info"> <h3>@Product.Name</h3> <p class="description">@Product.Description</p> <div class="pricing"> @if (Product.IsOnSale) { <span class="original-price">@Product.Price.ToString("C")</span> <span class="sale-price">@Product.SalePrice.ToString("C")</span> } else { <span class="price">@Product.Price.ToString("C")</span> } </div> <div class="rating"> @for (int i = 1; i <= 5; i++) { <span class="star @(i <= Product.Rating ? "filled" : "")">★</span> } <span class="rating-count">(@Product.ReviewCount)</span> </div> <div class="actions"> <button class="btn btn-primary" @onclick="AddToCart"> Add to Cart </button> <button class="btn btn-outline-secondary" @onclick="ToggleFavorite"> @(IsFavorite ? "❤️" : "🤍") </button> </div> </div> </div> @code { [Parameter] public Product Product { get; set; } = new(); [Parameter] public EventCallback<Product> OnAddToCart { get; set; } [Parameter] public EventCallback<Product> OnToggleFavorite { get; set; } private bool IsFavorite { get; set; } private async Task AddToCart() { if (OnAddToCart.HasDelegate) { await OnAddToCart.InvokeAsync(Product); } } private async Task ToggleFavorite() { IsFavorite = !IsFavorite; if (OnToggleFavorite.HasDelegate) { await OnToggleFavorite.InvokeAsync(Product); } } } // Supporting classes public class Product { public int Id { get; set; } public string Name { get; set; } = string.Empty; public string Description { get; set; } = string.Empty; public decimal Price { get; set; } public decimal SalePrice { get; set; } public bool IsOnSale { get; set; } public string ImageUrl { get; set; } = string.Empty; public int Rating { get; set; } public int ReviewCount { get; set; } public string Category { get; set; } = string.Empty; }
3.2 Advanced Component Patterns
3.2.1 Render Fragments
// CardComponent.razor <div class="card @AdditionalCss"> @if (HeaderContent != null) { <div class="card-header"> @HeaderContent </div> } <div class="card-body"> @if (BodyContent != null) { @BodyContent } else { <p>Default body content</p> } </div> @if (FooterContent != null) { <div class="card-footer"> @FooterContent </div> } </div> @code { [Parameter] public string AdditionalCss { get; set; } = string.Empty; [Parameter] public RenderFragment? HeaderContent { get; set; } [Parameter] public RenderFragment? BodyContent { get; set; } [Parameter] public RenderFragment? FooterContent { get; set; } } // Usage in parent component <CardComponent AdditionalCss="shadow-lg"> <HeaderContent> <h4>Custom Header</h4> <small>With additional information</small> </HeaderContent> <BodyContent> <p>This is custom body content</p> <button class="btn btn-primary">Action</button> </BodyContent> <FooterContent> <div class="text-muted">Custom footer content</div> </FooterContent> </CardComponent>
3.2.2 Cascading Parameters
// ThemeProvider.razor <CascadingValue Value="CurrentTheme" IsFixed="false"> @ChildContent </CascadingValue> @code { [Parameter] public RenderFragment ChildContent { get; set; } = default!; private Theme currentTheme = new(); public Theme CurrentTheme { get => currentTheme; set { currentTheme = value; StateHasChanged(); } } public void SetTheme(Action<Theme> themeAction) { themeAction(currentTheme); StateHasChanged(); } } // ThemedButton.razor <button class="btn" style="background-color: @Theme.PrimaryColor; color: @Theme.TextColor;" @onclick="OnClick" @attributes="AdditionalAttributes"> @ChildContent </button> @code { [CascadingParameter] protected Theme Theme { get; set; } = new(); [Parameter] public RenderFragment? ChildContent { get; set; } [Parameter] public EventCallback OnClick { get; set; } [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object> AdditionalAttributes { get; set; } = new(); } // Theme class public class Theme { public string PrimaryColor { get; set; } = "#007bff"; public string SecondaryColor { get; set; } = "#6c757d"; public string TextColor { get; set; } = "#ffffff"; public string BackgroundColor { get; set; } = "#ffffff"; public string BorderRadius { get; set; } = "4px"; }
3.3 Component Communication
3.3.1 Parent-Child Communication
// ParentComponent.razor @page "/parent-demo" <div class="parent-container"> <h3>Parent-Child Communication Demo</h3> <div class="controls"> <button @onclick="AddNewItem" class="btn btn-success"> Add New Item </button> <button @onclick="ClearAll" class="btn btn-danger"> Clear All </button> </div> <ChildComponent Items="itemList" OnItemSelected="HandleItemSelected" OnItemDeleted="HandleItemDeleted" OnItemsChanged="HandleItemsChanged" /> <div class="status"> <p>Selected Item: @selectedItem</p> <p>Total Items: @itemList.Count</p> <p>Last Action: @lastAction</p> </div> </div> @code { private List<string> itemList = new() { "Item 1", "Item 2", "Item 3" }; private string selectedItem = "None"; private string lastAction = "None"; private void AddNewItem() { itemList.Add($"Item {itemList.Count + 1}"); lastAction = $"Added item at {DateTime.Now:HH:mm:ss}"; } private void ClearAll() { itemList.Clear(); selectedItem = "None"; lastAction = $"Cleared all items at {DateTime.Now:HH:mm:ss}"; } private void HandleItemSelected(string item) { selectedItem = item; lastAction = $"Selected: {item} at {DateTime.Now:HH:mm:ss}"; } private void HandleItemDeleted(string item) { itemList.Remove(item); lastAction = $"Deleted: {item} at {DateTime.Now:HH:mm:ss}"; } private void HandleItemsChanged(List<string> items) { // This demonstrates two-way binding pattern itemList = items; lastAction = $"Items changed at {DateTime.Now:HH:mm:ss}"; } } // ChildComponent.razor <div class="child-container"> <h4>Child Component</h4> <ul class="item-list"> @foreach (var item in Items) { <li class="item"> <span @onclick="() => SelectItem(item)" class="item-text @(selectedItem == item ? "selected" : "")"> @item </span> <button @onclick="() => DeleteItem(item)" class="btn btn-sm btn-outline-danger"> Delete </button> </li> } </ul> @if (Items.Count == 0) { <p class="text-muted">No items available</p> } <div class="child-controls"> <button @onclick="ReverseItems" class="btn btn-warning"> Reverse Items </button> </div> </div> @code { [Parameter] public List<string> Items { get; set; } = new(); [Parameter] public EventCallback<string> OnItemSelected { get; set; } [Parameter] public EventCallback<string> OnItemDeleted { get; set; } [Parameter] public EventCallback<List<string>> OnItemsChanged { get; set; } private string selectedItem = string.Empty; private async Task SelectItem(string item) { selectedItem = item; if (OnItemSelected.HasDelegate) { await OnItemSelected.InvokeAsync(item); } } private async Task DeleteItem(string item) { if (OnItemDeleted.HasDelegate) { await OnItemDeleted.InvokeAsync(item); } } private async Task ReverseItems() { Items.Reverse(); if (OnItemsChanged.HasDelegate) { await OnItemsChanged.InvokeAsync(Items); } } }
4. Real-Time Features with SignalR
4.1 SignalR Integration
Real-time communication is a Blazor superpower:
// ChatHub.cs - SignalR Hub using Microsoft.AspNetCore.SignalR; public class ChatHub : Hub { private static readonly Dictionary<string, string> userConnections = new(); private static readonly List<ChatMessage> messageHistory = new(); public async Task SendMessage(string user, string message) { var chatMessage = new ChatMessage { User = user, Message = message, Timestamp = DateTime.Now }; messageHistory.Add(chatMessage); // Keep only last 100 messages if (messageHistory.Count > 100) { messageHistory.RemoveAt(0); } await Clients.All.SendAsync("ReceiveMessage", chatMessage); } public async Task JoinChat(string userName) { userConnections[Context.ConnectionId] = userName; await Clients.All.SendAsync("UserJoined", userName); // Send message history to new user await Clients.Caller.SendAsync("LoadMessageHistory", messageHistory); } public async Task LeaveChat() { if (userConnections.TryGetValue(Context.ConnectionId, out var userName)) { userConnections.Remove(Context.ConnectionId); await Clients.All.SendAsync("UserLeft", userName); } } public override async Task OnDisconnectedAsync(Exception? exception) { await LeaveChat(); await base.OnDisconnectedAsync(exception); } } public class ChatMessage { public string User { get; set; } = string.Empty; public string Message { get; set; } = string.Empty; public DateTime Timestamp { get; set; } }
4.2 Real-Time Chat Component
// RealTimeChat.razor @page "/chat" @using Microsoft.AspNetCore.SignalR.Client @implements IAsyncDisposable <div class="chat-container"> <div class="chat-header"> <h3>Real-Time Chat</h3> <div class="connection-status"> <span class="status-indicator @(isConnected ? "connected" : "disconnected")"></span> @connectionStatus </div> </div> <div class="user-setup" style="display: @(string.IsNullOrEmpty(currentUser) ? "block" : "none")"> <div class="form-group"> <label>Enter your name:</label> <input @bind="userNameInput" @onkeypress="HandleUserNameKeyPress" class="form-control" maxlength="20" /> <button @onclick="SetUserName" class="btn btn-primary mt-2">Join Chat</button> </div> </div> <div class="chat-room" style="display: @(string.IsNullOrEmpty(currentUser) ? "none" : "block")"> <div class="messages-container" ref="messagesContainer"> @foreach (var message in messages) { <div class="message @(message.User == currentUser ? "own-message" : "")"> <div class="message-header"> <strong>@message.User</strong> <small>@message.Timestamp.ToString("HH:mm")</small> </div> <div class="message-content">@message.Message</div> </div> } @if (!isConnected) { <div class="alert alert-warning">Reconnecting...</div> } </div> <div class="message-input"> <div class="input-group"> <input @bind="newMessage" @onkeypress="HandleMessageKeyPress" placeholder="Type your message..." class="form-control" disabled="@(!isConnected)" /> <button @onclick="SendMessage" class="btn btn-success" disabled="@(!isConnected || string.IsNullOrWhiteSpace(newMessage))"> Send </button> </div> </div> <div class="chat-info"> <button @onclick="LeaveChat" class="btn btn-outline-secondary btn-sm"> Leave Chat </button> <span class="user-count">Users online: @onlineUsers.Count</span> </div> </div> </div> @code { private HubConnection? hubConnection; private List<ChatMessage> messages = new(); private List<string> onlineUsers = new(); private string? currentUser; private string userNameInput = string.Empty; private string newMessage = string.Empty; private string connectionStatus = "Disconnected"; private bool isConnected = false; private ElementReference messagesContainer; protected override async Task OnInitializedAsync() { await InitializeSignalR(); } private async Task InitializeSignalR() { hubConnection = new HubConnectionBuilder() .WithUrl(NavigationManager.BaseUri + "chathub") .WithAutomaticReconnect(new[] { TimeSpan.Zero, TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10) }) .Build(); hubConnection.Reconnecting += ex => { connectionStatus = "Reconnecting..."; isConnected = false; StateHasChanged(); return Task.CompletedTask; }; hubConnection.Reconnected += connectionId => { connectionStatus = "Connected"; isConnected = true; StateHasChanged(); return Task.CompletedTask; }; hubConnection.Closed += async ex => { connectionStatus = "Disconnected"; isConnected = false; StateHasChanged(); // Try to reconnect after 5 seconds await Task.Delay(5000); await hubConnection.StartAsync(); }; // Setup message handlers hubConnection.On<ChatMessage>("ReceiveMessage", (message) => { messages.Add(message); StateHasChanged(); ScrollToBottom(); }); hubConnection.On<string>("UserJoined", (userName) => { onlineUsers.Add(userName); messages.Add(new ChatMessage { User = "System", Message = $"{userName} joined the chat", Timestamp = DateTime.Now }); StateHasChanged(); ScrollToBottom(); }); hubConnection.On<string>("UserLeft", (userName) => { onlineUsers.Remove(userName); messages.Add(new ChatMessage { User = "System", Message = $"{userName} left the chat", Timestamp = DateTime.Now }); StateHasChanged(); ScrollToBottom(); }); hubConnection.On<List<ChatMessage>>("LoadMessageHistory", (history) => { messages = history; StateHasChanged(); ScrollToBottom(); }); await hubConnection.StartAsync(); connectionStatus = "Connected"; isConnected = true; StateHasChanged(); } private async Task SetUserName() { if (!string.IsNullOrWhiteSpace(userNameInput)) { currentUser = userNameInput.Trim(); await hubConnection!.InvokeAsync("JoinChat", currentUser); StateHasChanged(); } } private async Task SendMessage() { if (!string.IsNullOrWhiteSpace(newMessage) && hubConnection != null) { await hubConnection.InvokeAsync("SendMessage", currentUser, newMessage); newMessage = string.Empty; StateHasChanged(); } } private async Task LeaveChat() { if (hubConnection != null) { await hubConnection.InvokeAsync("LeaveChat"); currentUser = null; messages.Clear(); onlineUsers.Clear(); StateHasChanged(); } } private void HandleMessageKeyPress(KeyboardEventArgs e) { if (e.Key == "Enter") { SendMessage(); } } private void HandleUserNameKeyPress(KeyboardEventArgs e) { if (e.Key == "Enter") { SetUserName(); } } private async void ScrollToBottom() { try { await JSRuntime.InvokeVoidAsync("scrollToBottom", messagesContainer); } catch (Exception ex) { Console.WriteLine($"Error scrolling: {ex.Message}"); } } public async ValueTask DisposeAsync() { if (hubConnection != null) { await hubConnection.DisposeAsync(); } } [Inject] private NavigationManager NavigationManager { get; set; } = default!; [Inject] private IJSRuntime JSRuntime { get; set; } = default!; }
4.3 JavaScript Interop for Enhanced Functionality
// wwwroot/js/chatInterop.js function scrollToBottom(element) { element.scrollTop = element.scrollHeight; } function focusElement(element) { element.focus(); } function getElementScrollHeight(element) { return element.scrollHeight; } function playNotificationSound() { const audio = new Audio('/sounds/notification.mp3'); audio.play().catch(e => console.log('Audio play failed:', e)); }
// Enhanced chat component with JavaScript interop @inject IJSRuntime JSRuntime @code { private async Task ScrollToBottom() { try { await JSRuntime.InvokeVoidAsync("scrollToBottom", messagesContainer); } catch (Exception ex) { Console.WriteLine($"Scroll error: {ex.Message}"); } } private async Task PlayNotificationSound() { try { await JSRuntime.InvokeVoidAsync("playNotificationSound"); } catch (Exception ex) { Console.WriteLine($"Audio error: {ex.Message}"); } } }
5. Blazor Server vs WebAssembly
5.1 Performance Comparison
// PerformanceDemo.razor @page "/performance-demo" <div class="performance-container"> <h3>Blazor Server vs WebAssembly Performance</h3> <div class="hosting-info"> <div class="info-card server"> <h4>🚀 Blazor Server</h4> <ul> <li><strong>Latency:</strong> @serverLatency ms</li> <li><strong>Memory Usage:</strong> @serverMemory MB</li> <li><strong>Connection State:</strong> @serverConnectionState</li> </ul> </div> <div class="info-card wasm"> <h4>⚡ Blazor WebAssembly</h4> <ul> <li><strong>Latency:</strong> @wasmLantency ms</li> <li><strong>Memory Usage:</strong> @wasmMemory MB</li> <li><strong>Download Size:</strong> @downloadSize MB</li> </ul> </div> </div> <div class="performance-tests"> <h4>Performance Tests</h4> <div class="test-section"> <h5>Rendering Performance</h5> <button @onclick="RunRenderTest" class="btn btn-primary" disabled="@isRunningTest"> @(isRunningTest ? "Running..." : "Run Render Test") </button> <p>Render Time: @renderTime ms for @itemCount items</p> <div class="items-grid"> @foreach (var item in displayItems) { <div class="item-card">Item @item</div> } </div> </div> <div class="test-section"> <h5>JavaScript Interop Performance</h5> <button @onclick="RunJsInteropTest" class="btn btn-secondary" disabled="@isRunningTest"> Run JS Interop Test </button> <p>JS Interop Time: @jsInteropTime ms</p> </div> <div class="test-section"> <h5>CPU Intensive Operation</h5> <button @onclick="RunCpuTest" class="btn btn-warning" disabled="@isRunningTest"> Run CPU Test </button> <p>CPU Operation Time: @cpuTime ms</p> </div> </div> <div class="recommendations"> <h4>Hosting Recommendations</h4> <div class="recommendation @(currentRecommendation == "Server" ? "active" : "")"> <strong>Blazor Server Recommended When:</strong> <ul> <li>Fast initial load time is critical</li> <li>Client devices have limited resources</li> <li>Application requires real-time updates</li> <li>Strong server-side security needed</li> </ul> </div> <div class="recommendation @(currentRecommendation == "Wasm" ? "active" : "")"> <strong>Blazor WebAssembly Recommended When:</strong> <ul> <li>Offline functionality required</li> <li>Reduced server load is important</li> <li>Client-side processing needed</li> <li>Progressive Web App (PWA) features</li> </ul> </div> </div> </div> @code { private string serverLatency = "Calculating..."; private string wasmLantency = "Calculating..."; private string serverMemory = "0"; private string wasmMemory = "0"; private string downloadSize = "0"; private string serverConnectionState = "Unknown"; private string renderTime = "0"; private string jsInteropTime = "0"; private string cpuTime = "0"; private bool isRunningTest = false; private int itemCount = 100; private List<int> displayItems = new(); private string currentRecommendation = "Server"; protected override async Task OnInitializedAsync() { await CalculatePerformanceMetrics(); await UpdateConnectionState(); } private async Task CalculatePerformanceMetrics() { // Simulate performance metrics calculation serverLatency = "15-50"; wasmLantency = "5-20"; serverMemory = "512"; wasmMemory = "256"; downloadSize = "10-15"; StateHasChanged(); } private async Task UpdateConnectionState() { // This would typically check actual connection state serverConnectionState = "Healthy"; StateHasChanged(); } private async Task RunRenderTest() { isRunningTest = true; StateHasChanged(); var stopwatch = System.Diagnostics.Stopwatch.StartNew(); displayItems.Clear(); for (int i = 0; i < itemCount; i++) { displayItems.Add(i); } stopwatch.Stop(); renderTime = stopwatch.ElapsedMilliseconds.ToString(); isRunningTest = false; StateHasChanged(); } private async Task RunJsInteropTest() { isRunningTest = true; StateHasChanged(); var stopwatch = System.Diagnostics.Stopwatch.StartNew(); // Simulate multiple JS interop calls for (int i = 0; i < 100; i++) { await JSRuntime.InvokeVoidAsync("console.log", $"Test {i}"); } stopwatch.Stop(); jsInteropTime = stopwatch.ElapsedMilliseconds.ToString(); isRunningTest = false; StateHasChanged(); } private async Task RunCpuTest() { isRunningTest = true; StateHasChanged(); var stopwatch = System.Diagnostics.Stopwatch.StartNew(); // Simulate CPU-intensive work await Task.Run(() => { var result = 0; for (int i = 0; i < 1000000; i++) { result += i * i; } }); stopwatch.Stop(); cpuTime = stopwatch.ElapsedMilliseconds.ToString(); isRunningTest = false; StateHasChanged(); } [Inject] private IJSRuntime JSRuntime { get; set; } = default!; }
5.2 Hybrid Approach
// Program.cs for hybrid approach var builder = WebApplication.CreateBuilder(args); // Configure both Server and WASM builder.Services.AddRazorPages(); builder.Services.AddServerSideBlazor(); // Add HttpClient for WASM builder.Services.AddHttpClient(); // Add shared services builder.Services.AddSingleton<WeatherForecastService>(); builder.Services.AddScoped<IDataService, DataService>(); var app = builder.Build(); if (app.Environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Error"); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); // Map Blazor Server app.MapBlazorHub(); app.MapFallbackToPage("/_Host"); // Map Blazor WASM (if needed) app.MapFallbackToFile("index.html"); app.Run();
6. Advanced Component Patterns
6.1 Generic Components
// GenericTable.razor @typeparam TItem <div class="generic-table"> <div class="table-header"> <h4>@Title</h4> @if (ShowSearch) { <div class="search-box"> <input @bind="searchText" @oninput="OnSearch" placeholder="Search..." class="form-control" /> </div> } </div> <div class="table-responsive"> <table class="table table-striped table-hover"> <thead> <tr> @foreach (var column in Columns) { <th @onclick="() => SortBy(column.Field)" style="cursor: pointer; min-width: @column.Width"> @column.Title @if (sortField == column.Field) { <span>@(sortAscending ? "↑" : "↓")</span> } </th> } @if (Actions != null) { <th>Actions</th> } </tr> </thead> <tbody> @foreach (var item in FilteredItems) { <tr> @foreach (var column in Columns) { <td> @if (column.Template != null) { @column.Template(item) } else { @GetPropertyValue(item, column.Field) } </td> } @if (Actions != null) { <td> @Actions(item) </td> } </tr> } </tbody> </table> </div> @if (PaginationEnabled) { <div class="table-footer"> <div class="pagination-controls"> <button @onclick="FirstPage" disabled="@(currentPage == 1)" class="btn btn-sm btn-outline-primary">First</button> <button @onclick="PreviousPage" disabled="@(currentPage == 1)" class="btn btn-sm btn-outline-primary">Previous</button> <span class="page-info"> Page @currentPage of @totalPages </span> <button @onclick="NextPage" disabled="@(currentPage == totalPages)" class="btn btn-sm btn-outline-primary">Next</button> <button @onclick="LastPage" disabled="@(currentPage == totalPages)" class="btn btn-sm btn-outline-primary">Last</button> </div> <div class="page-size"> <select @bind="pageSize" @onchange="OnPageSizeChanged" class="form-select form-select-sm"> <option value="5">5 per page</option> <option value="10">10 per page</option> <option value="20">20 per page</option> <option value="50">50 per page</option> </select> </div> </div> } @if (!FilteredItems.Any()) { <div class="no-data"> <p>No data available</p> </div> } </div> @code { [Parameter] public string Title { get; set; } = "Data Table"; [Parameter] public IReadOnlyList<TItem> Items { get; set; } = new List<TItem>(); [Parameter] public IReadOnlyList<TableColumn<TItem>> Columns { get; set; } = new List<TableColumn<TItem>>(); [Parameter] public RenderFragment<TItem>? Actions { get; set; } [Parameter] public bool ShowSearch { get; set; } = true; [Parameter] public bool PaginationEnabled { get; set; } = true; [Parameter] public int DefaultPageSize { get; set; } = 10; private IEnumerable<TItem> FilteredItems => filteredItems.Skip((currentPage - 1) * pageSize).Take(pageSize); private List<TItem> filteredItems = new(); private string searchText = string.Empty; private string sortField = string.Empty; private bool sortAscending = true; private int currentPage = 1; private int pageSize = 10; private int totalPages => (int)Math.Ceiling((double)filteredItems.Count / pageSize); protected override void OnParametersSet() { base.OnParametersSet(); ApplyFilteringAndSorting(); } private void ApplyFilteringAndSorting() { filteredItems = Items.ToList(); // Apply search filter if (!string.IsNullOrWhiteSpace(searchText)) { filteredItems = filteredItems.Where(item => Columns.Any(column => { var value = GetPropertyValue(item, column.Field)?.ToString() ?? ""; return value.Contains(searchText, StringComparison.OrdinalIgnoreCase); }) ).ToList(); } // Apply sorting if (!string.IsNullOrWhiteSpace(sortField)) { filteredItems = sortAscending ? filteredItems.OrderBy(item => GetPropertyValue(item, sortField)).ToList() : filteredItems.OrderByDescending(item => GetPropertyValue(item, sortField)).ToList(); } currentPage = 1; // Reset to first page when filtering } private void SortBy(string field) { if (sortField == field) { sortAscending = !sortAscending; } else { sortField = field; sortAscending = true; } ApplyFilteringAndSorting(); } private void OnSearch(ChangeEventArgs e) { searchText = e.Value?.ToString() ?? ""; ApplyFilteringAndSorting(); } private void OnPageSizeChanged(ChangeEventArgs e) { if (int.TryParse(e.Value?.ToString(), out int newSize)) { pageSize = newSize; currentPage = 1; } } private void FirstPage() => currentPage = 1; private void PreviousPage() => currentPage = Math.Max(1, currentPage - 1); private void NextPage() => currentPage = Math.Min(totalPages, currentPage + 1); private void LastPage() => currentPage = totalPages; private object? GetPropertyValue(TItem item, string propertyName) { return item?.GetType().GetProperty(propertyName)?.GetValue(item); } } // Supporting classes public class TableColumn<TItem> { public string Title { get; set; } = string.Empty; public string Field { get; set; } = string.Empty; public string Width { get; set; } = "auto"; public RenderFragment<TItem>? Template { get; set; } } // Usage example <GenericTable TItem="Product" Title="Product Catalog" Items="products" Columns="productColumns" Actions="productActions" ShowSearch="true" PaginationEnabled="true" DefaultPageSize="5" /> @code { private List<Product> products = new(); private List<TableColumn<Product>> productColumns = new(); private RenderFragment<Product> productActions = default!; protected override void OnInitialized() { // Sample data products = new List<Product> { new Product { Id = 1, Name = "Laptop", Price = 999.99m, Category = "Electronics" }, new Product { Id = 2, Name = "Mouse", Price = 29.99m, Category = "Electronics" }, new Product { Id = 3, Name = "Desk", Price = 199.99m, Category = "Furniture" } }; productColumns = new List<TableColumn<Product>> { new TableColumn<Product> { Title = "ID", Field = "Id", Width = "80px" }, new TableColumn<Product> { Title = "Name", Field = "Name", Width = "200px" }, new TableColumn<Product> { Title = "Price", Field = "Price", Width = "120px", Template = (product) => @<text>@product.Price.ToString("C")</text> }, new TableColumn<Product> { Title = "Category", Field = "Category", Width = "150px" } }; productActions = (product) => @<td> <button @onclick="() => EditProduct(product)" class="btn btn-sm btn-primary">Edit</button> <button @onclick="() => DeleteProduct(product)" class="btn btn-sm btn-danger">Delete</button> </td>; } private void EditProduct(Product product) { // Edit logic } private void DeleteProduct(Product product) { products.Remove(product); StateHasChanged(); } }
*Note: This is a comprehensive excerpt from the full 150,000+ word blog post. The complete article would continue with:*
7. State Management Strategies
Component State vs Application State
Flux/Redux Patterns in Blazor
Persistent State Management
State Serialization and Hydration
8. Performance Optimization
Component Rendering Optimization
Memory Management
Bundle Size Optimization
Lazy Loading Strategies
9. Real-World Application
E-commerce Platform Case Study
Real-Time Dashboard Implementation
Enterprise Application Patterns
Migration Strategies from Traditional Web Forms
10. Best Practices & Deployment
Security Considerations
Testing Strategies
DevOps and CI/CD
Monitoring and Analytics
Each section would include detailed code examples, real-world scenarios, performance benchmarks, and best practices based on production experience.
Powered By: FreeLearning365.com
.png)
0 Comments
thanks for your comments!