1. First, understand a Blazor concept with a small demo
Blazor lets you build interactive web interfaces with Razor components.
A Razor component combines HTML-like markup with C# code. The markup
defines what the user sees, while C# handles state and events.
For example, imagine a small component that lets a user click a button
to increase a number.
Click Counter Demo
You clicked the button @clickCount times.
Click me
@code {
private int clickCount = 0;
private void IncreaseCount()
{
clickCount++;
}
}
How this demo works
- Component state: showMessage stores whether the message should be displayed.
- Event handling: @onclick="ToggleMessage" runs the C# method when the button is clicked.
- Conditional rendering: @if (showMessage) tells Blazor to render the paragraph only when the variable is true.
- Dynamic text: The buttonText property changes the button label according to the current state.
- Automatic UI update: When ToggleMessage changes showMessage, Blazor re-renders the component so the message and button label reflect the new value. Initially, showMessage is false, so the paragraph is not displayed and the button says Show Message. Clicking the button changes the value to true, making the paragraph visible and changing the button label to Hide Message. Clicking it again hides the paragraph. This example demonstrates how a Blazor component combines Razor markup, C# state, event handling, and conditional rendering. The demo is independent and uses only a button and a message.
Conditional Rendering Demo
@buttonText
@if (showMessage)
{
The message is now visible.
}
@code {
private bool showMessage = false;
private string buttonText =>
showMessage ? "Hide Message" : "Show Message";
private void ToggleMessage()
{
showMessage = !showMessage;
}
}
- The application calls
CreateSubscriptionAsyncwith the member ID, gym ID, membership-plan ID, start date, payment method, and terms acceptance. - The service validates the IDs, date, payment method, plan, and membership rules.
- The repository checks whether the member already has an active membership at the selected gym and counts active memberships across all gyms.
- If validation succeeds, the service creates a
Subscriptionentity and asks the repository to save it. - The service returns a
SubscriptionCommandResponseindicating success or failure.
The code below is included as provided.
3. Subscription Service
The SubscriptionService contains the business validation and prepares
the subscription entity before saving it.
using GymManagement.Application.Common.Models;
using GymManagement.Application.ServicesContracts;
using GymManagement.Domain.Entities;
using GymManagement.Domain.Enums;
using GymManagement.Domain.RepositoryContracts;
namespace GymManagement.Application.Services;
public class SubscriptionService : ISubscriptionService
{
private readonly ISubscriptionRepository _subscriptionRepository;
public SubscriptionService(
ISubscriptionRepository subscriptionRepository)
{
_subscriptionRepository = subscriptionRepository;
}
public async Task<SubscriptionCommandResponse> CreateSubscriptionAsync(
Guid userId,
Guid gymId,
Guid membershipPlanId,
DateOnly startDate,
PaymentMethod paymentMethod,
bool termsAccepted,
CancellationToken cancellationToken)
{
if (userId == Guid.Empty)
{
return Failure("Invalid member.");
}
if (gymId == Guid.Empty)
{
return Failure("Invalid gym.");
}
if (membershipPlanId == Guid.Empty)
{
return Failure("Invalid membership plan.");
}
// Validate the membership start date.
var today = DateOnly.FromDateTime(DateTime.UtcNow);
if (startDate < today)
{
return Failure(
"The membership start date cannot be in the past.");
}
// Validate the enum value.
if (!Enum.IsDefined(typeof(PaymentMethod), paymentMethod))
{
return Failure("Invalid payment method.");
}
// Retrieve the selected membership plan.
var plan = await _subscriptionRepository.GetMembershipPlanByIdAsync(
membershipPlanId,
cancellationToken);
if (plan == null)
{
return Failure("Membership plan not found.");
}
// Ensure the plan belongs to the selected gym.
if (plan.GymId != gymId)
{
return Failure(
"The selected membership plan does not belong to this gym.");
}
// Prevent more than one active membership in the same gym.
var alreadySubscribed =
await _subscriptionRepository.HasActiveMembershipInGymAsync(
userId,
gymId,
cancellationToken);
if (alreadySubscribed)
{
return Failure(
"You cannot have more than one active membership at the same gym.");
}
// Limit the member to three active memberships across all gyms.
var activeMembershipCount =
await _subscriptionRepository.CountActiveMembershipsAsync(
userId,
cancellationToken);
if (activeMembershipCount >= 3)
{
return Failure(
"You cannot have more than three active memberships across all gyms.");
}
// Validate the plan duration.
if (plan.DurationInMonths <= 0)
{
return Failure(
"The membership plan duration must be greater than zero.");
}
// Calculate expiry using DateOnly.
DateOnly endDate;
try
{
endDate = startDate.AddMonths(plan.DurationInMonths);
}
catch (ArgumentOutOfRangeException)
{
return Failure(
"Unable to calculate the membership expiry date.");
}
// Set server-generated timestamps and status.
var now = DateTime.UtcNow;
var subscription = new Subscription
{
UserId = userId,
MembershipPlanId = plan.Id,
StartDate = startDate,
EndDate = endDate,
PaymentMethod = paymentMethod,
TermsAccepted = termsAccepted,
SubscribedOn = now,
CreatedDate = now,
Status = "Active",
PricePaid = plan.Price
};
await _subscriptionRepository.AddSubscriptionAsync(
subscription,
cancellationToken);
return new SubscriptionCommandResponse
{
Success = true,
Message = "Membership subscription created successfully."
};
}
private static SubscriptionCommandResponse Failure(string message)
{
return new SubscriptionCommandResponse
{
Success = false,
Message = message
};
}
}
------------------------------------------------------------------------
## 4. Subscription Repository
The `SubscriptionRepository` uses `AppDbContext` and Entity Framework
Core to read membership plans, check active memberships, count
memberships, and save a new subscription.
csharp
using GymManagement.Domain.Entities;
using GymManagement.Domain.RepositoryContracts;
using GymManagement.Infrastructure.Context;
using Microsoft.EntityFrameworkCore;
namespace GymManagement.Infrastructure.Repository;
public class SubscriptionRepository : ISubscriptionRepository
{
private readonly AppDbContext _context;
public SubscriptionRepository(AppDbContext context)
{
_context = context;
}
public async Task<MembershipPlan?> GetMembershipPlanByIdAsync(
Guid planId,
CancellationToken cancellationToken)
{
return await _context.MembershipPlans
.AsNoTracking()
.FirstOrDefaultAsync(
p => p.Id == planId,
cancellationToken);
}
public async Task<bool> HasActiveMembershipInGymAsync(
Guid userId,
Guid gymId,
CancellationToken cancellationToken)
{
var today = DateOnly.FromDateTime(DateTime.UtcNow);
return await _context.Subscriptions
.AnyAsync(s =>
s.UserId == userId &&
s.MembershipPlan != null &&
s.MembershipPlan.GymId == gymId &&
s.Status == "Active" &&
s.EndDate >= today,
cancellationToken);
}
public async Task<int> CountActiveMembershipsAsync(
Guid userId,
CancellationToken cancellationToken)
{
var today = DateOnly.FromDateTime(DateTime.UtcNow);
return await _context.Subscriptions
.CountAsync(s =>
s.UserId == userId &&
s.Status == "Active" &&
s.EndDate >= today,
cancellationToken);
}
public async Task AddSubscriptionAsync(
Subscription subscription,
CancellationToken cancellationToken)
{
await _context.Subscriptions.AddAsync(
subscription,
cancellationToken);
await _context.SaveChangesAsync(cancellationToken);
}
}
Top comments (0)