Understanding Conditional Rendering and Safe Data Operations in Blazor
Introduction: Conditional Rendering in Blazor
Blazor is a modern web UI framework that allows developers to build interactive web applications using C# and Razor syntax. One of its useful features is conditional rendering, which allows the UI to display different content depending on the application's state.
For example, consider a user profile page. If the user is logged in, the application displays their profile information. Otherwise, it displays a login message.
Demo: Conditional Rendering in Blazor
@page "/profile" <h3>User Profile</h3> @if (isLoggedIn) { <p>Welcome back! You are logged in.</p> } else { <p>Please log in to continue.</p> } <button class="btn btn-primary" @onclick="ToggleLogin"> Toggle Login State </button> @code { private bool isLoggedIn = false; private void ToggleLogin() { isLoggedIn = !isLoggedIn; } } How does this work?
- The
@ifdirective determines which content appears on the page. - The
isLoggedInvariable stores the current state. - The
@onclickevent invokes theToggleLogin()method when the button is clicked. - Blazor automatically updates the rendered UI when the component state changes.
This concept is useful when building administrative interfaces, forms, dashboards, and pages where available actions depend on application state.
Understanding Safe Data Deletion in Application Development
Deleting data is a common requirement in web applications, but permanently removing records is not always the best approach. In many business applications, records must remain available for auditing, reporting, and historical reference.
One solution is soft deletion.
Instead of physically removing a record from the database, the application marks it as deleted by updating specific properties. This preserves the original record while allowing the application to exclude it from normal operations.
A safe deletion workflow commonly involves:
- Retrieving the requested record.
- Checking whether the record exists and is eligible for deletion.
- Verifying that no business rule prevents the operation.
- Updating the record's deletion status and audit information.
- Saving the changes to the database.
- Returning a response that communicates the outcome.
This approach separates the decision to allow an operation from the actual database update.
Implementing the Deletion Operation
The following asynchronous C# method demonstrates how a service can implement a controlled deletion workflow using a repository, business-rule validation, and a response object.
Code Implementation
public async Task<DeleteGymCommandResponse> DeleteGymAsync( Guid id, Guid adminUserId, CancellationToken cancellationToken) { var gym = await _gymCenterRepository.GetGymByIdAsync( id, cancellationToken); if (gym == null || gym.IsDeleted) { return new DeleteGymCommandResponse { Success = false, Message = "Gym not found." }; } var hasActiveSubscriptions = await _gymCenterRepository.HasActiveSubscriptionsAsync( id, cancellationToken); if (hasActiveSubscriptions) { return new DeleteGymCommandResponse { Success = false, Message = "This gym cannot be deleted because it has active membership subscriptions." }; } gym.IsDeleted = true; gym.Status = "Inactive"; gym.DeletedBy = adminUserId; gym.DeletedDate = DateTime.UtcNow; await _gymCenterRepository.UpdateGymAsync( gym, cancellationToken); return new DeleteGymCommandResponse { Success = true, Message = "Gym deactivated successfully." }; } Understanding the Implementation Step by Step
1. Retrieving the Existing Record
var gym = await _gymCenterRepository.GetGymByIdAsync( id, cancellationToken); The service requests the record from the repository using its unique identifier.
The await keyword allows the asynchronous database operation to complete without synchronously blocking the calling thread. The cancellation token can also communicate a request to cancel the operation.
2. Checking Whether the Record Can Be Processed
if (gym == null || gym.IsDeleted) { return new DeleteGymCommandResponse { Success = false, Message = "Gym not found." }; } The service checks two conditions:
- The record does not exist.
- The record has already been marked as deleted.
If either condition is true, the method returns a failure response immediately. This prevents the remaining deletion workflow from executing for an unavailable record.
3. Enforcing a Business Rule
var hasActiveSubscriptions = await _gymCenterRepository.HasActiveSubscriptionsAsync( id, cancellationToken); Before proceeding, the service asks the repository whether active subscriptions exist for the specified record.
if (hasActiveSubscriptions) { return new DeleteGymCommandResponse { Success = false, Message = "This gym cannot be deleted because it has active membership subscriptions." }; } If active subscriptions are found, the operation is rejected.
This illustrates an important design principle: business rules should be enforced by the backend, not only by the user interface. Even if a frontend button is disabled, an API request can still be sent independently. Server-side validation is therefore essential.
4. Applying a Soft Delete
gym.IsDeleted = true; gym.Status = "Inactive"; gym.DeletedBy = adminUserId; gym.DeletedDate = DateTime.UtcNow; Instead of removing the database record, the method updates its properties.
-
IsDeletedmarks the record as deleted. -
Statuschanges its operational state to inactive. -
DeletedByrecords the identifier of the administrator responsible for the action. -
DeletedDaterecords when the operation occurred.
Using DateTime.UtcNow provides a UTC timestamp rather than a machine's local time.
These fields make it easier to maintain an audit trail and distinguish deleted records from active ones.
5. Saving the Changes
await _gymCenterRepository.UpdateGymAsync( gym, cancellationToken); The service delegates persistence to the repository. This keeps database operations separate from business logic.
The repository is responsible for updating the tracked entity and saving the changes through Entity Framework Core.
6. Returning the Operation Result
return new DeleteGymCommandResponse { Success = true, Message = "Gym deactivated successfully." }; Once the update completes, the service returns a response indicating success.
The response contains two important properties:
-
Success: indicates whether the operation succeeded. -
Message: provides a readable explanation of the result.
The calling handler or API controller can use this response to determine what information should be returned to the client.
Architectural Responsibilities
This method is part of a layered application design. Each layer has a distinct responsibility.
| Layer | Responsibility |
|---|---|
| Command | Carries the record identifier and administrator identifier. |
| Handler | Receives the command and delegates execution to the service. |
| Service | Enforces business rules and coordinates the operation. |
| Repository interface | Defines the data-access operations required by the service. |
| Repository implementation | Uses Entity Framework Core to query and update database records. |
| Database | Persists the updated record and its audit information. |
Keeping these responsibilities separate makes the application easier to test, maintain, and extend.
Important Considerations
A few details should be verified when implementing this pattern in a production application.
Authorization: The service receives adminUserId, but receiving an identifier does not prove that the caller is an administrator. The API endpoint should enforce authorization, and the administrator identity should come from a trusted, authenticated source.
Audit information: Confirm that the identifier assigned to DeletedBy corresponds to the actual administrator who performed the operation.
Repository behavior: Ensure that UpdateGymAsync() calls SaveChangesAsync() and that the entity is tracked or explicitly attached before saving.
Consistency: If subscriptions can be created concurrently with a deletion request, a simple existence check may not prevent a race condition. Appropriate transaction or concurrency controls may be necessary.
Soft-delete filtering: Normal listing and retrieval operations should exclude soft-deleted records unless the application specifically needs to show historical records.
Conclusion: Displaying Operation Results in Blazor
After a backend operation completes, a Blazor component can display its result to the user. This connects server-side business logic with a responsive frontend experience.
For example, a component can show a success or failure message based on an API response.
Demo: Displaying a Result Message
@if (!string.IsNullOrWhiteSpace(message)) { <div class="alert @(isSuccess ? "alert-success" : "alert-danger")"> @message </div> } @code { private string message = string.Empty; private bool isSuccess; } In a real component, the message and isSuccess properties would be assigned after the API request returns its result.
The @if directive conditionally displays the alert, while Bootstrap's alert-success and alert-danger classes provide visual feedback for successful and failed operations.
For example, the frontend could display a success message after a record is marked as deleted or a failure message when a business rule prevents the operation.
The important point is that the UI should reflect the actual backend response rather than assume that an operation succeeded merely because a button was clicked.
By combining conditional rendering, asynchronous operations, repository-based data access, and explicit response objects, developers can create applications that are easier to understand, maintain, and extend.
