diff --git a/src/Web/WebMVC/AppSettings.cs b/src/Web/WebMVC/AppSettings.cs index ae0a02650..8879abf2e 100644 --- a/src/Web/WebMVC/AppSettings.cs +++ b/src/Web/WebMVC/AppSettings.cs @@ -2,28 +2,7 @@ public class AppSettings { - //public Connectionstrings ConnectionStrings { get; set; } public string PurchaseUrl { get; set; } public string SignalrHubUrl { get; set; } - public bool ActivateCampaignDetailFunction { get; set; } - public Logging Logging { get; set; } public bool UseCustomizationData { get; set; } } - -public class Connectionstrings -{ - public string DefaultConnection { get; set; } -} - -public class Logging -{ - public bool IncludeScopes { get; set; } - public Loglevel LogLevel { get; set; } -} - -public class Loglevel -{ - public string Default { get; set; } - public string System { get; set; } - public string Microsoft { get; set; } -} diff --git a/src/Web/WebMVC/Controllers/TestController.cs b/src/Web/WebMVC/Controllers/TestController.cs index 3054038ee..680099cf4 100644 --- a/src/Web/WebMVC/Controllers/TestController.cs +++ b/src/Web/WebMVC/Controllers/TestController.cs @@ -32,7 +32,7 @@ public class TestController : Controller BasketId = _appUserParser.Parse(User).Id }; - var content = new StringContent(JsonSerializer.Serialize(payload), System.Text.Encoding.UTF8, "application/json"); + var content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json"); var response = await _client.CreateClient(nameof(IBasketService)) diff --git a/src/Web/WebMVC/Infrastructure/WebContextSeed.cs b/src/Web/WebMVC/Infrastructure/WebContextSeed.cs index 6ee7678e1..cc03c1797 100644 --- a/src/Web/WebMVC/Infrastructure/WebContextSeed.cs +++ b/src/Web/WebMVC/Infrastructure/WebContextSeed.cs @@ -5,7 +5,7 @@ public class WebContextSeed { public static void Seed(IApplicationBuilder applicationBuilder, IWebHostEnvironment env) { - var log = Serilog.Log.Logger; + var log = Log.Logger; var settings = (AppSettings)applicationBuilder .ApplicationServices.GetRequiredService>().Value; diff --git a/src/Web/WebMVC/Program.cs b/src/Web/WebMVC/Program.cs index 48a72fcf8..658ab2d9a 100644 --- a/src/Web/WebMVC/Program.cs +++ b/src/Web/WebMVC/Program.cs @@ -1,34 +1,62 @@ -var configuration = GetConfiguration(); +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddControllersWithViews(); -Log.Logger = CreateSerilogLogger(configuration); +AddApplicationInsights(builder); +AddHealthChecks(builder); +AddCustomMvc(builder); +AddHttpClientServices(builder); +AddCustomAuthentication(builder); -try -{ - Log.Information("Configuring web host ({ApplicationContext})...", Program.AppName); - var host = BuildWebHost(configuration, args); +builder.WebHost.CaptureStartupErrors(false); +builder.Host.UseSerilog(CreateSerilogLogger(builder.Configuration)); - Log.Information("Starting web host ({ApplicationContext})...", Program.AppName); - host.Run(); +var app = builder.Build(); - return 0; +JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub"); +if (app.Environment.IsDevelopment()) +{ + app.UseDeveloperExceptionPage(); } -catch (Exception ex) +else { - Log.Fatal(ex, "Program terminated unexpectedly ({ApplicationContext})!", Program.AppName); - return 1; + app.UseExceptionHandler("/Error"); } -finally + +var pathBase = builder.Configuration["PATH_BASE"]; + +if (!string.IsNullOrEmpty(pathBase)) { - Log.CloseAndFlush(); + app.UsePathBase(pathBase); } -IWebHost BuildWebHost(IConfiguration configuration, string[] args) => - WebHost.CreateDefaultBuilder(args) - .CaptureStartupErrors(false) - .ConfigureAppConfiguration(x => x.AddConfiguration(configuration)) - .UseStartup() - .UseSerilog() - .Build(); +app.UseStaticFiles(); +app.UseSession(); + +WebContextSeed.Seed(app, app.Environment); + +// Fix samesite issue when running eShop from docker-compose locally as by default http protocol is being used +// Refer to https://github.com/dotnet-architecture/eShopOnContainers/issues/1391 +app.UseCookiePolicy(new CookiePolicyOptions { MinimumSameSitePolicy = SameSiteMode.Lax }); + +app.UseRouting(); + +app.UseAuthentication(); +app.UseAuthorization(); + +app.MapControllerRoute("default", "{controller=Catalog}/{action=Index}/{id?}"); +app.MapControllerRoute("defaultError", "{controller=Error}/{action=Error}"); +app.MapControllers(); +app.MapHealthChecks("/liveness", new HealthCheckOptions +{ + Predicate = r => r.Name.Contains("self") +}); +app.MapHealthChecks("/hc", new HealthCheckOptions() +{ + Predicate = _ => true, + ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse +}); + +await app.RunAsync(); Serilog.ILogger CreateSerilogLogger(IConfiguration configuration) { @@ -36,7 +64,7 @@ Serilog.ILogger CreateSerilogLogger(IConfiguration configuration) var logstashUrl = configuration["Serilog:LogstashgUrl"]; var cfg = new LoggerConfiguration() .ReadFrom.Configuration(configuration) - .Enrich.WithProperty("ApplicationContext", Program.AppName) + .Enrich.WithProperty("ApplicationContext", AppName) .Enrich.FromLogContext() .WriteTo.Console(); if (!string.IsNullOrWhiteSpace(seqServerUrl)) @@ -50,19 +78,100 @@ Serilog.ILogger CreateSerilogLogger(IConfiguration configuration) return cfg.CreateLogger(); } -IConfiguration GetConfiguration() +static void AddApplicationInsights(WebApplicationBuilder builder) +{ + builder.Services.AddApplicationInsightsTelemetry(builder.Configuration); + builder.Services.AddApplicationInsightsKubernetesEnricher(); +} + +static void AddHealthChecks(WebApplicationBuilder builder) +{ + builder.Services.AddHealthChecks() + .AddCheck("self", () => HealthCheckResult.Healthy()) + .AddUrlGroup(new Uri(builder.Configuration["IdentityUrlHC"]), name: "identityapi-check", tags: new string[] { "identityapi" }); +} + +static void AddCustomMvc(WebApplicationBuilder builder) { - var builder = new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) - .AddEnvironmentVariables(); + builder.Services.AddOptions() + .Configure(builder.Configuration) + .AddSession() + .AddDistributedMemoryCache(); - return builder.Build(); + if (builder.Configuration.GetValue("IsClusterEnv") == bool.TrueString) + { + builder.Services.AddDataProtection(opts => + { + opts.ApplicationDiscriminator = "eshop.webmvc"; + }) + .PersistKeysToStackExchangeRedis(ConnectionMultiplexer.Connect(builder.Configuration["DPConnectionString"]), "DataProtection-Keys"); + } +} + +// Adds all Http client services +static void AddHttpClientServices(WebApplicationBuilder builder) +{ + builder.Services.AddSingleton(); + + //register delegating handlers + builder.Services.AddTransient() + .AddTransient(); + + //set 5 min as the lifetime for each HttpMessageHandler int the pool + builder.Services.AddHttpClient("extendedhandlerlifetime").SetHandlerLifetime(TimeSpan.FromMinutes(5)); + + //add http client services + builder.Services.AddHttpClient() + .SetHandlerLifetime(TimeSpan.FromMinutes(5)) //Sample. Default lifetime is 2 minutes + .AddHttpMessageHandler(); + + builder.Services.AddHttpClient(); + + builder.Services.AddHttpClient() + .AddHttpMessageHandler() + .AddHttpMessageHandler(); + + + //add custom application services + builder.Services.AddTransient, IdentityParser>(); } +static void AddCustomAuthentication(WebApplicationBuilder builder) +{ + var identityUrl = builder.Configuration.GetValue("IdentityUrl"); + var callBackUrl = builder.Configuration.GetValue("CallBackUrl"); + var sessionCookieLifetime = builder.Configuration.GetValue("SessionCookieLifetimeMinutes", 60); + + // Add Authentication services + + builder.Services.AddAuthentication(options => + { + options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; + }) + .AddCookie(setup => setup.ExpireTimeSpan = TimeSpan.FromMinutes(sessionCookieLifetime)) + .AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options => + { + options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; + options.Authority = identityUrl.ToString(); + options.SignedOutRedirectUri = callBackUrl.ToString(); + options.ClientId = "mvc"; + options.ClientSecret = "secret"; + options.ResponseType = "code"; + options.SaveTokens = true; + options.GetClaimsFromUserInfoEndpoint = true; + options.RequireHttpsMetadata = false; + options.Scope.Add("openid"); + options.Scope.Add("profile"); + options.Scope.Add("orders"); + options.Scope.Add("basket"); + options.Scope.Add("webshoppingagg"); + options.Scope.Add("orders.signalrhub"); + options.Scope.Add("webhooks"); + }); +} public partial class Program { - private static readonly string _namespace = typeof(Startup).Namespace; - public static readonly string AppName = _namespace.Substring(_namespace.LastIndexOf('.', _namespace.LastIndexOf('.') - 1) + 1); + public static readonly string AppName = typeof(Program).Assembly.GetName().Name; } \ No newline at end of file diff --git a/src/Web/WebMVC/Services/BasketService.cs b/src/Web/WebMVC/Services/BasketService.cs index eeadf37b7..a53e45306 100644 --- a/src/Web/WebMVC/Services/BasketService.cs +++ b/src/Web/WebMVC/Services/BasketService.cs @@ -39,7 +39,7 @@ public class BasketService : IBasketService { var uri = API.Basket.UpdateBasket(_basketByPassUrl); - var basketContent = new StringContent(JsonSerializer.Serialize(basket), System.Text.Encoding.UTF8, "application/json"); + var basketContent = new StringContent(JsonSerializer.Serialize(basket), Encoding.UTF8, "application/json"); var response = await _apiClient.PostAsync(uri, basketContent); @@ -51,7 +51,7 @@ public class BasketService : IBasketService public async Task Checkout(BasketDTO basket) { var uri = API.Basket.CheckoutBasket(_basketByPassUrl); - var basketContent = new StringContent(JsonSerializer.Serialize(basket), System.Text.Encoding.UTF8, "application/json"); + var basketContent = new StringContent(JsonSerializer.Serialize(basket), Encoding.UTF8, "application/json"); _logger.LogInformation("Uri chechout {uri}", uri); @@ -74,7 +74,7 @@ public class BasketService : IBasketService }).ToArray() }; - var basketContent = new StringContent(JsonSerializer.Serialize(basketUpdate), System.Text.Encoding.UTF8, "application/json"); + var basketContent = new StringContent(JsonSerializer.Serialize(basketUpdate), Encoding.UTF8, "application/json"); var response = await _apiClient.PutAsync(uri, basketContent); @@ -113,7 +113,7 @@ public class BasketService : IBasketService Quantity = 1 }; - var basketContent = new StringContent(JsonSerializer.Serialize(newItem), System.Text.Encoding.UTF8, "application/json"); + var basketContent = new StringContent(JsonSerializer.Serialize(newItem), Encoding.UTF8, "application/json"); var response = await _apiClient.PostAsync(uri, basketContent); } diff --git a/src/Web/WebMVC/Services/OrderingService.cs b/src/Web/WebMVC/Services/OrderingService.cs index f1422c51f..42129390d 100644 --- a/src/Web/WebMVC/Services/OrderingService.cs +++ b/src/Web/WebMVC/Services/OrderingService.cs @@ -55,7 +55,7 @@ public class OrderingService : IOrderingService }; var uri = API.Order.CancelOrder(_remoteServiceBaseUrl); - var orderContent = new StringContent(JsonSerializer.Serialize(order), System.Text.Encoding.UTF8, "application/json"); + var orderContent = new StringContent(JsonSerializer.Serialize(order), Encoding.UTF8, "application/json"); var response = await _httpClient.PutAsync(uri, orderContent); @@ -75,7 +75,7 @@ public class OrderingService : IOrderingService }; var uri = API.Order.ShipOrder(_remoteServiceBaseUrl); - var orderContent = new StringContent(JsonSerializer.Serialize(order), System.Text.Encoding.UTF8, "application/json"); + var orderContent = new StringContent(JsonSerializer.Serialize(order), Encoding.UTF8, "application/json"); var response = await _httpClient.PutAsync(uri, orderContent); diff --git a/src/Web/WebMVC/Startup.cs b/src/Web/WebMVC/Startup.cs deleted file mode 100644 index c5f8aece5..000000000 --- a/src/Web/WebMVC/Startup.cs +++ /dev/null @@ -1,185 +0,0 @@ -namespace Microsoft.eShopOnContainers.WebMVC; - -public class Startup -{ - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - // This method gets called by the runtime. Use this method to add services to the IoC container. - public void ConfigureServices(IServiceCollection services) - { - services.AddControllersWithViews() - .Services - .AddAppInsight(Configuration) - .AddHealthChecks(Configuration) - .AddCustomMvc(Configuration) - .AddHttpClientServices(Configuration); - - IdentityModelEventSource.ShowPII = true; // Caution! Do NOT use in production: https://aka.ms/IdentityModel/PII - - services.AddCustomAuthentication(Configuration); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub"); - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - else - { - app.UseExceptionHandler("/Error"); - } - - var pathBase = Configuration["PATH_BASE"]; - - if (!string.IsNullOrEmpty(pathBase)) - { - app.UsePathBase(pathBase); - } - - app.UseStaticFiles(); - app.UseSession(); - - WebContextSeed.Seed(app, env); - - // Fix samesite issue when running eShop from docker-compose locally as by default http protocol is being used - // Refer to https://github.com/dotnet-architecture/eShopOnContainers/issues/1391 - app.UseCookiePolicy(new CookiePolicyOptions { MinimumSameSitePolicy = AspNetCore.Http.SameSiteMode.Lax }); - - app.UseRouting(); - - app.UseAuthentication(); - app.UseAuthorization(); - - app.UseEndpoints(endpoints => - { - endpoints.MapControllerRoute("default", "{controller=Catalog}/{action=Index}/{id?}"); - endpoints.MapControllerRoute("defaultError", "{controller=Error}/{action=Error}"); - endpoints.MapControllers(); - endpoints.MapHealthChecks("/liveness", new HealthCheckOptions - { - Predicate = r => r.Name.Contains("self") - }); - endpoints.MapHealthChecks("/hc", new HealthCheckOptions() - { - Predicate = _ => true, - ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse - }); - }); - } -} - -static class ServiceCollectionExtensions -{ - - public static IServiceCollection AddAppInsight(this IServiceCollection services, IConfiguration configuration) - { - services.AddApplicationInsightsTelemetry(configuration); - services.AddApplicationInsightsKubernetesEnricher(); - - return services; - } - - public static IServiceCollection AddHealthChecks(this IServiceCollection services, IConfiguration configuration) - { - services.AddHealthChecks() - .AddCheck("self", () => HealthCheckResult.Healthy()) - .AddUrlGroup(new Uri(configuration["IdentityUrlHC"]), name: "identityapi-check", tags: new string[] { "identityapi" }); - - return services; - } - - public static IServiceCollection AddCustomMvc(this IServiceCollection services, IConfiguration configuration) - { - services.AddOptions(); - services.Configure(configuration); - services.AddSession(); - services.AddDistributedMemoryCache(); - - if (configuration.GetValue("IsClusterEnv") == bool.TrueString) - { - services.AddDataProtection(opts => - { - opts.ApplicationDiscriminator = "eshop.webmvc"; - }) - .PersistKeysToStackExchangeRedis(ConnectionMultiplexer.Connect(configuration["DPConnectionString"]), "DataProtection-Keys"); - } - - return services; - } - - // Adds all Http client services - public static IServiceCollection AddHttpClientServices(this IServiceCollection services, IConfiguration configuration) - { - services.AddSingleton(); - - //register delegating handlers - services.AddTransient(); - services.AddTransient(); - - //set 5 min as the lifetime for each HttpMessageHandler int the pool - services.AddHttpClient("extendedhandlerlifetime").SetHandlerLifetime(TimeSpan.FromMinutes(5)); - - //add http client services - services.AddHttpClient() - .SetHandlerLifetime(TimeSpan.FromMinutes(5)) //Sample. Default lifetime is 2 minutes - .AddHttpMessageHandler(); - - services.AddHttpClient(); - - services.AddHttpClient() - .AddHttpMessageHandler() - .AddHttpMessageHandler(); - - - //add custom application services - services.AddTransient, IdentityParser>(); - - return services; - } - - - public static IServiceCollection AddCustomAuthentication(this IServiceCollection services, IConfiguration configuration) - { - var identityUrl = configuration.GetValue("IdentityUrl"); - var callBackUrl = configuration.GetValue("CallBackUrl"); - var sessionCookieLifetime = configuration.GetValue("SessionCookieLifetimeMinutes", 60); - - // Add Authentication services - - services.AddAuthentication(options => - { - options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; - }) - .AddCookie(setup => setup.ExpireTimeSpan = TimeSpan.FromMinutes(sessionCookieLifetime)) - .AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options => - { - options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; - options.Authority = identityUrl.ToString(); - options.SignedOutRedirectUri = callBackUrl.ToString(); - options.ClientId = "mvc"; - options.ClientSecret = "secret"; - options.ResponseType = "code"; - options.SaveTokens = true; - options.GetClaimsFromUserInfoEndpoint = true; - options.RequireHttpsMetadata = false; - options.Scope.Add("openid"); - options.Scope.Add("profile"); - options.Scope.Add("orders"); - options.Scope.Add("basket"); - options.Scope.Add("webshoppingagg"); - options.Scope.Add("orders.signalrhub"); - options.Scope.Add("webhooks"); - }); - - return services; - } -}