Files
BirthList/src/BirthList.Web/Program.cs
T
Arne Moerman c1b11603e8
Build and Push Docker Image / build-and-push (push) Successful in 1m14s
Initial Blazor birth registry app, theming, and services
Implemented a Blazor Web App (.NET 8) for a public-by-link birth registry platform, following project guidelines. Added domain entities, EF Core context, and Blazor components for authentication, registry management, and public views. Introduced core services for registries, theming, user context, platform owner bootstrapping, and SMTP email. Included static assets (Bootstrap, favicon), launch settings, Dockerfile, CI workflow, and deployment configs. Added bootstrap.min.css.map for improved CSS debugging.
2026-05-14 11:49:47 +02:00

212 lines
6.8 KiB
C#

using BirthList.Infrastructure.Persistence;
using BirthList.Web.Authorization;
using BirthList.Web.Configuration;
using BirthList.Web.Features.Registries;
using BirthList.Web.Services;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;
using BirthList.Web.Components;
using BirthList.Web.Components.Account;
using BirthList.Web.Data;
using System.Data;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
builder.Services.AddHttpClient("RegistryMetadata", client =>
{
client.Timeout = TimeSpan.FromSeconds(10);
});
builder.Services.Configure<SmtpOptions>(builder.Configuration.GetSection("Smtp"));
builder.Services.AddCascadingAuthenticationState();
builder.Services.AddScoped<IdentityUserAccessor>();
builder.Services.AddScoped<IdentityRedirectManager>();
builder.Services.AddScoped<AuthenticationStateProvider, IdentityRevalidatingAuthenticationStateProvider>();
builder.Services.AddScoped<RegistryAuthorizationService>();
builder.Services.AddScoped<OwnerBootstrapService>();
builder.Services.AddScoped<RegistryService>();
builder.Services.AddScoped<RegistryMetadataService>();
builder.Services.AddScoped<RegistryThemeService>();
builder.Services.AddScoped<RegistryUserContext>();
builder.Services.AddScoped<SmtpConfigurationStatusService>();
var googleClientId = builder.Configuration["Authentication:Google:ClientId"];
var googleClientSecret = builder.Configuration["Authentication:Google:ClientSecret"];
var microsoftClientId = builder.Configuration["Authentication:Microsoft:ClientId"];
var microsoftClientSecret = builder.Configuration["Authentication:Microsoft:ClientSecret"];
var authBuilder = builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = IdentityConstants.ApplicationScheme;
options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
});
authBuilder.AddIdentityCookies();
if (!string.IsNullOrWhiteSpace(googleClientId) && !string.IsNullOrWhiteSpace(googleClientSecret))
{
authBuilder.AddGoogle(options =>
{
options.ClientId = googleClientId;
options.ClientSecret = googleClientSecret;
});
}
if (!string.IsNullOrWhiteSpace(microsoftClientId) && !string.IsNullOrWhiteSpace(microsoftClientSecret))
{
authBuilder.AddMicrosoftAccount(options =>
{
options.ClientId = microsoftClientId;
options.ClientSecret = microsoftClientSecret;
});
}
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
var dataProvider = builder.Configuration["Data:Provider"] ?? "SqlServer";
builder.Services.AddDbContext<ApplicationDbContext>(options =>
{
if (string.Equals(dataProvider, "SqlServer", StringComparison.OrdinalIgnoreCase))
{
options.UseSqlServer(connectionString);
return;
}
options.UseSqlite(connectionString);
});
builder.Services.AddDbContext<RegistryDbContext>(options =>
{
if (string.Equals(dataProvider, "SqlServer", StringComparison.OrdinalIgnoreCase))
{
options.UseSqlServer(connectionString);
return;
}
options.UseSqlite(connectionString);
});
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddIdentityCore<ApplicationUser>(options =>
{
options.SignIn.RequireConfirmedAccount = false;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddSignInManager()
.AddDefaultTokenProviders();
builder.Services.AddScoped<SmtpEmailSender>();
builder.Services.AddScoped<IEmailSender<ApplicationUser>>(serviceProvider => serviceProvider.GetRequiredService<SmtpEmailSender>());
var app = builder.Build();
using (var scope = app.Services.CreateScope())
{
var applicationDbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
await applicationDbContext.Database.MigrateAsync();
var registryDbContext = scope.ServiceProvider.GetRequiredService<RegistryDbContext>();
var registryMigrations = registryDbContext.Database.GetMigrations();
if (registryMigrations.Any())
{
await registryDbContext.Database.MigrateAsync();
}
else
{
var databaseCreator = registryDbContext.Database.GetService<IRelationalDatabaseCreator>();
if (!await databaseCreator.ExistsAsync())
{
await registryDbContext.Database.EnsureCreatedAsync();
}
else
{
var platformOwnersTableExists = await TableExistsAsync(registryDbContext, "PlatformOwners");
if (!platformOwnersTableExists)
{
await databaseCreator.CreateTablesAsync();
}
}
}
}
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Error", createScopeForErrors: true);
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAntiforgery();
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();
// Add additional endpoints required by the Identity /Account Razor components.
app.MapAdditionalIdentityEndpoints();
app.Run();
static async Task<bool> TableExistsAsync(DbContext context, string tableName)
{
ArgumentNullException.ThrowIfNull(context);
ArgumentException.ThrowIfNullOrWhiteSpace(tableName);
var connection = context.Database.GetDbConnection();
var closeConnection = connection.State != ConnectionState.Open;
if (closeConnection)
{
await connection.OpenAsync();
}
try
{
await using var command = connection.CreateCommand();
if (context.Database.IsSqlServer())
{
command.CommandText = "SELECT 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = @tableName";
}
else if (context.Database.IsSqlite())
{
command.CommandText = "SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = @tableName";
}
else
{
throw new NotSupportedException("Unsupported database provider for table existence check.");
}
var parameter = command.CreateParameter();
parameter.ParameterName = "@tableName";
parameter.Value = tableName;
command.Parameters.Add(parameter);
var result = await command.ExecuteScalarAsync();
return result is not null && result != DBNull.Value;
}
finally
{
if (closeConnection)
{
await connection.CloseAsync();
}
}
}