using EgwCoreLib.Lux.Data; using Lux.UI.Components; using Lux.UI.Components.Account; using Lux.UI.Data; using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Localization; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.FileProviders; using NLog; using NLog.Targets; using NLog.Web; using OpenTelemetry.Resources; using OpenTelemetry.Trace; using Radzen; using StackExchange.Redis; using System.Globalization; var builder = WebApplication.CreateBuilder(args); // recupero env corrente var env = builder.Environment; var logger = LogManager.Setup() .LoadConfigurationFromAppSettings() .GetCurrentClassLogger(); ConfigurationManager configuration = builder.Configuration; logger.Info("Lux.UI | Program.cs: startup"); logger.Info($"Current ASPNETCORE_ENVIRONMENT: {env.EnvironmentName}"); // costruzione connectionMultiplexer redis... string connStr = configuration.GetConnectionString("Redis") ?? "localhost"; ConnectionMultiplexer redisConn = ConnectionMultiplexer.Connect(connStr); // ==================================================================== // 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: "LUX", serviceVersion: appVersion)) .AddSource("Lux.API") .AddSource("Lux.DATA") .AddSource("Lux.UI") .AddAspNetCoreInstrumentation(options => { options.Filter = ctx => !ctx.Request.Path.StartsWithSegments("/health"); }) .AddEntityFrameworkCoreInstrumentation() .AddRedisInstrumentation(redisConn); // ==================================================================== // 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 = "LUX.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."); } // fix logging builder.Logging.AddFilter("Microsoft.EntityFrameworkCore", Microsoft.Extensions.Logging.LogLevel.None); // Add services to the container. builder.Services.AddRazorComponents() .AddInteractiveServerComponents() .AddInteractiveWebAssemblyComponents(); builder.Services.AddCascadingAuthenticationState(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddAuthentication(options => { options.DefaultScheme = IdentityConstants.ApplicationScheme; options.DefaultSignInScheme = IdentityConstants.ExternalScheme; }) .AddIdentityCookies(); // registro connMultiplexer REDIS builder.Services.AddSingleton(redisConn); var conn = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found."); // ApplicationDbContext (già presente) builder.Services.AddDbContext(options => { options.UseMySql( conn, ServerVersion.AutoDetect(conn)) .EnableSensitiveDataLogging(false) .EnableDetailedErrors(true); //.LogTo(_ => { }); // disabilita EF logging }); // DataLayerContext (manca!) conn = builder.Configuration.GetConnectionString("Lux.All") ?? ""; builder.Services.AddDbContextFactory(options => { options.UseMySql(conn, ServerVersion.AutoDetect(conn), mySqlOptions => { mySqlOptions.EnableStringComparisonTranslations(); }) .EnableSensitiveDataLogging(false) .EnableDetailedErrors(true); //.LogTo(_ => { }, Microsoft.Extensions.Logging.LogLevel.None); // disabilita EF logging }); builder.Services.AddDbContextFactory(options => { options.UseMySql(conn, ServerVersion.AutoDetect(conn), mySqlOptions => { mySqlOptions.EnableStringComparisonTranslations(); }) .EnableSensitiveDataLogging(false) .EnableDetailedErrors(true); //.LogTo(_ => { }, Microsoft.Extensions.Logging.LogLevel.None); // disabilita EF logging }); builder.Services.AddDatabaseDeveloperPageExceptionFilter(); // registrazione in blocco servizi con metodo extension custom builder.Services.AddLuxData(conn); builder.Services.AddIdentityCore(options => options.SignIn.RequireConfirmedAccount = true) .AddEntityFrameworkStores() .AddSignInManager() .AddDefaultTokenProviders(); builder.Services.AddSingleton, IdentityNoOpEmailSender>(); // aggiunta componenti Radzen builder.Services.AddRadzenComponents(); var app = builder.Build(); // aggiunt base URL x routing corretto string baseUrl = configuration.GetValue("ServerConf:BaseUrl") ?? ""; app.UsePathBase(baseUrl); logger.Info($"BaseUrl: {baseUrl}"); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseWebAssemblyDebugging(); app.UseMigrationsEndPoint(); } else { app.UseExceptionHandler("/Error", createScopeForErrors: true); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } // cultura IT... 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"); app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseAntiforgery(); // gestione fileshare x uploads: verifico da conf se sia linux o windows x file da accedere... if (configuration["ServerConf:HostOs"] == "Win") { app.UseFileServer(new FileServerOptions { FileProvider = new PhysicalFileProvider(@"\\stor01\TEAM DRIVES\40_FileUpload\LuxUploads"), RequestPath = new PathString("/unsafe_uploads"), EnableDirectoryBrowsing = true //EnableDirectoryBrowsing = false }); } else { app.UseFileServer(new FileServerOptions { FileProvider = new PhysicalFileProvider(Path.Combine(env.ContentRootPath, "unsafe_uploads")), RequestPath = new PathString("/unsafe_uploads"), EnableDirectoryBrowsing = true //EnableDirectoryBrowsing = false }); } app.MapRazorComponents() .AddInteractiveServerRenderMode() .AddInteractiveWebAssemblyRenderMode() .AddAdditionalAssemblies(typeof(Lux.UI.Client._Imports).Assembly); app.MapGet("/download", (HttpContext ctx, IWebHostEnvironment env) => { var fileName = ctx.Request.Query["fileName"].ToString() ?? "file.txt"; var filePath = Path.Combine(env.ContentRootPath, "temp", fileName); if (!File.Exists(filePath)) return Results.NotFound(); return Results.File( File.ReadAllBytes(filePath), "application/octet-stream", fileName ); }); // Add additional endpoints required by the Identity /Account Razor components. app.MapAdditionalIdentityEndpoints(); app.Run();