From 049c4af19696da1ec44eae099053623084ebdb2b Mon Sep 17 00:00:00 2001 From: Reuben Bond Date: Wed, 22 Feb 2023 16:32:53 -0800 Subject: [PATCH] WebSPA: migrate to WebApplicationBuilder and delete Startup --- src/Web/WebSPA/Program.cs | 157 +++++++++++++++++++++++++++++++------- src/Web/WebSPA/Startup.cs | 143 ---------------------------------- 2 files changed, 130 insertions(+), 170 deletions(-) delete mode 100644 src/Web/WebSPA/Startup.cs diff --git a/src/Web/WebSPA/Program.cs b/src/Web/WebSPA/Program.cs index 2631f7fd3..b8e83157d 100644 --- a/src/Web/WebSPA/Program.cs +++ b/src/Web/WebSPA/Program.cs @@ -1,27 +1,130 @@ -await BuildWebHost(args).RunAsync(); - -IWebHost BuildWebHost(string[] args) => - WebHost.CreateDefaultBuilder(args) - .UseStartup() - .UseContentRoot(Directory.GetCurrentDirectory()) - .ConfigureAppConfiguration((builderContext, config) => - { - config.AddEnvironmentVariables(); - }) - .ConfigureLogging((hostingContext, builder) => - { - builder.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); - builder.AddConsole(); - builder.AddDebug(); - builder.AddAzureWebAppDiagnostics(); - }) - .UseSerilog((builderContext, config) => - { - config - .MinimumLevel.Information() - .Enrich.FromLogContext() - .WriteTo.Seq("http://seq") - .ReadFrom.Configuration(builderContext.Configuration) - .WriteTo.Console(); - }) - .Build(); +var builder = WebApplication.CreateBuilder(args); +builder.WebHost.UseContentRoot(Directory.GetCurrentDirectory()); + +builder.Services.AddApplicationInsightsTelemetry(builder.Configuration); +builder.Services.AddApplicationInsightsKubernetesEnricher(); +builder.Services.AddHealthChecks() + .AddCheck("self", () => HealthCheckResult.Healthy()) + .AddUrlGroup(new Uri(builder.Configuration["IdentityUrlHC"]), name: "identityapi-check", tags: new string[] { "identityapi" }); + +builder.Services.Configure(builder.Configuration); +if (builder.Configuration.GetValue("IsClusterEnv") == bool.TrueString) +{ + builder.Services.AddDataProtection(opts => + { + opts.ApplicationDiscriminator = "eshop.webspa"; + }) + .PersistKeysToStackExchangeRedis(ConnectionMultiplexer.Connect(builder.Configuration["DPConnectionString"]), "DataProtection-Keys"); +} + + +// Add Anti-forgery services and configure the header name that angular will use by default. +builder.Services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN"); + +// Add controllers support and add a global AutoValidateAntiforgeryTokenFilter that will make the application check for an Anti-forgery token on all "mutating" requests (POST, PUT, DELETE). +// The AutoValidateAntiforgeryTokenFilter is an internal class registered when we register views, so we need to register controllers and views also. +builder.Services.AddControllersWithViews(options => options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute())) + .AddJsonOptions(options => + { + options.JsonSerializerOptions.PropertyNameCaseInsensitive = true; + }); + +// Setup where the compiled version of our spa application will be, when in production. +builder.Services.AddSpaStaticFiles(configuration => +{ + configuration.RootPath = "wwwroot"; +}); + +builder.Logging.AddConfiguration(builder.Configuration.GetSection("Logging")); +builder.Logging.AddAzureWebAppDiagnostics(); +builder.Host.UseSerilog((builderContext, config) => +{ + config + .MinimumLevel.Information() + .Enrich.FromLogContext() + .WriteTo.Seq("http://seq") + .ReadFrom.Configuration(builderContext.Configuration) + .WriteTo.Console(); +}) +.UseConsoleLifetime(); + +var app = builder.Build(); + +if (app.Environment.IsDevelopment()) +{ + app.UseDeveloperExceptionPage(); +} + +// Here we add Angular default Anti-forgery cookie name on first load. https://angular.io/guide/http#security-xsrf-protection +// This cookie will be read by Angular app and its value will be sent back to the application as the header configured in .AddAntiforgery() +var antiForgery = app.Services.GetRequiredService(); +app.Use(next => context => +{ + string path = context.Request.Path.Value; + + if ( + string.Equals(path, "/", StringComparison.OrdinalIgnoreCase) || + string.Equals(path, "/index.html", StringComparison.OrdinalIgnoreCase)) + { + // The request token has to be sent as a JavaScript-readable cookie, + // and Angular uses it by default. + var tokens = antiForgery.GetAndStoreTokens(context); + context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken, + new CookieOptions() { HttpOnly = false }); + } + + return next(context); +}); + +// Seed Data +var loggerFactory = app.Services.GetRequiredService(); +WebContextSeed.Seed(app, app.Environment, loggerFactory); + +var pathBase = app.Configuration["PATH_BASE"]; + +if (!string.IsNullOrEmpty(pathBase)) +{ + loggerFactory.CreateLogger().LogDebug("Using PATH_BASE '{PathBase}'", pathBase); + app.UsePathBase(pathBase); +} + +app.UseDefaultFiles(); +app.UseStaticFiles(); + +// This will make the application to respond with the index.html and the rest of the assets present on the configured folder (at AddSpaStaticFiles() (wwwroot)) +if (!app.Environment.IsDevelopment()) +{ + app.UseSpaStaticFiles(); +} + +app.UseRouting(); +app.MapDefaultControllerRoute(); +app.MapControllers(); +app.MapHealthChecks("/liveness", new HealthCheckOptions +{ + Predicate = r => r.Name.Contains("self") +}); +app.MapHealthChecks("/hc", new HealthCheckOptions() +{ + Predicate = _ => true, + ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse +}); + +// Handles all still unnatended (by any other middleware) requests by returning the default page of the SPA (wwwroot/index.html). +app.UseSpa(spa => +{ + // To learn more about options for serving an Angular SPA from ASP.NET Core, + // see https://go.microsoft.com/fwlink/?linkid=864501 + + // the root of the angular app. (Where the package.json lives) + spa.Options.SourcePath = "Client"; + + if (app.Environment.IsDevelopment()) + { + + // use the SpaServices extension method for angular, that will make the application to run "ng serve" for us, when in development. + spa.UseAngularCliServer(npmScript: "start"); + } +}); + +await app.RunAsync(); diff --git a/src/Web/WebSPA/Startup.cs b/src/Web/WebSPA/Startup.cs deleted file mode 100644 index a8812bc94..000000000 --- a/src/Web/WebSPA/Startup.cs +++ /dev/null @@ -1,143 +0,0 @@ -namespace eShopConContainers.WebSPA; - -public class Startup -{ - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - public Startup() - { - var localPath = new Uri(Configuration["ASPNETCORE_URLS"])?.LocalPath ?? "/"; - Configuration["BaseUrl"] = localPath; - } - - // This method gets called by the runtime. Use this method to add services to the container. - // For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940 - public void ConfigureServices(IServiceCollection services) - { - RegisterAppInsights(services); - - services.AddHealthChecks() - .AddCheck("self", () => HealthCheckResult.Healthy()) - .AddUrlGroup(new Uri(Configuration["IdentityUrlHC"]), name: "identityapi-check", tags: new string[] { "identityapi" }); - - services.Configure(Configuration); - - if (Configuration.GetValue("IsClusterEnv") == bool.TrueString) - { - services.AddDataProtection(opts => - { - opts.ApplicationDiscriminator = "eshop.webspa"; - }) - .PersistKeysToStackExchangeRedis(ConnectionMultiplexer.Connect(Configuration["DPConnectionString"]), "DataProtection-Keys"); - } - - // Add Antiforgery services and configure the header name that angular will use by default. - services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN"); - - // Add controllers support and add a global AutoValidateAntiforgeryTokenFilter that will make the application check for an Antiforgery token on all "mutating" requests (POST, PUT, DELETE). - // The AutoValidateAntiforgeryTokenFilter is an internal class registered when we register views, so we need to register controllers and views also. - services.AddControllersWithViews(options => options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute())) - .AddJsonOptions(options => - { - options.JsonSerializerOptions.PropertyNameCaseInsensitive = true; - }); - - // Setup where the compiled version of our spa application will be, when in production. - services.AddSpaStaticFiles(configuration => - { - configuration.RootPath = "wwwroot"; - }); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory, IAntiforgery antiforgery) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - - // Here we add Angular default Antiforgery cookie name on first load. https://angular.io/guide/http#security-xsrf-protection - // This cookie will be read by Angular app and its value will be sent back to the application as the header configured in .AddAntiforgery() - app.Use(next => context => - { - string path = context.Request.Path.Value; - - if ( - string.Equals(path, "/", StringComparison.OrdinalIgnoreCase) || - string.Equals(path, "/index.html", StringComparison.OrdinalIgnoreCase)) - { - // The request token has to be sent as a JavaScript-readable cookie, - // and Angular uses it by default. - var tokens = antiforgery.GetAndStoreTokens(context); - context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken, - new CookieOptions() { HttpOnly = false }); - } - - return next(context); - }); - - //Seed Data - WebContextSeed.Seed(app, env, loggerFactory); - - var pathBase = Configuration["PATH_BASE"]; - - if (!string.IsNullOrEmpty(pathBase)) - { - loggerFactory.CreateLogger().LogDebug("Using PATH BASE '{pathBase}'", pathBase); - app.UsePathBase(pathBase); - } - - app.UseDefaultFiles(); - app.UseStaticFiles(); - - // this will make the application to respond with the index.html and the rest of the assets present on the configured folder (at AddSpaStaticFiles() (wwwroot)) - if (!env.IsDevelopment()) - { - app.UseSpaStaticFiles(); - } - app.UseRouting(); - app.UseEndpoints(endpoints => - { - endpoints.MapDefaultControllerRoute(); - endpoints.MapControllers(); - endpoints.MapHealthChecks("/liveness", new HealthCheckOptions - { - Predicate = r => r.Name.Contains("self") - }); - endpoints.MapHealthChecks("/hc", new HealthCheckOptions() - { - Predicate = _ => true, - ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse - }); - }); - - // Handles all still unnatended (by any other middleware) requests by returning the default page of the SPA (wwwroot/index.html). - app.UseSpa(spa => - { - // To learn more about options for serving an Angular SPA from ASP.NET Core, - // see https://go.microsoft.com/fwlink/?linkid=864501 - - // the root of the angular app. (Where the package.json lives) - spa.Options.SourcePath = "Client"; - - if (env.IsDevelopment()) - { - - // use the SpaServices extension method for angular, that will make the application to run "ng serve" for us, when in development. - spa.UseAngularCliServer(npmScript: "start"); - } - }); - } - - private void RegisterAppInsights(IServiceCollection services) - { - services.AddApplicationInsightsTelemetry(Configuration); - services.AddApplicationInsightsKubernetesEnricher(); - } -}