using Azure.Monitor.OpenTelemetry.AspNetCore; using Serilog; using Serilog.Events; using Microsoft.EntityFrameworkCore; using SmartStack.Api.Extensions; using SmartStack.Infrastructure.Persistence; using SmartStack.Infrastructure.Persistence.Extensions; using {{ProjectName}}.Infrastructure; using {{ProjectName}}.Infrastructure.Persistence; using {{ProjectName}}.Application; // Bootstrap logger for startup errors Log.Logger = new LoggerConfiguration() .MinimumLevel.Override("Microsoft", LogEventLevel.Information) .Enrich.FromLogContext() .WriteTo.Console() .CreateBootstrapLogger(); try { Log.Information("Starting {{ProjectName}} API"); var builder = WebApplication.CreateBuilder(args); // Configure Kestrel to use HTTP/1.1 on all configured endpoints builder.WebHost.ConfigureKestrel((context, options) => { options.ConfigureEndpointDefaults(listenOptions => { listenOptions.Protocols = Microsoft.AspNetCore.Server.Kestrel.Core.HttpProtocols.Http1; }); }); // Load appsettings.Local.json if it exists (for local development overrides). // // Use the SmartStack helper, NOT builder.Configuration.AddJsonFile(...): configuration sources are // last-wins, and CreateBuilder(args) has already registered the environment-variable and // command-line providers. Appending the file gives it the LAST word, so a container's // ConnectionStrings__DefaultConnection is silently ignored on any machine that has the file — the // app then runs against a different database than the one it was told to use, without a word in // the logs. AddSmartStackLocalOverrides inserts it BELOW the environment instead. builder.Configuration.AddSmartStackLocalOverrides(); // Serilog configuration from appsettings builder.Host.UseSmartStackSerilog(); // Observability — Azure Monitor via OpenTelemetry (replaces legacy Microsoft.ApplicationInsights.AspNetCore) var aiConnectionString = builder.Configuration["Logging:Sinks:ApplicationInsights:ConnectionString"]; var aiEnabled = builder.Configuration.GetValue("Logging:Sinks:ApplicationInsights:Enabled"); if (!string.IsNullOrEmpty(aiConnectionString) && aiEnabled) { builder.Services.AddOpenTelemetry() .UseAzureMonitor(options => { options.ConnectionString = aiConnectionString; }); } // R6 — Hangfire (durable background scheduling). Wired BEFORE AddSmartStack // because IBackgroundJobClient must be available when Infrastructure DI registers // IWorkflowResumeScheduler. // // Registration is LAZY: nothing touches the database here. The job storage is materialised // later, by InitializeSmartStackAsync (see step 3). Hangfire is a hard dependency of // SmartStack — there is no switch to disable it. builder.Services.AddSmartStackHangfire(builder.Configuration); // Recurring jobs — declare them HERE, through the SmartStack seam. The job is installed at host // start via the DI-resolved IRecurringJobManager, so it can never be order-sensitive, and a // storage problem logs a warning instead of killing startup. // // builder.Services.AddSmartStackRecurringJob( // "my-job", job => job.RunAsync(CancellationToken.None), "0 2 * * *"); // // Avoid Hangfire's STATIC API (RecurringJob.AddOrUpdate): it reads JobStorage.Current at the // exact moment it is called, so it only works AFTER `await app.InitializeSmartStackAsync()`. // Called any earlier it throws "Current JobStorage instance has not been initialized yet" and // takes the whole application down at boot. // // scaffold-business fills the markers below — one AddSmartStackRecurringJob per // `scheduled[period]` use case (derive-job-specs). Idempotent; hand lines outside // the block are honoured. // <<< RECURRING-JOBS BEGIN >>> // builder.Services.AddSmartStackRecurringJob("app-module-notify", s => s.RunNotifyAsync(default, CancellationToken.None), "0 3 * * *"); // <<< RECURRING-JOBS END >>> // =================================================================== // 1. Add SmartStack Core services (from NuGet package) // =================================================================== builder.Services.AddSmartStack(builder.Configuration, options => { options.EnableDevSeeding = builder.Environment.IsDevelopment(); }); // =================================================================== // 2. Add client-specific services (Dual-DbContext pattern) // =================================================================== builder.Services.Add{{ProjectName}}Infrastructure(builder.Configuration); builder.Services.Add{{ProjectName}}Application(); var app = builder.Build(); // =================================================================== // 3. Initialize SmartStack + apply migrations (Core → Extensions order) // =================================================================== // InitializeSmartStackAsync applies the Core migrations (always — a failure is fatal), seeds // Core (navigation, roles, refs, …), resolves navigation routing, checks the license, and // finally materialises the Hangfire job storage. Everything you post AFTER this line therefore // runs against a migrated database and a ready job storage, in EVERY environment. await app.InitializeSmartStackAsync(); // SmartStackDbInitializer applies pending migrations in the right order: // Core first (so core.* tables exist), then extensions (so extension FKs // to core.* succeed). Idempotent — safe to call on every startup. using (var scope = app.Services.CreateScope()) { var coreDb = scope.ServiceProvider.GetRequiredService(); var extDb = scope.ServiceProvider.GetRequiredService(); var init = scope.ServiceProvider.GetRequiredService(); await init.ApplyAllAsync(coreDb, new[] { extDb }); } // =================================================================== // 4. SmartStack middleware & endpoints // =================================================================== // Swagger UI (development only) app.UseSmartStackSwagger(); // SmartStack middleware pipeline app.UseSmartStack(); // R6 — Hangfire dashboard (admin-only). Mounted AFTER UseAuthentication (inside UseSmartStack) // so the dashboard filter sees the authenticated user principal. app.UseSmartStackHangfireDashboard(app.Environment, app.Configuration); // Map controllers and SignalR hubs app.MapSmartStack(); Log.Information("{{ProjectName}} API started successfully"); app.Run(); } catch (Exception ex) { Log.Fatal(ex, "Application terminated unexpectedly"); } finally { Log.CloseAndFlush(); }