# Blazor Framework - Quick Reference (SKILL.md) **Version**: 1.0.0 **Target**: .NET 8.0+ with Blazor Server/WebAssembly **UI Library**: Microsoft Fluent UI Blazor Components **Purpose**: Fast lookup for common Blazor patterns and best practices --- ## 1. Blazor Hosting Models ### Blazor Server **Characteristics**: - Server-side rendering with SignalR connection - Stateful connection to server required - Small initial download (~2MB), fast startup - UI events sent to server via SignalR **Setup**: ```csharp // Program.cs builder.Services.AddServerSideBlazor(); app.MapBlazorHub(); app.MapFallbackToPage("/_Host"); ``` **Use Cases**: Internal apps, intranet, enterprise applications --- ### Blazor WebAssembly **Characteristics**: - Client-side execution in browser via WebAssembly - Full .NET runtime in browser - Larger initial download (~10MB), but offline capable - No server connection after initial load **Setup**: ```csharp // Program.cs (Client) builder.Services.AddBlazorWebAssembly(); await builder.Build().RunAsync(); ``` **Use Cases**: Public-facing apps, PWAs, offline-first applications --- ### Render Modes (.NET 8+) ```razor @* Server-side with SignalR *@ @rendermode InteractiveServer @* Client-side WebAssembly *@ @rendermode InteractiveWebAssembly @* Auto: Server initially, then WebAssembly after download *@ @rendermode InteractiveAuto @* Static: Server-side rendering without interactivity *@ @rendermode @(new Microsoft.AspNetCore.Components.Web.RenderMode.Static) ``` --- ## 2. Component Architecture ### Basic Component Structure ```razor @page "/counter" @using MyApp.Services

@Title

Current count: @currentCount

@code { [Parameter] public string Title { get; set; } = "Counter"; [Parameter] public EventCallback OnCountChanged { get; set; } private int currentCount = 0; private async Task IncrementCount() { currentCount++; await OnCountChanged.InvokeAsync(currentCount); } } ``` --- ### Component Parameters ```csharp // Required parameter [Parameter, EditorRequired] public string Name { get; set; } = ""; // Optional parameter with default [Parameter] public int MaxCount { get; set; } = 10; // EventCallback (parent-child communication) [Parameter] public EventCallback OnValueChanged { get; set; } // Cascading parameter (from ancestor) [CascadingParameter] public AppState AppState { get; set; } = default!; // Capture unmatched attributes [Parameter(CaptureUnmatchedValues = true)] public Dictionary? AdditionalAttributes { get; set; } ``` --- ### Component Lifecycle ```csharp // 1. Parameters set (before initialization) public override void SetParametersAsync(ParameterView parameters) { base.SetParametersAsync(parameters); } // 2. Component initialization (once) protected override void OnInitialized() { // Synchronous initialization } protected override async Task OnInitializedAsync() { // Async initialization (preferred for data loading) data = await DataService.GetDataAsync(); } // 3. Parameters set (every time parameters change) protected override void OnParametersSet() { // React to parameter changes } protected override async Task OnParametersSetAsync() { // Async parameter processing } // 4. After rendering protected override void OnAfterRender(bool firstRender) { if (firstRender) { // One-time setup after first render } } protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { // JS interop, focus management await JS.InvokeVoidAsync("initializeComponent"); } } // 5. Determine if re-render needed protected override bool ShouldRender() { // Return false to skip re-render return true; } // 6. Disposal (cleanup) public void Dispose() { // Unsubscribe from events, dispose resources AppState.OnChange -= StateHasChanged; } public async ValueTask DisposeAsync() { // Async disposal if (hubConnection is not null) { await hubConnection.DisposeAsync(); } } ``` --- ## 3. Fluent UI Components ### Layout Components ```razor @using Microsoft.FluentUI.AspNetCore.Components @* Vertical stack with gap *@

Title

Content

@* Horizontal stack *@ Cancel Save @* Grid layout *@ Column 1 Column 2 ``` --- ### Input Components ```razor @* Text input *@ @* Text area *@ @* Number input *@ @* Select dropdown *@ @* Checkbox *@ @* Switch toggle *@ @* Date picker *@ ``` --- ### Buttons ```razor @* Primary button *@ Submit @* Secondary button *@ Cancel @* Icon button *@ @* Split button *@ Edit Delete ``` --- ### Data Display ```razor @* Card *@

Card Title

Card content goes here.

@* Data grid with sorting *@ Edit @* Accordion *@ Content for section 1 Content for section 2 ``` --- ### Feedback Components ```razor @* Dialog *@

Confirmation

Are you sure you want to delete this item?

Yes, Delete Cancel
@* Message bar (notification) *@ Item saved successfully! @* Progress ring (loading spinner) *@ @* Toast notification *@ @inject IToastService ToastService @code { private void ShowToast() { ToastService.ShowSuccess("Operation completed successfully!"); } } ``` --- ## 4. State Management ### Component State ```csharp @code { // Private field for component-local state private int count = 0; private string message = ""; private List items = new(); // State change triggers re-render automatically private void UpdateState() { count++; // Component re-renders automatically } } ``` --- ### Service-Based State ```csharp // AppState.cs public class AppState { private string currentUser = ""; public string CurrentUser { get => currentUser; set { if (currentUser != value) { currentUser = value; NotifyStateChanged(); } } } public event Action? OnChange; private void NotifyStateChanged() => OnChange?.Invoke(); } // Program.cs builder.Services.AddScoped(); // Component @inject AppState AppState @implements IDisposable @code { protected override void OnInitialized() { AppState.OnChange += StateHasChanged; } public void Dispose() { AppState.OnChange -= StateHasChanged; } private void UpdateUser() { AppState.CurrentUser = "John Doe"; // All subscribed components re-render } } ``` --- ### Cascading Values ```razor @* App.razor or parent component *@ @* Child components *@ @code { private string currentTheme = "light"; } @* Child component *@ @code { [CascadingParameter] public string Theme { get; set; } = ""; // Access Theme without prop drilling } ``` --- ## 5. Forms & Validation ### Basic EditForm ```razor Submit @code { private Person person = new(); private async Task HandleValidSubmit() { // Form is valid await SavePersonAsync(person); } public class Person { [Required(ErrorMessage = "Name is required")] [MaxLength(100)] public string Name { get; set; } = ""; [Range(0, 150, ErrorMessage = "Age must be between 0 and 150")] public int Age { get; set; } [Required] [EmailAddress] public string Email { get; set; } = ""; } } ``` --- ### FluentValidation ```csharp // Install: FluentValidation.Blazor using FluentValidation; public class PersonValidator : AbstractValidator { public PersonValidator() { RuleFor(x => x.Name) .NotEmpty().WithMessage("Name is required") .MaximumLength(100); RuleFor(x => x.Age) .InclusiveBetween(0, 150); RuleFor(x => x.Email) .NotEmpty() .EmailAddress(); } } // Component @* Form fields *@ ``` --- ### Custom Validation ```csharp // Custom attribute public class FutureDateAttribute : ValidationAttribute { protected override ValidationResult? IsValid(object? value, ValidationContext validationContext) { if (value is DateTime date && date > DateTime.Now) { return ValidationResult.Success; } return new ValidationResult("Date must be in the future"); } } // Usage public class Event { [FutureDate] public DateTime EventDate { get; set; } } ``` --- ## 6. Routing & Navigation ### Route Declaration ```razor @* Basic route *@ @page "/products" @* Multiple routes *@ @page "/products" @page "/items" @* Route parameter *@ @page "/product/{id}" @code { [Parameter] public string Id { get; set; } = ""; } @* Optional parameter *@ @page "/product/{id?}" @* Route constraint *@ @page "/product/{id:int}" @page "/user/{userId:guid}" @* Catch-all parameter *@ @page "/docs/{*path}" ``` --- ### Navigation ```csharp @inject NavigationManager Navigation @code { private void NavigateToProduct(int id) { Navigation.NavigateTo($"/product/{id}"); } private void NavigateExternal() { Navigation.NavigateTo("https://example.com", forceLoad: true); } private void Refresh() { Navigation.NavigateTo(Navigation.Uri, forceLoad: true); } // Get current URL var currentUrl = Navigation.Uri; var baseUrl = Navigation.BaseUri; // Query string var uri = new Uri(Navigation.Uri); var query = System.Web.HttpUtility.ParseQueryString(uri.Query); var searchTerm = query["search"]; } ``` --- ### NavLink Component ```razor @* Exact match (active only for exact path) *@ Home @* Prefix match (active for path and descendants) *@ Products ``` --- ## 7. JavaScript Interop ### Calling JavaScript from Blazor ```csharp @inject IJSRuntime JS @code { protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { // Call void function await JS.InvokeVoidAsync("alert", "Hello from Blazor!"); // Call function with return value var result = await JS.InvokeAsync("localStorage.getItem", "key"); // Call custom function await JS.InvokeVoidAsync("myApp.initialize", elementRef); } } } ``` **JavaScript file (wwwroot/js/app.js)**: ```javascript window.myApp = { initialize: function(element) { console.log("Initializing", element); }, getData: function() { return { value: 42 }; } }; ``` --- ### Calling Blazor from JavaScript ```csharp // Component public class InteropComponent : ComponentBase { private DotNetObjectReference? dotNetHelper; protected override void OnInitialized() { dotNetHelper = DotNetObjectReference.Create(this); } [JSInvokable] public void CallMeFromJS(string message) { Console.WriteLine($"Called from JS: {message}"); StateHasChanged(); // Re-render if needed } [JSInvokable] public Task GetDataFromBlazor() { return Task.FromResult("Data from Blazor"); } public void Dispose() { dotNetHelper?.Dispose(); } } ``` **JavaScript**: ```javascript // Call static method DotNet.invokeMethodAsync('MyApp', 'StaticMethod', 'arg1'); // Call instance method dotNetHelper.invokeMethodAsync('CallMeFromJS', 'Hello'); ``` --- ## 8. Accessibility (WCAG 2.1 AA) ### Semantic HTML ```razor

Page Title

Article Heading

Content...

``` --- ### ARIA Attributes ```razor @* Button with accessible label *@ @* Form with descriptive text *@ We'll never share your email. @* Hidden decorative icon *@