namespace Microsoft.eShopOnContainers.Services.Catalog.API { using Autofac; using Autofac.Extensions.DependencyInjection; using global::Catalog.API.Infrastructure.Filters; using global::Catalog.API.IntegrationEvents; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Azure.ServiceBus; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ; using Microsoft.eShopOnContainers.BuildingBlocks.EventBusServiceBus; using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF; using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services; using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure; using Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents.EventHandling; using Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents.Events; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.HealthChecks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.WindowsAzure.Storage; using Microsoft.WindowsAzure.Storage.Auth; using Polly; using RabbitMQ.Client; using System; using System.Data.Common; using System.Data.SqlClient; using System.Reflection; using System.Threading.Tasks; public class Startup { public IConfigurationRoot Configuration { get; } public Startup(IHostingEnvironment env) { var builder = new ConfigurationBuilder() .SetBasePath(env.ContentRootPath) .AddJsonFile($"settings.json", optional: false, reloadOnChange: true) .AddJsonFile($"settings.{env.EnvironmentName}.json", optional: true); if (env.IsDevelopment()) { builder.AddUserSecrets(typeof(Startup).GetTypeInfo().Assembly); } builder.AddEnvironmentVariables(); Configuration = builder.Build(); } public IServiceProvider ConfigureServices(IServiceCollection services) { // Add framework services. services.AddHealthChecks(checks => { var minutes = 1; if (int.TryParse(Configuration["HealthCheck:Timeout"], out var minutesParsed)) { minutes = minutesParsed; } checks.AddSqlCheck("CatalogDb", Configuration["ConnectionString"], TimeSpan.FromMinutes(minutes)); var accountName = Configuration.GetValue("AzureStorageAccountName"); var accountKey = Configuration.GetValue("AzureStorageAccountKey"); if (!string.IsNullOrEmpty(accountName) && !string.IsNullOrEmpty(accountKey)) { checks.AddAzureBlobStorageCheck(accountName, accountKey); } }); services.AddMvc(options => { options.Filters.Add(typeof(HttpGlobalExceptionFilter)); }).AddControllersAsServices(); services.AddDbContext(options => { options.UseSqlServer(Configuration["ConnectionString"], sqlServerOptionsAction: sqlOptions => { sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name); //Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency sqlOptions.EnableRetryOnFailure(maxRetryCount: 5, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null); }); // Changing default behavior when client evaluation occurs to throw. // Default in EF Core would be to log a warning when client evaluation is performed. options.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning)); //Check Client vs. Server evaluation: https://docs.microsoft.com/en-us/ef/core/querying/client-eval }); services.Configure(Configuration); // Add framework services. services.AddSwaggerGen(options => { options.DescribeAllEnumsAsStrings(); options.SwaggerDoc("v1", new Swashbuckle.AspNetCore.Swagger.Info { Title = "eShopOnContainers - Catalog HTTP API", Version = "v1", Description = "The Catalog Microservice HTTP API. This is a Data-Driven/CRUD microservice sample", TermsOfService = "Terms Of Service" }); }); services.AddCors(options => { options.AddPolicy("CorsPolicy", builder => builder.AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader() .AllowCredentials()); }); services.AddTransient>( sp => (DbConnection c) => new IntegrationEventLogService(c)); services.AddTransient(); if (Configuration.GetValue("AzureServiceBusEnabled")) { services.AddSingleton(sp => { var settings = sp.GetRequiredService>().Value; var logger = sp.GetRequiredService>(); var serviceBusConnection = new ServiceBusConnectionStringBuilder(settings.EventBusConnection); return new DefaultServiceBusPersisterConnection(serviceBusConnection, logger); }); } else { services.AddSingleton(sp => { var settings = sp.GetRequiredService>().Value; var logger = sp.GetRequiredService>(); var factory = new ConnectionFactory() { HostName = settings.EventBusConnection }; return new DefaultRabbitMQPersistentConnection(factory, logger); }); } RegisterEventBus(services); var container = new ContainerBuilder(); container.Populate(services); return new AutofacServiceProvider(container.Build()); } public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { //Configure logs loggerFactory.AddConsole(Configuration.GetSection("Logging")); loggerFactory.AddDebug(); app.UseCors("CorsPolicy"); app.UseMvcWithDefaultRoute(); app.UseSwagger() .UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); }); var context = (CatalogContext)app .ApplicationServices.GetService(typeof(CatalogContext)); WaitForSqlAvailabilityAsync(context, loggerFactory, app, env).Wait(); ConfigureEventBus(app); var integrationEventLogContext = new IntegrationEventLogContext( new DbContextOptionsBuilder() .UseSqlServer(Configuration["ConnectionString"], b => b.MigrationsAssembly("Catalog.API")) .Options); integrationEventLogContext.Database.Migrate(); } private async Task WaitForSqlAvailabilityAsync(CatalogContext ctx, ILoggerFactory loggerFactory, IApplicationBuilder app, IHostingEnvironment env, int retries = 0) { var logger = loggerFactory.CreateLogger(nameof(Startup)); var policy = CreatePolicy(retries, logger, nameof(WaitForSqlAvailabilityAsync)); await policy.ExecuteAsync(async () => { await CatalogContextSeed.SeedAsync(app, env, loggerFactory); }); } private Policy CreatePolicy(int retries, ILogger logger, string prefix) { return Policy.Handle(). WaitAndRetryAsync( retryCount: retries, sleepDurationProvider: retry => TimeSpan.FromSeconds(5), onRetry: (exception, timeSpan, retry, ctx) => { logger.LogTrace($"[{prefix}] Exception {exception.GetType().Name} with message ${exception.Message} detected on attempt {retry} of {retries}"); } ); } private void RegisterEventBus(IServiceCollection services) { if (Configuration.GetValue("AzureServiceBusEnabled")) { services.AddSingleton(sp => { var serviceBusPersisterConnection = sp.GetRequiredService(); var iLifetimeScope = sp.GetRequiredService(); var logger = sp.GetRequiredService>(); var eventBusSubcriptionsManager = sp.GetRequiredService(); var subscriptionClientName = Configuration["SubscriptionClientName"]; return new EventBusServiceBus(serviceBusPersisterConnection, logger, eventBusSubcriptionsManager, subscriptionClientName, iLifetimeScope); }); } else { services.AddSingleton(); } services.AddSingleton(); services.AddTransient(); services.AddTransient(); } protected virtual void ConfigureEventBus(IApplicationBuilder app) { var eventBus = app.ApplicationServices.GetRequiredService(); eventBus.Subscribe(); eventBus.Subscribe(); } } }