Initial attempt at making a common service configuration
This commit is contained in:
		
							parent
							
								
									e7e0eed9cc
								
							
						
					
					
						commit
						9af6d6342d
					
				| @ -46,8 +46,9 @@ | |||||||
| 	</ItemGroup> | 	</ItemGroup> | ||||||
| 
 | 
 | ||||||
| 	<ItemGroup> | 	<ItemGroup> | ||||||
| 		<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBusRabbitMQ\EventBusRabbitMQ.csproj" /> | 		<ProjectReference Include="..\..\BuildingBlocks\EventBus\EventBusRabbitMQ\EventBusRabbitMQ.csproj" /> | ||||||
| 		<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBusServiceBus\EventBusServiceBus.csproj" /> | 		<ProjectReference Include="..\..\BuildingBlocks\EventBus\EventBusServiceBus\EventBusServiceBus.csproj" /> | ||||||
| 		<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBus\EventBus.csproj" /> | 		<ProjectReference Include="..\..\BuildingBlocks\EventBus\EventBus\EventBus.csproj" /> | ||||||
|  | 		<ProjectReference Include="..\..\Services.Common\Services.Common.csproj" /> | ||||||
| 	</ItemGroup> | 	</ItemGroup> | ||||||
| </Project> | </Project> | ||||||
|  | |||||||
| @ -2,23 +2,12 @@ | |||||||
| 
 | 
 | ||||||
| public static class CustomExtensionMethods | public static class CustomExtensionMethods | ||||||
| { | { | ||||||
|     public static ConfigurationManager AddKeyVault(this ConfigurationManager configuration) |  | ||||||
|     { |  | ||||||
|         if (configuration.GetValue("UseVault", false)) |  | ||||||
|         { |  | ||||||
|             var credential = new ClientSecretCredential( |  | ||||||
|                    configuration["Vault:TenantId"], |  | ||||||
|                 configuration["Vault:ClientId"], |  | ||||||
|                 configuration["Vault:ClientSecret"]); |  | ||||||
| 
 |  | ||||||
|             configuration.AddAzureKeyVault(new Uri($"https://{configuration["Vault:Name"]}.vault.azure.net/"), credential); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return configuration; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public static IServiceCollection AddRedis(this IServiceCollection services) |     public static IServiceCollection AddRedis(this IServiceCollection services) | ||||||
|     { |     { | ||||||
|  |         // { | ||||||
|  |         //  "ConnectionString": "..." | ||||||
|  |         // } | ||||||
|  | 
 | ||||||
|         return services.AddSingleton(sp => |         return services.AddSingleton(sp => | ||||||
|         { |         { | ||||||
|             var settings = sp.GetRequiredService<IOptions<BasketSettings>>().Value; |             var settings = sp.GetRequiredService<IOptions<BasketSettings>>().Value; | ||||||
| @ -27,114 +16,4 @@ public static class CustomExtensionMethods | |||||||
|             return ConnectionMultiplexer.Connect(configuration); |             return ConnectionMultiplexer.Connect(configuration); | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     public static IServiceCollection AddCustomHealthCheck(this IServiceCollection services, IConfiguration configuration) |  | ||||||
|     { |  | ||||||
|         var hcBuilder = services.AddHealthChecks(); |  | ||||||
| 
 |  | ||||||
|         hcBuilder.AddCheck("self", () => HealthCheckResult.Healthy()); |  | ||||||
| 
 |  | ||||||
|         hcBuilder |  | ||||||
|             .AddRedis( |  | ||||||
|                 configuration["ConnectionString"], |  | ||||||
|                 name: "redis-check", |  | ||||||
|                 tags: new string[] { "redis" }); |  | ||||||
| 
 |  | ||||||
|         if (configuration.GetValue<bool>("AzureServiceBusEnabled")) |  | ||||||
|         { |  | ||||||
|             hcBuilder |  | ||||||
|                 .AddAzureServiceBusTopic( |  | ||||||
|                     configuration["EventBusConnection"], |  | ||||||
|                     topicName: "eshop_event_bus", |  | ||||||
|                     name: "basket-servicebus-check", |  | ||||||
|                     tags: new string[] { "servicebus" }); |  | ||||||
|         } |  | ||||||
|         else |  | ||||||
|         { |  | ||||||
|             hcBuilder |  | ||||||
|                 .AddRabbitMQ( |  | ||||||
|                     $"amqp://{configuration["EventBusConnection"]}", |  | ||||||
|                     name: "basket-rabbitmqbus-check", |  | ||||||
|                     tags: new string[] { "rabbitmqbus" }); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return services; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public static IServiceCollection AddEventBus(this IServiceCollection services, IConfiguration configuration) |  | ||||||
|     { |  | ||||||
|         if (configuration.GetValue("AzureServiceBusEnabled", false)) |  | ||||||
|         { |  | ||||||
|             services.AddSingleton<IServiceBusPersisterConnection>(sp => |  | ||||||
|             { |  | ||||||
|                 var serviceBusConnectionString = configuration["EventBusConnection"]; |  | ||||||
| 
 |  | ||||||
|                 return new DefaultServiceBusPersisterConnection(serviceBusConnectionString); |  | ||||||
|             }); |  | ||||||
| 
 |  | ||||||
|             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<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); |  | ||||||
|             }); |  | ||||||
| 
 |  | ||||||
|             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,104 +1,52 @@ | |||||||
| var builder = WebApplication.CreateBuilder(args); | using Services.Common; | ||||||
|  | 
 | ||||||
|  | var builder = WebApplication.CreateBuilder(args); | ||||||
| 
 | 
 | ||||||
| builder.Configuration.AddKeyVault(); | builder.Configuration.AddKeyVault(); | ||||||
| 
 | 
 | ||||||
|  | builder.Services.AddApplicationInsights(builder.Configuration); | ||||||
|  | 
 | ||||||
| builder.Services.AddGrpc(options => | builder.Services.AddGrpc(options => | ||||||
| { | { | ||||||
|     options.EnableDetailedErrors = true; |     options.EnableDetailedErrors = true; | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| builder.Services.AddApplicationInsightsTelemetry(builder.Configuration); |  | ||||||
| builder.Services.AddApplicationInsightsKubernetesEnricher(); |  | ||||||
| 
 |  | ||||||
| builder.Services.AddControllers(options => | builder.Services.AddControllers(options => | ||||||
| { | { | ||||||
|     options.Filters.Add(typeof(HttpGlobalExceptionFilter)); |     options.Filters.Add(typeof(HttpGlobalExceptionFilter)); | ||||||
|     options.Filters.Add(typeof(ValidateModelStateFilter)); |     options.Filters.Add(typeof(ValidateModelStateFilter)); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| builder.Services.AddSwaggerGen(options => | builder.Services.AddDefaultOpenApi(builder.Configuration); | ||||||
| { |  | ||||||
|     options.SwaggerDoc("v1", new OpenApiInfo |  | ||||||
|     { |  | ||||||
|         Title = "eShopOnContainers - Basket HTTP API", |  | ||||||
|         Version = "v1", |  | ||||||
|         Description = "The Basket Service HTTP API" |  | ||||||
|     }); |  | ||||||
| 
 | 
 | ||||||
|     options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme | builder.Services.AddDefaultAuthentication(builder.Configuration); | ||||||
|     { |  | ||||||
|         Type = SecuritySchemeType.OAuth2, |  | ||||||
|         Flows = new OpenApiOAuthFlows() |  | ||||||
|         { |  | ||||||
|             Implicit = new OpenApiOAuthFlow() |  | ||||||
|             { |  | ||||||
|                 AuthorizationUrl = new Uri($"{builder.Configuration["IdentityUrlExternal"]}/connect/authorize"), |  | ||||||
|                 TokenUrl = new Uri($"{builder.Configuration["IdentityUrlExternal"]}/connect/token"), |  | ||||||
|                 Scopes = new Dictionary<string, string>() { { "basket", "Basket API" } } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     }); |  | ||||||
| 
 | 
 | ||||||
|     options.OperationFilter<AuthorizeCheckOperationFilter>(); | builder.Services.AddDefaultHealthChecks(builder.Configuration); | ||||||
| }); |  | ||||||
| 
 | 
 | ||||||
| // prevent from mapping "sub" claim to nameidentifier. | builder.Host.UseDefaultSerilog(builder.Configuration, AppName); | ||||||
| JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub"); |  | ||||||
| 
 | 
 | ||||||
| var identityUrl = builder.Configuration["IdentityUrl"]; | builder.WebHost.UseDefaultPorts(builder.Configuration); | ||||||
| 
 | 
 | ||||||
| builder.Services.AddAuthentication().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.AddRedis(); |  | ||||||
| 
 |  | ||||||
| builder.Services.AddEventBus(builder.Configuration); |  | ||||||
| 
 |  | ||||||
| builder.Services.AddHttpContextAccessor(); |  | ||||||
| 
 |  | ||||||
| builder.Services.AddTransient<IBasketRepository, RedisBasketRepository>(); |  | ||||||
| builder.Services.AddTransient<IIdentityService, IdentityService>(); |  | ||||||
| 
 |  | ||||||
| builder.WebHost.UseKestrel(options => |  | ||||||
| { |  | ||||||
|     var ports = GetDefinedPorts(builder.Configuration); |  | ||||||
|     options.Listen(IPAddress.Any, ports.httpPort, listenOptions => |  | ||||||
|     { |  | ||||||
|         listenOptions.Protocols = HttpProtocols.Http1AndHttp2; |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     options.Listen(IPAddress.Any, ports.grpcPort, listenOptions => |  | ||||||
|     { |  | ||||||
|         listenOptions.Protocols = HttpProtocols.Http2; |  | ||||||
|     }); |  | ||||||
| }); |  | ||||||
| 
 |  | ||||||
| builder.Host.UseSerilog(CreateSerilogLogger(builder.Configuration)); |  | ||||||
| builder.WebHost.UseFailing(options => | builder.WebHost.UseFailing(options => | ||||||
| { | { | ||||||
|     options.ConfigPath = "/Failing"; |     options.ConfigPath = "/Failing"; | ||||||
|     options.NotFilteredPaths.AddRange(new[] { "/hc", "/liveness" }); |     options.NotFilteredPaths.AddRange(new[] { "/hc", "/liveness" }); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
|  | builder.Services.AddEventBus(builder.Configuration); | ||||||
|  | 
 | ||||||
|  | builder.Services.Configure<BasketSettings>(builder.Configuration); | ||||||
|  | 
 | ||||||
|  | builder.Services.AddRedis(); | ||||||
|  | 
 | ||||||
|  | builder.Services.AddTransient<ProductPriceChangedIntegrationEventHandler>(); | ||||||
|  | builder.Services.AddTransient<OrderStartedIntegrationEventHandler>(); | ||||||
|  | 
 | ||||||
|  | builder.Services.AddTransient<IBasketRepository, RedisBasketRepository>(); | ||||||
|  | builder.Services.AddTransient<IIdentityService, IdentityService>(); | ||||||
|  | 
 | ||||||
| var app = builder.Build(); | var app = builder.Build(); | ||||||
|  | 
 | ||||||
| app.MapGet("hello", () => "hello"); | app.MapGet("hello", () => "hello"); | ||||||
| 
 | 
 | ||||||
| if (!app.Environment.IsDevelopment()) | if (!app.Environment.IsDevelopment()) | ||||||
| @ -112,59 +60,31 @@ if (!string.IsNullOrEmpty(pathBase)) | |||||||
|     app.UsePathBase(pathBase); |     app.UsePathBase(pathBase); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| app.UseSwagger(); | app.UseDefaultOpenApi(builder.Configuration); | ||||||
| 
 |  | ||||||
| app.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.MapGrpcService<BasketService>(); | app.MapGrpcService<BasketService>(); | ||||||
| app.MapControllers(); | app.MapControllers(); | ||||||
| 
 | 
 | ||||||
| app.MapGet("/_proto/", async ctx => | app.MapDefaultHealthChecks(); | ||||||
| { |  | ||||||
|     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() | var eventBus = app.Services.GetRequiredService<IEventBus>(); | ||||||
| { |  | ||||||
|     Predicate = _ => true, |  | ||||||
|     ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse |  | ||||||
| }); |  | ||||||
| 
 | 
 | ||||||
| app.MapHealthChecks("/liveness", new HealthCheckOptions | eventBus.Subscribe<ProductPriceChangedIntegrationEvent, ProductPriceChangedIntegrationEventHandler>(); | ||||||
| { | eventBus.Subscribe<OrderStartedIntegrationEvent, OrderStartedIntegrationEventHandler>(); | ||||||
|     Predicate = r => r.Name.Contains("self") |  | ||||||
| }); |  | ||||||
| 
 |  | ||||||
| ConfigureEventBus(app); |  | ||||||
| 
 | 
 | ||||||
| try | try | ||||||
| { | { | ||||||
|     Log.Information("Configuring web host ({ApplicationContext})...", Program.AppName); |     Log.Information("Configuring web host ({ApplicationContext})...", AppName); | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     Log.Information("Starting web host ({ApplicationContext})...", Program.AppName); |     Log.Information("Starting web host ({ApplicationContext})...", AppName); | ||||||
|     await app.RunAsync(); |     await app.RunAsync(); | ||||||
| 
 | 
 | ||||||
|     return 0; |     return 0; | ||||||
| } | } | ||||||
| catch (Exception ex) | catch (Exception ex) | ||||||
| { | { | ||||||
|     Log.Fatal(ex, "Program terminated unexpectedly ({ApplicationContext})!", Program.AppName); |     Log.Fatal(ex, "Program terminated unexpectedly ({ApplicationContext})!", AppName); | ||||||
|     return 1; |     return 1; | ||||||
| } | } | ||||||
| finally | finally | ||||||
| @ -172,36 +92,6 @@ finally | |||||||
|     Log.CloseAndFlush(); |     Log.CloseAndFlush(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| Serilog.ILogger CreateSerilogLogger(IConfiguration configuration) |  | ||||||
| { |  | ||||||
|     var seqServerUrl = configuration["Serilog:SeqServerUrl"]; |  | ||||||
|     var logstashUrl = configuration["Serilog:LogstashgUrl"]; |  | ||||||
|     return new LoggerConfiguration() |  | ||||||
|         .MinimumLevel.Verbose() |  | ||||||
|         .Enrich.WithProperty("ApplicationContext", Program.AppName) |  | ||||||
|         .Enrich.FromLogContext() |  | ||||||
|         .WriteTo.Console() |  | ||||||
|         .WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl) |  | ||||||
|         .WriteTo.Http(string.IsNullOrWhiteSpace(logstashUrl) ? "http://logstash:8080" : logstashUrl, null) |  | ||||||
|         .ReadFrom.Configuration(configuration) |  | ||||||
|         .CreateLogger(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| (int httpPort, int grpcPort) GetDefinedPorts(IConfiguration config) |  | ||||||
| { |  | ||||||
|     var grpcPort = config.GetValue("GRPC_PORT", 5001); |  | ||||||
|     var port = config.GetValue("PORT", 80); |  | ||||||
|     return (port, grpcPort); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void ConfigureEventBus(IApplicationBuilder app) |  | ||||||
| { |  | ||||||
|     var eventBus = app.ApplicationServices.GetRequiredService<IEventBus>(); |  | ||||||
| 
 |  | ||||||
|     eventBus.Subscribe<ProductPriceChangedIntegrationEvent, ProductPriceChangedIntegrationEventHandler>(); |  | ||||||
|     eventBus.Subscribe<OrderStartedIntegrationEvent, OrderStartedIntegrationEventHandler>(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| public partial class Program | public partial class Program | ||||||
| { | { | ||||||
|     private static string Namespace = typeof(Program).Assembly.GetName().Name; |     private static string Namespace = typeof(Program).Assembly.GetName().Name; | ||||||
|  | |||||||
| @ -16,11 +16,16 @@ | |||||||
|       "Protocols": "Http2" |       "Protocols": "Http2" | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   "SubscriptionClientName": "Basket", |   "EventBus": { | ||||||
|  |     "SubscriptionClientName": "Basket", | ||||||
|  |     "ConnectionString": "your-event-bus-connection-string", | ||||||
|  |     "UserName": "your-event-bus-username", | ||||||
|  |     "Password": "your-event-bus-password", | ||||||
|  |     "RetryCount": 5 | ||||||
|  |   }, | ||||||
|   "ApplicationInsights": { |   "ApplicationInsights": { | ||||||
|     "InstrumentationKey": "" |     "InstrumentationKey": "" | ||||||
|   }, |   }, | ||||||
|   "EventBusRetryCount": 5, |  | ||||||
|   "UseVault": false, |   "UseVault": false, | ||||||
|   "Vault": { |   "Vault": { | ||||||
|     "Name": "eshop", |     "Name": "eshop", | ||||||
|  | |||||||
| @ -0,0 +1,38 @@ | |||||||
|  | using Microsoft.AspNetCore.Authorization; | ||||||
|  | using Microsoft.Extensions.Configuration; | ||||||
|  | using Microsoft.OpenApi.Models; | ||||||
|  | using Swashbuckle.AspNetCore.SwaggerGen; | ||||||
|  | 
 | ||||||
|  | namespace Services.Common; | ||||||
|  | internal class AuthorizeCheckOperationFilter : IOperationFilter | ||||||
|  | { | ||||||
|  |     public AuthorizeCheckOperationFilter(IConfiguration configuration) | ||||||
|  |     { | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void Apply(OpenApiOperation operation, OperationFilterContext context) | ||||||
|  |     { | ||||||
|  |         // Check for authorize attribute | ||||||
|  |         var hasAuthorize = context.MethodInfo.DeclaringType.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Any() || | ||||||
|  |                             context.MethodInfo.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Any(); | ||||||
|  | 
 | ||||||
|  |         if (!hasAuthorize) return; | ||||||
|  | 
 | ||||||
|  |         operation.Responses.TryAdd("401", new OpenApiResponse { Description = "Unauthorized" }); | ||||||
|  |         operation.Responses.TryAdd("403", new OpenApiResponse { Description = "Forbidden" }); | ||||||
|  | 
 | ||||||
|  |         var oAuthScheme = new OpenApiSecurityScheme | ||||||
|  |         { | ||||||
|  |             Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" } | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         operation.Security = new List<OpenApiSecurityRequirement> | ||||||
|  |             { | ||||||
|  |                 new() | ||||||
|  |                 { | ||||||
|  |                     [ oAuthScheme ] = new [] { "basketapi" } | ||||||
|  |                 } | ||||||
|  |             }; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										398
									
								
								src/Services/Services.Common/CommonExtensions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										398
									
								
								src/Services/Services.Common/CommonExtensions.cs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,398 @@ | |||||||
|  | using System.IdentityModel.Tokens.Jwt; | ||||||
|  | using System.Net; | ||||||
|  | using Azure.Identity; | ||||||
|  | using HealthChecks.UI.Client; | ||||||
|  | using Microsoft.AspNetCore.Builder; | ||||||
|  | using Microsoft.AspNetCore.Diagnostics.HealthChecks; | ||||||
|  | using Microsoft.AspNetCore.Hosting; | ||||||
|  | using Microsoft.AspNetCore.Routing; | ||||||
|  | using Microsoft.AspNetCore.Server.Kestrel.Core; | ||||||
|  | using Microsoft.eShopOnContainers.BuildingBlocks.EventBus; | ||||||
|  | using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; | ||||||
|  | using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ; | ||||||
|  | using Microsoft.eShopOnContainers.BuildingBlocks.EventBusServiceBus; | ||||||
|  | using Microsoft.Extensions.Configuration; | ||||||
|  | using Microsoft.Extensions.DependencyInjection; | ||||||
|  | using Microsoft.Extensions.Diagnostics.HealthChecks; | ||||||
|  | using Microsoft.Extensions.Hosting; | ||||||
|  | using Microsoft.Extensions.Logging; | ||||||
|  | using Microsoft.OpenApi.Models; | ||||||
|  | using RabbitMQ.Client; | ||||||
|  | using Serilog; | ||||||
|  | 
 | ||||||
|  | namespace Services.Common; | ||||||
|  | 
 | ||||||
|  | public static class CommonExtensions | ||||||
|  | { | ||||||
|  |     public static WebApplicationBuilder AddServiceDefaults(this WebApplicationBuilder builder) | ||||||
|  |     { | ||||||
|  |         // Shared configuration via key vault | ||||||
|  |         builder.Configuration.AddKeyVault(); | ||||||
|  | 
 | ||||||
|  |         // Shared app insights configuration | ||||||
|  |         builder.Services.AddApplicationInsights(builder.Configuration); | ||||||
|  | 
 | ||||||
|  |         // Default health checks assume the event bus and self health checks | ||||||
|  |         builder.Services.AddDefaultHealthChecks(builder.Configuration); | ||||||
|  | 
 | ||||||
|  |         // Configure the default logging for this application | ||||||
|  |         builder.Host.UseDefaultSerilog(builder.Configuration, builder.Environment.ApplicationName); | ||||||
|  | 
 | ||||||
|  |         // Configure the default ports for this service (http and grpc ports read from configuration) | ||||||
|  |         builder.WebHost.UseDefaultPorts(builder.Configuration); | ||||||
|  | 
 | ||||||
|  |         // Customizations for this application | ||||||
|  | 
 | ||||||
|  |         // Add the event bus | ||||||
|  |         builder.Services.AddEventBus(builder.Configuration); | ||||||
|  | 
 | ||||||
|  |         builder.Services.AddDefaultAuthentication(builder.Configuration); | ||||||
|  | 
 | ||||||
|  |         builder.Services.AddDefaultOpenApi(builder.Configuration); | ||||||
|  | 
 | ||||||
|  |         // Add the accessor | ||||||
|  |         builder.Services.AddHttpContextAccessor(); | ||||||
|  | 
 | ||||||
|  |         return builder; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static WebApplication UseServiceDefaults(this WebApplication app) | ||||||
|  |     { | ||||||
|  |         var pathBase = app.Configuration["PATH_BASE"]; | ||||||
|  | 
 | ||||||
|  |         if (!string.IsNullOrEmpty(pathBase)) | ||||||
|  |         { | ||||||
|  |             app.UsePathBase(pathBase); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         app.UseDefaultOpenApi(app.Configuration); | ||||||
|  | 
 | ||||||
|  |         app.MapDefaultHealthChecks(); | ||||||
|  | 
 | ||||||
|  |         return app; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static IApplicationBuilder UseDefaultOpenApi(this IApplicationBuilder app, IConfiguration configuration) | ||||||
|  |     { | ||||||
|  |         app.UseSwagger(); | ||||||
|  |         app.UseSwaggerUI(setup => | ||||||
|  |         { | ||||||
|  |             var pathBase = configuration["PATH_BASE"]; | ||||||
|  |             var openApiSection = configuration.GetRequiredSection("OpenApi"); | ||||||
|  |             var authSection = openApiSection.GetRequiredSection("Auth"); | ||||||
|  |             var endpointSection = openApiSection.GetRequiredSection("Endpoint"); | ||||||
|  | 
 | ||||||
|  |             var swaggerUrl = endpointSection["Url"] ?? $"{(!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty)}/swagger/v1/swagger.json"; | ||||||
|  | 
 | ||||||
|  |             setup.SwaggerEndpoint(swaggerUrl, endpointSection.GetRequiredValue("Name")); | ||||||
|  |             setup.OAuthClientId(authSection.GetRequiredValue("ClientId")); | ||||||
|  |             setup.OAuthAppName(authSection.GetRequiredValue("AppName")); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         return app; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static IServiceCollection AddDefaultOpenApi(this IServiceCollection services, IConfiguration configuration) => | ||||||
|  |         services.AddSwaggerGen(options => | ||||||
|  |         { | ||||||
|  |             var openApi = configuration.GetRequiredSection("OpenApi"); | ||||||
|  | 
 | ||||||
|  |             /// { | ||||||
|  |             ///   "OpenApi": { | ||||||
|  |             ///     "Endpoint: { | ||||||
|  |             ///         "Name":  | ||||||
|  |             ///     }, | ||||||
|  |             ///     "Document": { | ||||||
|  |             ///         "Title": .. | ||||||
|  |             ///         "Version": .. | ||||||
|  |             ///         "Description": .. | ||||||
|  |             ///     }, | ||||||
|  |             ///     "Auth": { | ||||||
|  |             ///         "ClientId": .., | ||||||
|  |             ///         "AppName": .. | ||||||
|  |             ///     } | ||||||
|  |             ///   } | ||||||
|  |             /// } | ||||||
|  | 
 | ||||||
|  |             var version = openApi.GetRequiredValue("Version") ?? "v1"; | ||||||
|  | 
 | ||||||
|  |             options.SwaggerDoc(version, new OpenApiInfo | ||||||
|  |             { | ||||||
|  |                 Title = openApi.GetRequiredValue("Title"), | ||||||
|  |                 Version = version, | ||||||
|  |                 Description = openApi.GetRequiredValue("Description") | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |             var identityUrlExternal = configuration.GetRequiredValue("IdentityUrlExternal"); | ||||||
|  |             var scopes = openApi.GetRequiredSection("Scopes").AsEnumerable().ToDictionary(p => p.Key, p => p.Value); | ||||||
|  | 
 | ||||||
|  |             options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme | ||||||
|  |             { | ||||||
|  |                 Type = SecuritySchemeType.OAuth2, | ||||||
|  |                 Flows = new OpenApiOAuthFlows() | ||||||
|  |                 { | ||||||
|  |                     Implicit = new OpenApiOAuthFlow() | ||||||
|  |                     { | ||||||
|  |                         AuthorizationUrl = new Uri($"{identityUrlExternal}/connect/authorize"), | ||||||
|  |                         TokenUrl = new Uri($"{identityUrlExternal}/connect/token"), | ||||||
|  |                         Scopes = openApi.GetRequiredSection("Scopes").AsEnumerable().ToDictionary(p => p.Key, p => p.Value), | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |             options.OperationFilter<AuthorizeCheckOperationFilter>(); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |     public static IServiceCollection AddDefaultAuthentication(this IServiceCollection services, IConfiguration configuration) | ||||||
|  |     { | ||||||
|  |         // prevent from mapping "sub" claim to nameidentifier. | ||||||
|  |         JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub"); | ||||||
|  | 
 | ||||||
|  |         services.AddAuthentication().AddJwtBearer(options => | ||||||
|  |         { | ||||||
|  |             var identityUrl = configuration.GetRequiredValue("IdentityUrl"); | ||||||
|  |             var audience = configuration.GetRequiredValue("Audience"); | ||||||
|  | 
 | ||||||
|  |             options.Authority = identityUrl; | ||||||
|  |             options.RequireHttpsMetadata = false; | ||||||
|  |             options.Audience = audience; | ||||||
|  |             options.TokenValidationParameters.ValidateAudience = false; | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         services.AddAuthorization(options => | ||||||
|  |         { | ||||||
|  |             var scope = configuration.GetRequiredValue("Scope"); | ||||||
|  | 
 | ||||||
|  |             options.AddPolicy("ApiScope", policy => | ||||||
|  |             { | ||||||
|  |                 policy.RequireAuthenticatedUser(); | ||||||
|  |                 policy.RequireClaim("scope", scope); | ||||||
|  |             }); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         return services; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static IWebHostBuilder UseDefaultPorts(this IWebHostBuilder builder, IConfiguration configuration) | ||||||
|  |     { | ||||||
|  |         builder.UseKestrel(options => | ||||||
|  |         { | ||||||
|  |             var (httpPort, grpcPort) = GetDefinedPorts(configuration); | ||||||
|  | 
 | ||||||
|  |             options.Listen(IPAddress.Any, httpPort, listenOptions => | ||||||
|  |             { | ||||||
|  |                 listenOptions.Protocols = HttpProtocols.Http1AndHttp2; | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |             options.Listen(IPAddress.Any, grpcPort, listenOptions => | ||||||
|  |             { | ||||||
|  |                 listenOptions.Protocols = HttpProtocols.Http2; | ||||||
|  |             }); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         return builder; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static ConfigurationManager AddKeyVault(this ConfigurationManager configuration) | ||||||
|  |     { | ||||||
|  |         if (configuration.GetValue("UseVault", false)) | ||||||
|  |         { | ||||||
|  |             // { | ||||||
|  |             //   "Vault": { | ||||||
|  |             //     "Name": "myvault", | ||||||
|  |             //     "TenantId": "mytenantid", | ||||||
|  |             //     "ClientId": "myclientid", | ||||||
|  |             //    } | ||||||
|  |             // } | ||||||
|  | 
 | ||||||
|  |             var vaultSection = configuration.GetRequiredSection("Vault"); | ||||||
|  | 
 | ||||||
|  |             var credential = new ClientSecretCredential( | ||||||
|  |                 vaultSection.GetRequiredValue("TenantId"), | ||||||
|  |                 vaultSection.GetRequiredValue("ClientId"), | ||||||
|  |                 vaultSection.GetRequiredValue("ClientSecret")); | ||||||
|  | 
 | ||||||
|  |             var name = vaultSection.GetRequiredValue("Name"); | ||||||
|  | 
 | ||||||
|  |             configuration.AddAzureKeyVault(new Uri($"https://{name}.vault.azure.net/"), credential); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return configuration; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static IServiceCollection AddApplicationInsights(this IServiceCollection services, IConfiguration configuration) | ||||||
|  |     { | ||||||
|  |         services.AddApplicationInsightsTelemetry(configuration); | ||||||
|  |         services.AddApplicationInsightsKubernetesEnricher(); | ||||||
|  |         return services; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static IHealthChecksBuilder AddDefaultHealthChecks(this IServiceCollection services, IConfiguration configuration) | ||||||
|  |     { | ||||||
|  |         var hcBuilder = services.AddHealthChecks(); | ||||||
|  | 
 | ||||||
|  |         // Health check for the application itself | ||||||
|  |         hcBuilder.AddCheck("self", () => HealthCheckResult.Healthy()); | ||||||
|  | 
 | ||||||
|  |         // { | ||||||
|  |         //   "EventBus": { | ||||||
|  |         //     "ProviderName": "ServiceBus | RabbitMQ", | ||||||
|  |         //     "ConnectionString": "Endpoint=sb://eshop-eventbus.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=..." | ||||||
|  |         //   } | ||||||
|  |         // } | ||||||
|  | 
 | ||||||
|  |         var eventBusSection = configuration.GetRequiredSection("EventBus"); | ||||||
|  |         var eventBusConnectionString = eventBusSection.GetRequiredValue("ConnectionString"); | ||||||
|  | 
 | ||||||
|  |         return eventBusSection.GetRequiredValue("ProviderName").ToLowerInvariant() switch | ||||||
|  |         { | ||||||
|  |             "servicebus" => hcBuilder.AddAzureServiceBusTopic( | ||||||
|  |                     eventBusConnectionString, | ||||||
|  |                     topicName: "eshop_event_bus", | ||||||
|  |                     name: "servicebus-check", | ||||||
|  |                     tags: new string[] { "servicebus" }), | ||||||
|  | 
 | ||||||
|  |             _ => hcBuilder.AddRabbitMQ( | ||||||
|  |                     $"amqp://{eventBusConnectionString}", | ||||||
|  |                     name: "rabbitmqbus-check", | ||||||
|  |                     tags: new string[] { "rabbitmqbus" }) | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static IServiceCollection AddEventBus(this IServiceCollection services, IConfiguration configuration) | ||||||
|  |     { | ||||||
|  |         // { | ||||||
|  |         //   "EventBus": { | ||||||
|  |         //     "ProviderName": "ServiceBus | RabbitMQ", | ||||||
|  |         //     "ConnectionString": "...", | ||||||
|  |         //     ... | ||||||
|  |         //   } | ||||||
|  |         // } | ||||||
|  | 
 | ||||||
|  |         // { | ||||||
|  |         //   "EventBus": { | ||||||
|  |         //     "ProviderName": "ServiceBus", | ||||||
|  |         //     "ConnectionString": "Endpoint=sb://eshop-eventbus.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=..." | ||||||
|  |         //     "SubscriptionClientName": "eshop_event_bus" | ||||||
|  |         //   } | ||||||
|  |         // } | ||||||
|  | 
 | ||||||
|  |         // { | ||||||
|  |         //   "EventBus": { | ||||||
|  |         //     "ProviderName": "RabbitMQ", | ||||||
|  |         //     "ConnectionString": "...", | ||||||
|  |         //     "SubscriptionClientName": "...", | ||||||
|  |         //     "UserName": "...", | ||||||
|  |         //     "Password": "...", | ||||||
|  |         //     "RetryCount": 1 | ||||||
|  |         //   } | ||||||
|  |         // } | ||||||
|  | 
 | ||||||
|  |         var eventBusSection = configuration.GetRequiredSection("EventBus"); | ||||||
|  |         if (string.Equals(eventBusSection["ProviderName"], "ServiceBus", StringComparison.OrdinalIgnoreCase)) | ||||||
|  |         { | ||||||
|  |             services.AddSingleton<IServiceBusPersisterConnection>(sp => | ||||||
|  |             { | ||||||
|  |                 var serviceBusConnectionString = eventBusSection.GetRequiredValue("ConnectionString"); | ||||||
|  | 
 | ||||||
|  |                 return new DefaultServiceBusPersisterConnection(serviceBusConnectionString); | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |             services.AddSingleton<IEventBus, EventBusServiceBus>(sp => | ||||||
|  |             { | ||||||
|  |                 var serviceBusPersisterConnection = sp.GetRequiredService<IServiceBusPersisterConnection>(); | ||||||
|  |                 var logger = sp.GetRequiredService<ILogger<EventBusServiceBus>>(); | ||||||
|  |                 var eventBusSubscriptionsManager = sp.GetRequiredService<IEventBusSubscriptionsManager>(); | ||||||
|  |                 string subscriptionName = eventBusSection.GetRequiredValue("SubscriptionClientName"); | ||||||
|  | 
 | ||||||
|  |                 return new EventBusServiceBus(serviceBusPersisterConnection, logger, | ||||||
|  |                     eventBusSubscriptionsManager, sp, subscriptionName); | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             services.AddSingleton<IRabbitMQPersistentConnection>(sp => | ||||||
|  |             { | ||||||
|  |                 var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersistentConnection>>(); | ||||||
|  | 
 | ||||||
|  |                 var factory = new ConnectionFactory() | ||||||
|  |                 { | ||||||
|  |                     HostName = eventBusSection.GetRequiredValue("ConnectionString"), | ||||||
|  |                     DispatchConsumersAsync = true | ||||||
|  |                 }; | ||||||
|  | 
 | ||||||
|  |                 if (!string.IsNullOrEmpty(eventBusSection["UserName"])) | ||||||
|  |                 { | ||||||
|  |                     factory.UserName = eventBusSection["UserName"]; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 if (!string.IsNullOrEmpty(eventBusSection["Password"])) | ||||||
|  |                 { | ||||||
|  |                     factory.Password = eventBusSection["Password"]; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 var retryCount = eventBusSection.GetValue("RetryCount", 5); | ||||||
|  | 
 | ||||||
|  |                 return new DefaultRabbitMQPersistentConnection(factory, logger, retryCount); | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |             services.AddSingleton<IEventBus, EventBusRabbitMQ>(sp => | ||||||
|  |             { | ||||||
|  |                 var subscriptionClientName = eventBusSection.GetRequiredValue("SubscriptionClientName"); | ||||||
|  |                 var rabbitMQPersistentConnection = sp.GetRequiredService<IRabbitMQPersistentConnection>(); | ||||||
|  |                 var logger = sp.GetRequiredService<ILogger<EventBusRabbitMQ>>(); | ||||||
|  |                 var eventBusSubscriptionsManager = sp.GetRequiredService<IEventBusSubscriptionsManager>(); | ||||||
|  |                 var retryCount = eventBusSection.GetValue("RetryCount", 5); | ||||||
|  | 
 | ||||||
|  |                 return new EventBusRabbitMQ(rabbitMQPersistentConnection, logger, sp, eventBusSubscriptionsManager, subscriptionClientName, retryCount); | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         services.AddSingleton<IEventBusSubscriptionsManager, InMemoryEventBusSubscriptionsManager>(); | ||||||
|  |         return services; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static void UseDefaultSerilog(this IHostBuilder builder, IConfiguration configuration, string name) | ||||||
|  |     { | ||||||
|  |         builder.UseSerilog(CreateSerilogLogger(configuration)); | ||||||
|  | 
 | ||||||
|  |         Serilog.ILogger CreateSerilogLogger(IConfiguration configuration) | ||||||
|  |         { | ||||||
|  |             var seqServerUrl = configuration["Serilog:SeqServerUrl"]; | ||||||
|  |             var logstashUrl = configuration["Serilog:LogstashgUrl"]; | ||||||
|  |             return new LoggerConfiguration() | ||||||
|  |                 .MinimumLevel.Verbose() | ||||||
|  |                 .Enrich.WithProperty("ApplicationContext", name) | ||||||
|  |                 .Enrich.FromLogContext() | ||||||
|  |                 .WriteTo.Console() | ||||||
|  |                 .WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl) | ||||||
|  |                 .WriteTo.Http(string.IsNullOrWhiteSpace(logstashUrl) ? "http://logstash:8080" : logstashUrl, null) | ||||||
|  |                 .ReadFrom.Configuration(configuration) | ||||||
|  |                 .CreateLogger(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static void MapDefaultHealthChecks(this IEndpointRouteBuilder routes) | ||||||
|  |     { | ||||||
|  |         routes.MapHealthChecks("/hc", new HealthCheckOptions() | ||||||
|  |         { | ||||||
|  |             Predicate = _ => true, | ||||||
|  |             ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         routes.MapHealthChecks("/liveness", new HealthCheckOptions | ||||||
|  |         { | ||||||
|  |             Predicate = r => r.Name.Contains("self") | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     static (int httpPort, int grpcPort) GetDefinedPorts(IConfiguration config) | ||||||
|  |     { | ||||||
|  |         var grpcPort = config.GetValue("GRPC_PORT", 5001); | ||||||
|  |         var port = config.GetValue("PORT", 80); | ||||||
|  |         return (port, grpcPort); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private static string GetRequiredValue(this IConfiguration configuration, string name) => | ||||||
|  |         configuration[name] ?? throw new InvalidOperationException($"Configuration missing value for: {(configuration is IConfigurationSection s ? s.Key + ":" + name : name)}"); | ||||||
|  | } | ||||||
							
								
								
									
										64
									
								
								src/Services/Services.Common/Services.Common.csproj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								src/Services/Services.Common/Services.Common.csproj
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,64 @@ | |||||||
|  | <Project Sdk="Microsoft.NET.Sdk"> | ||||||
|  | 
 | ||||||
|  |   <PropertyGroup> | ||||||
|  |     <TargetFramework>net7.0</TargetFramework> | ||||||
|  |     <ImplicitUsings>enable</ImplicitUsings> | ||||||
|  |     <Nullable>enable</Nullable> | ||||||
|  |   </PropertyGroup> | ||||||
|  | 
 | ||||||
|  |   <ItemGroup> | ||||||
|  |     <FrameworkReference Include="Microsoft.AspNetCore.App" /> | ||||||
|  |   </ItemGroup> | ||||||
|  | 
 | ||||||
|  |   <ItemGroup> | ||||||
|  |     <PackageReference Include="AspNetCore.HealthChecks.AzureServiceBus" /> | ||||||
|  |     <PackageReference Include="AspNetCore.HealthChecks.Rabbitmq" /> | ||||||
|  |     <PackageReference Include="AspNetCore.HealthChecks.SqlServer" /> | ||||||
|  |     <PackageReference Include="AspNetCore.HealthChecks.UI.Client" /> | ||||||
|  |     <PackageReference Include="Autofac.Extensions.DependencyInjection" /> | ||||||
|  |     <PackageReference Include="Duende.IdentityServer.AspNetIdentity" /> | ||||||
|  |     <PackageReference Include="Duende.IdentityServer.EntityFramework.Storage" /> | ||||||
|  |     <PackageReference Include="Duende.IdentityServer.EntityFramework" /> | ||||||
|  |     <PackageReference Include="Duende.IdentityServer.Storage" /> | ||||||
|  |     <PackageReference Include="Duende.IdentityServer" /> | ||||||
|  |     <PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" /> | ||||||
|  |     <PackageReference Include="Microsoft.ApplicationInsights.DependencyCollector" /> | ||||||
|  |     <PackageReference Include="Microsoft.ApplicationInsights.Kubernetes" /> | ||||||
|  |     <PackageReference Include="Microsoft.AspNetCore.DataProtection.StackExchangeRedis" /> | ||||||
|  |     <PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" /> | ||||||
|  |     <PackageReference Include="Microsoft.AspNetCore.Diagnostics.HealthChecks" /> | ||||||
|  |     <PackageReference Include="Microsoft.AspNetCore.HealthChecks" /> | ||||||
|  |     <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" /> | ||||||
|  |     <PackageReference Include="Microsoft.AspNetCore.Identity.UI" /> | ||||||
|  |     <PackageReference Include="Microsoft.EntityFrameworkCore.Design"> | ||||||
|  |       <PrivateAssets>all</PrivateAssets> | ||||||
|  |       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||||
|  |     </PackageReference> | ||||||
|  |     <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" /> | ||||||
|  |     <PackageReference Include="Microsoft.EntityFrameworkCore" /> | ||||||
|  |     <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" /> | ||||||
|  |     <PackageReference Include="Microsoft.Extensions.Logging.AzureAppServices" /> | ||||||
|  |     <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" /> | ||||||
|  |     <PackageReference Include="Microsoft.Web.LibraryManager.Build" /> | ||||||
|  |     <PackageReference Include="Polly" /> | ||||||
|  |     <PackageReference Include="Serilog.AspNetCore" /> | ||||||
|  |     <PackageReference Include="Serilog.Enrichers.Environment" /> | ||||||
|  |     <PackageReference Include="Serilog.Settings.Configuration" /> | ||||||
|  |     <PackageReference Include="Serilog.Sinks.Console" /> | ||||||
|  |     <PackageReference Include="Serilog.Sinks.Http" /> | ||||||
|  |     <PackageReference Include="Serilog.Sinks.Seq" /> | ||||||
|  |     <PackageReference Include="Swashbuckle.AspNetCore" /> | ||||||
|  |     <PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" /> | ||||||
|  |     <PackageReference Include="System.Data.SqlClient" /> | ||||||
|  |     <PackageReference Include="Azure.Extensions.AspNetCore.Configuration.Secrets" /> | ||||||
|  |     <PackageReference Include="Azure.Identity" /> | ||||||
|  |     <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" /> | ||||||
|  |   </ItemGroup> | ||||||
|  | 
 | ||||||
|  |   <ItemGroup> | ||||||
|  |     <ProjectReference Include="..\..\BuildingBlocks\EventBus\EventBusRabbitMQ\EventBusRabbitMQ.csproj" /> | ||||||
|  |     <ProjectReference Include="..\..\BuildingBlocks\EventBus\EventBusServiceBus\EventBusServiceBus.csproj" /> | ||||||
|  |     <ProjectReference Include="..\..\BuildingBlocks\EventBus\EventBus\EventBus.csproj" /> | ||||||
|  |   </ItemGroup> | ||||||
|  |    | ||||||
|  | </Project> | ||||||
| @ -122,6 +122,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{373D8AA1 | |||||||
| EndProject | EndProject | ||||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EventBus.Tests", "BuildingBlocks\EventBus\EventBus.Tests\EventBus.Tests.csproj", "{95D735BE-2899-4495-BE3F-2600E93B4E3C}" | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EventBus.Tests", "BuildingBlocks\EventBus\EventBus.Tests\EventBus.Tests.csproj", "{95D735BE-2899-4495-BE3F-2600E93B4E3C}" | ||||||
| EndProject | EndProject | ||||||
|  | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Services.Common", "Services\Services.Common\Services.Common.csproj", "{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}" | ||||||
|  | EndProject | ||||||
|  | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Common", "Common", "{42B85D0F-2ED6-4C00-91FA-103DACC3D5E2}" | ||||||
|  | EndProject | ||||||
| Global | Global | ||||||
| 	GlobalSection(SolutionConfigurationPlatforms) = preSolution | 	GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||||
| 		Ad-Hoc|Any CPU = Ad-Hoc|Any CPU | 		Ad-Hoc|Any CPU = Ad-Hoc|Any CPU | ||||||
| @ -1480,6 +1484,54 @@ Global | |||||||
| 		{95D735BE-2899-4495-BE3F-2600E93B4E3C}.Release|x64.Build.0 = Release|Any CPU | 		{95D735BE-2899-4495-BE3F-2600E93B4E3C}.Release|x64.Build.0 = Release|Any CPU | ||||||
| 		{95D735BE-2899-4495-BE3F-2600E93B4E3C}.Release|x86.ActiveCfg = Release|Any CPU | 		{95D735BE-2899-4495-BE3F-2600E93B4E3C}.Release|x86.ActiveCfg = Release|Any CPU | ||||||
| 		{95D735BE-2899-4495-BE3F-2600E93B4E3C}.Release|x86.Build.0 = Release|Any CPU | 		{95D735BE-2899-4495-BE3F-2600E93B4E3C}.Release|x86.Build.0 = Release|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.Ad-Hoc|x64.Build.0 = Debug|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.Ad-Hoc|x86.Build.0 = Debug|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.AppStore|Any CPU.Build.0 = Debug|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.AppStore|ARM.ActiveCfg = Debug|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.AppStore|ARM.Build.0 = Debug|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.AppStore|iPhone.ActiveCfg = Debug|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.AppStore|iPhone.Build.0 = Debug|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.AppStore|x64.ActiveCfg = Debug|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.AppStore|x64.Build.0 = Debug|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.AppStore|x86.ActiveCfg = Debug|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.AppStore|x86.Build.0 = Debug|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.Debug|ARM.ActiveCfg = Debug|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.Debug|ARM.Build.0 = Debug|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.Debug|iPhone.ActiveCfg = Debug|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.Debug|iPhone.Build.0 = Debug|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.Debug|x64.ActiveCfg = Debug|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.Debug|x64.Build.0 = Debug|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.Debug|x86.ActiveCfg = Debug|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.Debug|x86.Build.0 = Debug|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.Release|Any CPU.Build.0 = Release|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.Release|ARM.ActiveCfg = Release|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.Release|ARM.Build.0 = Release|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.Release|iPhone.ActiveCfg = Release|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.Release|iPhone.Build.0 = Release|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.Release|iPhoneSimulator.Build.0 = Release|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.Release|x64.ActiveCfg = Release|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.Release|x64.Build.0 = Release|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.Release|x86.ActiveCfg = Release|Any CPU | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5}.Release|x86.Build.0 = Release|Any CPU | ||||||
| 	EndGlobalSection | 	EndGlobalSection | ||||||
| 	GlobalSection(SolutionProperties) = preSolution | 	GlobalSection(SolutionProperties) = preSolution | ||||||
| 		HideSolutionNode = FALSE | 		HideSolutionNode = FALSE | ||||||
| @ -1536,6 +1588,8 @@ Global | |||||||
| 		{B62E859F-825E-4C8B-93EC-5966EACFD026} = {798BFC44-2CCD-45FA-B37A-5173B03C2B30} | 		{B62E859F-825E-4C8B-93EC-5966EACFD026} = {798BFC44-2CCD-45FA-B37A-5173B03C2B30} | ||||||
| 		{373D8AA1-36BE-49EC-89F0-6CB736666285} = {807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F} | 		{373D8AA1-36BE-49EC-89F0-6CB736666285} = {807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F} | ||||||
| 		{95D735BE-2899-4495-BE3F-2600E93B4E3C} = {373D8AA1-36BE-49EC-89F0-6CB736666285} | 		{95D735BE-2899-4495-BE3F-2600E93B4E3C} = {373D8AA1-36BE-49EC-89F0-6CB736666285} | ||||||
|  | 		{CD430CE4-D5E0-4C96-84F5-AEC9162651B5} = {42B85D0F-2ED6-4C00-91FA-103DACC3D5E2} | ||||||
|  | 		{42B85D0F-2ED6-4C00-91FA-103DACC3D5E2} = {91CF7717-08AB-4E65-B10E-0B426F01E2E8} | ||||||
| 	EndGlobalSection | 	EndGlobalSection | ||||||
| 	GlobalSection(ExtensibilityGlobals) = postSolution | 	GlobalSection(ExtensibilityGlobals) = postSolution | ||||||
| 		SolutionGuid = {25728519-5F0F-4973-8A64-0A81EB4EA8D9} | 		SolutionGuid = {25728519-5F0F-4973-8A64-0A81EB4EA8D9} | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user