Basket.API: Commit to migrate to WebApplication Builder
This commit is contained in:
		
							parent
							
								
									4a86d5e93f
								
							
						
					
					
						commit
						19217e0939
					
				| @ -1,57 +1,221 @@ | |||||||
| var configuration = GetConfiguration(); | using Autofac.Core; | ||||||
|  | using Microsoft.Azure.Amqp.Framing; | ||||||
|  | using Microsoft.Extensions.Configuration; | ||||||
| 
 | 
 | ||||||
| Log.Logger = CreateSerilogLogger(configuration); | var appName = "Basket.API"; | ||||||
| 
 | var builder = WebApplication.CreateBuilder(new WebApplicationOptions { | ||||||
| try |     Args = args, | ||||||
| { |     ApplicationName = typeof(Program).Assembly.FullName, | ||||||
|     Log.Information("Configuring web host ({ApplicationContext})...", Program.AppName); |     ContentRootPath = Directory.GetCurrentDirectory() | ||||||
|     var host = BuildWebHost(configuration, args); | }); | ||||||
| 
 | if (builder.Configuration.GetValue<bool>("UseVault", false)) { | ||||||
|     Log.Information("Starting web host ({ApplicationContext})...", Program.AppName); |     TokenCredential credential = new ClientSecretCredential( | ||||||
|     host.Run(); |            builder.Configuration["Vault:TenantId"], | ||||||
| 
 |         builder.Configuration["Vault:ClientId"], | ||||||
|     return 0; |         builder.Configuration["Vault:ClientSecret"]); | ||||||
| } |     builder.Configuration.AddAzureKeyVault(new Uri($"https://{builder.Configuration["Vault:Name"]}.vault.azure.net/"), credential); | ||||||
| catch (Exception ex) |  | ||||||
| { |  | ||||||
|     Log.Fatal(ex, "Program terminated unexpectedly ({ApplicationContext})!", Program.AppName); |  | ||||||
|     return 1; |  | ||||||
| } |  | ||||||
| finally |  | ||||||
| { |  | ||||||
|     Log.CloseAndFlush(); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| IWebHost BuildWebHost(IConfiguration configuration, string[] args) => | builder.Services.AddGrpc(options => { | ||||||
|     WebHost.CreateDefaultBuilder(args) |     options.EnableDetailedErrors = true; | ||||||
|         .CaptureStartupErrors(false) | }); | ||||||
|         .ConfigureKestrel(options => | builder.Services.AddApplicationInsightsTelemetry(builder.Configuration); | ||||||
|         { | builder.Services.AddApplicationInsightsKubernetesEnricher(); | ||||||
|             var ports = GetDefinedPorts(configuration); | builder.Services.AddControllers(options => { | ||||||
|             options.Listen(IPAddress.Any, ports.httpPort, listenOptions => |     options.Filters.Add(typeof(HttpGlobalExceptionFilter)); | ||||||
|  |     options.Filters.Add(typeof(ValidateModelStateFilter)); | ||||||
|  | 
 | ||||||
|  | }) // Added for functional tests | ||||||
|  |             .AddApplicationPart(typeof(BasketController).Assembly) | ||||||
|  |             .AddJsonOptions(options => options.JsonSerializerOptions.WriteIndented = true); | ||||||
|  | builder.Services.AddSwaggerGen(options => { | ||||||
|  |     options.SwaggerDoc("v1", new OpenApiInfo { | ||||||
|  |         Title = "eShopOnContainers - Basket HTTP API", | ||||||
|  |         Version = "v1", | ||||||
|  |         Description = "The Basket Service HTTP API" | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme { | ||||||
|  |         Type = SecuritySchemeType.OAuth2, | ||||||
|  |         Flows = new OpenApiOAuthFlows() { | ||||||
|  |             Implicit = new OpenApiOAuthFlow() { | ||||||
|  |                 AuthorizationUrl = new Uri($"{builder.Configuration.GetValue<string>("IdentityUrlExternal")}/connect/authorize"), | ||||||
|  |                 TokenUrl = new Uri($"{builder.Configuration.GetValue<string>("IdentityUrlExternal")}/connect/token"), | ||||||
|  |                 Scopes = new Dictionary<string, string>() | ||||||
|                 { |                 { | ||||||
|  |                             { "basket", "Basket API" } | ||||||
|  |                         } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     options.OperationFilter<AuthorizeCheckOperationFilter>(); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | // prevent from mapping "sub" claim to nameidentifier. | ||||||
|  | JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub"); | ||||||
|  | 
 | ||||||
|  | var identityUrl = builder.Configuration.GetValue<string>("IdentityUrl"); | ||||||
|  | 
 | ||||||
|  | builder.Services.AddAuthentication("Bearer").AddJwtBearer(options => { | ||||||
|  |     options.Authority = identityUrl; | ||||||
|  |     options.RequireHttpsMetadata = false; | ||||||
|  |     options.Audience = "basket"; | ||||||
|  |     options.TokenValidationParameters.ValidateAudience = false; | ||||||
|  | }); | ||||||
|  | builder.Services.AddAuthorization(options => { | ||||||
|  |     options.AddPolicy("ApiScope", policy => { | ||||||
|  |         policy.RequireAuthenticatedUser(); | ||||||
|  |         policy.RequireClaim("scope", "basket"); | ||||||
|  |     }); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | builder.Services.AddCustomHealthCheck(builder.Configuration); | ||||||
|  | 
 | ||||||
|  | builder.Services.Configure<BasketSettings>(builder.Configuration); | ||||||
|  | 
 | ||||||
|  | builder.Services.AddSingleton<ConnectionMultiplexer>(sp => { | ||||||
|  |     var settings = sp.GetRequiredService<IOptions<BasketSettings>>().Value; | ||||||
|  |     var configuration = ConfigurationOptions.Parse(settings.ConnectionString, true); | ||||||
|  | 
 | ||||||
|  |     return ConnectionMultiplexer.Connect(configuration); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | if (builder.Configuration.GetValue<bool>("AzureServiceBusEnabled")) { | ||||||
|  |     builder.Services.AddSingleton<IServiceBusPersisterConnection>(sp => { | ||||||
|  |         var serviceBusConnectionString = builder.Configuration["EventBusConnection"]; | ||||||
|  | 
 | ||||||
|  |         return new DefaultServiceBusPersisterConnection(serviceBusConnectionString); | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | else { | ||||||
|  |     builder.Services.AddSingleton<IRabbitMQPersistentConnection>(sp => { | ||||||
|  |         var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersistentConnection>>(); | ||||||
|  | 
 | ||||||
|  |         var factory = new ConnectionFactory() { | ||||||
|  |             HostName = builder.Configuration["EventBusConnection"], | ||||||
|  |             DispatchConsumersAsync = true | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         if (!string.IsNullOrEmpty(builder.Configuration["EventBusUserName"])) { | ||||||
|  |             factory.UserName = builder.Configuration["EventBusUserName"]; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (!string.IsNullOrEmpty(builder.Configuration["EventBusPassword"])) { | ||||||
|  |             factory.Password = builder.Configuration["EventBusPassword"]; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         var retryCount = 5; | ||||||
|  |         if (!string.IsNullOrEmpty(builder.Configuration["EventBusRetryCount"])) { | ||||||
|  |             retryCount = int.Parse(builder.Configuration["EventBusRetryCount"]); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return new DefaultRabbitMQPersistentConnection(factory, logger, retryCount); | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | builder.Services.RegisterEventBus(builder.Configuration); | ||||||
|  | builder.Services.AddCors(options => { | ||||||
|  |     options.AddPolicy("CorsPolicy", | ||||||
|  |         builder => builder | ||||||
|  |         .SetIsOriginAllowed((host) => true) | ||||||
|  |         .AllowAnyMethod() | ||||||
|  |         .AllowAnyHeader() | ||||||
|  |         .AllowCredentials()); | ||||||
|  | }); | ||||||
|  | builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); | ||||||
|  | builder.Services.AddTransient<IBasketRepository, RedisBasketRepository>(); | ||||||
|  | builder.Services.AddTransient<IIdentityService, IdentityService>(); | ||||||
|  | 
 | ||||||
|  | builder.Services.AddOptions(); | ||||||
|  | builder.Configuration.SetBasePath(Directory.GetCurrentDirectory()); | ||||||
|  | builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true); | ||||||
|  | builder.Configuration.AddEnvironmentVariables(); | ||||||
|  | builder.WebHost.UseKestrel(options => { | ||||||
|  |     var ports = GetDefinedPorts(builder.Configuration); | ||||||
|  |     options.Listen(IPAddress.Any, ports.httpPort, listenOptions => { | ||||||
|         listenOptions.Protocols = HttpProtocols.Http1AndHttp2; |         listenOptions.Protocols = HttpProtocols.Http1AndHttp2; | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|             options.Listen(IPAddress.Any, ports.grpcPort, listenOptions => |     options.Listen(IPAddress.Any, ports.grpcPort, listenOptions => { | ||||||
|             { |  | ||||||
|         listenOptions.Protocols = HttpProtocols.Http2; |         listenOptions.Protocols = HttpProtocols.Http2; | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|         }) | }); | ||||||
|         .ConfigureAppConfiguration(x => x.AddConfiguration(configuration)) | builder.WebHost.CaptureStartupErrors(false); | ||||||
|         .UseFailing(options => | builder.Host.UseSerilog(CreateSerilogLogger(builder.Configuration)); | ||||||
|         { | builder.WebHost.UseFailing(options => { | ||||||
|     options.ConfigPath = "/Failing"; |     options.ConfigPath = "/Failing"; | ||||||
|     options.NotFilteredPaths.AddRange(new[] { "/hc", "/liveness" }); |     options.NotFilteredPaths.AddRange(new[] { "/hc", "/liveness" }); | ||||||
|         }) | }); | ||||||
|         .UseStartup<Startup>() | var app = builder.Build(); | ||||||
|         .UseContentRoot(Directory.GetCurrentDirectory()) |  | ||||||
|         .UseSerilog() |  | ||||||
|         .Build(); |  | ||||||
| 
 | 
 | ||||||
| Serilog.ILogger CreateSerilogLogger(IConfiguration configuration) | if (app.Environment.IsDevelopment()) { | ||||||
| { |     app.UseDeveloperExceptionPage(); | ||||||
|  | } | ||||||
|  | else { | ||||||
|  |     app.UseExceptionHandler("/Home/Error"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var pathBase = app.Configuration["PATH_BASE"]; | ||||||
|  | if (!string.IsNullOrEmpty(pathBase)) { | ||||||
|  |     app.UsePathBase(pathBase); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | app.UseSwagger() | ||||||
|  |             .UseSwaggerUI(setup => { | ||||||
|  |                 setup.SwaggerEndpoint($"{(!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty)}/swagger/v1/swagger.json", "Basket.API V1"); | ||||||
|  |                 setup.OAuthClientId("basketswaggerui"); | ||||||
|  |                 setup.OAuthAppName("Basket Swagger UI"); | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  | app.UseRouting(); | ||||||
|  | app.UseCors("CorsPolicy"); | ||||||
|  | app.UseAuthentication(); | ||||||
|  | app.UseAuthorization(); | ||||||
|  | app.UseStaticFiles(); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | app.MapGrpcService<BasketService>(); | ||||||
|  | app.MapDefaultControllerRoute(); | ||||||
|  | app.MapControllers(); | ||||||
|  | app.MapGet("/_proto/", async ctx => { | ||||||
|  |     ctx.Response.ContentType = "text/plain"; | ||||||
|  |     using var fs = new FileStream(Path.Combine(app.Environment.ContentRootPath, "Proto", "basket.proto"), FileMode.Open, FileAccess.Read); | ||||||
|  |     using var sr = new StreamReader(fs); | ||||||
|  |     while (!sr.EndOfStream) { | ||||||
|  |         var line = await sr.ReadLineAsync(); | ||||||
|  |         if (line != "/* >>" || line != "<< */") { | ||||||
|  |             await ctx.Response.WriteAsync(line); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | }); | ||||||
|  | app.MapHealthChecks("/hc", new HealthCheckOptions() { | ||||||
|  |     Predicate = _ => true, | ||||||
|  |     ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse | ||||||
|  | }); | ||||||
|  | app.MapHealthChecks("/liveness", new HealthCheckOptions { | ||||||
|  |     Predicate = r => r.Name.Contains("self") | ||||||
|  | }); | ||||||
|  | ConfigureEventBus(app); | ||||||
|  | try { | ||||||
|  |     Log.Information("Configuring web host ({ApplicationContext})...", Program.AppName); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     Log.Information("Starting web host ({ApplicationContext})...", Program.AppName); | ||||||
|  |     await app.RunAsync(); | ||||||
|  | 
 | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
|  | catch (Exception ex) { | ||||||
|  |     Log.Fatal(ex, "Program terminated unexpectedly ({ApplicationContext})!", Program.AppName); | ||||||
|  |     return 1; | ||||||
|  | } | ||||||
|  | finally { | ||||||
|  |     Log.CloseAndFlush(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | Serilog.ILogger CreateSerilogLogger(IConfiguration configuration) { | ||||||
|     var seqServerUrl = configuration["Serilog:SeqServerUrl"]; |     var seqServerUrl = configuration["Serilog:SeqServerUrl"]; | ||||||
|     var logstashUrl = configuration["Serilog:LogstashgUrl"]; |     var logstashUrl = configuration["Serilog:LogstashgUrl"]; | ||||||
|     return new LoggerConfiguration() |     return new LoggerConfiguration() | ||||||
| @ -65,37 +229,59 @@ Serilog.ILogger CreateSerilogLogger(IConfiguration configuration) | |||||||
|         .CreateLogger(); |         .CreateLogger(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| IConfiguration GetConfiguration() | (int httpPort, int grpcPort) GetDefinedPorts(IConfiguration config) { | ||||||
| { |  | ||||||
|     var builder = new ConfigurationBuilder() |  | ||||||
|         .SetBasePath(Directory.GetCurrentDirectory()) |  | ||||||
|         .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) |  | ||||||
|         .AddEnvironmentVariables(); |  | ||||||
| 
 |  | ||||||
|     var config = builder.Build(); |  | ||||||
| 
 |  | ||||||
|     if (config.GetValue<bool>("UseVault", false)) |  | ||||||
|     { |  | ||||||
|         TokenCredential credential = new ClientSecretCredential( |  | ||||||
|             config["Vault:TenantId"], |  | ||||||
|             config["Vault:ClientId"], |  | ||||||
|             config["Vault:ClientSecret"]); |  | ||||||
|         builder.AddAzureKeyVault(new Uri($"https://{config["Vault:Name"]}.vault.azure.net/"), credential); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return builder.Build(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| (int httpPort, int grpcPort) GetDefinedPorts(IConfiguration config) |  | ||||||
| { |  | ||||||
|     var grpcPort = config.GetValue("GRPC_PORT", 5001); |     var grpcPort = config.GetValue("GRPC_PORT", 5001); | ||||||
|     var port = config.GetValue("PORT", 80); |     var port = config.GetValue("PORT", 80); | ||||||
|     return (port, grpcPort); |     return (port, grpcPort); | ||||||
| } | } | ||||||
|  | void ConfigureEventBus(IApplicationBuilder app) { | ||||||
|  |     var eventBus = app.ApplicationServices.GetRequiredService<IEventBus>(); | ||||||
| 
 | 
 | ||||||
| public partial class Program |     eventBus.Subscribe<ProductPriceChangedIntegrationEvent, ProductPriceChangedIntegrationEventHandler>(); | ||||||
| { |     eventBus.Subscribe<OrderStartedIntegrationEvent, OrderStartedIntegrationEventHandler>(); | ||||||
|  | } | ||||||
|  | public partial class Program { | ||||||
| 
 | 
 | ||||||
|     public static string Namespace = typeof(Startup).Namespace; |     public static string Namespace = typeof(Program).Assembly.GetName().Name; | ||||||
|     public static string AppName = Namespace.Substring(Namespace.LastIndexOf('.', Namespace.LastIndexOf('.') - 1) + 1); |     public static string AppName = Namespace.Substring(Namespace.LastIndexOf('.', Namespace.LastIndexOf('.') - 1) + 1); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | public static class CustomExtensionMethods { | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     public static IServiceCollection RegisterEventBus(this IServiceCollection services, IConfiguration configuration) { | ||||||
|  |         if (configuration.GetValue<bool>("AzureServiceBusEnabled")) { | ||||||
|  |             services.AddSingleton<IEventBus, EventBusServiceBus>(sp => { | ||||||
|  |                 var serviceBusPersisterConnection = sp.GetRequiredService<IServiceBusPersisterConnection>(); | ||||||
|  |                 var logger = sp.GetRequiredService<ILogger<EventBusServiceBus>>(); | ||||||
|  |                 var eventBusSubscriptionsManager = sp.GetRequiredService<IEventBusSubscriptionsManager>(); | ||||||
|  |                 string subscriptionName = configuration["SubscriptionClientName"]; | ||||||
|  | 
 | ||||||
|  |                 return new EventBusServiceBus(serviceBusPersisterConnection, logger, | ||||||
|  |                     eventBusSubscriptionsManager, sp, subscriptionName); | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |         else { | ||||||
|  |             services.AddSingleton<IEventBus, EventBusRabbitMQ>(sp => { | ||||||
|  |                 var subscriptionClientName = configuration["SubscriptionClientName"]; | ||||||
|  |                 var rabbitMQPersistentConnection = sp.GetRequiredService<IRabbitMQPersistentConnection>(); | ||||||
|  |                 var logger = sp.GetRequiredService<ILogger<EventBusRabbitMQ>>(); | ||||||
|  |                 var eventBusSubscriptionsManager = sp.GetRequiredService<IEventBusSubscriptionsManager>(); | ||||||
|  | 
 | ||||||
|  |                 var retryCount = 5; | ||||||
|  |                 if (!string.IsNullOrEmpty(configuration["EventBusRetryCount"])) { | ||||||
|  |                     retryCount = int.Parse(configuration["EventBusRetryCount"]); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 return new EventBusRabbitMQ(rabbitMQPersistentConnection, logger, sp, eventBusSubscriptionsManager, subscriptionClientName, retryCount); | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         services.AddSingleton<IEventBusSubscriptionsManager, InMemoryEventBusSubscriptionsManager>(); | ||||||
|  | 
 | ||||||
|  |         services.AddTransient<ProductPriceChangedIntegrationEventHandler>(); | ||||||
|  |         services.AddTransient<OrderStartedIntegrationEventHandler>(); | ||||||
|  |         return services; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | |||||||
| @ -1,287 +0,0 @@ | |||||||
| namespace Microsoft.eShopOnContainers.Services.Basket.API; |  | ||||||
| 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 container. |  | ||||||
|     public virtual IServiceProvider ConfigureServices(IServiceCollection services) |  | ||||||
|     { |  | ||||||
|         services.AddGrpc(options => |  | ||||||
|         { |  | ||||||
|             options.EnableDetailedErrors = true; |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|         RegisterAppInsights(services); |  | ||||||
| 
 |  | ||||||
|         services.AddControllers(options => |  | ||||||
|             { |  | ||||||
|                 options.Filters.Add(typeof(HttpGlobalExceptionFilter)); |  | ||||||
|                 options.Filters.Add(typeof(ValidateModelStateFilter)); |  | ||||||
| 
 |  | ||||||
|             }) // Added for functional tests |  | ||||||
|             .AddApplicationPart(typeof(BasketController).Assembly) |  | ||||||
|             .AddJsonOptions(options => options.JsonSerializerOptions.WriteIndented = true); |  | ||||||
| 
 |  | ||||||
|         services.AddSwaggerGen(options => |  | ||||||
|         {             |  | ||||||
|             options.SwaggerDoc("v1", new OpenApiInfo |  | ||||||
|             { |  | ||||||
|                 Title = "eShopOnContainers - Basket HTTP API", |  | ||||||
|                 Version = "v1", |  | ||||||
|                 Description = "The Basket Service HTTP API" |  | ||||||
|             }); |  | ||||||
| 
 |  | ||||||
|             options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme |  | ||||||
|             { |  | ||||||
|                 Type = SecuritySchemeType.OAuth2, |  | ||||||
|                 Flows = new OpenApiOAuthFlows() |  | ||||||
|                 { |  | ||||||
|                     Implicit = new OpenApiOAuthFlow() |  | ||||||
|                     { |  | ||||||
|                         AuthorizationUrl = new Uri($"{Configuration.GetValue<string>("IdentityUrlExternal")}/connect/authorize"), |  | ||||||
|                         TokenUrl = new Uri($"{Configuration.GetValue<string>("IdentityUrlExternal")}/connect/token"), |  | ||||||
|                         Scopes = new Dictionary<string, string>() |  | ||||||
|                         { |  | ||||||
|                             { "basket", "Basket API" } |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             }); |  | ||||||
| 
 |  | ||||||
|             options.OperationFilter<AuthorizeCheckOperationFilter>(); |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|         ConfigureAuthService(services); |  | ||||||
| 
 |  | ||||||
|         services.AddCustomHealthCheck(Configuration); |  | ||||||
| 
 |  | ||||||
|         services.Configure<BasketSettings>(Configuration); |  | ||||||
| 
 |  | ||||||
|         //By connecting here we are making sure that our service |  | ||||||
|         //cannot start until redis is ready. This might slow down startup, |  | ||||||
|         //but given that there is a delay on resolving the ip address |  | ||||||
|         //and then creating the connection it seems reasonable to move |  | ||||||
|         //that cost to startup instead of having the first request pay the |  | ||||||
|         //penalty. |  | ||||||
|         services.AddSingleton<ConnectionMultiplexer>(sp => |  | ||||||
|         { |  | ||||||
|             var settings = sp.GetRequiredService<IOptions<BasketSettings>>().Value; |  | ||||||
|             var configuration = ConfigurationOptions.Parse(settings.ConnectionString, true); |  | ||||||
| 
 |  | ||||||
|             return ConnectionMultiplexer.Connect(configuration); |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|         if (Configuration.GetValue<bool>("AzureServiceBusEnabled")) |  | ||||||
|         { |  | ||||||
|             services.AddSingleton<IServiceBusPersisterConnection>(sp => |  | ||||||
|             { |  | ||||||
|                 var serviceBusConnectionString = Configuration["EventBusConnection"]; |  | ||||||
| 
 |  | ||||||
|                 return new DefaultServiceBusPersisterConnection(serviceBusConnectionString); |  | ||||||
|             }); |  | ||||||
|         } |  | ||||||
|         else |  | ||||||
|         { |  | ||||||
|             services.AddSingleton<IRabbitMQPersistentConnection>(sp => |  | ||||||
|             { |  | ||||||
|                 var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersistentConnection>>(); |  | ||||||
| 
 |  | ||||||
|                 var factory = new ConnectionFactory() |  | ||||||
|                 { |  | ||||||
|                     HostName = Configuration["EventBusConnection"], |  | ||||||
|                     DispatchConsumersAsync = true |  | ||||||
|                 }; |  | ||||||
| 
 |  | ||||||
|                 if (!string.IsNullOrEmpty(Configuration["EventBusUserName"])) |  | ||||||
|                 { |  | ||||||
|                     factory.UserName = Configuration["EventBusUserName"]; |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 if (!string.IsNullOrEmpty(Configuration["EventBusPassword"])) |  | ||||||
|                 { |  | ||||||
|                     factory.Password = Configuration["EventBusPassword"]; |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 var retryCount = 5; |  | ||||||
|                 if (!string.IsNullOrEmpty(Configuration["EventBusRetryCount"])) |  | ||||||
|                 { |  | ||||||
|                     retryCount = int.Parse(Configuration["EventBusRetryCount"]); |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 return new DefaultRabbitMQPersistentConnection(factory, logger, retryCount); |  | ||||||
|             }); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         RegisterEventBus(services); |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|         services.AddCors(options => |  | ||||||
|         { |  | ||||||
|             options.AddPolicy("CorsPolicy", |  | ||||||
|                 builder => builder |  | ||||||
|                 .SetIsOriginAllowed((host) => true) |  | ||||||
|                 .AllowAnyMethod() |  | ||||||
|                 .AllowAnyHeader() |  | ||||||
|                 .AllowCredentials()); |  | ||||||
|         }); |  | ||||||
|         services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); |  | ||||||
|         services.AddTransient<IBasketRepository, RedisBasketRepository>(); |  | ||||||
|         services.AddTransient<IIdentityService, IdentityService>(); |  | ||||||
| 
 |  | ||||||
|         services.AddOptions(); |  | ||||||
| 
 |  | ||||||
|         var container = new ContainerBuilder(); |  | ||||||
|         container.Populate(services); |  | ||||||
| 
 |  | ||||||
|         return new AutofacServiceProvider(container.Build()); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. |  | ||||||
|     public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) |  | ||||||
|     { |  | ||||||
|         //loggerFactory.AddAzureWebAppDiagnostics(); |  | ||||||
|         //loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace); |  | ||||||
| 
 |  | ||||||
|         var pathBase = Configuration["PATH_BASE"]; |  | ||||||
|         if (!string.IsNullOrEmpty(pathBase)) |  | ||||||
|         { |  | ||||||
|             app.UsePathBase(pathBase); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         app.UseSwagger() |  | ||||||
|             .UseSwaggerUI(setup => |  | ||||||
|             { |  | ||||||
|                 setup.SwaggerEndpoint($"{ (!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty) }/swagger/v1/swagger.json", "Basket.API V1"); |  | ||||||
|                 setup.OAuthClientId("basketswaggerui"); |  | ||||||
|                 setup.OAuthAppName("Basket Swagger UI"); |  | ||||||
|             }); |  | ||||||
| 
 |  | ||||||
|         app.UseRouting(); |  | ||||||
|         app.UseCors("CorsPolicy"); |  | ||||||
|         ConfigureAuth(app); |  | ||||||
| 
 |  | ||||||
|         app.UseStaticFiles(); |  | ||||||
| 
 |  | ||||||
|         app.UseEndpoints(endpoints => |  | ||||||
|         { |  | ||||||
|             endpoints.MapGrpcService<BasketService>(); |  | ||||||
|             endpoints.MapDefaultControllerRoute(); |  | ||||||
|             endpoints.MapControllers(); |  | ||||||
|             endpoints.MapGet("/_proto/", async ctx => |  | ||||||
|             { |  | ||||||
|                 ctx.Response.ContentType = "text/plain"; |  | ||||||
|                 using var fs = new FileStream(Path.Combine(env.ContentRootPath, "Proto", "basket.proto"), FileMode.Open, FileAccess.Read); |  | ||||||
|                 using var sr = new StreamReader(fs); |  | ||||||
|                 while (!sr.EndOfStream) |  | ||||||
|                 { |  | ||||||
|                     var line = await sr.ReadLineAsync(); |  | ||||||
|                     if (line != "/* >>" || line != "<< */") |  | ||||||
|                     { |  | ||||||
|                         await ctx.Response.WriteAsync(line); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             }); |  | ||||||
|             endpoints.MapHealthChecks("/hc", new HealthCheckOptions() |  | ||||||
|             { |  | ||||||
|                 Predicate = _ => true, |  | ||||||
|                 ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse |  | ||||||
|             }); |  | ||||||
|             endpoints.MapHealthChecks("/liveness", new HealthCheckOptions |  | ||||||
|             { |  | ||||||
|                 Predicate = r => r.Name.Contains("self") |  | ||||||
|             }); |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|         ConfigureEventBus(app); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private void RegisterAppInsights(IServiceCollection services) |  | ||||||
|     { |  | ||||||
|         services.AddApplicationInsightsTelemetry(Configuration); |  | ||||||
|         services.AddApplicationInsightsKubernetesEnricher(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private void ConfigureAuthService(IServiceCollection services) |  | ||||||
|     { |  | ||||||
|         // prevent from mapping "sub" claim to nameidentifier. |  | ||||||
|         JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub"); |  | ||||||
| 
 |  | ||||||
|         var identityUrl = Configuration.GetValue<string>("IdentityUrl"); |  | ||||||
| 
 |  | ||||||
|         services.AddAuthentication("Bearer").AddJwtBearer(options => |  | ||||||
|         { |  | ||||||
|             options.Authority = identityUrl; |  | ||||||
|             options.RequireHttpsMetadata = false; |  | ||||||
|             options.Audience = "basket"; |  | ||||||
|             options.TokenValidationParameters.ValidateAudience = false; |  | ||||||
|         }); |  | ||||||
|         services.AddAuthorization(options => |  | ||||||
|         { |  | ||||||
|             options.AddPolicy("ApiScope", policy => |  | ||||||
|             { |  | ||||||
|                 policy.RequireAuthenticatedUser(); |  | ||||||
|                 policy.RequireClaim("scope", "basket"); |  | ||||||
|             }); |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     protected virtual void ConfigureAuth(IApplicationBuilder app) |  | ||||||
|     { |  | ||||||
|         app.UseAuthentication(); |  | ||||||
|         app.UseAuthorization(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private void RegisterEventBus(IServiceCollection services) |  | ||||||
|     { |  | ||||||
|         if (Configuration.GetValue<bool>("AzureServiceBusEnabled")) |  | ||||||
|         { |  | ||||||
|             services.AddSingleton<IEventBus, EventBusServiceBus>(sp => |  | ||||||
|             { |  | ||||||
|                 var serviceBusPersisterConnection = sp.GetRequiredService<IServiceBusPersisterConnection>(); |  | ||||||
|                 var logger = sp.GetRequiredService<ILogger<EventBusServiceBus>>(); |  | ||||||
|                 var eventBusSubscriptionsManager = sp.GetRequiredService<IEventBusSubscriptionsManager>(); |  | ||||||
|                 string subscriptionName = Configuration["SubscriptionClientName"]; |  | ||||||
| 
 |  | ||||||
|                 return new EventBusServiceBus(serviceBusPersisterConnection, logger, |  | ||||||
|                     eventBusSubscriptionsManager, sp, subscriptionName); |  | ||||||
|             }); |  | ||||||
|         } |  | ||||||
|         else |  | ||||||
|         { |  | ||||||
|             services.AddSingleton<IEventBus, EventBusRabbitMQ>(sp => |  | ||||||
|             { |  | ||||||
|                 var subscriptionClientName = Configuration["SubscriptionClientName"]; |  | ||||||
|                 var rabbitMQPersistentConnection = sp.GetRequiredService<IRabbitMQPersistentConnection>(); |  | ||||||
|                 var logger = sp.GetRequiredService<ILogger<EventBusRabbitMQ>>(); |  | ||||||
|                 var eventBusSubscriptionsManager = sp.GetRequiredService<IEventBusSubscriptionsManager>(); |  | ||||||
| 
 |  | ||||||
|                 var retryCount = 5; |  | ||||||
|                 if (!string.IsNullOrEmpty(Configuration["EventBusRetryCount"])) |  | ||||||
|                 { |  | ||||||
|                     retryCount = int.Parse(Configuration["EventBusRetryCount"]); |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 return new EventBusRabbitMQ(rabbitMQPersistentConnection, logger, sp, eventBusSubscriptionsManager, subscriptionClientName, retryCount); |  | ||||||
|             }); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         services.AddSingleton<IEventBusSubscriptionsManager, InMemoryEventBusSubscriptionsManager>(); |  | ||||||
| 
 |  | ||||||
|         services.AddTransient<ProductPriceChangedIntegrationEventHandler>(); |  | ||||||
|         services.AddTransient<OrderStartedIntegrationEventHandler>(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private void ConfigureEventBus(IApplicationBuilder app) |  | ||||||
|     { |  | ||||||
|         var eventBus = app.ApplicationServices.GetRequiredService<IEventBus>(); |  | ||||||
| 
 |  | ||||||
|         eventBus.Subscribe<ProductPriceChangedIntegrationEvent, ProductPriceChangedIntegrationEventHandler>(); |  | ||||||
|         eventBus.Subscribe<OrderStartedIntegrationEvent, OrderStartedIntegrationEventHandler>(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -15,7 +15,7 @@ public class BasketScenarioBase | |||||||
|             { |             { | ||||||
|                 cb.AddJsonFile("appsettings.json", optional: false) |                 cb.AddJsonFile("appsettings.json", optional: false) | ||||||
|                 .AddEnvironmentVariables(); |                 .AddEnvironmentVariables(); | ||||||
|             }).UseStartup<BasketTestsStartup>(); |             }); | ||||||
| 
 | 
 | ||||||
|         return new TestServer(hostBuilder); |         return new TestServer(hostBuilder); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -1,31 +0,0 @@ | |||||||
|  |  | ||||||
| 
 |  | ||||||
| namespace Basket.FunctionalTests.Base |  | ||||||
| { |  | ||||||
|     class BasketTestsStartup : Startup |  | ||||||
|     { |  | ||||||
|         public BasketTestsStartup(IConfiguration env) : base(env) |  | ||||||
|         { |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         public override IServiceProvider ConfigureServices(IServiceCollection services) |  | ||||||
|         { |  | ||||||
|             // Added to avoid the Authorize data annotation in test environment.  |  | ||||||
|             // Property "SuppressCheckForUnhandledSecurityMetadata" in appsettings.json |  | ||||||
|             services.Configure<RouteOptions>(Configuration); |  | ||||||
|             return base.ConfigureServices(services); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         protected override void ConfigureAuth(IApplicationBuilder app) |  | ||||||
|         { |  | ||||||
|             if (Configuration["isTest"] == bool.TrueString.ToLowerInvariant()) |  | ||||||
|             { |  | ||||||
|                 app.UseMiddleware<AutoAuthorizeMiddleware>(); |  | ||||||
|             } |  | ||||||
|             else |  | ||||||
|             { |  | ||||||
|                 base.ConfigureAuth(app); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user