using Elastic.Clients.Elasticsearch; using Microsoft.EntityFrameworkCore; using Prometheus; using Serilog; using StackExchange.Redis; using System.Text.Json.Serialization; using FluentValidation; using FluentValidation.AspNetCore; using Microsoft.AspNetCore.Mvc; using System.Reflection; Log.Logger = new LoggerConfiguration() .WriteTo.Console() .CreateBootstrapLogger(); try { var builder = WebApplication.CreateBuilder(args); builder.Services.AddFluentValidationAutoValidation(); builder.Services.AddValidatorsFromAssemblyContaining(); // Serilog's reloadable logger can only be frozen once per process; skip in // integration tests where WebApplicationFactory may build multiple hosts. if (!builder.Environment.IsEnvironment("Testing")) { builder.Host.UseSerilog((ctx, services, config) => config.ReadFrom.Configuration(ctx.Configuration) .ReadFrom.Services(services) .Enrich.FromLogContext() .Enrich.WithMachineName() .Enrich.WithThreadId()); } builder.Services.AddDbContext(opts => opts.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection"))); builder.Services.AddSingleton(sp => ConnectionMultiplexer.Connect(sp.GetRequiredService()["Redis:ConnectionString"]!)); builder.Services.Configure( builder.Configuration.GetSection(KafkaOptions.Section)); builder.Services.Configure( builder.Configuration.GetSection(ElasticsearchOptions.Section)); var esOptions = builder.Configuration .GetSection(ElasticsearchOptions.Section) .Get()!; builder.Services.AddSingleton( new ElasticsearchClient(new Uri(esOptions.Uri))); builder.Services.Configure( builder.Configuration.GetSection(RabbitMqOptions.Section)); builder.Services.Configure( builder.Configuration.GetSection(MinioOptions.Section)); builder.Services.AddSingleton(); builder.Services.AddHostedService(sp => sp.GetRequiredService()); builder.Services.Configure( builder.Configuration.GetSection(ReconciliationJobOptions.Section)); builder.Services.Configure( builder.Configuration.GetSection(DataLakeOptions.Section)); builder.Services.Configure( builder.Configuration.GetSection(TrendDetectionOptions.SectionName)); builder.Services.Configure( builder.Configuration.GetSection(SuppressionOptions.SectionName)); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddSingleton(); builder.Services.AddHostedService(); builder.Services.AddHostedService(); builder.Services.AddHostedService(); builder.Services.AddHostedService(); builder.Services.AddHostedService(); builder.Services.AddHostedService(); builder.Services.AddHostedService(); builder.Services.AddHostedService(); builder.Services.AddHostedService(); builder.Services.AddHostedService(); builder.Services.AddHostedService(); builder.Services.AddSingleton(); builder.Services.AddHostedService(); builder.Services.AddHostedService(); builder.Services.AddHostedService(); builder.Services.AddHostedService(); builder.Services.AddHostedService(); builder.Services.AddHostedService(); builder.Services.AddHostedService(); builder.Services.AddControllers() .AddJsonOptions(opts => { opts.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles; opts.JsonSerializerOptions.Converters.Add(new BloodTypeJsonConverter()); opts.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); opts.JsonSerializerOptions.Converters.Add(new ObservationSourceJsonConverter()); opts.JsonSerializerOptions.Converters.Add(new DepartmentJsonConverter()); }); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(options => { var xmlPath = Path.Combine(AppContext.BaseDirectory, $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"); options.IncludeXmlComments(xmlPath); }); builder.Services.Configure(options => { options.InvalidModelStateResponseFactory = context => { var errors = context.ModelState .Where(e => e.Value?.Errors.Count > 0) .SelectMany(e => e.Value!.Errors.Select(err => new { field = e.Key, message = err.ErrorMessage })) .ToList(); var response = ApiResponse.Fail(400, "One or more validation errors occurred.", "VALIDATION_ERROR"); return new BadRequestObjectResult(new { response.Success, response.StatusCode, data = (object?)null, error = new { message = "One or more validation errors occurred.", code = "VALIDATION_ERROR", details = errors } }); }; }); var app = builder.Build(); if (!app.Environment.IsEnvironment("Testing")) { using var scope = app.Services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); var redis = scope.ServiceProvider.GetRequiredService(); await DataSeeder.SeedAsync(db, redis); } if (!app.Environment.IsEnvironment("Testing")) { app.UseSerilogRequestLogging(options => { options.MessageTemplate = "HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.000}ms"; }); } app.UseMiddleware(); app.UseMiddleware(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.MapMetrics("/metrics"); app.MapControllers(); app.Run(); } /*** dotnet ef tools use HostFactoryResolver which throws HostAbortedException internally as a control-flow mechanism to stop the host after discovering the DbContext. Your generic catch was swallowing it instead of letting it propagate, so EF saw the process exit abnormally. ***/ catch (HostAbortedException) { throw; } catch (Exception ex) { Log.Fatal(ex, "Application failed to start."); throw; } finally { Log.CloseAndFlush(); } public partial class Program { }