Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use extension methods to clean up Web Program.cs file #55

Merged
merged 2 commits into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Infrastructure/Dependencies.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/PublicApi/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<AppIdentityDbContext>()
Expand Down
7 changes: 7 additions & 0 deletions src/Web/Extensions/IWebHostEnvironmentExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Microsoft.eShopWeb.Web.Extensions;

public static class IWebHostEnvironmentExtensions
{
public static bool IsDocker(this IWebHostEnvironment environment)
=> environment.EnvironmentName == "Docker";
}
81 changes: 81 additions & 0 deletions src/Web/Extensions/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
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 AddDatabaseContexts(this IServiceCollection services, IWebHostEnvironment environment, ConfigurationManager configuration)
{
if (environment.IsDevelopment() || environment.IsDocker())
{
// 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<CatalogContext>(c =>
{
var connectionString = configuration[configuration["AZURE_SQL_CATALOG_CONNECTION_STRING_KEY"] ?? ""];
c.UseSqlServer(connectionString, sqlOptions => sqlOptions.EnableRetryOnFailure());
});
services.AddDbContext<AppIdentityDbContext>(options =>
{
var connectionString = configuration[configuration["AZURE_SQL_IDENTITY_CONNECTION_STRING_KEY"] ?? ""];
options.UseSqlServer(connectionString, sqlOptions => sqlOptions.EnableRetryOnFailure());
});
}
}

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<ApiHealthCheck>("api_health_check", tags: new[] { "apiHealthCheck" })
.AddCheck<HomePageHealthCheck>("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<BaseUrlConfiguration>(configSection);
var baseUrlConfig = configSection.Get<BaseUrlConfiguration>();

// Blazor Admin Required Services for Prerendering
services.AddScoped<HttpClient>(s => new HttpClient
{
BaseAddress = new Uri(baseUrlConfig!.WebBase)
});

// add blazor services
services.AddBlazoredLocalStorage();
services.AddServerSideBlazor();
services.AddScoped<ToastService>();
services.AddScoped<HttpService>();
services.AddBlazorServices();
}
}
74 changes: 74 additions & 0 deletions src/Web/Extensions/WebApplicationExtensions.cs
Original file line number Diff line number Diff line change
@@ -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<CatalogContext>();
await CatalogContextSeed.SeedAsync(catalogContext, app.Logger);

var userManager = scopedProvider.GetRequiredService<UserManager<ApplicationUser>>();
var roleManager = scopedProvider.GetRequiredService<RoleManager<IdentityRole>>();
var identityContext = scopedProvider.GetRequiredService<AppIdentityDbContext>();
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();
}
}
}
134 changes: 13 additions & 121 deletions src/Web/Program.cs
Original file line number Diff line number Diff line change
@@ -1,65 +1,26 @@
using System.Net.Mime;
using Ardalis.ListStartupServices;
using Azure.Identity;
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;
using Microsoft.eShopWeb.Infrastructure.Data;
using Microsoft.eShopWeb.Infrastructure.Identity;
using Microsoft.eShopWeb.Web;
using Microsoft.eShopWeb.Web.Configuration;
using Microsoft.eShopWeb.Web.HealthChecks;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.eShopWeb.Web.Extensions;

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<CatalogContext>(c =>
{
var connectionString = builder.Configuration[builder.Configuration["AZURE_SQL_CATALOG_CONNECTION_STRING_KEY"] ?? ""];
c.UseSqlServer(connectionString, sqlOptions => sqlOptions.EnableRetryOnFailure());
});
builder.Services.AddDbContext<AppIdentityDbContext>(options =>
{
var connectionString = builder.Configuration[builder.Configuration["AZURE_SQL_IDENTITY_CONNECTION_STRING_KEY"] ?? ""];
options.UseSqlServer(connectionString, sqlOptions => sqlOptions.EnableRetryOnFailure());
});
}
builder.Services.AddDatabaseContexts(builder.Environment, builder.Configuration);

builder.Services.AddCookieSettings();

// 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.AddCookieAuthentication();

builder.Services.AddIdentity<ApplicationUser, IdentityRole>()
.AddDefaultUI()
.AddEntityFrameworkStores<AppIdentityDbContext>()
.AddDefaultTokenProviders();
.AddDefaultTokenProviders();

builder.Services.AddScoped<ITokenClaimsService, IdentityTokenClaimService>();
builder.Configuration.AddEnvironmentVariables();
builder.Services.AddCoreServices(builder.Configuration);
builder.Services.AddWebServices(builder.Configuration);

Expand All @@ -84,62 +45,24 @@
options.Conventions.AuthorizePage("/Basket/Checkout");
});
builder.Services.AddHttpContextAccessor();
builder.Services
.AddHealthChecks()
.AddCheck<ApiHealthCheck>("api_health_check", tags: new[] { "apiHealthCheck" })
.AddCheck<HomePageHealthCheck>("home_page_health_check", tags: new[] { "homePageHealthCheck" });

builder.Services.AddCustomHealthChecks();

builder.Services.Configure<ServiceConfig>(config =>
{
config.Services = new List<ServiceDescriptor>(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<BaseUrlConfiguration>(configSection);
var baseUrlConfig = configSection.Get<BaseUrlConfiguration>();

// Blazor Admin Required Services for Prerendering
builder.Services.AddScoped<HttpClient>(s => new HttpClient
{
BaseAddress = new Uri(baseUrlConfig!.WebBase)
});

// add blazor services
builder.Services.AddBlazoredLocalStorage();
builder.Services.AddServerSideBlazor();
builder.Services.AddScoped<ToastService>();
builder.Services.AddScoped<HttpService>();
builder.Services.AddBlazorServices();
builder.Services.AddBlazor(builder.Configuration);

builder.Services.AddDatabaseDeveloperPageExceptionFilter();

var app = builder.Build();

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<CatalogContext>();
await CatalogContextSeed.SeedAsync(catalogContext, app.Logger);

var userManager = scopedProvider.GetRequiredService<UserManager<ApplicationUser>>();
var roleManager = scopedProvider.GetRequiredService<RoleManager<IdentityRole>>();
var identityContext = scopedProvider.GetRequiredService<AppIdentityDbContext>();
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))
Expand All @@ -151,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();
Expand All @@ -194,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") });
Expand Down
Loading