From 18e14752c0c2379678cb27aff033dc6466f9f7b5 Mon Sep 17 00:00:00 2001 From: Cesar Aguirre Date: Wed, 10 Jul 2024 12:04:44 -0500 Subject: [PATCH 1/2] Configure database contexts. Rename ConfigureServices -> ConfigureLocalDatabaseContexts --- src/Infrastructure/Dependencies.cs | 2 +- src/PublicApi/Program.cs | 2 +- .../Extensions/ServiceCollectionExtensions.cs | 36 +++++++++++++++++++ src/Web/Program.cs | 26 ++------------ 4 files changed, 40 insertions(+), 26 deletions(-) create mode 100644 src/Web/Extensions/ServiceCollectionExtensions.cs diff --git a/src/Infrastructure/Dependencies.cs b/src/Infrastructure/Dependencies.cs index 6ef27704..d82fa318 100644 --- a/src/Infrastructure/Dependencies.cs +++ b/src/Infrastructure/Dependencies.cs @@ -8,7 +8,7 @@ namespace Microsoft.eShopWeb.Infrastructure; public static class Dependencies { - public static void ConfigureServices(this IServiceCollection services, IConfiguration configuration) + public static void ConfigureLocalDatabaseContexts(this IServiceCollection services, IConfiguration configuration) { bool useOnlyInMemoryDatabase = false; if (configuration["UseOnlyInMemoryDatabase"] != null) diff --git a/src/PublicApi/Program.cs b/src/PublicApi/Program.cs index fde4edf8..c8b8543f 100644 --- a/src/PublicApi/Program.cs +++ b/src/PublicApi/Program.cs @@ -20,7 +20,7 @@ // Use to force loading of appsettings.json of test project builder.Configuration.AddConfigurationFile("appsettings.test.json"); -builder.Services.ConfigureServices(builder.Configuration); +builder.Services.ConfigureLocalDatabaseContexts(builder.Configuration); builder.Services.AddIdentity() .AddEntityFrameworkStores() diff --git a/src/Web/Extensions/ServiceCollectionExtensions.cs b/src/Web/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..4c22c5db --- /dev/null +++ b/src/Web/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,36 @@ +using Azure.Identity; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.Infrastructure; +using Microsoft.eShopWeb.Infrastructure.Data; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Web.Extensions; + +public static class ServiceCollectionExtensions +{ + public static void ConfigureDatabaseContexts(this IServiceCollection services, IWebHostEnvironment environment, ConfigurationManager configuration) + { + if (environment.IsDevelopment() || environment.EnvironmentName == "Docker") + { + // Configure SQL Server (local) + services.ConfigureLocalDatabaseContexts(configuration); + } + else + { + // Configure SQL Server (prod) + var credential = new ChainedTokenCredential(new AzureDeveloperCliCredential(), new DefaultAzureCredential()); + configuration.AddAzureKeyVault(new Uri(configuration["AZURE_KEY_VAULT_ENDPOINT"] ?? ""), credential); + + services.AddDbContext(c => + { + var connectionString = configuration[configuration["AZURE_SQL_CATALOG_CONNECTION_STRING_KEY"] ?? ""]; + c.UseSqlServer(connectionString, sqlOptions => sqlOptions.EnableRetryOnFailure()); + }); + services.AddDbContext(options => + { + var connectionString = configuration[configuration["AZURE_SQL_IDENTITY_CONNECTION_STRING_KEY"] ?? ""]; + options.UseSqlServer(connectionString, sqlOptions => sqlOptions.EnableRetryOnFailure()); + }); + } + } +} diff --git a/src/Web/Program.cs b/src/Web/Program.cs index 725ec549..21ddc510 100644 --- a/src/Web/Program.cs +++ b/src/Web/Program.cs @@ -1,6 +1,5 @@ using System.Net.Mime; using Ardalis.ListStartupServices; -using Azure.Identity; using BlazorAdmin; using BlazorAdmin.Services; using Blazored.LocalStorage; @@ -11,37 +10,17 @@ using Microsoft.EntityFrameworkCore; using Microsoft.eShopWeb; using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using Microsoft.eShopWeb.Infrastructure; using Microsoft.eShopWeb.Infrastructure.Data; using Microsoft.eShopWeb.Infrastructure.Identity; using Microsoft.eShopWeb.Web; using Microsoft.eShopWeb.Web.Configuration; +using Microsoft.eShopWeb.Web.Extensions; using Microsoft.eShopWeb.Web.HealthChecks; using Microsoft.Extensions.Diagnostics.HealthChecks; var builder = WebApplication.CreateBuilder(args); -builder.Logging.AddConsole(); - -if (builder.Environment.IsDevelopment() || builder.Environment.EnvironmentName == "Docker"){ - // Configure SQL Server (local) - builder.Services.ConfigureServices(builder.Configuration); -} -else{ - // Configure SQL Server (prod) - var credential = new ChainedTokenCredential(new AzureDeveloperCliCredential(), new DefaultAzureCredential()); - builder.Configuration.AddAzureKeyVault(new Uri(builder.Configuration["AZURE_KEY_VAULT_ENDPOINT"] ?? ""), credential); - builder.Services.AddDbContext(c => - { - var connectionString = builder.Configuration[builder.Configuration["AZURE_SQL_CATALOG_CONNECTION_STRING_KEY"] ?? ""]; - c.UseSqlServer(connectionString, sqlOptions => sqlOptions.EnableRetryOnFailure()); - }); - builder.Services.AddDbContext(options => - { - var connectionString = builder.Configuration[builder.Configuration["AZURE_SQL_IDENTITY_CONNECTION_STRING_KEY"] ?? ""]; - options.UseSqlServer(connectionString, sqlOptions => sqlOptions.EnableRetryOnFailure()); - }); -} +builder.Services.ConfigureDatabaseContexts(builder.Environment, builder.Configuration); builder.Services.AddCookieSettings(); // TODO: Move to extension method to wire up auth @@ -59,7 +38,6 @@ .AddDefaultTokenProviders(); builder.Services.AddScoped(); -builder.Configuration.AddEnvironmentVariables(); builder.Services.AddCoreServices(builder.Configuration); builder.Services.AddWebServices(builder.Configuration); From 873951b4c7825d944f84ee18c02cd05a07c08ab3 Mon Sep 17 00:00:00 2001 From: Cesar Aguirre Date: Wed, 10 Jul 2024 12:35:48 -0500 Subject: [PATCH 2/2] Clean up Program.cs - 2 --- .../IWebHostEnvironmentExtensions.cs | 7 ++ .../Extensions/ServiceCollectionExtensions.cs | 49 +++++++- .../Extensions/WebApplicationExtensions.cs | 74 ++++++++++++ src/Web/Program.cs | 112 ++---------------- 4 files changed, 141 insertions(+), 101 deletions(-) create mode 100644 src/Web/Extensions/IWebHostEnvironmentExtensions.cs create mode 100644 src/Web/Extensions/WebApplicationExtensions.cs diff --git a/src/Web/Extensions/IWebHostEnvironmentExtensions.cs b/src/Web/Extensions/IWebHostEnvironmentExtensions.cs new file mode 100644 index 00000000..6a52fd28 --- /dev/null +++ b/src/Web/Extensions/IWebHostEnvironmentExtensions.cs @@ -0,0 +1,7 @@ +namespace Microsoft.eShopWeb.Web.Extensions; + +public static class IWebHostEnvironmentExtensions +{ + public static bool IsDocker(this IWebHostEnvironment environment) + => environment.EnvironmentName == "Docker"; +} diff --git a/src/Web/Extensions/ServiceCollectionExtensions.cs b/src/Web/Extensions/ServiceCollectionExtensions.cs index 4c22c5db..76fcba41 100644 --- a/src/Web/Extensions/ServiceCollectionExtensions.cs +++ b/src/Web/Extensions/ServiceCollectionExtensions.cs @@ -1,16 +1,22 @@ using Azure.Identity; +using BlazorAdmin; +using BlazorAdmin.Services; +using Blazored.LocalStorage; +using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.EntityFrameworkCore; using Microsoft.eShopWeb.Infrastructure; using Microsoft.eShopWeb.Infrastructure.Data; using Microsoft.eShopWeb.Infrastructure.Identity; +using Microsoft.eShopWeb.Web.Configuration; +using Microsoft.eShopWeb.Web.HealthChecks; namespace Microsoft.eShopWeb.Web.Extensions; public static class ServiceCollectionExtensions { - public static void ConfigureDatabaseContexts(this IServiceCollection services, IWebHostEnvironment environment, ConfigurationManager configuration) + public static void AddDatabaseContexts(this IServiceCollection services, IWebHostEnvironment environment, ConfigurationManager configuration) { - if (environment.IsDevelopment() || environment.EnvironmentName == "Docker") + if (environment.IsDevelopment() || environment.IsDocker()) { // Configure SQL Server (local) services.ConfigureLocalDatabaseContexts(configuration); @@ -33,4 +39,43 @@ public static void ConfigureDatabaseContexts(this IServiceCollection services, I }); } } + + public static void AddCookieAuthentication(this IServiceCollection services) + { + services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) + .AddCookie(options => + { + options.Cookie.HttpOnly = true; + options.Cookie.SecurePolicy = CookieSecurePolicy.Always; + options.Cookie.SameSite = SameSiteMode.Lax; + }); + } + + public static void AddCustomHealthChecks(this IServiceCollection services) + { + services + .AddHealthChecks() + .AddCheck("api_health_check", tags: new[] { "apiHealthCheck" }) + .AddCheck("home_page_health_check", tags: new[] { "homePageHealthCheck" }); + } + + public static void AddBlazor(this IServiceCollection services, ConfigurationManager configuration) + { + var configSection = configuration.GetRequiredSection(BaseUrlConfiguration.CONFIG_NAME); + services.Configure(configSection); + var baseUrlConfig = configSection.Get(); + + // Blazor Admin Required Services for Prerendering + services.AddScoped(s => new HttpClient + { + BaseAddress = new Uri(baseUrlConfig!.WebBase) + }); + + // add blazor services + services.AddBlazoredLocalStorage(); + services.AddServerSideBlazor(); + services.AddScoped(); + services.AddScoped(); + services.AddBlazorServices(); + } } diff --git a/src/Web/Extensions/WebApplicationExtensions.cs b/src/Web/Extensions/WebApplicationExtensions.cs new file mode 100644 index 00000000..1c336baf --- /dev/null +++ b/src/Web/Extensions/WebApplicationExtensions.cs @@ -0,0 +1,74 @@ +using System.Net.Mime; +using Ardalis.ListStartupServices; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.AspNetCore.Identity; +using Microsoft.eShopWeb.Infrastructure.Data; +using Microsoft.eShopWeb.Infrastructure.Identity; +using Microsoft.Extensions.Diagnostics.HealthChecks; + +namespace Microsoft.eShopWeb.Web.Extensions; + +public static class WebApplicationExtensions +{ + public static async Task SeedDatabaseAsync(this WebApplication app) + { + app.Logger.LogInformation("Seeding Database..."); + + using var scope = app.Services.CreateScope(); + var scopedProvider = scope.ServiceProvider; + try + { + var catalogContext = scopedProvider.GetRequiredService(); + await CatalogContextSeed.SeedAsync(catalogContext, app.Logger); + + var userManager = scopedProvider.GetRequiredService>(); + var roleManager = scopedProvider.GetRequiredService>(); + var identityContext = scopedProvider.GetRequiredService(); + await AppIdentityDbContextSeed.SeedAsync(identityContext, userManager, roleManager); + } + catch (Exception ex) + { + app.Logger.LogError(ex, "An error occurred seeding the DB."); + } + } + + public static void UseCustomHealthChecks(this WebApplication app) + { + app.UseHealthChecks("/health", + new HealthCheckOptions + { + ResponseWriter = async (context, report) => + { + var result = new + { + status = report.Status.ToString(), + errors = report.Entries.Select(e => new + { + key = e.Key, + value = Enum.GetName(typeof(HealthStatus), e.Value.Status) + }) + }.ToJson(); + context.Response.ContentType = MediaTypeNames.Application.Json; + await context.Response.WriteAsync(result); + } + }); + } + + public static void UseTroubleshootingMiddlewares(this WebApplication app) + { + if (app.Environment.IsDevelopment() || app.Environment.IsDocker()) + { + app.Logger.LogInformation("Adding Development middleware..."); + app.UseDeveloperExceptionPage(); + app.UseShowAllServicesMiddleware(); + app.UseMigrationsEndPoint(); + app.UseWebAssemblyDebugging(); + } + else + { + app.Logger.LogInformation("Adding non-Development middleware..."); + app.UseExceptionHandler("/Error"); + app.UseHsts(); + } + } +} diff --git a/src/Web/Program.cs b/src/Web/Program.cs index 21ddc510..8d5cd6bd 100644 --- a/src/Web/Program.cs +++ b/src/Web/Program.cs @@ -1,41 +1,24 @@ -using System.Net.Mime; -using Ardalis.ListStartupServices; -using BlazorAdmin; -using BlazorAdmin.Services; -using Blazored.LocalStorage; -using Microsoft.AspNetCore.Authentication.Cookies; +using Ardalis.ListStartupServices; using Microsoft.AspNetCore.Diagnostics.HealthChecks; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc.ApplicationModels; -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb; using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using Microsoft.eShopWeb.Infrastructure.Data; using Microsoft.eShopWeb.Infrastructure.Identity; using Microsoft.eShopWeb.Web; using Microsoft.eShopWeb.Web.Configuration; using Microsoft.eShopWeb.Web.Extensions; -using Microsoft.eShopWeb.Web.HealthChecks; -using Microsoft.Extensions.Diagnostics.HealthChecks; var builder = WebApplication.CreateBuilder(args); -builder.Services.ConfigureDatabaseContexts(builder.Environment, builder.Configuration); -builder.Services.AddCookieSettings(); +builder.Services.AddDatabaseContexts(builder.Environment, builder.Configuration); -// TODO: Move to extension method to wire up auth -builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) - .AddCookie(options => - { - options.Cookie.HttpOnly = true; - options.Cookie.SecurePolicy = CookieSecurePolicy.Always; - options.Cookie.SameSite = SameSiteMode.Lax; - }); +builder.Services.AddCookieSettings(); +builder.Services.AddCookieAuthentication(); builder.Services.AddIdentity() .AddDefaultUI() .AddEntityFrameworkStores() - .AddDefaultTokenProviders(); + .AddDefaultTokenProviders(); builder.Services.AddScoped(); builder.Services.AddCoreServices(builder.Configuration); @@ -62,34 +45,16 @@ options.Conventions.AuthorizePage("/Basket/Checkout"); }); builder.Services.AddHttpContextAccessor(); -builder.Services - .AddHealthChecks() - .AddCheck("api_health_check", tags: new[] { "apiHealthCheck" }) - .AddCheck("home_page_health_check", tags: new[] { "homePageHealthCheck" }); + +builder.Services.AddCustomHealthChecks(); + builder.Services.Configure(config => { config.Services = new List(builder.Services); config.Path = "/allservices"; }); -// TODO: Move Blazor configuration to an extension method -// blazor configuration -var configSection = builder.Configuration.GetRequiredSection(BaseUrlConfiguration.CONFIG_NAME); -builder.Services.Configure(configSection); -var baseUrlConfig = configSection.Get(); - -// Blazor Admin Required Services for Prerendering -builder.Services.AddScoped(s => new HttpClient -{ - BaseAddress = new Uri(baseUrlConfig!.WebBase) -}); - -// add blazor services -builder.Services.AddBlazoredLocalStorage(); -builder.Services.AddServerSideBlazor(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddBlazorServices(); +builder.Services.AddBlazor(builder.Configuration); builder.Services.AddDatabaseDeveloperPageExceptionFilter(); @@ -97,27 +62,7 @@ app.Logger.LogInformation("App created..."); -// TODO: Move seeding to an extension method -app.Logger.LogInformation("Seeding Database..."); - -using (var scope = app.Services.CreateScope()) -{ - var scopedProvider = scope.ServiceProvider; - try - { - var catalogContext = scopedProvider.GetRequiredService(); - await CatalogContextSeed.SeedAsync(catalogContext, app.Logger); - - var userManager = scopedProvider.GetRequiredService>(); - var roleManager = scopedProvider.GetRequiredService>(); - var identityContext = scopedProvider.GetRequiredService(); - await AppIdentityDbContextSeed.SeedAsync(identityContext, userManager, roleManager); - } - catch (Exception ex) - { - app.Logger.LogError(ex, "An error occurred seeding the DB."); - } -} +await app.SeedDatabaseAsync(); var catalogBaseUrl = builder.Configuration.GetValue(typeof(string), "CatalogBaseUrl") as string; if (!string.IsNullOrEmpty(catalogBaseUrl)) @@ -129,39 +74,9 @@ }); } -// TODO: Move to extension method -app.UseHealthChecks("/health", - new HealthCheckOptions - { - ResponseWriter = async (context, report) => - { - var result = new - { - status = report.Status.ToString(), - errors = report.Entries.Select(e => new - { - key = e.Key, - value = Enum.GetName(typeof(HealthStatus), e.Value.Status) - }) - }.ToJson(); - context.Response.ContentType = MediaTypeNames.Application.Json; - await context.Response.WriteAsync(result); - } - }); -if (app.Environment.IsDevelopment() || app.Environment.EnvironmentName == "Docker") -{ - app.Logger.LogInformation("Adding Development middleware..."); - app.UseDeveloperExceptionPage(); - app.UseShowAllServicesMiddleware(); - app.UseMigrationsEndPoint(); - app.UseWebAssemblyDebugging(); -} -else -{ - app.Logger.LogInformation("Adding non-Development middleware..."); - app.UseExceptionHandler("/Error"); - app.UseHsts(); -} +app.UseCustomHealthChecks(); + +app.UseTroubleshootingMiddlewares(); app.UseHttpsRedirection(); app.UseBlazorFrameworkFiles(); @@ -172,7 +87,6 @@ app.UseAuthentication(); app.UseAuthorization(); - app.MapControllerRoute("default", "{controller:slugify=Home}/{action:slugify=Index}/{id?}"); app.MapRazorPages(); app.MapHealthChecks("home_page_health_check", new HealthCheckOptions { Predicate = check => check.Tags.Contains("homePageHealthCheck") });