using HealthChecks.UI.Client; using Microsoft.ApplicationInsights.Extensibility; using Microsoft.ApplicationInsights.ServiceFabric; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Diagnostics.HealthChecks; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.eShopOnContainers.WebMVC.Services; using Microsoft.eShopOnContainers.WebMVC.ViewModels; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Logging; using Polly; using Polly.Extensions.Http; using StackExchange.Redis; using System; using System.IdentityModel.Tokens.Jwt; using System.Net.Http; using WebMVC.Infrastructure; using WebMVC.Infrastructure.Middlewares; using WebMVC.Services; 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.AddAppInsight(Configuration) .AddHealthChecks(Configuration) .AddCustomMvc(Configuration) .AddHttpClientServices(Configuration) //.AddHttpClientLogging(Configuration) //Opt-in HttpClientLogging config .AddCustomAuthentication(Configuration); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); //loggerFactory.AddAzureWebAppDiagnostics(); //loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace); app.UseHealthChecks("/hc", new HealthCheckOptions() { Predicate = _ => true, ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse }); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Error"); app.UseHsts(); } var pathBase = Configuration["PATH_BASE"]; if (!string.IsNullOrEmpty(pathBase)) { loggerFactory.CreateLogger().LogDebug("Using PATH BASE '{PathBase}'", pathBase); app.UsePathBase(pathBase); } app.UseHealthChecks("/liveness", new HealthCheckOptions { Predicate = r => r.Name.Contains("self") }); app.UseSession(); app.UseStaticFiles(); if (Configuration.GetValue("UseLoadTest")) { app.UseMiddleware(); } app.UseAuthentication(); WebContextSeed.Seed(app, env, loggerFactory); app.UseHttpsRedirection(); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Catalog}/{action=Index}/{id?}"); routes.MapRoute( name: "defaultError", template: "{controller=Error}/{action=Error}"); }); } } static class ServiceCollectionExtensions { public static IServiceCollection AddAppInsight(this IServiceCollection services, IConfiguration configuration) { services.AddApplicationInsightsTelemetry(configuration); var orchestratorType = configuration.GetValue("OrchestratorType"); if (orchestratorType?.ToUpper() == "K8S") { // Enable K8s telemetry initializer services.AddApplicationInsightsKubernetesEnricher(); } if (orchestratorType?.ToUpper() == "SF") { // Enable SF telemetry initializer services.AddSingleton((serviceProvider) => new FabricTelemetryInitializer()); } return services; } public static IServiceCollection AddHealthChecks(this IServiceCollection services, IConfiguration configuration) { services.AddHealthChecks() .AddCheck("self", () => HealthCheckResult.Healthy()) .AddUrlGroup(new Uri(configuration["PurchaseUrlHC"]), name: "purchaseapigw-check", tags: new string[] { "purchaseapigw" }) .AddUrlGroup(new Uri(configuration["MarketingUrlHC"]), name: "marketingapigw-check", tags: new string[] { "marketingapigw" }) .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.AddMvc() .SetCompatibilityVersion(CompatibilityVersion.Version_2_2); services.AddSession(); if (configuration.GetValue("IsClusterEnv") == bool.TrueString) { services.AddDataProtection(opts => { opts.ApplicationDiscriminator = "eshop.webmvc"; }) .PersistKeysToRedis(ConnectionMultiplexer.Connect(configuration["DPConnectionString"]), "DataProtection-Keys"); } return services; } // Adds all Http client services (like Service-Agents) using resilient Http requests based on HttpClient factory and Polly's policies 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() .AddPolicyHandler(GetRetryPolicy()) .AddPolicyHandler(GetCircuitBreakerPolicy()); services.AddHttpClient() .AddPolicyHandler(GetRetryPolicy()) .AddPolicyHandler(GetCircuitBreakerPolicy()); services.AddHttpClient() .AddHttpMessageHandler() .AddHttpMessageHandler() .AddPolicyHandler(GetRetryPolicy()) .AddPolicyHandler(GetCircuitBreakerPolicy()); services.AddHttpClient() .AddHttpMessageHandler() .AddPolicyHandler(GetRetryPolicy()) .AddPolicyHandler(GetCircuitBreakerPolicy()); services.AddHttpClient() .AddHttpMessageHandler() .AddPolicyHandler(GetRetryPolicy()) .AddPolicyHandler(GetCircuitBreakerPolicy()); //add custom application services services.AddTransient, IdentityParser>(); return services; } public static IServiceCollection AddHttpClientLogging(this IServiceCollection services, IConfiguration configuration) { services.AddLogging(b => { b.AddFilter((category, level) => true); // Spam the world with logs. // Add console logger so we can see all the logging produced by the client by default. b.AddConsole(c => c.IncludeScopes = true); // Add console logger b.AddDebug(); }); return services; } public static IServiceCollection AddCustomAuthentication(this IServiceCollection services, IConfiguration configuration) { var useLoadTest = configuration.GetValue("UseLoadTest"); var identityUrl = configuration.GetValue("IdentityUrl"); var callBackUrl = configuration.GetValue("CallBackUrl"); // Add Authentication services services.AddAuthentication(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; }) .AddCookie(setup=>setup.ExpireTimeSpan = TimeSpan.FromHours(2)) .AddOpenIdConnect(options => { options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.Authority = identityUrl.ToString(); options.SignedOutRedirectUri = callBackUrl.ToString(); options.ClientId = useLoadTest ? "mvctest" : "mvc"; options.ClientSecret = "secret"; options.ResponseType = useLoadTest ? "code id_token token" : "code id_token"; 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("marketing"); options.Scope.Add("locations"); options.Scope.Add("webshoppingagg"); options.Scope.Add("orders.signalrhub"); }); return services; } static IAsyncPolicy GetRetryPolicy() { return HttpPolicyExtensions .HandleTransientHttpError() .OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound) .WaitAndRetryAsync(6, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))); } static IAsyncPolicy GetCircuitBreakerPolicy() { return HttpPolicyExtensions .HandleTransientHttpError() .CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)); } } }