using Microsoft.AspNetCore.Authentication.Negotiate; using Microsoft.AspNetCore.StaticFiles; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.FileProviders; using MP.Data; using MP.SPEC.Components; using MP.SPEC.Data; using MP.SPEC.Services; using NLog; using NLog.Targets; using NLog.Web; using OpenTelemetry.Resources; using OpenTelemetry.Trace; using StackExchange.Redis; using ZiggyCreatures.Caching.Fusion; using ZiggyCreatures.Caching.Fusion.Backplane.StackExchangeRedis; using ZiggyCreatures.Caching.Fusion.Serialization.NewtonsoftJson; var builder = WebApplication.CreateBuilder(args); ConfigurationManager configuration = builder.Configuration; var logger = LogManager.Setup() .LoadConfigurationFromAppSettings() .GetCurrentClassLogger(); logger.Info("Program.cs: startup"); // REDIS setup logger.Info("Setup REDIS"); string connStringRedis = configuration.GetConnectionString("Redis") ?? "localhost:6379"; //string connStringRedis = ConfMan.GetConnectionString("RedisAdmin"); string redisSrvAddr = connStringRedis.Substring(0, connStringRedis.IndexOf(":")); // avvio oggetto shared x redis... IConnectionMultiplexer redisMultiplexer = ConnectionMultiplexer.Connect(connStringRedis); // ==================================================================== // Setup Tracing e Telemetria... // ==================================================================== // 1. Leggiamo la configurazione var otelEnabled = builder.Configuration.GetValue("Otel:EnableTracing", false); var otelEndpoint = builder.Configuration["Otel:Endpoint"]; var otelDsn = builder.Configuration["Otel:Dsn"]; if (otelEnabled) { // ==================================================================== // SETUP OPENTELEMETRY BASE (Genera gli oggetti Activity) // Questo gira per i Livelli 1, 2 e 3. // ==================================================================== var appVersion = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "1.0.0"; builder.Services.AddOpenTelemetry() .WithTracing(tracerProviderBuilder => { tracerProviderBuilder .SetResourceBuilder(OpenTelemetry.Resources.ResourceBuilder.CreateDefault() .AddService(serviceName: "MAPO.SPEC", serviceVersion: appVersion)) .AddSource("MP.DATA.Tracer") .AddAspNetCoreInstrumentation(options => { options.Filter = ctx => !ctx.Request.Path.StartsWithSegments("/health"); }) .AddSqlClientInstrumentation(options => { options.RecordException = true; }) .AddRedisInstrumentation(redisMultiplexer); // ==================================================================== // ESPORTAZIONE DI RETE (Solo Livelli 1 e 2) // ==================================================================== if (!string.IsNullOrWhiteSpace(otelEndpoint)) { tracerProviderBuilder.AddOtlpExporter(options => { options.Endpoint = new Uri(otelEndpoint); if (!string.IsNullOrWhiteSpace(otelDsn)) { options.Headers = $"uptrace-dsn={otelDsn}"; } options.Protocol = OpenTelemetry.Exporter.OtlpExportProtocol.Grpc; }); } // Se otelEndpoint è vuoto (Livello 3), le tracce nascono e muoiono in RAM. }); // ==================================================================== // ESPORTAZIONE NLOG REALTIME (Solo Livelli 1 e 2) // ==================================================================== if (!string.IsNullOrWhiteSpace(otelEndpoint)) { var otlpTarget = new OtlpTarget { Name = "UptraceRealtime", Endpoint = otelEndpoint, ServiceName = "MP.DATA.Tracer" }; if (!string.IsNullOrWhiteSpace(otelDsn)) { otlpTarget.Headers = $"uptrace-dsn={otelDsn}"; } var config = LogManager.Configuration ?? new NLog.Config.LoggingConfiguration(); config.AddTarget(otlpTarget); config.AddRule(NLog.LogLevel.Info, NLog.LogLevel.Fatal, otlpTarget); LogManager.Configuration = config; LogManager.ReconfigExistingLoggers(); logger.Info($"🚀 NLog & OTel attivi e in invio verso: {otelEndpoint}"); } else { logger.Info("ℹ️ OTel attivo (Local mode). Esportazione di rete disabilitata."); } } else { // ==================================================================== // LIVELLO 4: TUTTO SPENTO // ==================================================================== logger.Info("⏸️ Telemetria e Tracing completamente disabilitati."); } // Add services to the container. logger.Info("Setup Auth"); builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme) .AddNegotiate(); builder.Services.AddAuthorization(options => { // By default, all incoming requests will be authorized according to the default policy. options.FallbackPolicy = options.DefaultPolicy; }); //setup Blazor builder.Services.AddRazorComponents() .AddInteractiveServerComponents(); builder.Services.AddRazorPages(); // memory + redis preliminare builder.Services.AddSingleton(redisMultiplexer); // ✅ Distributed cache (necessario per FusionCache) builder.Services.AddStackExchangeRedisCache(options => { options.Configuration = connStringRedis; }); // ✅ FusionCache builder.Services.AddFusionCache() .WithDistributedCache(sp => sp.GetRequiredService()) .WithSerializer(new FusionCacheNewtonsoftJsonSerializer()) .WithBackplane(new RedisBackplane(new RedisBackplaneOptions { ConnectionMultiplexerFactory = () => Task.FromResult(redisMultiplexer) })); // Metodi principali x accesso dati var connStr = builder.Configuration.GetConnectionString("MP.Data") ?? throw new InvalidOperationException("ConnString 'MP.Data' mancante."); // aggiungo il costruttore x i vari DbContextFactory builder.Services.AddDbContextFactory(options => options.UseSqlServer(connStr) .EnableSensitiveDataLogging(false) // true solo in Sviluppo .ConfigureWarnings(w => w.Ignore(CoreEventId.ManyServiceProvidersCreatedWarning))); builder.Services.AddDbContextFactory(options => options.UseSqlServer(connStr) .EnableSensitiveDataLogging(false) // true solo in Sviluppo .ConfigureWarnings(w => w.Ignore(CoreEventId.ManyServiceProvidersCreatedWarning))); // MP.Data Services Utils - Statistiche DB builder.Services.AddSpecDataLayer(); // servizi del progetto SPEC builder.Services.TryAddScoped(); builder.Services.TryAddSingleton(); builder.Services.TryAddScoped(); #if false builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); #endif #if false // aggiunta helper local/session storage service builder.Services.AddScoped(); builder.Services.AddScoped(); #endif builder.Services.AddHttpClient(); logger.Info("Aggiunti services"); var app = builder.Build(); logger.Info("Build App"); // aggiunt base URL x routing corretto string baseUrl = configuration.GetValue("SpecialConf:AppUrl") ?? ""; app.UsePathBase(baseUrl); logger.Info($"BaseUrl: {baseUrl}"); // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Error"); // 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.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseAntiforgery(); // gestione static files: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/static-files?view=aspnetcore-8.0 string BasePathOdlReturn = configuration.GetValue("ServerConf:BasePathOdlReturn") ?? configuration.GetValue("OptConf:BasePathOdlReturn") ?? ""; if (!string.IsNullOrEmpty(BasePathOdlReturn)) { // preparo mappings opzionali se presenti in conf... var provider = new FileExtensionContentTypeProvider(); // vedere https://code-maze.com/dotnet-appsettings-json-content-to-dictionary/ var mimeSection = configuration.GetSection("ServerConf:MimeMappings"); if (mimeSection != null) { var mimeDict = mimeSection .AsEnumerable() .Where(x => !string.IsNullOrWhiteSpace(x.Value)) .ToDictionary(x => x.Key.Replace("ServerConf:MimeMappings:", ""), x => x.Value); // se ne ho trovati if (mimeDict != null && mimeDict.Count > 0) { // li aggiungo! vedere // https://thechrisgreen.com/2022/05/add-a-mime-type-to-an-asp-net-core-net-6-app/ // https://harrybellamy.com/posts/getting-mime-types-from-file-extensions-in-net-core/ foreach (var item in mimeDict) { if (!string.IsNullOrEmpty(item.Value)) { // Add new mappings provider.Mappings[item.Key] = item.Value; } } } } // verifico esista folder if (Directory.Exists(BasePathOdlReturn)) { // gestione cartella x file ritornati x ODL app.UseStaticFiles(new StaticFileOptions { ContentTypeProvider = provider, FileProvider = new PhysicalFileProvider(BasePathOdlReturn), RequestPath = "/RET_DATA", }); } } app.MapRazorComponents() .AddInteractiveServerRenderMode(); logger.Info("Run App"); app.Run();