/// /// EN: ASP.NET Core BFF (Backend for Frontend) with YARP Reverse Proxy. /// VI: ASP.NET Core BFF (Backend for Frontend) với YARP Reverse Proxy. /// using Microsoft.AspNetCore.Rewrite; var builder = WebApplication.CreateBuilder(args); // ═══════════════════════════════════════════════════════════════════════════════ // EN: Add services to the container // VI: Thêm các services vào container // ═══════════════════════════════════════════════════════════════════════════════ // EN: Load YARP configuration from yarp.json // VI: Load cấu hình YARP từ yarp.json builder.Configuration.AddJsonFile("yarp.json", optional: false, reloadOnChange: true); // EN: Override YARP cluster addresses from Docker environment variables // VI: Override địa chỉ YARP cluster từ biến môi trường Docker var iamBaseUrl = Environment.GetEnvironmentVariable("IamService__BaseUrl"); var gatewayUrl = Environment.GetEnvironmentVariable("ApiSettings__GatewayUrl"); if (!string.IsNullOrEmpty(iamBaseUrl)) { builder.Configuration["ReverseProxy:Clusters:iam-cluster:Destinations:destination1:Address"] = iamBaseUrl; } // EN: Merchant service — discover via env or construct from gateway naming convention // VI: Merchant service — phát hiện qua env hoặc tạo từ naming convention gateway var merchantBaseUrl = Environment.GetEnvironmentVariable("MerchantService__BaseUrl"); if (string.IsNullOrEmpty(merchantBaseUrl) && !string.IsNullOrEmpty(iamBaseUrl)) { // EN: If no explicit merchant URL, try to construct from Docker network naming // VI: Nếu không có URL merchant rõ ràng, thử tạo từ Docker network naming merchantBaseUrl = iamBaseUrl.Replace("iam-service-net", "merchant-service-net"); } if (!string.IsNullOrEmpty(merchantBaseUrl)) { builder.Configuration["ReverseProxy:Clusters:merchant-cluster:Destinations:destination1:Address"] = merchantBaseUrl; } var catalogBaseUrl = Environment.GetEnvironmentVariable("CatalogService__BaseUrl"); if (string.IsNullOrEmpty(catalogBaseUrl) && !string.IsNullOrEmpty(iamBaseUrl)) { catalogBaseUrl = iamBaseUrl.Replace("iam-service-net", "catalog-service-net"); } if (!string.IsNullOrEmpty(catalogBaseUrl)) { builder.Configuration["ReverseProxy:Clusters:catalog-cluster:Destinations:destination1:Address"] = catalogBaseUrl; } var orderBaseUrl = Environment.GetEnvironmentVariable("OrderService__BaseUrl"); if (string.IsNullOrEmpty(orderBaseUrl) && !string.IsNullOrEmpty(iamBaseUrl)) { orderBaseUrl = iamBaseUrl.Replace("iam-service-net", "order-service-net"); } if (!string.IsNullOrEmpty(orderBaseUrl)) { builder.Configuration["ReverseProxy:Clusters:order-cluster:Destinations:destination1:Address"] = orderBaseUrl; } // EN: Add YARP Reverse Proxy // VI: Thêm YARP Reverse Proxy builder.Services.AddReverseProxy() .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy")); // EN: Add OpenAPI/Swagger support // VI: Thêm hỗ trợ OpenAPI/Swagger builder.Services.AddOpenApi(); // EN: Add CORS for Blazor WebAssembly client // VI: Thêm CORS cho Blazor WebAssembly client builder.Services.AddCors(options => { options.AddPolicy("BlazorClient", policy => { policy.AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader(); }); }); // EN: Add health checks // VI: Thêm health checks builder.Services.AddHealthChecks(); // EN: Add MVC controllers for BFF data endpoints // VI: Thêm MVC controllers cho BFF data endpoints builder.Services.AddControllers(); var app = builder.Build(); // ═══════════════════════════════════════════════════════════════════════════════ // EN: Configure the HTTP request pipeline // VI: Cấu hình HTTP request pipeline // ═══════════════════════════════════════════════════════════════════════════════ if (app.Environment.IsDevelopment()) { app.MapOpenApi(); app.UseDeveloperExceptionPage(); app.UseWebAssemblyDebugging(); } app.UseHttpsRedirection(); // EN: Redirect exact root "/" to "/home" before Blazor SPA catches it // VI: Redirect chính xác "/" về "/home" trước khi Blazor SPA xử lý app.Use(async (context, next) => { if (context.Request.Path == "/" && context.Request.Method == "GET" && !context.Request.Path.StartsWithSegments("/api") && !context.Request.Headers.Accept.ToString().Contains("application/json")) { context.Response.Redirect("/home"); return; } await next(); }); // EN: Enable CORS // VI: Kích hoạt CORS app.UseCors("BlazorClient"); // EN: Rewrite localized framework/content requests to root // VI: Viết lại các yêu cầu framework/content từ đường dẫn ngôn ngữ về root var rewriteOptions = new RewriteOptions() .AddRewrite(@"^(en-US|vi-VN)/(_framework|_content)/(.*)", "$2/$3", skipRemainingRules: true); app.UseRewriter(rewriteOptions); // EN: Serve static files with fingerprinting support (.NET 10+) // VI: Phục vụ static files với hỗ trợ fingerprinting (.NET 10+) app.MapStaticAssets(); // EN: Map health check endpoint // VI: Map endpoint health check app.MapHealthChecks("/health"); // EN: Map YARP Reverse Proxy routes to microservices // VI: Map các routes YARP Reverse Proxy đến microservices app.MapReverseProxy(); // EN: Localization Support - Serve index.html with dynamic base tag for specific cultures // VI: Hỗ trợ đa ngôn ngữ - Phục vụ index.html với base tag động cho các ngôn ngữ cụ thể var supportedCultures = new[] { "en-US", "vi-VN" }; var localizationOptions = new RequestLocalizationOptions() .SetDefaultCulture("en-US") .AddSupportedCultures(supportedCultures) .AddSupportedUICultures(supportedCultures); app.UseRequestLocalization(localizationOptions); // Handle mapped culture routes (e.g. /en-US/home, /vi-VN/solutions) app.Map("{culture:regex(^(en-US|vi-VN)$)}/{**slug}", async (string culture, HttpContext context, IWebHostEnvironment env) => { // Try to find index.html var fileInfo = env.WebRootFileProvider.GetFileInfo("index.html"); if (!fileInfo.Exists) { // In Development with Hosted Blazor, index.html might not be in Server's wwwroot strictly directly depending on setup, // but typically it is served via StaticFiles/BlazorFrameworkFiles. // If we can't find it easily via IWebHostEnvironment in Dev, we might fail. // However, for this task let's assume standard structure or handle gracefully. return Results.NotFound("index.html not found in wwwroot. Ensure the Client project is built."); } using var stream = fileInfo.CreateReadStream(); using var reader = new StreamReader(stream); var html = await reader.ReadToEndAsync(); // Replace base tag: -> // Be robust with spaces or standard format var modifiedHtml = html.Replace("", $"") .Replace("", $""); return Results.Content(modifiedHtml, "text/html"); }); // EN: Map BFF API controllers // VI: Map BFF API controllers app.MapControllers(); // EN: Fallback to index.html for SPA routing (default culture) // VI: Fallback đến index.html cho SPA routing (ngôn ngữ mặc định) app.MapFallbackToFile("index.html"); app.Run();