using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Distributed; using MP.Core.Conf; using MP.Data; using MP.RIOC.Services; using NLog; using NLog.Web; using StackExchange.Redis; using System.Diagnostics; using System.Net; using System.Reflection; using ZiggyCreatures.Caching.Fusion; using ZiggyCreatures.Caching.Fusion.Backplane.StackExchangeRedis; using ZiggyCreatures.Caching.Fusion.Serialization; using ZiggyCreatures.Caching.Fusion.Serialization.NewtonsoftJson; // Forza il ThreadPool a tenere pronti almeno 500 thread per i calcoli e 500 per l'I/O di rete ThreadPool.SetMinThreads(500, 500); var builder = WebApplication.CreateBuilder(args); // RECUPERO L'AMBIENTE REALE (Che ora IIS passa correttamente come 'Staging') var env = builder.Environment; // recupero env corrente var logger = LogManager.Setup() .LoadConfigurationFromAppSettings() .GetCurrentClassLogger(); // FORZA IL CARICAMENTO CORRETTO DEI JSON CON LA GERARCHIA DI AMBIENTE builder.Configuration .SetBasePath(env.ContentRootPath) .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true) .AddEnvironmentVariables(); builder.Logging.ClearProviders(); builder.Host.UseNLog(); var assemblyVersion = Assembly.GetExecutingAssembly().GetName().Version?.ToString(); logger.Info($"MP.RIOC | Program.cs: startup | v.{assemblyVersion}"); logger.Info($"Current ASPNETCORE_ENVIRONMENT: {env.EnvironmentName}"); // Config setup ConfigurationManager configuration = builder.Configuration; // REDIS setup logger.Info("Config OK"); string confRedis = configuration.GetConnectionString("Redis") ?? "localhost:6379"; string redisSrvAddr = confRedis.Substring(0, confRedis.IndexOf(":")); logger.Info("Setup REDIS OK"); builder.Services.Configure( builder.Configuration.GetSection("RedisScripts")); logger.Info("RedisScript Provider configured"); // MP.Data DbContext for Stats repositories string utilsConnString = builder.Configuration.GetConnectionString("MP.Utils") ?? "Server=localhost;Database=MoonPro_Utils; integrated security=True; MultipleActiveResultSets=True; App=MP.IOC;"; builder.Services.AddDbContextFactory(options => options.UseSqlServer(utilsConnString)); // MP.Data Services Utils - Statistiche DB builder.Services.AddRIocDataLayer(); // 1. Configurazione dell'invoker personalizzato (Potenziato con il Pooling) var httpClientInvoker = new HttpMessageInvoker(new SocketsHttpHandler { UseProxy = false, AllowAutoRedirect = false, AutomaticDecompression = DecompressionMethods.None, UseCookies = false, ActivityHeadersPropagator = DistributedContextPropagator.Current, ConnectTimeout = TimeSpan.FromSeconds(60), // ================================================================== // 🚀 LE TRE RIGHE PER ABBATTERE IL MURO DEI 2 SECONDI SOTTO STRESS // ================================================================== // Permette a YARP di aprire fino a 5000 canali paralleli contemporanei verso IIS MaxConnectionsPerServer = 5000, // Tiene caldi i socket ed evita di rifare l'handshake TCP/TLS a ogni richiesta PooledConnectionLifetime = TimeSpan.FromMinutes(15), PooledConnectionIdleTimeout = TimeSpan.FromMinutes(2), // ================================================================== // Gestione certificato (Invariata) SslOptions = new System.Net.Security.SslClientAuthenticationOptions { RemoteCertificateValidationCallback = (sender, cert, chain, sslPolicyErrors) => true } }); // Registrazione nella Dependency Injection (Invariata) builder.Services.AddSingleton(httpClientInvoker); builder.Services.AddHttpForwarder(); // avvio oggetto shared x redis... var redisMux = ConnectionMultiplexer.Connect(confRedis); builder.Services.AddSingleton(redisMux); // 1. Registra il serializzatore NewtonsoftJson per FusionCache builder.Services.AddSingleton(new FusionCacheNewtonsoftJsonSerializer()); // 2. Configura FusionCache (L1 Memory + L2 Redis Distributed + L3 DB via factory) //builder.Services.AddFusionCache("MAPO_MES_FusionCache") builder.Services.AddFusionCache() .WithDistributedCache(sp => sp.GetRequiredService()) .WithSerializer(new FusionCacheNewtonsoftJsonSerializer()) .WithBackplane(new RedisBackplane(new RedisBackplaneOptions { ConnectionMultiplexerFactory = () => Task.FromResult(redisMux) })) .WithDefaultEntryOptions(options => { // Durata di default dei dati in memoria options.Duration = TimeSpan.FromMinutes(5); // Jitter: variazione casuale alla scadenza per evitare scadenze in blocco options.JitterMaxDuration = TimeSpan.FromSeconds(5); }); //// 3. LA RIGA MAGICA: Estrae l'istanza nominata e la mappa come IFusionCache standard //builder.Services.AddSingleton(sp => // sp.GetRequiredService().GetCache("MAPO_MES_FusionCache")); // Registrazione dei servizi custom builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddHostedService(); builder.Services.AddHostedService(); builder.Services.AddSingleton(); logger.Info("Standard service configured"); // WeightProvider: Redis/Memory da config var weightOnRedis = builder.Configuration.GetValue("ServerConf:RedisWeight", false); if (weightOnRedis) { builder.Services.AddSingleton(); } else { builder.Services.AddSingleton(); } logger.Info($"Weight service configured | use Redis: {weightOnRedis}"); // RouteManager registration (singleton) builder.Services.AddSingleton(); logger.Info("Singleton Route Manager registered"); // aggiunta pagina razor di stato builder.Services.AddRazorPages(); var app = builder.Build(); // Blocco per la migrazione automatica del DB Utils... using (var scope = app.Services.CreateScope()) { var services = scope.ServiceProvider; try { var context = services.GetRequiredService(); context.Database.Migrate(); } catch (Exception ex) { var migrateLogger = services.GetRequiredService>(); migrateLogger.LogError(ex, "Si � verificato un errore durante l'aggiornamento del database."); } } // 1. Configurazione Base Path string baseUrl = configuration.GetValue("ServerConf:BaseUrlIoc") ?? "/MP/RIOC"; app.UsePathBase(baseUrl); // 2. Middleware statici (essenziali per CSS/JS delle pagine Razor) app.UseStaticFiles(); // 3. Abilita il Routing (necessario per MapGet e MapRazorPages) app.UseRouting(); // 4. Logging middleware app.Use(async (ctx, next) => { logger.Debug($"Incoming request PathBase='{ctx.Request.PathBase}' Path='{ctx.Request.Path}'"); await next(); }); // test per ambiente di esecuzione InProcess... app.MapGet("api/alive", () => $"OK - Girando in: {System.Diagnostics.Process.GetCurrentProcess().ProcessName}"); app.MapGet("/router-status", (RouteStatsManager stats) => Results.Ok(new { Status = "Online", Version = assemblyVersion, Mode = weightOnRedis ? "Redis" : "InMemory", Time = DateTime.Now, Metrics = stats.Snapshot() })); // Endpoint per la rimozione mirata di una singola rotta dalla cache app.MapPost("/api/admin/cache/purge-route/{method}", (string method, IWeightProvider weightProvider) => { try { if (weightProvider is RedisWeightProvider redisProvider) { redisProvider.EvictLocalCacheFor(method); return Results.Ok(new { Success = true, Message = $"Cache RAM per '{method}' svuotata." }); } return Results.Problem("Provider non valido."); } catch (Exception ex) { return Results.Problem($"Errore: {ex.Message}"); } }); // 5. Il cuore del Proxy (MapWhen è terminale per le richieste che lo soddisfano) string routePath = configuration.GetValue("ServerConf:RoutePath") ?? "/api/IOB"; string fullPath = $"{baseUrl}{routePath}".Replace("//", "/"); logger.Info($"BaseUrl: {baseUrl}"); app.MapWhen(ctx => ctx.Request.Path.StartsWithSegments(routePath, StringComparison.OrdinalIgnoreCase), builder => { builder.Run(async ctx => { var routeManager = ctx.RequestServices.GetRequiredService(); await routeManager.HandleAsync(ctx); }); }); // 6. Definizione degli Endpoints locali app.MapRazorPages(); // 7. Fallback "intelligente" // Invece di app.Run, usiamo MapFallback che viene eseguito SOLO se nessun altro endpoint o MapWhen ha risposto app.MapFallback(async context => { context.Response.StatusCode = 404; await context.Response.WriteAsync("Router: Endpoint non trovato o non mappato."); }); app.Run();