diff --git a/src/Services/Catalog/Catalog.API/Catalog.API.csproj b/src/Services/Catalog/Catalog.API/Catalog.API.csproj
index 7d778716b..63ae7ce25 100644
--- a/src/Services/Catalog/Catalog.API/Catalog.API.csproj
+++ b/src/Services/Catalog/Catalog.API/Catalog.API.csproj
@@ -79,6 +79,7 @@
+
diff --git a/src/Services/Catalog/Catalog.API/CustomExtensionMethods.cs b/src/Services/Catalog/Catalog.API/CustomExtensionMethods.cs
new file mode 100644
index 000000000..7b3633a12
--- /dev/null
+++ b/src/Services/Catalog/Catalog.API/CustomExtensionMethods.cs
@@ -0,0 +1,89 @@
+public static class CustomExtensionMethods
+{
+ public static IServiceCollection AddCustomHealthCheck(this IServiceCollection services, IConfiguration configuration)
+ {
+ var hcBuilder = services.AddHealthChecks();
+
+ hcBuilder
+ .AddSqlServer(
+ configuration.GetConnectionString("Application"),
+ name: "CatalogDB-check",
+ tags: new string[] { "catalogdb" });
+
+ var accountName = configuration["AzureStorageAccountName"];
+ var accountKey = configuration["AzureStorageAccountKey"];
+
+ if (!string.IsNullOrEmpty(accountName) && !string.IsNullOrEmpty(accountKey))
+ {
+ hcBuilder
+ .AddAzureBlobStorage(
+ $"DefaultEndpointsProtocol=https;AccountName={accountName};AccountKey={accountKey};EndpointSuffix=core.windows.net",
+ name: "catalog-storage-check",
+ tags: new string[] { "catalogstorage" });
+ }
+
+ return services;
+ }
+
+ public static IServiceCollection AddCustomDbContext(this IServiceCollection services, IConfiguration configuration)
+ {
+ services.AddEntityFrameworkSqlServer()
+ .AddDbContext(options =>
+ {
+ options.UseSqlServer(configuration["ConnectionString"],
+ sqlServerOptionsAction: sqlOptions =>
+ {
+ sqlOptions.MigrationsAssembly(typeof(Program).GetTypeInfo().Assembly.GetName().Name);
+ //Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency
+ sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
+ });
+ });
+
+ services.AddDbContext(options =>
+ {
+ options.UseSqlServer(configuration["ConnectionString"],
+ sqlServerOptionsAction: sqlOptions =>
+ {
+ sqlOptions.MigrationsAssembly(typeof(Program).GetTypeInfo().Assembly.GetName().Name);
+ //Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency
+ sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
+ });
+ });
+
+ return services;
+ }
+
+ public static IServiceCollection AddCustomOptions(this IServiceCollection services, IConfiguration configuration)
+ {
+ services.Configure(configuration);
+ services.Configure(options =>
+ {
+ options.InvalidModelStateResponseFactory = context =>
+ {
+ var problemDetails = new ValidationProblemDetails(context.ModelState)
+ {
+ Instance = context.HttpContext.Request.Path,
+ Status = StatusCodes.Status400BadRequest,
+ Detail = "Please refer to the errors property for additional details."
+ };
+
+ return new BadRequestObjectResult(problemDetails)
+ {
+ ContentTypes = { "application/problem+json", "application/problem+xml" }
+ };
+ };
+ });
+
+ return services;
+ }
+
+ public static IServiceCollection AddIntegrationServices(this IServiceCollection services)
+ {
+ services.AddTransient>(
+ sp => (DbConnection c) => new IntegrationEventLogService(c));
+
+ services.AddTransient();
+
+ return services;
+ }
+}
diff --git a/src/Services/Catalog/Catalog.API/Program.cs b/src/Services/Catalog/Catalog.API/Program.cs
index 9c919dbc1..f8fd8b4ab 100644
--- a/src/Services/Catalog/Catalog.API/Program.cs
+++ b/src/Services/Catalog/Catalog.API/Program.cs
@@ -1,117 +1,56 @@
-var builder = WebApplication.CreateBuilder(new WebApplicationOptions
-{
- Args = args,
- ApplicationName = typeof(Program).Assembly.FullName,
- ContentRootPath = Directory.GetCurrentDirectory(),
- WebRootPath = "Pics",
-});
-if (builder.Configuration.GetValue("UseVault", false))
-{
- TokenCredential credential = new ClientSecretCredential(
- builder.Configuration["Vault:TenantId"],
- builder.Configuration["Vault:ClientId"],
- builder.Configuration["Vault:ClientSecret"]);
- //builder.AddAzureKeyVault(new Uri($"https://{builder.Configuration["Vault:Name"]}.vault.azure.net/"), credential);
-}
-builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
-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;
- });
+using Services.Common;
-});
-builder.Services.AddAppInsight(builder.Configuration);
-builder.Services.AddGrpc().Services
- .AddCustomMVC(builder.Configuration)
- .AddCustomDbContext(builder.Configuration)
- .AddCustomOptions(builder.Configuration)
- .AddCustomHealthCheck(builder.Configuration)
- .AddIntegrationServices(builder.Configuration)
- .AddEventBus(builder.Configuration)
- .AddSwagger(builder.Configuration);
+var builder = WebApplication.CreateBuilder(args);
-var app = builder.Build();
+builder.AddServiceDefaults();
-if (app.Environment.IsDevelopment())
+builder.Services.AddControllers(options =>
{
- app.UseDeveloperExceptionPage();
-}
-else
-{
- app.UseExceptionHandler("/Home/Error");
-}
+ options.Filters.Add(typeof(HttpGlobalExceptionFilter));
+})
+.AddJsonOptions(options => options.JsonSerializerOptions.WriteIndented = true);
-var pathBase = app.Configuration["PATH_BASE"];
-if (!string.IsNullOrEmpty(pathBase))
-{
- app.UsePathBase(pathBase);
-}
+builder.Services.AddGrpc();
+
+builder.Services.AddCustomDbContext(builder.Configuration);
+builder.Services.AddCustomOptions(builder.Configuration);
+builder.Services.AddCustomHealthCheck(builder.Configuration);
+builder.Services.AddIntegrationServices();
-app.UseSwagger()
- .UseSwaggerUI(c =>
- {
- c.SwaggerEndpoint($"{(!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty)}/swagger/v1/swagger.json", "Catalog.API V1");
- });
+builder.Services.AddTransient();
+builder.Services.AddTransient();
+
+var app = builder.Build();
+
+app.UseServiceDefaults();
-app.UseRouting();
-app.UseCors("CorsPolicy");
app.MapDefaultControllerRoute();
app.MapControllers();
+
app.UseFileServer(new FileServerOptions
{
FileProvider = new PhysicalFileProvider(Path.Combine(app.Environment.ContentRootPath, "Pics")),
RequestPath = "/pics"
});
-app.UseStaticFiles(new StaticFileOptions
-{
- FileProvider = new PhysicalFileProvider(Path.Combine(app.Environment.ContentRootPath, "Pics")),
- RequestPath = "/pics"
-});
-app.MapGet("/_proto/", async ctx =>
-{
- ctx.Response.ContentType = "text/plain";
- using var fs = new FileStream(Path.Combine(app.Environment.ContentRootPath, "Proto", "catalog.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.MapGrpcService();
-app.MapHealthChecks("/hc", new HealthCheckOptions()
-{
- Predicate = _ => true,
- ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
-});
-app.MapHealthChecks("/liveness", new HealthCheckOptions
-{
- Predicate = r => r.Name.Contains("self")
-});
-ConfigureEventBus(app);
+var eventBus = app.Services.GetRequiredService();
+
+eventBus.Subscribe();
+eventBus.Subscribe();
try
{
- Log.Information("Configuring web host ({ApplicationContext})...", Program.AppName);
+ app.Logger.LogInformation("Configuring web host ({ApplicationContext})...", AppName);
+
using var scope = app.Services.CreateScope();
var context = scope.ServiceProvider.GetRequiredService();
- var env = app.Services.GetService();
var settings = app.Services.GetService>();
var logger = app.Services.GetService>();
await context.Database.MigrateAsync();
- await new CatalogContextSeed().SeedAsync(context, env, settings, logger);
+ await new CatalogContextSeed().SeedAsync(context, app.Environment, settings, logger);
var integEventContext = scope.ServiceProvider.GetRequiredService();
await integEventContext.Database.MigrateAsync();
app.Logger.LogInformation("Starting web host ({ApplicationName})...", AppName);
@@ -121,268 +60,12 @@ try
}
catch (Exception ex)
{
- Log.Fatal(ex, "Program terminated unexpectedly ({ApplicationContext})!", Program.AppName);
+ app.Logger.LogCritical(ex, "Program terminated unexpectedly ({ApplicationContext})!", AppName);
return 1;
}
-finally
-{
- Log.CloseAndFlush();
-}
-void ConfigureEventBus(IApplicationBuilder app)
-{
- var eventBus = app.ApplicationServices.GetRequiredService();
- eventBus.Subscribe();
- eventBus.Subscribe();
-}
-
-(int httpPort, int grpcPort) GetDefinedPorts(IConfiguration config)
-{
- var grpcPort = config.GetValue("GRPC_PORT", 81);
- var port = config.GetValue("PORT", 80);
- return (port, grpcPort);
-}
public partial class Program
{
public static string Namespace = typeof(Program).Assembly.GetName().Name;
public static string AppName = Namespace.Substring(Namespace.LastIndexOf('.', Namespace.LastIndexOf('.') - 1) + 1);
}
-
-public static class CustomExtensionMethods
-{
- public static IServiceCollection AddAppInsight(this IServiceCollection services, IConfiguration configuration)
- {
- services.AddApplicationInsightsTelemetry(configuration);
- services.AddApplicationInsightsKubernetesEnricher();
-
- return services;
- }
-
- public static IServiceCollection AddCustomMVC(this IServiceCollection services, IConfiguration configuration)
- {
- services.AddControllers(options =>
- {
- options.Filters.Add(typeof(HttpGlobalExceptionFilter));
- })
- .AddJsonOptions(options => options.JsonSerializerOptions.WriteIndented = true);
-
- services.AddCors(options =>
- {
- options.AddPolicy("CorsPolicy",
- builder => builder
- .SetIsOriginAllowed((host) => true)
- .AllowAnyMethod()
- .AllowAnyHeader()
- .AllowCredentials());
- });
-
- return services;
- }
-
- public static IServiceCollection AddCustomHealthCheck(this IServiceCollection services, IConfiguration configuration)
- {
- var accountName = configuration.GetValue("AzureStorageAccountName");
- var accountKey = configuration.GetValue("AzureStorageAccountKey");
-
- var hcBuilder = services.AddHealthChecks();
-
- hcBuilder
- .AddCheck("self", () => HealthCheckResult.Healthy())
- .AddSqlServer(
- configuration["ConnectionString"],
- name: "CatalogDB-check",
- tags: new string[] { "catalogdb" });
-
- if (!string.IsNullOrEmpty(accountName) && !string.IsNullOrEmpty(accountKey))
- {
- hcBuilder
- .AddAzureBlobStorage(
- $"DefaultEndpointsProtocol=https;AccountName={accountName};AccountKey={accountKey};EndpointSuffix=core.windows.net",
- name: "catalog-storage-check",
- tags: new string[] { "catalogstorage" });
- }
-
- if (configuration.GetValue("AzureServiceBusEnabled"))
- {
- hcBuilder
- .AddAzureServiceBusTopic(
- configuration["EventBusConnection"],
- topicName: "eshop_event_bus",
- name: "catalog-servicebus-check",
- tags: new string[] { "servicebus" });
- }
- else
- {
- hcBuilder
- .AddRabbitMQ(
- $"amqp://{configuration["EventBusConnection"]}",
- name: "catalog-rabbitmqbus-check",
- tags: new string[] { "rabbitmqbus" });
- }
-
- return services;
- }
-
- public static IServiceCollection AddCustomDbContext(this IServiceCollection services, IConfiguration configuration)
- {
- services.AddEntityFrameworkSqlServer()
- .AddDbContext(options =>
- {
- options.UseSqlServer(configuration["ConnectionString"],
- sqlServerOptionsAction: sqlOptions =>
- {
- sqlOptions.MigrationsAssembly(typeof(Program).GetTypeInfo().Assembly.GetName().Name);
- //Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency
- sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
- });
- });
-
- services.AddDbContext(options =>
- {
- options.UseSqlServer(configuration["ConnectionString"],
- sqlServerOptionsAction: sqlOptions =>
- {
- sqlOptions.MigrationsAssembly(typeof(Program).GetTypeInfo().Assembly.GetName().Name);
- //Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency
- sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
- });
- });
-
- return services;
- }
-
- public static IServiceCollection AddCustomOptions(this IServiceCollection services, IConfiguration configuration)
- {
- services.Configure(configuration);
- services.Configure(options =>
- {
- options.InvalidModelStateResponseFactory = context =>
- {
- var problemDetails = new ValidationProblemDetails(context.ModelState)
- {
- Instance = context.HttpContext.Request.Path,
- Status = StatusCodes.Status400BadRequest,
- Detail = "Please refer to the errors property for additional details."
- };
-
- return new BadRequestObjectResult(problemDetails)
- {
- ContentTypes = { "application/problem+json", "application/problem+xml" }
- };
- };
- });
-
- return services;
- }
-
- public static IServiceCollection AddSwagger(this IServiceCollection services, IConfiguration configuration)
- {
- services.AddSwaggerGen(options =>
- {
- options.SwaggerDoc("v1", new OpenApiInfo
- {
- Title = "eShopOnContainers - Catalog HTTP API",
- Version = "v1",
- Description = "The Catalog Microservice HTTP API. This is a Data-Driven/CRUD microservice sample"
- });
- });
-
- return services;
-
- }
-
- public static IServiceCollection AddIntegrationServices(this IServiceCollection services, IConfiguration configuration)
- {
- services.AddTransient>(
- sp => (DbConnection c) => new IntegrationEventLogService(c));
-
- services.AddTransient();
-
- if (configuration.GetValue("AzureServiceBusEnabled"))
- {
- services.AddSingleton(sp =>
- {
- var settings = sp.GetRequiredService>().Value;
- var serviceBusConnection = settings.EventBusConnection;
-
- return new DefaultServiceBusPersisterConnection(serviceBusConnection);
- });
- }
- else
- {
- services.AddSingleton(sp =>
- {
- var settings = sp.GetRequiredService>().Value;
- var logger = sp.GetRequiredService>();
-
- 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);
- });
- }
-
- return services;
- }
-
- public static IServiceCollection AddEventBus(this IServiceCollection services, IConfiguration configuration)
- {
- if (configuration.GetValue("AzureServiceBusEnabled"))
- {
- services.AddSingleton(sp =>
- {
- var serviceBusPersisterConnection = sp.GetRequiredService();
- var logger = sp.GetRequiredService>();
- var eventBusSubcriptionsManager = sp.GetRequiredService();
- string subscriptionName = configuration["SubscriptionClientName"];
-
- return new EventBusServiceBus(serviceBusPersisterConnection, logger,
- eventBusSubcriptionsManager, sp, subscriptionName);
- });
-
- }
- else
- {
- services.AddSingleton(sp =>
- {
- var subscriptionClientName = configuration["SubscriptionClientName"];
- var rabbitMQPersistentConnection = sp.GetRequiredService();
- var logger = sp.GetRequiredService>();
- var eventBusSubcriptionsManager = sp.GetRequiredService();
-
- var retryCount = 5;
- if (!string.IsNullOrEmpty(configuration["EventBusRetryCount"]))
- {
- retryCount = int.Parse(configuration["EventBusRetryCount"]);
- }
-
- return new EventBusRabbitMQ(rabbitMQPersistentConnection, logger, sp, eventBusSubcriptionsManager, subscriptionClientName, retryCount);
- });
- }
-
- services.AddSingleton();
- services.AddTransient();
- services.AddTransient();
-
- return services;
- }
-}
diff --git a/src/Services/Catalog/Catalog.API/appsettings.json b/src/Services/Catalog/Catalog.API/appsettings.json
index f8342fe8d..eac02f2fc 100644
--- a/src/Services/Catalog/Catalog.API/appsettings.json
+++ b/src/Services/Catalog/Catalog.API/appsettings.json
@@ -18,13 +18,30 @@
"ApplicationInsights": {
"InstrumentationKey": ""
},
- "EventBusRetryCount": 5,
"UseVault": false,
"Vault": {
"Name": "eshop",
"ClientId": "your-client-id",
"ClientSecret": "your-client-secret"
+ },
+ "OpenApi": {
+ "Endpoint": {
+ "Name": ""
+ },
+ "Document": {
+ "Name": "Catalog.API V1",
+ "Description": "The Catalog Microservice HTTP API. This is a Data-Driven/CRUD microservice sample",
+ "Title": "eShopOnContainers - Catalog HTTP API",
+ "Version": "v1"
+ }
+ },
+ "ConnectionStrings": {
+ "Application": ""
+ },
+ "EventBus": {
+ "SubscriptionClientName": "Basket",
+ "ConnectionString": "localhost",
+ "RetryCount": 5
}
-
}