diff --git a/src/Services/Ordering/Ordering.API/.editorconfig b/src/Services/Ordering/Ordering.API/.editorconfig
new file mode 100644
index 000000000..a2816621e
--- /dev/null
+++ b/src/Services/Ordering/Ordering.API/.editorconfig
@@ -0,0 +1,132 @@
+###############################
+# Core EditorConfig Options #
+###############################
+root = true
+# All files
+[*]
+indent_style = space
+
+# XML project files
+[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]
+indent_size = 2
+
+# XML config files
+[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}]
+indent_size = 2
+
+# Code files
+[*.{cs,csx,vb,vbx}]
+indent_size = 4
+insert_final_newline = true
+charset = utf-8-bom
+###############################
+# .NET Coding Conventions #
+###############################
+[*.{cs,vb}]
+# Organize usings
+dotnet_sort_system_directives_first = true
+# this. preferences
+dotnet_style_qualification_for_field = false:silent
+dotnet_style_qualification_for_property = false:silent
+dotnet_style_qualification_for_method = false:silent
+dotnet_style_qualification_for_event = false:silent
+# Language keywords vs BCL types preferences
+dotnet_style_predefined_type_for_locals_parameters_members = true:silent
+dotnet_style_predefined_type_for_member_access = true:silent
+# Parentheses preferences
+dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
+dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
+dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
+dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
+# Modifier preferences
+dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
+dotnet_style_readonly_field = true:suggestion
+# Expression-level preferences
+dotnet_style_object_initializer = true:suggestion
+dotnet_style_collection_initializer = true:suggestion
+dotnet_style_explicit_tuple_names = true:suggestion
+dotnet_style_null_propagation = true:suggestion
+dotnet_style_coalesce_expression = true:suggestion
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
+dotnet_style_prefer_inferred_tuple_names = true:suggestion
+dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
+dotnet_style_prefer_auto_properties = true:silent
+dotnet_style_prefer_conditional_expression_over_assignment = true:silent
+dotnet_style_prefer_conditional_expression_over_return = true:silent
+###############################
+# Naming Conventions #
+###############################
+# Style Definitions
+dotnet_naming_style.pascal_case_style.capitalization = pascal_case
+# Use PascalCase for constant fields
+dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
+dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
+dotnet_naming_symbols.constant_fields.applicable_kinds = field
+dotnet_naming_symbols.constant_fields.applicable_accessibilities = *
+dotnet_naming_symbols.constant_fields.required_modifiers = const
+###############################
+# C# Coding Conventions #
+###############################
+[*.cs]
+# var preferences
+csharp_style_var_for_built_in_types = true:silent
+csharp_style_var_when_type_is_apparent = true:silent
+csharp_style_var_elsewhere = true:silent
+# Expression-bodied members
+csharp_style_expression_bodied_methods = false:silent
+csharp_style_expression_bodied_constructors = false:silent
+csharp_style_expression_bodied_operators = false:silent
+csharp_style_expression_bodied_properties = true:silent
+csharp_style_expression_bodied_indexers = true:silent
+csharp_style_expression_bodied_accessors = true:silent
+# Pattern matching preferences
+csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
+csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
+# Null-checking preferences
+csharp_style_throw_expression = true:suggestion
+csharp_style_conditional_delegate_call = true:suggestion
+# Modifier preferences
+csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion
+# Expression-level preferences
+csharp_prefer_braces = true:silent
+csharp_style_deconstructed_variable_declaration = true:suggestion
+csharp_prefer_simple_default_expression = true:suggestion
+csharp_style_prefer_local_over_anonymous_function = true:suggestion
+csharp_style_inlined_variable_declaration = true:suggestion
+###############################
+# C# Formatting Rules #
+###############################
+# New line preferences
+csharp_new_line_before_open_brace = all
+csharp_new_line_before_else = true
+csharp_new_line_before_catch = true
+csharp_new_line_before_finally = true
+csharp_new_line_before_members_in_object_initializers = true
+csharp_new_line_before_members_in_anonymous_types = true
+csharp_new_line_between_query_expression_clauses = true
+# Indentation preferences
+csharp_indent_case_contents = true
+csharp_indent_switch_labels = true
+csharp_indent_labels = flush_left
+# Space preferences
+csharp_space_after_cast = false
+csharp_space_after_keywords_in_control_flow_statements = true
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+csharp_space_between_parentheses = false
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_around_binary_operators = before_and_after
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+# Wrapping preferences
+csharp_preserve_single_line_statements = true
+csharp_preserve_single_line_blocks = true
+###############################
+# VB Coding Conventions #
+###############################
+[*.vb]
+# Modifier preferences
+visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion
\ No newline at end of file
diff --git a/src/Services/Ordering/Ordering.API/Ordering.API.csproj b/src/Services/Ordering/Ordering.API/Ordering.API.csproj
index 573ce2665..166685321 100644
--- a/src/Services/Ordering/Ordering.API/Ordering.API.csproj
+++ b/src/Services/Ordering/Ordering.API/Ordering.API.csproj
@@ -76,4 +76,8 @@
+
+
+
+
diff --git a/src/Services/Ordering/Ordering.API/Program.cs b/src/Services/Ordering/Ordering.API/Program.cs
index 66499d73e..ba62a4dcc 100644
--- a/src/Services/Ordering/Ordering.API/Program.cs
+++ b/src/Services/Ordering/Ordering.API/Program.cs
@@ -1,27 +1,134 @@
-var configuration = GetConfiguration();
+using Autofac.Core;
+using Microsoft.Azure.Amqp.Framing;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
-Log.Logger = CreateSerilogLogger(configuration);
-
-try
+var appName = "Ordering.API";
+var builder = WebApplication.CreateBuilder(new WebApplicationOptions
+{
+ Args = args,
+ ApplicationName = typeof(Program).Assembly.FullName,
+ ContentRootPath = Directory.GetCurrentDirectory()
+});
+if (builder.Configuration.GetValue("UseVault", false))
+{
+ TokenCredential credential = new ClientSecretCredential(
+ builder.Configuration["Vault:TenantId"],
+ builder.Configuration["Vault:ClientId"],
+ builder.Configuration["Vault:ClientSecret"]);
+ builder.Configuration.AddAzureKeyVault(new Uri($"https://{builder.Configuration["Vault:Name"]}.vault.azure.net/"), credential);
+}
+builder.Configuration.SetBasePath(Directory.GetCurrentDirectory());
+builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
+builder.Configuration.AddEnvironmentVariables();
+builder.WebHost.ConfigureKestrel(options =>
{
- Log.Information("Configuring web host ({ApplicationContext})...", Program.AppName);
- var host = BuildWebHost(configuration, args);
+ var ports = GetDefinedPorts(builder.Configuration);
+ options.Listen(IPAddress.Any, ports.httpPort, listenOptions =>
+ {
+ listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
+ });
- Log.Information("Applying migrations ({ApplicationContext})...", Program.AppName);
- host.MigrateDbContext((context, services) =>
+ options.Listen(IPAddress.Any, ports.grpcPort, listenOptions =>
{
- var env = services.GetService();
- var settings = services.GetService>();
- var logger = services.GetService>();
+ listenOptions.Protocols = HttpProtocols.Http2;
+ });
- new OrderingContextSeed()
- .SeedAsync(context, env, settings, logger)
- .Wait();
+});
+builder.WebHost.CaptureStartupErrors(false);
+builder.Host.UseSerilog(CreateSerilogLogger(builder.Configuration));
+builder.Services
+ .AddGrpc(options =>
+ {
+ options.EnableDetailedErrors = true;
})
- .MigrateDbContext((_, __) => { });
+ .Services
+ .AddApplicationInsights(builder.Configuration)
+ .AddCustomMvc()
+ .AddHealthChecks(builder.Configuration)
+ .AddCustomDbContext(builder.Configuration)
+ .AddCustomSwagger(builder.Configuration)
+ .AddCustomAuthentication(builder.Configuration)
+ .AddCustomAuthorization(builder.Configuration)
+ .AddCustomIntegrations(builder.Configuration)
+ .AddCustomConfiguration(builder.Configuration)
+ .AddEventBus(builder.Configuration);
+
+builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
+// Register your own things directly with Autofac here. Don't
+// call builder.Populate(), that happens in AutofacServiceProviderFactory
+// for you.
+builder.Host.ConfigureContainer(conbuilder => conbuilder.RegisterModule(new MediatorModule()));
+builder.Host.ConfigureContainer(conbuilder => conbuilder.RegisterModule(new ApplicationModule(builder.Configuration["ConnectionString"])));
+var app = builder.Build();
+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(c =>
+ {
+ c.SwaggerEndpoint($"{(!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty)}/swagger/v1/swagger.json", "Ordering.API V1");
+ c.OAuthClientId("orderingswaggerui");
+ c.OAuthAppName("Ordering Swagger UI");
+ });
+
+app.UseRouting();
+app.UseCors("CorsPolicy");
+app.UseAuthentication();
+app.UseAuthorization();
+app.MapGrpcService();
+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("Applying migrations ({ApplicationContext})...", Program.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 OrderingContextSeed().SeedAsync(context, env, settings, logger);
+ var integEventContext = scope.ServiceProvider.GetRequiredService();
+ await integEventContext.Database.MigrateAsync();
Log.Information("Starting web host ({ApplicationContext})...", Program.AppName);
- host.Run();
+ await app.RunAsync();
return 0;
}
@@ -34,30 +141,17 @@ finally
{
Log.CloseAndFlush();
}
+void ConfigureEventBus(IApplicationBuilder app)
+{
+ var eventBus = app.ApplicationServices.GetRequiredService();
-IWebHost BuildWebHost(IConfiguration configuration, string[] args) =>
- WebHost.CreateDefaultBuilder(args)
- .CaptureStartupErrors(false)
- .ConfigureKestrel(options =>
- {
- var ports = GetDefinedPorts(configuration);
- options.Listen(IPAddress.Any, ports.httpPort, listenOptions =>
- {
- listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
- });
-
- options.Listen(IPAddress.Any, ports.grpcPort, listenOptions =>
- {
- listenOptions.Protocols = HttpProtocols.Http2;
- });
-
- })
- .ConfigureAppConfiguration(x => x.AddConfiguration(configuration))
- .UseStartup()
- .UseContentRoot(Directory.GetCurrentDirectory())
- .UseSerilog()
- .Build();
-
+ eventBus.Subscribe>();
+ eventBus.Subscribe>();
+ eventBus.Subscribe>();
+ eventBus.Subscribe>();
+ eventBus.Subscribe>();
+ eventBus.Subscribe>();
+}
Serilog.ILogger CreateSerilogLogger(IConfiguration configuration)
{
var seqServerUrl = configuration["Serilog:SeqServerUrl"];
@@ -68,42 +162,295 @@ Serilog.ILogger CreateSerilogLogger(IConfiguration configuration)
.Enrich.FromLogContext()
.WriteTo.Console()
.WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl)
- .WriteTo.Http(string.IsNullOrWhiteSpace(logstashUrl) ? "http://logstash:8080" : logstashUrl,null)
+ .WriteTo.Http(string.IsNullOrWhiteSpace(logstashUrl) ? "http://logstash:8080" : logstashUrl, null)
.ReadFrom.Configuration(configuration)
.CreateLogger();
}
-
-IConfiguration GetConfiguration()
-{
- var builder = new ConfigurationBuilder()
- .SetBasePath(Directory.GetCurrentDirectory())
- .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
- .AddEnvironmentVariables();
-
- var config = builder.Build();
-
- if (config.GetValue("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 port = config.GetValue("PORT", 80);
return (port, grpcPort);
}
-
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);
-}
\ No newline at end of file
+}
+static class CustomExtensionsMethods
+{
+ public static IServiceCollection AddApplicationInsights(this IServiceCollection services, IConfiguration configuration)
+ {
+ services.AddApplicationInsightsTelemetry(configuration);
+ services.AddApplicationInsightsKubernetesEnricher();
+
+ return services;
+ }
+
+ public static IServiceCollection AddCustomMvc(this IServiceCollection services)
+ {
+ // Add framework services.
+ services.AddControllers(options =>
+ {
+ options.Filters.Add(typeof(HttpGlobalExceptionFilter));
+ })
+ // Added for functional tests
+ .AddApplicationPart(typeof(OrdersController).Assembly)
+ .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 AddHealthChecks(this IServiceCollection services, IConfiguration configuration)
+ {
+ var hcBuilder = services.AddHealthChecks();
+
+ hcBuilder.AddCheck("self", () => HealthCheckResult.Healthy());
+
+ hcBuilder
+ .AddSqlServer(
+ configuration["ConnectionString"],
+ name: "OrderingDB-check",
+ tags: new string[] { "orderingdb" });
+
+ if (configuration.GetValue("AzureServiceBusEnabled"))
+ {
+ hcBuilder
+ .AddAzureServiceBusTopic(
+ configuration["EventBusConnection"],
+ topicName: "eshop_event_bus",
+ name: "ordering-servicebus-check",
+ tags: new string[] { "servicebus" });
+ }
+ else
+ {
+ hcBuilder
+ .AddRabbitMQ(
+ $"amqp://{configuration["EventBusConnection"]}",
+ name: "ordering-rabbitmqbus-check",
+ tags: new string[] { "rabbitmqbus" });
+ }
+
+ return services;
+ }
+
+ public static IServiceCollection AddCustomDbContext(this IServiceCollection services, IConfiguration configuration)
+ {
+ services.AddDbContext(options =>
+ {
+ options.UseSqlServer(configuration["ConnectionString"],
+ sqlServerOptionsAction: sqlOptions =>
+ {
+ sqlOptions.MigrationsAssembly(typeof(Program).GetTypeInfo().Assembly.GetName().Name);
+ sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
+ });
+ },
+ ServiceLifetime.Scoped //Showing explicitly that the DbContext is shared across the HTTP request scope (graph of objects started in the HTTP request)
+ );
+
+ 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 AddCustomSwagger(this IServiceCollection services, IConfiguration configuration)
+ {
+ services.AddSwaggerGen(options =>
+ {
+ options.SwaggerDoc("v1", new OpenApiInfo
+ {
+ Title = "eShopOnContainers - Ordering HTTP API",
+ Version = "v1",
+ Description = "The Ordering Service HTTP API"
+ });
+ options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
+ {
+ Type = SecuritySchemeType.OAuth2,
+ Flows = new OpenApiOAuthFlows()
+ {
+ Implicit = new OpenApiOAuthFlow()
+ {
+ AuthorizationUrl = new Uri($"{configuration.GetValue("IdentityUrlExternal")}/connect/authorize"),
+ TokenUrl = new Uri($"{configuration.GetValue("IdentityUrlExternal")}/connect/token"),
+ Scopes = new Dictionary()
+ {
+ { "orders", "Ordering API" }
+ }
+ }
+ }
+ });
+ options.OperationFilter();
+ });
+ return services;
+ }
+
+ public static IServiceCollection AddCustomIntegrations(this IServiceCollection services, IConfiguration configuration)
+ {
+ services.AddSingleton();
+ services.AddTransient();
+ services.AddTransient>(
+ sp => (DbConnection c) => new IntegrationEventLogService(c));
+
+ services.AddTransient();
+
+ if (configuration.GetValue("AzureServiceBusEnabled"))
+ {
+ services.AddSingleton(sp =>
+ {
+ var serviceBusConnectionString = configuration["EventBusConnection"];
+
+ var subscriptionClientName = configuration["SubscriptionClientName"];
+
+ return new DefaultServiceBusPersisterConnection(serviceBusConnectionString);
+ });
+ }
+ else
+ {
+ services.AddSingleton(sp =>
+ {
+ 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 AddCustomConfiguration(this IServiceCollection services, IConfiguration configuration)
+ {
+ services.AddOptions();
+ 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 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();
+
+ return services;
+ }
+
+ public static IServiceCollection AddCustomAuthentication(this IServiceCollection services, IConfiguration configuration)
+ {
+ // prevent from mapping "sub" claim to nameidentifier.
+ JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub");
+
+ var identityUrl = configuration.GetValue("IdentityUrl");
+
+ services.AddAuthentication("Bearer").AddJwtBearer(options =>
+ {
+ options.Authority = identityUrl;
+ options.RequireHttpsMetadata = false;
+ options.Audience = "orders";
+ options.TokenValidationParameters.ValidateAudience = false;
+ });
+
+ return services;
+ }
+ public static IServiceCollection AddCustomAuthorization(this IServiceCollection services, IConfiguration configuration)
+ {
+ services.AddAuthorization(options =>
+ {
+ options.AddPolicy("ApiScope", policy =>
+ {
+ policy.RequireAuthenticatedUser();
+ policy.RequireClaim("scope", "orders");
+ });
+ });
+ return services;
+ }
+}
diff --git a/src/Services/Ordering/Ordering.API/Startup.cs b/src/Services/Ordering/Ordering.API/Startup.cs
deleted file mode 100644
index 8c8ecb117..000000000
--- a/src/Services/Ordering/Ordering.API/Startup.cs
+++ /dev/null
@@ -1,396 +0,0 @@
-namespace Microsoft.eShopOnContainers.Services.Ordering.API;
-
-public class Startup
-{
- public Startup(IConfiguration configuration)
- {
- Configuration = configuration;
- }
-
- public IConfiguration Configuration { get; }
-
- public virtual IServiceProvider ConfigureServices(IServiceCollection services)
- {
- services
- .AddGrpc(options =>
- {
- options.EnableDetailedErrors = true;
- })
- .Services
- .AddApplicationInsights(Configuration)
- .AddCustomMvc()
- .AddHealthChecks(Configuration)
- .AddCustomDbContext(Configuration)
- .AddCustomSwagger(Configuration)
- .AddCustomAuthentication(Configuration)
- .AddCustomAuthorization(Configuration)
- .AddCustomIntegrations(Configuration)
- .AddCustomConfiguration(Configuration)
- .AddEventBus(Configuration);
- //configure autofac
-
- var container = new ContainerBuilder();
- container.Populate(services);
-
- container.RegisterModule(new MediatorModule());
- container.RegisterModule(new ApplicationModule(Configuration["ConnectionString"]));
-
- return new AutofacServiceProvider(container.Build());
- }
-
-
- 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))
- {
- loggerFactory.CreateLogger().LogDebug("Using PATH BASE '{pathBase}'", pathBase);
- app.UsePathBase(pathBase);
- }
-
- app.UseSwagger()
- .UseSwaggerUI(c =>
- {
- c.SwaggerEndpoint($"{ (!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty) }/swagger/v1/swagger.json", "Ordering.API V1");
- c.OAuthClientId("orderingswaggerui");
- c.OAuthAppName("Ordering Swagger UI");
- });
-
- app.UseRouting();
- app.UseCors("CorsPolicy");
- ConfigureAuth(app);
-
- app.UseEndpoints(endpoints =>
- {
- endpoints.MapGrpcService();
- 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 ConfigureEventBus(IApplicationBuilder app)
- {
- var eventBus = app.ApplicationServices.GetRequiredService();
-
- eventBus.Subscribe>();
- eventBus.Subscribe>();
- eventBus.Subscribe>();
- eventBus.Subscribe>();
- eventBus.Subscribe>();
- eventBus.Subscribe>();
- }
-
- protected virtual void ConfigureAuth(IApplicationBuilder app)
- {
- app.UseAuthentication();
- app.UseAuthorization();
- }
-}
-
-static class CustomExtensionsMethods
-{
- public static IServiceCollection AddApplicationInsights(this IServiceCollection services, IConfiguration configuration)
- {
- services.AddApplicationInsightsTelemetry(configuration);
- services.AddApplicationInsightsKubernetesEnricher();
-
- return services;
- }
-
- public static IServiceCollection AddCustomMvc(this IServiceCollection services)
- {
- // Add framework services.
- services.AddControllers(options =>
- {
- options.Filters.Add(typeof(HttpGlobalExceptionFilter));
- })
- // Added for functional tests
- .AddApplicationPart(typeof(OrdersController).Assembly)
- .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 AddHealthChecks(this IServiceCollection services, IConfiguration configuration)
- {
- var hcBuilder = services.AddHealthChecks();
-
- hcBuilder.AddCheck("self", () => HealthCheckResult.Healthy());
-
- hcBuilder
- .AddSqlServer(
- configuration["ConnectionString"],
- name: "OrderingDB-check",
- tags: new string[] { "orderingdb" });
-
- if (configuration.GetValue("AzureServiceBusEnabled"))
- {
- hcBuilder
- .AddAzureServiceBusTopic(
- configuration["EventBusConnection"],
- topicName: "eshop_event_bus",
- name: "ordering-servicebus-check",
- tags: new string[] { "servicebus" });
- }
- else
- {
- hcBuilder
- .AddRabbitMQ(
- $"amqp://{configuration["EventBusConnection"]}",
- name: "ordering-rabbitmqbus-check",
- tags: new string[] { "rabbitmqbus" });
- }
-
- return services;
- }
-
- public static IServiceCollection AddCustomDbContext(this IServiceCollection services, IConfiguration configuration)
- {
- services.AddDbContext(options =>
- {
- options.UseSqlServer(configuration["ConnectionString"],
- sqlServerOptionsAction: sqlOptions =>
- {
- sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name);
- sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
- });
- },
- ServiceLifetime.Scoped //Showing explicitly that the DbContext is shared across the HTTP request scope (graph of objects started in the HTTP request)
- );
-
- services.AddDbContext(options =>
- {
- options.UseSqlServer(configuration["ConnectionString"],
- sqlServerOptionsAction: sqlOptions =>
- {
- sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name);
- //Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency
- sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
- });
- });
-
- return services;
- }
-
- public static IServiceCollection AddCustomSwagger(this IServiceCollection services, IConfiguration configuration)
- {
- services.AddSwaggerGen(options =>
- {
- options.SwaggerDoc("v1", new OpenApiInfo
- {
- Title = "eShopOnContainers - Ordering HTTP API",
- Version = "v1",
- Description = "The Ordering Service HTTP API"
- });
- options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
- {
- Type = SecuritySchemeType.OAuth2,
- Flows = new OpenApiOAuthFlows()
- {
- Implicit = new OpenApiOAuthFlow()
- {
- AuthorizationUrl = new Uri($"{configuration.GetValue("IdentityUrlExternal")}/connect/authorize"),
- TokenUrl = new Uri($"{configuration.GetValue("IdentityUrlExternal")}/connect/token"),
- Scopes = new Dictionary()
- {
- { "orders", "Ordering API" }
- }
- }
- }
- });
-
- options.OperationFilter();
- });
-
- return services;
- }
-
- public static IServiceCollection AddCustomIntegrations(this IServiceCollection services, IConfiguration configuration)
- {
- services.AddSingleton();
- services.AddTransient();
- services.AddTransient>(
- sp => (DbConnection c) => new IntegrationEventLogService(c));
-
- services.AddTransient();
-
- if (configuration.GetValue("AzureServiceBusEnabled"))
- {
- services.AddSingleton(sp =>
- {
- var serviceBusConnectionString = configuration["EventBusConnection"];
-
- var subscriptionClientName = configuration["SubscriptionClientName"];
-
- return new DefaultServiceBusPersisterConnection(serviceBusConnectionString);
- });
- }
- else
- {
- services.AddSingleton(sp =>
- {
- 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 AddCustomConfiguration(this IServiceCollection services, IConfiguration configuration)
- {
- services.AddOptions();
- 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 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();
-
- return services;
- }
-
- public static IServiceCollection AddCustomAuthentication(this IServiceCollection services, IConfiguration configuration)
- {
- // prevent from mapping "sub" claim to nameidentifier.
- JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub");
-
- var identityUrl = configuration.GetValue("IdentityUrl");
-
- services.AddAuthentication("Bearer").AddJwtBearer(options =>
- {
- options.Authority = identityUrl;
- options.RequireHttpsMetadata = false;
- options.Audience = "orders";
- options.TokenValidationParameters.ValidateAudience = false;
- });
-
- return services;
- }
- public static IServiceCollection AddCustomAuthorization(this IServiceCollection services, IConfiguration configuration)
- {
- services.AddAuthorization(options =>
- {
- options.AddPolicy("ApiScope", policy =>
- {
- policy.RequireAuthenticatedUser();
- policy.RequireClaim("scope", "orders");
- });
- });
- return services;
- }
-}
\ No newline at end of file
diff --git a/src/Services/Ordering/Ordering.FunctionalTests/OrderingScenarioBase.cs b/src/Services/Ordering/Ordering.FunctionalTests/OrderingScenarioBase.cs
index d03d54380..a0b31568b 100644
--- a/src/Services/Ordering/Ordering.FunctionalTests/OrderingScenarioBase.cs
+++ b/src/Services/Ordering/Ordering.FunctionalTests/OrderingScenarioBase.cs
@@ -13,7 +13,7 @@ public class OrderingScenarioBase
{
cb.AddJsonFile("appsettings.json", optional: false)
.AddEnvironmentVariables();
- }).UseStartup();
+ });
var testServer = new TestServer(hostBuilder);
diff --git a/src/Services/Ordering/Ordering.FunctionalTests/OrderingTestStartup.cs b/src/Services/Ordering/Ordering.FunctionalTests/OrderingTestStartup.cs
deleted file mode 100644
index 7367042de..000000000
--- a/src/Services/Ordering/Ordering.FunctionalTests/OrderingTestStartup.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-namespace Ordering.FunctionalTests;
-
-public class OrderingTestsStartup : Startup
-{
- public OrderingTestsStartup(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(Configuration);
- return base.ConfigureServices(services);
- }
- protected override void ConfigureAuth(IApplicationBuilder app)
- {
- if (Configuration["isTest"] == bool.TrueString.ToLowerInvariant())
- {
- app.UseMiddleware();
- }
- else
- {
- base.ConfigureAuth(app);
- }
- }
-}
diff --git a/src/Tests/Services/Application.FunctionalTests/Services/Basket/BasketScenariosBase.cs b/src/Tests/Services/Application.FunctionalTests/Services/Basket/BasketScenariosBase.cs
index 71487d7b4..0cb668b8a 100644
--- a/src/Tests/Services/Application.FunctionalTests/Services/Basket/BasketScenariosBase.cs
+++ b/src/Tests/Services/Application.FunctionalTests/Services/Basket/BasketScenariosBase.cs
@@ -16,7 +16,7 @@ public class BasketScenariosBase
{
cb.AddJsonFile("Services/Basket/appsettings.json", optional: false)
.AddEnvironmentVariables();
- }).UseStartup();
+ });
return new TestServer(hostBuilder);
}
diff --git a/src/Tests/Services/Application.FunctionalTests/Services/Basket/BasketTestsStartup.cs b/src/Tests/Services/Application.FunctionalTests/Services/Basket/BasketTestsStartup.cs
deleted file mode 100644
index 23523a9f4..000000000
--- a/src/Tests/Services/Application.FunctionalTests/Services/Basket/BasketTestsStartup.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-namespace FunctionalTests.Services.Basket;
-using Microsoft.eShopOnContainers.Services.Basket.API;
-class BasketTestsStartup : Startup
-{
- public BasketTestsStartup(IConfiguration env) : base(env)
- {
- }
-
- protected override void ConfigureAuth(IApplicationBuilder app)
- {
- if (Configuration["isTest"] == bool.TrueString.ToLowerInvariant())
- {
- app.UseMiddleware();
- app.UseAuthorization();
- }
- else
- {
- base.ConfigureAuth(app);
- }
- }
-}
diff --git a/src/Tests/Services/Application.FunctionalTests/Services/Catalog/CatalogScenariosBase.cs b/src/Tests/Services/Application.FunctionalTests/Services/Catalog/CatalogScenariosBase.cs
index ec762a947..0fcf32e3c 100644
--- a/src/Tests/Services/Application.FunctionalTests/Services/Catalog/CatalogScenariosBase.cs
+++ b/src/Tests/Services/Application.FunctionalTests/Services/Catalog/CatalogScenariosBase.cs
@@ -14,7 +14,7 @@ public class CatalogScenariosBase
{
cb.AddJsonFile("Services/Catalog/appsettings.json", optional: false)
.AddEnvironmentVariables();
- }).UseStartup();
+ });
var testServer = new TestServer(hostBuilder);
diff --git a/src/Tests/Services/Application.FunctionalTests/Services/Ordering/OrderingScenariosBase.cs b/src/Tests/Services/Application.FunctionalTests/Services/Ordering/OrderingScenariosBase.cs
index fce6bca18..3d419eb83 100644
--- a/src/Tests/Services/Application.FunctionalTests/Services/Ordering/OrderingScenariosBase.cs
+++ b/src/Tests/Services/Application.FunctionalTests/Services/Ordering/OrderingScenariosBase.cs
@@ -13,7 +13,7 @@ public class OrderingScenariosBase
{
cb.AddJsonFile("Services/Ordering/appsettings.json", optional: false)
.AddEnvironmentVariables();
- }).UseStartup();
+ });
var testServer = new TestServer(hostBuilder);
diff --git a/src/Tests/Services/Application.FunctionalTests/Services/Ordering/OrderingTestsStartup.cs b/src/Tests/Services/Application.FunctionalTests/Services/Ordering/OrderingTestsStartup.cs
deleted file mode 100644
index f6f3d8e51..000000000
--- a/src/Tests/Services/Application.FunctionalTests/Services/Ordering/OrderingTestsStartup.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-namespace FunctionalTests.Services.Ordering;
-using Microsoft.eShopOnContainers.Services.Ordering.API;
-
-public class OrderingTestsStartup : Startup
-{
- public OrderingTestsStartup(IConfiguration env) : base(env)
- {
- }
-
- protected override void ConfigureAuth(IApplicationBuilder app)
- {
- if (Configuration["isTest"] == bool.TrueString.ToLowerInvariant())
- {
- app.UseMiddleware();
- app.UseAuthorization();
- }
- else
- {
- base.ConfigureAuth(app);
- }
- }
-}