Real code from BlazorBluePrint. Clean Architecture with clear separation of concerns across 5 well-structured projects.
Blazor WebAssembly frontend with MudBlazor components, dark mode, and responsive design.
ASP.NET Core Web API with versioning, Swagger documentation, and rate limiting.
CQRS command/query handlers with MediatR and FluentValidation rules.
Core entities, domain events, interfaces, and business rule constants.
EF Core persistence, Identity authentication, email, and file storage services.
Every API endpoint is protected with fine-grained permission policies. Easy to understand, easy to extend.
[Authorize]
[ApiController]
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[controller]")]
[EnableRateLimiting("api")]
public sealed class UsersController : ControllerBase
{
private readonly IMediator _mediator;
public UsersController(IMediator mediator)
{
_mediator = mediator;
}
[HttpGet]
[Authorize(Policy = Permissions.Users.View)]
public async Task<ActionResult<ApiResponse<IEnumerable<UserDto>>>> GetUsers()
{
var result = await _mediator.Send(new GetUsersQuery());
return Ok(new ApiResponse<IEnumerable<UserDto>>
{
Data = result,
Success = true,
StatusCode = StatusCodes.Status200OK
});
}
[HttpPost]
[Authorize(Policy = Permissions.Users.Create)]
public async Task<ActionResult<ApiResponse<UserDto>>> CreateUser(UserDto user)
{
// Implementation...
}
}
Clean separation of queries and commands. Each handler does one thing well.
public record GetUsersQuery : IRequest<IEnumerable<UserDto>>;
public sealed class GetUsersQueryHandler : IRequestHandler<GetUsersQuery, IEnumerable<UserDto>>
{
private readonly UserManager<AppUser> _userManager;
public GetUsersQueryHandler(UserManager<AppUser> userManager)
{
_userManager = userManager;
}
public async Task<IEnumerable<UserDto>> Handle(
GetUsersQuery request,
CancellationToken cancellationToken)
{
var users = await _userManager.Users.ToListAsync(cancellationToken);
return users.Select(user => new UserDto
{
Id = Guid.Parse(user.Id),
Email = user.Email,
UserName = user.UserName,
FirstName = user.FirstName,
LastName = user.LastName,
IsEmailConfirmed = user.EmailConfirmed,
IsLockedOut = user.LockoutEnd.HasValue && user.LockoutEnd > DateTimeOffset.UtcNow
});
}
}
Pages automatically check permissions. No access? No page.
@page "/settings/security"
@using BlazorBluePrint.Domain.Authorization
@attribute [Authorize(Policy = Permissions.Settings.ViewSecurity)]
<PageTitle>Security Settings</PageTitle>
<MudContainer MaxWidth="MaxWidth.Large" Class="mt-4">
<MudText Typo="Typo.h4" Class="mb-4">@Localizer["SecuritySettings"]</MudText>
<MudCard>
<MudCardContent>
<MudSwitch @bind-Value="settings.RequireTwoFactor"
Label="@Localizer["RequireTwoFactor"]"
Color="Color.Primary" />
<MudTextField @bind-Value="settings.PasswordMinLength"
Label="@Localizer["MinPasswordLength"]"
Variant="Variant.Outlined" />
</MudCardContent>
</MudCard>
</MudContainer>
All permissions defined in one place. Add new permissions in seconds.
public static class Permissions
{
public static class Users
{
public const string View = "Permissions.Users.View";
public const string Create = "Permissions.Users.Create";
public const string Edit = "Permissions.Users.Edit";
public const string Delete = "Permissions.Users.Delete";
}
public static class Roles
{
public const string View = "Permissions.Roles.View";
public const string Create = "Permissions.Roles.Create";
public const string Edit = "Permissions.Roles.Edit";
public const string ManagePermissions = "Permissions.Roles.ManagePermissions";
}
public static class Settings
{
public const string ViewSecurity = "Permissions.Settings.ViewSecurity";
public const string ViewTokens = "Permissions.Settings.ViewTokens";
public const string ViewFileStorage = "Permissions.Settings.ViewFileStorage";
}
// Easy to add more permission groups...
}
Every command is validated before it runs. Clear, testable validation logic separate from your handlers.
public class CreateUserCommandValidator : AbstractValidator<CreateUserCommand>
{
public CreateUserCommandValidator(IUserRepository userRepository)
{
RuleFor(x => x.Email)
.NotEmpty().WithMessage("Email is required")
.EmailAddress().WithMessage("Invalid email format")
.MustAsync(async (email, ct) =>
!await userRepository.ExistsAsync(email, ct))
.WithMessage("Email is already registered");
RuleFor(x => x.FirstName)
.NotEmpty().WithMessage("First name is required")
.MaximumLength(100);
RuleFor(x => x.Password)
.NotEmpty()
.MinimumLength(8).WithMessage("Password must be at least 8 characters")
.Matches("[A-Z]").WithMessage("Must contain an uppercase letter")
.Matches("[0-9]").WithMessage("Must contain a digit");
}
}
Push notifications to specific users or broadcast to all. Built-in hub with typed clients.
[Authorize]
public class NotificationHub : Hub<INotificationClient>
{
private readonly ILogger<NotificationHub> _logger;
public NotificationHub(ILogger<NotificationHub> logger)
{
_logger = logger;
}
public override async Task OnConnectedAsync()
{
var userId = Context.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if (userId != null)
{
await Groups.AddToGroupAsync(Context.ConnectionId, $"user-{userId}");
_logger.LogInformation("User {UserId} connected to notifications", userId);
}
await base.OnConnectedAsync();
}
}
public interface INotificationClient
{
Task ReceiveNotification(NotificationDto notification);
Task ReceiveToast(string message, string severity);
}
Automatic per-tenant database switching. Each tenant's data is completely isolated.
public class TenantDbContextFactory : IDbContextFactory<AppDbContext>
{
private readonly ITenantResolver _tenantResolver;
private readonly IConfiguration _configuration;
public TenantDbContextFactory(
ITenantResolver tenantResolver,
IConfiguration configuration)
{
_tenantResolver = tenantResolver;
_configuration = configuration;
}
public AppDbContext CreateDbContext()
{
var tenant = _tenantResolver.GetCurrentTenant();
var connectionString = tenant?.ConnectionString
?? _configuration.GetConnectionString("DefaultConnection");
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseSqlServer(connectionString)
.Options;
return new AppDbContext(options, _tenantResolver);
}
}
Schedule recurring jobs, fire-and-forget tasks, and delayed operations with automatic retries.
public static class BackgroundJobRegistration
{
public static void RegisterRecurringJobs()
{
RecurringJob.AddOrUpdate<IAuditLogCleanupJob>(
"cleanup-audit-logs",
job => job.ExecuteAsync(CancellationToken.None),
Cron.Daily(2, 0), // Run at 2:00 AM
new RecurringJobOptions { TimeZone = TimeZoneInfo.Utc });
RecurringJob.AddOrUpdate<IDatabaseMaintenanceJob>(
"database-maintenance",
job => job.ExecuteAsync(CancellationToken.None),
Cron.Weekly(DayOfWeek.Sunday, 3, 0));
RecurringJob.AddOrUpdate<ISystemHealthReportJob>(
"system-health-report",
job => job.ExecuteAsync(CancellationToken.None),
Cron.Hourly());
}
}
Every action is tracked. Built-in audit trail with user, IP address, timestamp, and change details.
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
var userId = _currentUserService.UserId;
var auditEntries = new List<AuditEntry>();
foreach (var entry in ChangeTracker.Entries<IAuditableEntity>())
{
switch (entry.State)
{
case EntityState.Added:
entry.Entity.CreatedBy = userId;
entry.Entity.CreatedOn = DateTime.UtcNow;
auditEntries.Add(AuditEntry.ForCreate(entry, userId));
break;
case EntityState.Modified:
entry.Entity.LastModifiedBy = userId;
entry.Entity.LastModifiedOn = DateTime.UtcNow;
auditEntries.Add(AuditEntry.ForUpdate(entry, userId));
break;
case EntityState.Deleted:
auditEntries.Add(AuditEntry.ForDelete(entry, userId));
break;
}
}
var result = await base.SaveChangesAsync(cancellationToken);
await AuditLogs.AddRangeAsync(auditEntries.Select(e => e.ToAuditLog()));
await base.SaveChangesAsync(cancellationToken);
return result;
}
Secure token generation with roles, permissions, and tenant claims baked right in.
public async Task<TokenResponse> GenerateTokenAsync(AppUser user)
{
var roles = await _userManager.GetRolesAsync(user);
var permissions = await _permissionService.GetPermissionsAsync(user.Id);
var claims = new List<Claim>
{
new(ClaimTypes.NameIdentifier, user.Id),
new(ClaimTypes.Email, user.Email!),
new("fullName", $"{user.FirstName} {user.LastName}"),
new("tenantId", user.TenantId ?? string.Empty),
new("avatarUrl", user.AvatarUrl ?? string.Empty)
};
claims.AddRange(roles.Select(r => new Claim(ClaimTypes.Role, r)));
claims.AddRange(permissions.Select(p => new Claim("permission", p)));
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtSettings.Secret));
var token = new JwtSecurityToken(
issuer: _jwtSettings.Issuer,
audience: _jwtSettings.Audience,
claims: claims,
expires: DateTime.UtcNow.AddMinutes(_jwtSettings.ExpiryMinutes),
signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256));
return new TokenResponse(
Token: new JwtSecurityTokenHandler().WriteToken(token),
RefreshToken: GenerateRefreshToken(),
ExpiresAt: token.ValidTo);
}
Export any data grid to Excel or PDF with one click. Built with ClosedXML and QuestPDF.
public class ExcelExportService : IExcelExportService
{
public byte[] ExportToExcel<T>(IEnumerable<T> data, string sheetName = "Sheet1")
{
using var workbook = new XLWorkbook();
var worksheet = workbook.Worksheets.Add(sheetName);
var properties = typeof(T).GetProperties()
.Where(p => p.GetCustomAttribute<ExportIgnoreAttribute>() == null)
.ToList();
// Headers with styling
for (int i = 0; i < properties.Count; i++)
{
var displayName = properties[i].GetCustomAttribute<DisplayNameAttribute>()?.DisplayName
?? properties[i].Name;
var cell = worksheet.Cell(1, i + 1);
cell.Value = displayName;
cell.Style.Font.Bold = true;
cell.Style.Fill.BackgroundColor = XLColor.FromHtml("#667eea");
cell.Style.Font.FontColor = XLColor.White;
}
// Data rows
var items = data.ToList();
for (int row = 0; row < items.Count; row++)
for (int col = 0; col < properties.Count; col++)
worksheet.Cell(row + 2, col + 1).Value =
XLCellValue.FromObject(properties[col].GetValue(items[row]));
worksheet.Columns().AdjustToContents();
using var stream = new MemoryStream();
workbook.SaveAs(stream);
return stream.ToArray();
}
}
Production-ready health checks for database, Hangfire, and external dependencies.
builder.Services.AddHealthChecks()
.AddSqlServer(
connectionString: builder.Configuration.GetConnectionString("DefaultConnection")!,
name: "database",
tags: new[] { "ready" })
.AddHangfire(options =>
{
options.MinimumAvailableServers = 1;
}, name: "hangfire", tags: new[] { "ready" })
.AddCheck<DiskStorageHealthCheck>("disk-storage", tags: new[] { "ready" });
// Map endpoints
app.MapHealthChecks("/health", new HealthCheckOptions
{
Predicate = _ => true,
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("ready")
});
Toggle between light and dark themes with persisted user preference. Custom MudBlazor theme throughout.
<MudThemeProvider @ref="_themeProvider" Theme="_theme" IsDarkMode="@_isDarkMode" />
@code {
private MudThemeProvider _themeProvider = null!;
private bool _isDarkMode;
private readonly MudTheme _theme = new()
{
PaletteLight = new PaletteLight
{
Primary = "#667eea",
Secondary = "#764ba2",
AppbarBackground = "#667eea",
DrawerBackground = "#f8f9fa"
},
PaletteDark = new PaletteDark
{
Primary = "#667eea",
Secondary = "#764ba2",
Surface = "#1e1e2d",
Background = "#151521",
DrawerBackground = "#1e1e2d"
}
};
private async Task ToggleDarkMode()
{
_isDarkMode = !_isDarkMode;
await LocalStorage.SetItemAsync("darkMode", _isDarkMode);
}
}
15+ languages out of the box. Switch languages at runtime with a single click.
<MudMenu Icon="@Icons.Material.Filled.Language" Color="Color.Inherit">
@foreach (var culture in SupportedCultures)
{
<MudMenuItem OnClick="@(() => SetCulture(culture))">
<div class="d-flex align-center gap-2">
<MudImage Src="@($"images/flags/{culture.Name}.svg")"
Width="20" Height="15" />
@culture.NativeName
</div>
</MudMenuItem>
}
</MudMenu>
@code {
private CultureInfo[] SupportedCultures => new[]
{
new CultureInfo("en-US"), new CultureInfo("es-ES"),
new CultureInfo("fr-FR"), new CultureInfo("de-DE"),
new CultureInfo("pt-BR"), new CultureInfo("ja-JP"),
new CultureInfo("zh-CN"), new CultureInfo("ko-KR"),
new CultureInfo("ar-SA"), new CultureInfo("hi-IN"),
// ... 15+ languages supported
};
}
One command to deploy. Production-ready Docker setup with health checks and auto-restart.
services:
db:
image: mcr.microsoft.com/mssql/server:2022-latest
environment:
SA_PASSWORD: ${DB_PASSWORD}
ACCEPT_EULA: "Y"
volumes:
- sqldata:/var/opt/mssql
healthcheck:
test: /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P "$$SA_PASSWORD" -Q "SELECT 1" -C
interval: 10s
retries: 5
app:
build: .
ports:
- "5000:8080"
environment:
- ConnectionStrings__DefaultConnection=Server=db;Database=BlazorBluePrint;User=sa;Password=${DB_PASSWORD};TrustServerCertificate=true
- JwtSettings__Secret=${JWT_SECRET}
depends_on:
db:
condition: service_healthy
healthcheck:
test: curl -f http://localhost:8080/health || exit 1
interval: 30s
retries: 3
volumes:
sqldata:
Get the full source code and start building your enterprise app today.