214 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			214 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| 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.EntityFrameworkCore;
 | |
|     using Microsoft.EntityFrameworkCore.Infrastructure;
 | |
|     using Microsoft.eShopOnContainers.BuildingBlocks.EventBus;
 | |
|     using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
 | |
|     using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ;
 | |
|     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 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));
 | |
|             });
 | |
| 
 | |
|             services.AddMvc(options =>
 | |
|             {
 | |
|                 options.Filters.Add(typeof(HttpGlobalExceptionFilter));
 | |
|             }).AddControllersAsServices();
 | |
| 
 | |
|             services.AddDbContext<CatalogContext>(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<CatalogSettings>(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<Func<DbConnection, IIntegrationEventLogService>>(
 | |
|                 sp => (DbConnection c) => new IntegrationEventLogService(c));
 | |
| 
 | |
|             services.AddTransient<ICatalogIntegrationEventService, CatalogIntegrationEventService>();
 | |
| 
 | |
|             services.AddSingleton<IRabbitMQPersistentConnection>(sp =>
 | |
|             {
 | |
|                 var settings = sp.GetRequiredService<IOptions<CatalogSettings>>().Value;
 | |
|                 var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersistentConnection>>();
 | |
|                 var factory = new ConnectionFactory()
 | |
|                 {
 | |
|                     HostName = settings.EventBusConnection
 | |
|                 };
 | |
| 
 | |
|                 return new DefaultRabbitMQPersistentConnection(factory, logger);
 | |
|             });
 | |
| 
 | |
|             RegisterServiceBus(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).Wait();
 | |
| 
 | |
|             ConfigureEventBus(app);
 | |
| 
 | |
|             var integrationEventLogContext = new IntegrationEventLogContext(
 | |
|                 new DbContextOptionsBuilder<IntegrationEventLogContext>()
 | |
|                 .UseSqlServer(Configuration["ConnectionString"], b => b.MigrationsAssembly("Catalog.API"))
 | |
|                 .Options);
 | |
| 
 | |
|             integrationEventLogContext.Database.Migrate();
 | |
|         }
 | |
| 
 | |
|         private async Task WaitForSqlAvailabilityAsync(CatalogContext ctx, ILoggerFactory loggerFactory, IApplicationBuilder app, int retries = 0)
 | |
|         {
 | |
|             var logger = loggerFactory.CreateLogger(nameof(Startup));
 | |
|             var policy = CreatePolicy(retries, logger, nameof (WaitForSqlAvailabilityAsync));
 | |
|             await policy.ExecuteAsync(async () =>
 | |
|             {
 | |
|                 await CatalogContextSeed.SeedAsync(app, loggerFactory);
 | |
|             });
 | |
| 
 | |
|         }
 | |
| 
 | |
|         private Policy CreatePolicy(int retries, ILogger logger, string prefix)
 | |
|         {
 | |
|             return Policy.Handle<SqlException>().
 | |
|                 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 RegisterServiceBus(IServiceCollection services)
 | |
|         {
 | |
|             services.AddSingleton<IEventBusSubscriptionsManager, InMemoryEventBusSubscriptionsManager>();
 | |
|             services.AddSingleton<IEventBus, EventBusRabbitMQ>();
 | |
| 
 | |
|             services.AddTransient<IIntegrationEventHandler<OrderStatusChangedToAwaitingValidationIntegrationEvent>,
 | |
|                 OrderStatusChangedToAwaitingValidationIntegrationEventHandler>();
 | |
|             services.AddTransient<IIntegrationEventHandler<OrderStatusChangedToPaidIntegrationEvent>,
 | |
|                 OrderStatusChangedToPaidIntegrationEventHandler>();
 | |
|         }
 | |
| 
 | |
|         private void ConfigureEventBus(IApplicationBuilder app)
 | |
|         {
 | |
|             var eventBus = app.ApplicationServices.GetRequiredService<IEventBus>();
 | |
| 
 | |
|             eventBus.Subscribe<OrderStatusChangedToAwaitingValidationIntegrationEvent, 
 | |
|                 IIntegrationEventHandler<OrderStatusChangedToAwaitingValidationIntegrationEvent>>();
 | |
|             eventBus.Subscribe<OrderStatusChangedToPaidIntegrationEvent, 
 | |
|                 IIntegrationEventHandler<OrderStatusChangedToPaidIntegrationEvent>>();
 | |
|         }
 | |
|     }
 | |
| }
 |