Signup Form
Here is the complete Blazor Signup form:
@page "/signup"
@using System.ComponentModel.DataAnnotations
@inject HttpClient http
@inject NavigationManager navigation
@inject SweetAlertService Swal
<div class="d-flex justify-content-center">
<div class="w-25 rounded border shadow">
<EditForm Enhance
EditContext="editContext"
method="post"
FormName="Signup"
OnValidSubmit="SignUped"
class="bg-light rounded p-4 shadow-sm">
<DataAnnotationsValidator />
<div class="mb-3">
<label class="form-label">Name</label>
<InputText class="form-control" @bind-Value="user.Name" />
<ValidationMessage For="@(() => user.Name)" />
</div>
<div class="mb-3">
<label>Email</label>
<InputText class="form-control" @bind-Value="user.Email" />
<ValidationMessage For="@(() => user.Email)" />
</div>
<div class="mb-3">
<label>Password</label>
<div class="d-flex flex-row">
<InputText
type="@(IsPasswordVisible ? "password" : "text")"
class="form-control d-flex"
@bind-Value:after="OnPassChanged"
@bind-Value="user.Password" />
<button class="btn border"
type="button"
@onclick="() => IsPasswordVisible = !IsPasswordVisible">
<i class="bi @(IsPasswordVisible
? "bi-eye-slash"
: "bi-eye")">
</i>
</button>
</div>
<ValidationMessage For="@(() => user.Password)" />
</div>
<div class="mb-3">
<label>Confirm Password</label>
<InputText class="form-control"
@bind-Value="user.ConfirmPassword" />
<ValidationMessage For="@(() => user.ConfirmPassword)" />
</div>
<div class="mb-3">
<label>DateOfBirth</label>
<InputDate class="form-control"
@bind-Value="user.DateOfBirth" />
<ValidationMessage For="@(() => user.DateOfBirth)" />
</div>
<div class="mb-3">
<label>Address</label>
<InputText class="form-control"
@bind-Value="user.Address" />
<ValidationMessage For="@(() => user.Address)" />
</div>
<div class="mb-3">
<label>PhoneNumber</label>
<InputText class="form-control"
@bind-Value="user.PhoneNumber" />
<ValidationMessage For="@(() => user.PhoneNumber)" />
</div>
<div class="d-flex justify-content-around flex-row">
<button type="submit"
class="btn btn-primary">
SignUp
</button>
<button type="button"
@onclick="GoToLogin"
class="btn btn-link">
Login
</button>
</div>
</EditForm>
</div>
</div>
Code Behind
The @code section contains the component's logic, API call, navigation, and validation models.
@code {
private string errorMessage = "";
public User user = new();
bool IsPasswordVisible = false;
EditContext? editContext;
protected override void OnInitialized()
{
editContext = new EditContext(user);
}
void OnPassChanged()
{
if (editContext is null)
{
return;
}
var ConfirmPassWordFields =
editContext.Field(nameof(user.ConfirmPassword));
editContext.NotifyFieldChanged(ConfirmPassWordFields);
}
public async Task SignUped()
{
var response =
await http.PostAsJsonAsync(
"api/Auth/signup",
user);
if (response.IsSuccessStatusCode)
{
Console.WriteLine(
response.Content.ToString());
navigation.NavigateTo("/login");
}
else
{
// Get error message returned by API
var message =
await response.Content
.ReadFromJsonAsync<ErrorResponse>();
List<string> showError =
message.Message.Split(",").ToList();
var stringOfError = "";
foreach (var i in showError)
{
stringOfError += $"{i} \n";
}
await Swal.FireAsync(
new SweetAlertOptions
{
Title = stringOfError,
Icon = SweetAlertIcon.Error
});
}
}
void GoToLogin()
{
navigation.NavigateTo("login");
}
public class User
{
[Required]
[RegularExpression(
@"^[A-Za-z\s]+$",
ErrorMessage =
"Only alphabets and spaces are allowed.")]
[MaxLength(100)]
public string Name { get; set; }
[Required]
[RegularExpression(
@"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$",
ErrorMessage = "Enter Valid Email")]
[EmailAddress(
ErrorMessage = "Invalid email address.")]
public string Email { get; set; }
[Required(
ErrorMessage = "Password is required")]
[RegularExpression(
@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])" +
@"[A-Za-z\d@$!%*?&]{8,}$",
ErrorMessage = "Enter Valid Passord")]
[DataType(DataType.Password)]
public string Password { get; set; }
[Required(
ErrorMessage = "Confirm Password is required")]
[DataType(DataType.Password)]
[Compare(
nameof(Password),
ErrorMessage = "Passwords do not match")]
public string ConfirmPassword { get; set; }
[Required]
[MinimumAge(18)]
public DateOnly? DateOfBirth { get; set; }
[Required]
[StringLength(
300,
ErrorMessage =
"Home Address cannot exceed 300 characters.")]
public string? Address { get; set; }
[Required]
[RegularExpression(
@"^\d{10}$",
ErrorMessage =
"Phone number must be exactly 10 digits.")]
public string? PhoneNumber { get; set; }
}
public class ErrorResponse
{
public int Statuscode { get; set; }
public string Message { get; set; }
public string? Details { get; set; }
public DateTime TimeSpent { get; set; }
}
}
Creating a Custom Minimum Age Validation Attribute
For the Date of Birth field, I created a custom validation attribute called MinimumAgeAttribute.
This allows us to validate that the user is at least 18 years old.
Create a file named:
MinimumAgeAttribute.cs
using System.ComponentModel.DataAnnotations;
namespace Frontend.Services
{
public class MinimumAgeAttribute : ValidationAttribute
{
private readonly int _minimumAge;
public MinimumAgeAttribute(int minimumAge)
{
_minimumAge = minimumAge;
ErrorMessage =
$"You must be at least {_minimumAge} years old.";
}
protected override ValidationResult? IsValid(
object? value,
ValidationContext validationContext)
{
// No value - let [Required] handle null
if (value == null)
{
return ValidationResult.Success;
}
// DateOnly? is boxed as DateOnly when it has a value
if (value is not DateOnly dob)
{
return ValidationResult.Success;
}
var today =
DateOnly.FromDateTime(DateTime.Today);
// Prevent future dates
if (dob > today)
{
return new ValidationResult(
"Date of birth cannot be in the future.",
new[]
{
validationContext.MemberName!
});
}
// Calculate age
var age = today.Year - dob.Year;
if (dob > today.AddYears(-age))
{
age--;
}
// Check minimum age
if (age < _minimumAge)
{
return new ValidationResult(
ErrorMessage,
new[]
{
validationContext.MemberName!
});
}
return ValidationResult.Success;
}
}
}
Using the Custom Attribute
Now we can use the custom attribute on the DateOfBirth property:
[Required]
[MinimumAge(18)]
public DateOnly? DateOfBirth { get; set; }
The 18 passed to [MinimumAge(18)] becomes the minimum age required for the user.
For example:
MinimumAge(18)
↓
User enters Date of Birth
↓
Calculate user's age
↓
Is age >= 18?
↙ ↘
Yes No
↓ ↓
Valid Error
Why use a custom ValidationAttribute?
Instead of putting the age calculation directly inside the Blazor component, we keep the validation logic in a reusable class.
This makes the component cleaner and allows the same validation attribute to be reused for other models or forms.
The attribute also handles an important edge case: a future Date of Birth is rejected with a specific validation message.
Top comments (0)