dotnet new blazor -o DnsMan --auth Individual
cd DnsMan
dotnet add package Pomelo.EntityFrameworkCore.MySql --version 9.0.*
dotnet add package Microsoft.EntityFrameworkCore.Design --version 9.0.*
dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore --version 9.0.*
dotnet add package MudBlazor
MariaDb Connection string
Server=192.168.1.154;Port=3306;Database=DnsMan;User=user;Password=password;
Program.cs
remove
app.UseAntiforgery();
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlite(connectionString));
Add
using Pomelo.EntityFrameworkCore.MySql.Infrastructure;
builder.Services.AddDbContext<ApplicationDbContext>(options => options.UseMySql(
builder.Configuration.GetConnectionString("DefaultConnection"),
ServerVersion.AutoDetect(
builder.Configuration.GetConnectionString("DefaultConnection")
)
));
Create SeedData.cs
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace DnsMan.Data;
public static class SeedData
{
public static async Task Initialize(IServiceProvider serviceProvider)
{
using var scope = serviceProvider.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
var roleManager = scope.ServiceProvider.GetRequiredService<RoleManager<IdentityRole>>();
// Ensure DB exists
await context.Database.MigrateAsync();
// ---- Roles ----
const string adminRole = "Admin";
if (!await roleManager.RoleExistsAsync(adminRole))
{
await roleManager.CreateAsync(new IdentityRole(adminRole));
}
// ---- Admin User ----
const string adminEmail = "admin@dnsman.local";
const string adminPassword = "Admin123!"; // change in production
var adminUser = await userManager.FindByEmailAsync(adminEmail);
if (adminUser == null)
{
adminUser = new ApplicationUser
{
UserName = adminEmail,
Email = adminEmail,
EmailConfirmed = true
};
var result = await userManager.CreateAsync(adminUser, adminPassword);
if (!result.Succeeded)
{
throw new Exception(
"Failed to create admin user: " +
string.Join(", ", result.Errors.Select(e => e.Description)));
}
}
if (!await userManager.IsInRoleAsync(adminUser, adminRole))
{
await userManager.AddToRoleAsync(adminUser, adminRole);
}
// ---- Dummy User ----
const string userEmail = "user@dnsman.local";
const string userPassword = "User123!";
var dummyUser = await userManager.FindByEmailAsync(userEmail);
if (dummyUser == null)
{
dummyUser = new ApplicationUser
{
UserName = userEmail,
Email = userEmail,
EmailConfirmed = true
};
await userManager.CreateAsync(dummyUser, userPassword);
}
}
}
Add this to the bottom (just beforeapp.Run();
// Apply migrations and seed database
using (var scope = app.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
db.Database.Migrate(); // ensure tables exist
await SeedData.Initialize(scope.ServiceProvider);
}
app.UseAuthentication();
app.UseAuthorization();
app.UseAntiforgery(); // <- required
app.UseRouting();
Change in Program.cs
builder.Services.AddIdentityCore<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddSignInManager()
.AddDefaultTokenProviders();
to
builder.Services.AddIdentityCore<ApplicationUser>(options =>
options.SignIn.RequireConfirmedAccount = true)
.AddRoles<IdentityRole>() // 👈 THIS LINE
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddSignInManager()
.AddDefaultTokenProviders();
Check here in Program.cs that all this is set
app.UseHttpsRedirection();
app.MapStaticAssets();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseAntiforgery(); // <- required
app.MapRazorComponents<App>()
Add to _Imports.razor
@using MudBlazor
Add to Program.cs
using MudBlazor.Services;
builder.Services.AddMudServices();
Replace App.razor
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<base href="/" />
<link rel="stylesheet" href="@Assets["app.css"]" />
<link rel="stylesheet" href="@Assets["DnsMan.styles.css"]" />
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" />
<link href="@Assets["_content/MudBlazor/MudBlazor.min.css"]" rel="stylesheet" />
<ImportMap />
<link rel="icon" type="image/png" href="favicon.png" />
<HeadOutlet />
</head>
<body>
<Routes />
<script src="_framework/blazor.web.js"></script>
<script src="@Assets["_content/MudBlazor/MudBlazor.min.js"]"></script>
</body>
</html>
Improved MainLayout.razor
@inherits LayoutComponentBase
@* Required *@
<MudThemeProvider />
<MudPopoverProvider />
@* Needed for dialogs *@
<MudDialogProvider />
@* Needed for snackbars *@
<MudSnackbarProvider />
<MudPaper Class="layout-container">
<!-- Header (full width) -->
<MudPaper Class="header" Elevation="0">
<MudText Typo="Typo.h6">Header Section</MudText>
</MudPaper>
<!-- Main content area (3 panels) -->
<div class="main-content">
<!-- Left panel -->
<MudPaper Class="left-panel" Elevation="0">
<NavMenu />
</MudPaper>
<!-- Center panel -->
<MudPaper Class="center-panel" Elevation="0">
@Body
</MudPaper>
<!-- Right panel -->
<MudPaper Class="right-panel" Elevation="0">
<MudText Typo="Typo.body1">Right Panel</MudText>
</MudPaper>
</div>
<!-- Footer (full width) -->
<MudPaper Class="footer" Elevation="0">
<MudText Typo="Typo.body2">Footer Section</MudText>
</MudPaper>
</MudPaper>
<style>
/* Layout container: vertical flex */
.layout-container {
display: flex;
flex-direction: column;
height: 100vh;
}
/* Header: full width */
.header {
width: 100%;
background-color: white;
border-bottom: 2px solid #1976d2;
padding: 1rem;
}
/* Footer: full width */
.footer {
width: 100%;
background-color: white;
border-top: 2px solid #1976d2;
padding: 0.5rem;
margin-top: auto;
}
/* Main content area: horizontal flex, between header and footer */
.main-content {
display: flex;
flex: 1; /* fills remaining height */
overflow: hidden;
}
/* Left panel: fixed width, blue background, border only on right */
.left-panel {
width: 200px;
background-color: #1976d2;
color: white;
padding: 1rem;
border-right: 2px solid #1976d2;
}
/* Center panel: fills remaining space, white, border on right */
.center-panel {
flex: 1;
background-color: white;
padding: 1rem;
border-right: 2px solid #1976d2;
}
/* Right panel: fixed width, white, no right border */
.right-panel {
width: 200px;
background-color: white;
padding: 1rem;
}
</style>
Improved NavMenu.razor
@using MudBlazor
@inherits LayoutComponentBase
<MudNavMenu Class="nav-menu">
<MudNavLink Href="#" Icon="@Icons.Material.Filled.Home" Match="NavLinkMatch.All">
Home
@* Navigate to: /home *@
</MudNavLink>
@if (!_isLoggedIn)
{
<MudNavLink Href="/auth">
Login
</MudNavLink>
}
@if (_isLoggedIn)
{
<MudNavLink Href="#">
Profile
@* Navigate to: /profile *@
</MudNavLink>
<MudNavLink Href="#">
Settings
@* Navigate to: /settings *@
</MudNavLink>
<MudNavLink Href="#">
Notifications
@* Navigate to: /notifications *@
</MudNavLink>
<MudNavLink Href="#">
Help
@* Navigate to: /help *@
</MudNavLink>
<MudNavLink Href="#">
About
@* Navigate to: /about *@
</MudNavLink>
<MudNavLink Href="#">
Logout
@* Navigate to: /logout *@
</MudNavLink>
}
</MudNavMenu>
@code {
[Inject] AuthenticationStateProvider AuthStateProvider { get; set; }
private bool _isLoggedIn = false;
protected override async Task OnInitializedAsync()
{
var authState = await AuthStateProvider.GetAuthenticationStateAsync();
_isLoggedIn = authState.User.Identity.IsAuthenticated;
}
}
<style>
.nav-menu {
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
padding: 0;
margin: 0;
}
.nav-menu .mud-nav-link {
color: white; /* text color inside the blue left panel */
margin: 0;
padding: 1rem;
border-bottom: 1px solid rgba(255,255,255,0.2); /* subtle divider */
transition: background-color 0.2s;
}
.nav-menu .mud-nav-link:hover {
background-color: rgba(255,255,255,0.1);
text-decoration: none;
}
</style>
Create Database and Migrate
dotnet ef migrations add InitialCreate
dotnet ef database update
Any errors try this
dotnet ef migrations remove # repeat until none left
rm -rf Migrations