Understanding Blazor Component State

Blazor components can respond to user interactions by updating component
state and rendering the UI again. A variable such as a Boolean can
control whether a section is visible, while event handlers connect
buttons and form submissions to C# methods. Data binding keeps input
values synchronized with component properties, and EditForm can use
validation attributes to check values before calling a submit handler.

@page "/component-state-demo"

Component State Demo

 <h3> Understanding Component State </h3> <p> The current counter value is stored in component state. </p> @count Increase Reset <hr> <p> @message </p> 

@code {
private int count = 0;

private string message = "Click Increase to update the state."; private void IncrementCount() { count++; message = $"The state changed. Current value: {count}"; } private void ResetCount() { count = 0; message = "The counter state has been reset."; } 

}

The following examples demonstrate these ideas through complete Razor
components. The code is kept in separate blocks so each component can be
copied and reviewed independently.

@page "/admin/membership-plans"
@attribute [Microsoft.AspNetCore.Authorization.Authorize(Roles = "Admin")]

@using System.Net.Http.Json
@using System.ComponentModel.DataAnnotations
@using Microsoft.AspNetCore.Components.Forms

@inject HttpClient Http
@inject IJSRuntime JS

Manage Membership Plans

 <h2>Manage Membership Plans</h2> <p>Create and maintain gym membership plans.</p> Add Plan Search Gym All gyms @foreach (var gym in gyms) { @gym.GymName } Sort by Plan name Price Duration Gym Status Page size 5 10 20 @if (isLoading) { <p>Loading membership plans...</p> } else if (!string.IsNullOrWhiteSpace(errorMessage)) { @errorMessage Retry } else { @foreach (var plan in PagedPlans) { } @if (PagedPlans.Count == 0) { } <table> <thead> <tr> <th>Plan</th> <th>Gym</th> <th>Price</th> <th>Duration</th> <th>Classes/week</th> <th>Personal training</th> <th>Locker</th> <th>Guest access</th> <th>Status</th> <th>Actions</th> </tr> </thead> <tbody><tr> <td>@plan.PlanName</td> <td>@plan.GymName</td> <td>@plan.Price.ToString("C")</td> <td>@plan.DurationInMonths months</td> <td>@plan.ClassSessionsPerWeek</td> <td>@plan.PersonalTrainingSessions</td> <td>@(plan.LockerAccess ? "Yes" : "No")</td> <td> @(plan.GuestAccess ? $"Yes ({plan.GuestPasses} passes)" : "No") </td> <td> <span> @plan.Status </span> </td> <td> </td> </tr><tr> <td colspan="10"> No membership plans found. </td> </tr></tbody> </table> <span> Showing @ShowingFrom–@ShowingTo of @FilteredPlans.Count plans </span> Previous <span>Page @currentPage of @TotalPages</span> Next } 

@if (showForm)
{




@(isEditMode ? "Edit Plan" : "Add Plan")


 <div class="card-body"> <EditForm Model="@formModel"> <DataAnnotationsValidator /> <ValidationSummary /> <div class="row g-3"> <div class="col-12"> <label class="form-label">Gym</label> <InputSelect class="form-select" @bind-Value="formModel.GymId"> <option value="@Guid.Empty">Select gym</option> @foreach (var gym in gyms.Where(g => g.Status == "Active")) { <option value="@gym.Id">@gym.GymName</option> } </InputSelect> <ValidationMessage For="@(() => formModel.GymId)" /> </div> <div class="col-md-6"> <label class="form-label">Plan name</label> <InputText class="form-control" @bind-Value="formModel.PlanName" /> <ValidationMessage For="@(() => formModel.PlanName)" /> </div> <div class="col-md-6"> <label class="form-label">Price</label> <InputNumber class="form-control" @bind-Value="formModel.Price" /> <ValidationMessage For="@(() => formModel.Price)" /> </div> <div class="col-md-6"> <label class="form-label">Duration (months)</label> <InputNumber class="form-control" @bind-Value="formModel.DurationInMonths" /> <ValidationMessage For="@(() => formModel.DurationInMonths)" /> </div> <div class="col-md-6"> <label class="form-label">Class sessions per week</label> <InputNumber class="form-control" @bind-Value="formModel.ClassSessionsPerWeek" /> </div> <div class="col-md-6"> <label class="form-label">Personal training sessions</label> <InputNumber class="form-control" @bind-Value="formModel.PersonalTrainingSessions" /> </div> <div class="col-md-6"> <label class="form-label">Guest passes</label> <InputNumber class="form-control" @bind-Value="formModel.GuestPasses" /> </div> <div class="col-md-6"> <label class="form-label">Status</label> <InputSelect class="form-select" @bind-Value="formModel.Status"> <option value="Active">Active</option> <option value="Inactive">Inactive</option> </InputSelect> </div> <div class="col-md-6 form-check ms-2"> <InputCheckbox class="form-check-input" @bind-Value="formModel.LockerAccess" /> <label class="form-check-label">Locker access</label> </div> <div class="col-md-6 form-check ms-2"> <InputCheckbox class="form-check-input" @bind-Value="formModel.GuestAccess" /> <label class="form-check-label">Guest access</label> </div> </div> <div class="d-flex justify-content-end gap-2 mt-4"> <button type="button" class="btn btn-outline-secondary" @onclick="CloseForm" disabled="@isSaving"> Cancel </button> <button type="submit" class="btn btn-primary" disabled="@isSaving"> @(isSaving ? "Saving..." : isEditMode ? "Update Plan" : "Save Plan") </button> </div> </EditForm> </div> </div> </div> 

}

@code {
public class GymOption
{
public Guid Id { get; set; }
public string GymName { get; set; } = "";
public string Status { get; set; } = "";
}

public class PlanRow { public Guid Id { get; set; } public Guid GymId { get; set; } public string GymName { get; set; } = ""; public string PlanName { get; set; } = ""; public decimal Price { get; set; } public int DurationInMonths { get; set; } public int ClassSessionsPerWeek { get; set; } public int PersonalTrainingSessions { get; set; } public bool LockerAccess { get; set; } public bool GuestAccess { get; set; } public int GuestPasses { get; set; } public string Status { get; set; } = "Active"; } public class PlanForm { public Guid Id { get; set; } [Required] public Guid GymId { get; set; } [Required, StringLength(100)] public string PlanName { get; set; } = ""; [Range(typeof(decimal), "0.01", "100000000")] public decimal Price { get; set; } [Range(1, 120)] public int DurationInMonths { get; set; } = 1; [Range(0, 100)] public int ClassSessionsPerWeek { get; set; } [Range(0, 100)] public int PersonalTrainingSessions { get; set; } public bool LockerAccess { get; set; } public bool GuestAccess { get; set; } [Range(0, 10000)] public int GuestPasses { get; set; } public string Status { get; set; } = "Active"; } public class OperationResponse { public bool Success { get; set; } public string Message { get; set; } = ""; } private List<GymOption> gyms = new(); private List<PlanRow> plans = new(); private PlanForm formModel = new(); private bool isLoading; private bool isSaving; private bool showForm; private bool isEditMode; private string errorMessage = ""; private string searchText = ""; private string sortBy = "name"; private string selectedGymId = ""; private int currentPage = 1; private int pageSize = 10; private List<PlanRow> FilteredPlans { get { IEnumerable<PlanRow> query = plans; if (!string.IsNullOrWhiteSpace(searchText)) { query = query.Where(p => p.PlanName.Contains(searchText, StringComparison.OrdinalIgnoreCase) || p.GymName.Contains(searchText, StringComparison.OrdinalIgnoreCase)); } if (Guid.TryParse(selectedGymId, out var gymId)) query = query.Where(p => p.GymId == gymId); query = sortBy switch { "price" => query.OrderBy(p => p.Price), "duration" => query.OrderBy(p => p.DurationInMonths), "gym" => query.OrderBy(p => p.GymName), "status" => query.OrderBy(p => p.Status), _ => query.OrderBy(p => p.PlanName) }; return query.ToList(); } } private List<PlanRow> PagedPlans => FilteredPlans.Skip((currentPage - 1) * pageSize).Take(pageSize).ToList(); private int TotalPages => Math.Max(1, (int)Math.Ceiling(FilteredPlans.Count / (double)pageSize)); private int ShowingFrom => FilteredPlans.Count == 0 ? 0 : ((currentPage - 1) * pageSize) + 1; private int ShowingTo => Math.Min(currentPage * pageSize, FilteredPlans.Count); protected override async Task OnInitializedAsync() => await LoadDataAsync(); private async Task LoadDataAsync() { isLoading = true; errorMessage = ""; try { gyms = await Http.GetFromJsonAsync<List<GymOption>>("api/GymCenter") ?? new(); plans = await Http.GetFromJsonAsync<List<PlanRow>>("api/MembershipPlan") ?? new(); currentPage = Math.Min(currentPage, TotalPages); } catch { errorMessage = "Unable to load gyms or membership plans. Verify the API endpoints."; } finally { isLoading = false; } } private void ResetPage() => currentPage = 1; private void PreviousPage() { if (currentPage > 1) currentPage--; } private void NextPage() { if (currentPage < TotalPages) currentPage++; } private void OpenAddForm() { formModel = new PlanForm(); isEditMode = false; showForm = true; } private void OpenEditForm(Guid id) { var plan = plans.FirstOrDefault(p => p.Id == id); if (plan is null) return; formModel = new PlanForm { Id = plan.Id, GymId = plan.GymId, PlanName = plan.PlanName, Price = plan.Price, DurationInMonths = plan.DurationInMonths, ClassSessionsPerWeek = plan.ClassSessionsPerWeek, PersonalTrainingSessions = plan.PersonalTrainingSessions, LockerAccess = plan.LockerAccess, GuestAccess = plan.GuestAccess, GuestPasses = plan.GuestPasses, Status = plan.Status }; isEditMode = true; showForm = true; } private async Task SavePlanAsync() { if (formModel.GymId == Guid.Empty) { await ShowMessageAsync("Select a gym", "Please select a gym for this plan.", false); return; } if (formModel.GuestAccess == false) formModel.GuestPasses = 0; isSaving = true; try { HttpResponseMessage response = isEditMode ? await Http.PutAsJsonAsync($"api/MembershipPlan/{formModel.Id}", formModel) : await Http.PostAsJsonAsync("api/MembershipPlan", formModel); var result = await response.Content.ReadFromJsonAsync<OperationResponse>(); if (!response.IsSuccessStatusCode || result?.Success != true) { await ShowMessageAsync("Unable to save plan", result?.Message ?? "The plan could not be saved.", false); return; } showForm = false; await LoadDataAsync(); await ShowMessageAsync("Success", result.Message, true); } catch { await ShowMessageAsync("Error", "An error occurred while saving the plan.", false); } finally { isSaving = false; } } private async Task DeletePlanAsync(Guid id) { var confirmed = await JS.InvokeAsync<bool>( "gymUi.confirmDelete", "Delete membership plan?", "The API will check whether active subscriptions prevent deletion."); if (!confirmed) return; try { var response = await Http.DeleteAsync($"api/MembershipPlan/{id}"); var result = await response.Content.ReadFromJsonAsync<OperationResponse>(); if (!response.IsSuccessStatusCode || result?.Success != true) { await ShowMessageAsync("Unable to delete", result?.Message ?? "The plan could not be deleted.", false); return; } await LoadDataAsync(); await ShowMessageAsync("Success", result.Message, true); } catch { await ShowMessageAsync("Error", "An error occurred while deleting the plan.", false); } } private void CloseForm() { if (!isSaving) showForm = false; } private Task ShowMessageAsync(string title, string message, bool success) => JS.InvokeVoidAsync("gymUi.showMessage", title, message, success).AsTask(); 

}

@page "/admin/memberships"
@attribute [Microsoft.AspNetCore.Authorization.Authorize(Roles = "Admin")]

@using System.Net.Http.Json
@using System.Globalization

@inject HttpClient Http

Manage Memberships

Manage Memberships

Review subscriptions, registration dates, and membership status.

Search Gym All gyms @foreach (var gym in GymOptions) { @gym.GymName } Member All members @foreach (var member in MemberOptions) { @member.MemberName } Plan All plans @foreach (var plan in PlanOptions) { @plan.PlanName } Status All statuses Pending Active Expiring Soon Expired Cancelled Registered from Registered to Sort by Registration date Member Gym Plan Start date End date Status Page size 5 10 20 Clear filters Refresh @if (isLoading) {

Loading memberships...

} else if (!string.IsNullOrWhiteSpace(errorMessage)) { @errorMessage Retry } else { @foreach (var membership in PagedMemberships) { var status = GetMembershipStatus(membership); } @if (PagedMemberships.Count == 0) { }
Member Gym Plan Registered Start date End date Status
@membership.MemberName @membership.MemberEmail @membership.GymName @membership.PlanName @membership.RegistrationDate.ToString("dd MMM yyyy") @membership.StartDate.ToString("dd MMM yyyy") @membership.EndDate.ToString("dd MMM yyyy") @status
No memberships match the selected filters.
Showing @ShowingFrom–@ShowingTo of @FilteredMemberships.Count memberships Previous Page @currentPage of @TotalPages Next }

@code {
public class MembershipRowModel
{
public Guid Id { get; set; }
public Guid GymId { get; set; }
public Guid MembershipPlanId { get; set; }
public Guid UserId { get; set; }

 public string GymName { get; set; } = ""; public string MemberName { get; set; } = ""; public string MemberEmail { get; set; } = ""; public string PlanName { get; set; } = ""; public DateTime RegistrationDate { get; set; } public DateTime StartDate { get; set; } public DateTime EndDate { get; set; } // Expected values: Pending, Active, Cancelled, Expired. // Expiring Soon is derived from an active membership near its end date. public string Status { get; set; } = ""; } public class GymOption { public Guid Id { get; set; } public string GymName { get; set; } = ""; } public class MemberOption { public Guid Id { get; set; } public string MemberName { get; set; } = ""; } public class PlanOption { public Guid Id { get; set; } public string PlanName { get; set; } = ""; } private List<MembershipRowModel> memberships = new(); private bool isLoading; private string errorMessage = ""; private string searchText = ""; private string selectedGymId = ""; private string selectedUserId = ""; private string selectedPlanId = ""; private string selectedStatus = ""; private string sortBy = "registration"; private DateTime? registrationFrom; private DateTime? registrationTo; private int currentPage = 1; private int pageSize = 10; private List<GymOption> GymOptions => memberships.GroupBy(m => m.GymId) .Select(g => new GymOption { Id = g.Key, GymName = g.First().GymName }) .OrderBy(g => g.GymName) .ToList(); private List<MemberOption> MemberOptions => memberships.GroupBy(m => m.UserId) .Select(g => new MemberOption { Id = g.Key, MemberName = g.First().MemberName }) .OrderBy(m => m.MemberName) .ToList(); private List<PlanOption> PlanOptions => memberships.GroupBy(m => m.MembershipPlanId) .Select(g => new PlanOption { Id = g.Key, PlanName = g.First().PlanName }) .OrderBy(p => p.PlanName) .ToList(); private List<MembershipRowModel> FilteredMemberships { get { IEnumerable<MembershipRowModel> query = memberships; if (!string.IsNullOrWhiteSpace(searchText)) { query = query.Where(m => m.MemberName.Contains(searchText, StringComparison.OrdinalIgnoreCase) || m.MemberEmail.Contains(searchText, StringComparison.OrdinalIgnoreCase) || m.GymName.Contains(searchText, StringComparison.OrdinalIgnoreCase) || m.PlanName.Contains(searchText, StringComparison.OrdinalIgnoreCase)); } if (Guid.TryParse(selectedGymId, out var gymId)) query = query.Where(m => m.GymId == gymId); if (Guid.TryParse(selectedUserId, out var userId)) query = query.Where(m => m.UserId == userId); if (Guid.TryParse(selectedPlanId, out var planId)) query = query.Where(m => m.MembershipPlanId == planId); if (registrationFrom.HasValue) query = query.Where(m => m.RegistrationDate.Date >= registrationFrom.Value.Date); if (registrationTo.HasValue) query = query.Where(m => m.RegistrationDate.Date <= registrationTo.Value.Date); if (!string.IsNullOrWhiteSpace(selectedStatus)) query = query.Where(m => GetMembershipStatus(m) == selectedStatus); query = sortBy switch { "member" => query.OrderBy(m => m.MemberName), "gym" => query.OrderBy(m => m.GymName), "plan" => query.OrderBy(m => m.PlanName), "start" => query.OrderBy(m => m.StartDate), "end" => query.OrderBy(m => m.EndDate), "status" => query.OrderBy(m => GetMembershipStatus(m)), _ => query.OrderByDescending(m => m.RegistrationDate) }; return query.ToList(); } } private List<MembershipRowModel> PagedMemberships => FilteredMemberships.Skip((currentPage - 1) * pageSize).Take(pageSize).ToList(); private int TotalPages => Math.Max(1, (int)Math.Ceiling(FilteredMemberships.Count / (double)pageSize)); private int ShowingFrom => FilteredMemberships.Count == 0 ? 0 : ((currentPage - 1) * pageSize) + 1; private int ShowingTo => Math.Min(currentPage * pageSize, FilteredMemberships.Count); protected override async Task OnInitializedAsync() => await LoadMembershipsAsync(); private async Task LoadMembershipsAsync() { isLoading = true; errorMessage = ""; try { memberships = await Http.GetFromJsonAsync<List<MembershipRowModel>>( "api/Subscription/AdminList") ?? new(); currentPage = Math.Min(currentPage, TotalPages); } catch { errorMessage = "Unable to load memberships. Check that api/Subscription/AdminList exists and returns the expected data."; } finally { isLoading = false; } } private static string GetMembershipStatus(MembershipRowModel membership) { // Cancellation always takes precedence over date-based statuses. if (string.Equals(membership.Status, "Cancelled", StringComparison.OrdinalIgnoreCase)) return "Cancelled"; if (string.Equals(membership.Status, "Pending", StringComparison.OrdinalIgnoreCase) || membership.StartDate.Date > DateTime.Today) return "Pending"; if (membership.EndDate.Date < DateTime.Today || string.Equals(membership.Status, "Expired", StringComparison.OrdinalIgnoreCase)) return "Expired"; var daysRemaining = (membership.EndDate.Date - DateTime.Today).Days; if (daysRemaining <= 7) return "Expiring Soon"; return "Active"; } private static string GetStatusBadgeClass(string status) => status switch { "Pending" => "bg-warning text-dark", "Active" => "bg-success", "Expiring Soon" => "bg-info text-dark", "Expired" => "bg-secondary", "Cancelled" => "bg-danger", _ => "bg-secondary" }; private void ResetPage() => currentPage = 1; private void PreviousPage() { if (currentPage > 1) currentPage--; } private void NextPage() { if (currentPage < TotalPages) currentPage++; } private void ClearFilters() { searchText = ""; selectedGymId = ""; selectedUserId = ""; selectedPlanId = ""; selectedStatus = ""; registrationFrom = null; registrationTo = null; sortBy = "registration"; currentPage = 1; } 

}