using GWMS.Data; using GWMS.UI.Areas.Identity; using GWMS.UI.Data; using HealthChecks.UI.Client; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Diagnostics.HealthChecks; using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity.UI.Services; using Microsoft.AspNetCore.Localization; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using NLog; using NLog.Targets; using NLog.Web; using OpenTelemetry.Resources; using OpenTelemetry.Trace; using StackExchange.Redis; using System; using System.Globalization; // ==================================================================== // 1. IL "FIX" CRITICO PER HTTP/2 (OTLP GRPC) // Deve essere la prima riga eseguita. // ==================================================================== AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true); var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger(); logger.Info("GMWS.UI Application Starting Up"); try { var builder = WebApplication.CreateBuilder(args); // Setup NLog come provider di logging builder.Logging.ClearProviders(); builder.Host.UseNLog(); // ==================================================================== // 2. CONFIGURAZIONE SERVIZI (ex ConfigureServices) // ==================================================================== var Configuration = builder.Configuration; // REDIS setup string connStringRedis = Configuration.GetConnectionString("Redis"); string redisSrvAddr = connStringRedis.Contains(":") ? connStringRedis.Substring(0, connStringRedis.IndexOf(":")) : "127.0.0.1"; var redisMultiplexer = ConnectionMultiplexer.Connect(connStringRedis); builder.Services.AddSingleton(redisMultiplexer); // --- SETUP OPENTELEMETRY --- var otelEnabled = Configuration.GetValue("Otel:EnableTracing", false); var otelEndpoint = Configuration["Otel:Endpoint"]; var otelDsn = Configuration["Otel:Dsn"]; if (otelEnabled) { var appVersion = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "1.0.0"; builder.Services.AddOpenTelemetry() .WithTracing(tracerProviderBuilder => { tracerProviderBuilder .SetResourceBuilder(ResourceBuilder.CreateDefault() .AddService(serviceName: "GWMS", serviceVersion: appVersion)) .AddSource("GWMS.Data") .AddSource("GWMS.UI") .AddAspNetCoreInstrumentation(options => { options.Filter = ctx => !ctx.Request.Path.StartsWithSegments("/health"); }) //.AddHttpClientInstrumentation() .AddHttpClientInstrumentation(options => { // Questo evita di tracciare le chiamate in USCITA verso l'endpoint health options.FilterHttpRequestMessage = (httpRequestMessage) => { var uri = httpRequestMessage.RequestUri?.ToString() ?? ""; // Escludi chiamate che contengono /health o che puntano a localhost/loopback return !uri.Contains("/health") && !uri.Contains("[::]") && !uri.Contains("127.0.0.1"); }; }) .AddEntityFrameworkCoreInstrumentation() .AddRedisInstrumentation(redisMultiplexer); if (!string.IsNullOrWhiteSpace(otelEndpoint)) { tracerProviderBuilder.AddOtlpExporter(options => { options.Endpoint = new Uri(otelEndpoint); // --- LOGICA ADATTIVA PROTOCOLLO --- if (otelEndpoint.Contains(":4318")) { options.Protocol = OpenTelemetry.Exporter.OtlpExportProtocol.HttpProtobuf; // L'SDK aggiunge automaticamente /v1/traces se non presente } else { options.Protocol = OpenTelemetry.Exporter.OtlpExportProtocol.Grpc; } if (!string.IsNullOrWhiteSpace(otelDsn)) { options.Headers = $"uptrace-dsn={otelDsn}"; } }); } }); // Configurazione NLog OTLP Target if (!string.IsNullOrWhiteSpace(otelEndpoint)) { var otlpTarget = new OtlpTarget { Name = "UptraceRealtime", Endpoint = otelEndpoint, ServiceName = "GWMS.Data" }; // --- LOGICA ADATTIVA PER NLOG --- if (otelEndpoint.Contains(":4318")) { otlpTarget.UseHttp = true; // NLog richiede l'URL completo per i log se usi HTTP if (!otelEndpoint.EndsWith("/v1/logs")) { otlpTarget.Endpoint = otelEndpoint.TrimEnd('/') + "/v1/logs"; } } if (!string.IsNullOrWhiteSpace(otelDsn)) { otlpTarget.Headers = $"uptrace-dsn={otelDsn}"; } var nlogConfig = LogManager.Configuration ?? new NLog.Config.LoggingConfiguration(); nlogConfig.AddTarget(otlpTarget); nlogConfig.AddRule(NLog.LogLevel.Info, NLog.LogLevel.Fatal, otlpTarget); LogManager.Configuration = nlogConfig; LogManager.ReconfigExistingLoggers(); } } // Init DB Logic string dbServerAddr = Configuration["DbConfig:Server"]; string nKey = Configuration["DbConfig:nKey"]; string sKey = Configuration["DbConfig:sKey"]; DbConfig.InitDb(dbServerAddr, nKey, sKey); DbConfig.CheckUser(nKey, sKey); DbConfig.ExecMigrationMain(); string connStringDB = DbConfig.CONNECTION_STRING; // HealthChecks builder.Services.AddHealthChecks() .AddMySql(connStringDB, "MySql instance") .AddAsyncCheck($"DB PING ({dbServerAddr})", () => GWMS.UI.Health.Checks.PingCheck(dbServerAddr)) .AddAsyncCheck($"Redis PING ({redisSrvAddr})", () => GWMS.UI.Health.Checks.PingCheck(redisSrvAddr)) .AddProcessAllocatedMemoryHealthCheck(512, "Max Process memory (<512MB)", failureStatus: HealthStatus.Degraded) .AddRedis(connStringRedis, "Redis", failureStatus: HealthStatus.Degraded) .AddAsyncCheck("MySql Root User", () => GWMS.UI.Health.Checks.DbUserRoot("MySql")) .AddAsyncCheck("MySql Identity", () => GWMS.UI.Health.Checks.DbIdentity(DbConfig.DATABASE_NAME)) .AddAsyncCheck("MySql PlantLog", () => GWMS.UI.Health.Checks.DbPlantTable(DbConfig.DATABASE_NAME)); builder.Services.AddHealthChecksUI(s => { s.AddHealthCheckEndpoint("GWMS_Services", "health"); s.SetEvaluationTimeInSeconds(60); s.SetHeaderText("GWMS Health Check Status"); }).AddInMemoryStorage(); // Identity & DB var serverVersion = DbConfig.MysqlServerVersion(connStringDB); builder.Services.AddDbContext(options => options.UseMySql(connStringDB, serverVersion)); builder.Services.AddDefaultIdentity(options => options.SignIn.RequireConfirmedAccount = true) .AddRoles() .AddEntityFrameworkStores(); // Auth & Cookies builder.Services.ConfigureApplicationCookie(o => { o.ExpireTimeSpan = TimeSpan.FromDays(30); o.SlidingExpiration = true; }); builder.Services.Configure(o => o.TokenLifespan = TimeSpan.FromHours(3)); // Email builder.Services.AddTransient(); builder.Services.Configure(options => { options.Host_Address = Configuration["ExternalProviders:MailKit:SMTP:Address"]; options.Host_Port = Convert.ToInt32(Configuration["ExternalProviders:MailKit:SMTP:Port"]); options.Host_Username = Configuration["ExternalProviders:MailKit:SMTP:Account"]; options.Host_Password = Configuration["ExternalProviders:MailKit:SMTP:Password"]; options.Sender_EMail = Configuration["ExternalProviders:MailKit:SMTP:SenderEmail"]; options.Sender_Name = Configuration["ExternalProviders:MailKit:SMTP:SenderName"]; }); builder.Services.AddLocalization(); builder.Services.AddRazorPages(); builder.Services.AddServerSideBlazor(); builder.Services.AddDatabaseDeveloperPageExceptionFilter(); // Services builder.Services.AddScoped>(); builder.Services.AddScoped(); builder.Services.AddScoped(); var app = builder.Build(); // ==================================================================== // 3. CONFIGURAZIONE PIPELINE (ex Configure) // ==================================================================== app.UsePathBase(Configuration["RuntimeOpt:BaseAppPath"]); if (app.Environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseMigrationsEndPoint(); } else { app.UseExceptionHandler("/Error"); app.UseHsts(); } // ==================================================================== // Imposta cultura globale // ==================================================================== #if true var supportedCultures = new[] { new CultureInfo("it-IT") }; app.UseRequestLocalization(new RequestLocalizationOptions { DefaultRequestCulture = new RequestCulture("it-IT"), SupportedCultures = supportedCultures, FallBackToParentCultures = false }); CultureInfo.DefaultThreadCurrentCulture = CultureInfo.CreateSpecificCulture("it-IT"); #else var culture = new CultureInfo("en-US"); var localizationOptions = new RequestLocalizationOptions { DefaultRequestCulture = new Microsoft.AspNetCore.Localization.RequestCulture(culture), SupportedCultures = new List { culture }, SupportedUICultures = new List { culture } }; // ⚠️ IMPORTANTE: questo deve venire PRIMA di MapBlazorHub app.UseRequestLocalization(localizationOptions); CultureInfo.DefaultThreadCurrentCulture = culture; CultureInfo.DefaultThreadCurrentUICulture = culture; #endif app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto }); app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.MapBlazorHub(); app.MapHealthChecksUI(); app.MapHealthChecks("/health", new HealthCheckOptions { Predicate = _ => true, ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse }); app.MapFallbackToPage("/_Host"); app.Run(); } catch (Exception exception) { logger.Error(exception, "Stopped GMWS.UI program because of exception"); throw; } finally { LogManager.Shutdown(); }