using Microsoft.EntityFrameworkCore; using ContactManagementAPI.Data; using ContactManagementAPI.Services; using Microsoft.Data.Sqlite; using Microsoft.AspNetCore.HttpOverrides; using Microsoft.Extensions.FileProviders; var builder = WebApplication.CreateBuilder(args); // Add services to the container builder.Services.AddControllersWithViews() .AddJsonOptions(options => { // Prevent circular references options.JsonSerializerOptions.ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.IgnoreCycles; }); builder.Services.AddRazorPages(); builder.Services.AddHttpContextAccessor(); builder.Services.AddSession(options => { options.Cookie.HttpOnly = true; options.Cookie.IsEssential = true; options.IdleTimeout = TimeSpan.FromHours(2); // Reduced from 8 to 2 hours to prevent memory buildup }); // Add memory cache with size limit builder.Services.AddMemoryCache(options => { options.SizeLimit = 1024; // Limit cache size }); // Configure Antiforgery with SameSite settings builder.Services.AddAntiforgery(options => { options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest; options.Cookie.SameSite = SameSiteMode.Lax; }); // Configure database connection (SQLite preferred for portable installs) var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found."); var useSqlite = connectionString.TrimStart().StartsWith("Data Source=", StringComparison.OrdinalIgnoreCase); if (useSqlite) { var sqliteBuilder = new SqliteConnectionStringBuilder(connectionString); var sqliteDbPathOverride = Environment.GetEnvironmentVariable("SQLITE_DB_PATH"); var dataSource = sqliteBuilder.DataSource; if (!string.IsNullOrWhiteSpace(sqliteDbPathOverride)) { dataSource = sqliteDbPathOverride; } if (string.IsNullOrWhiteSpace(dataSource)) { dataSource = "ContactManagement.db"; } if (!Path.IsPathRooted(dataSource)) { var appDataFolder = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "ContactManagementSystem"); Directory.CreateDirectory(appDataFolder); dataSource = Path.Combine(appDataFolder, dataSource); } sqliteBuilder.DataSource = dataSource; connectionString = sqliteBuilder.ToString(); } builder.Services.AddDbContext(options => { if (useSqlite) { options.UseSqlite(connectionString); } else { options.UseSqlServer(connectionString); } // Optimize EF Core for better memory management options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); options.EnableSensitiveDataLogging(false); options.EnableDetailedErrors(false); }); // Add custom services builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); // Configure CORS if needed for future API consumption builder.Services.AddCors(options => { options.AddPolicy("AllowAll", policy => { policy.WithOrigins("http://localhost:5000", "https://localhost:5001") .AllowAnyMethod() .AllowAnyHeader() .AllowCredentials(); }); }); var app = builder.Build(); // Support reverse proxies/load balancers in cloud hosting app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto }); var disableHttpsRedirection = app.Configuration.GetValue("DisableHttpsRedirection") || string.Equals(Environment.GetEnvironmentVariable("DISABLE_HTTPS_REDIRECTION"), "true", StringComparison.OrdinalIgnoreCase); // Ensure database exists and seed defaults using (var scope = app.Services.CreateScope()) { var dbContext = scope.ServiceProvider.GetRequiredService(); if (useSqlite) { dbContext.Database.EnsureCreated(); } else { dbContext.Database.Migrate(); } SeedData.Initialize(dbContext); } // Configure the HTTP request pipeline if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Home/Error"); app.UseHsts(); } if (!disableHttpsRedirection) { app.UseHttpsRedirection(); } app.UseStaticFiles(); var uploadsRoot = Environment.GetEnvironmentVariable("UPLOADS_ROOT"); if (!string.IsNullOrWhiteSpace(uploadsRoot) && Directory.Exists(uploadsRoot)) { Directory.CreateDirectory(Path.Combine(uploadsRoot, "photos")); Directory.CreateDirectory(Path.Combine(uploadsRoot, "documents")); app.UseStaticFiles(new StaticFileOptions { FileProvider = new PhysicalFileProvider(uploadsRoot), RequestPath = "/uploads" }); } app.UseRouting(); app.UseSession(); app.UseCors("AllowAll"); app.UseAuthorization(); app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); app.MapRazorPages(); app.Run();