From 909f08675b8d63195f59a8763e0469f46a7ca42f Mon Sep 17 00:00:00 2001 From: David Fowler Date: Thu, 4 May 2023 22:45:35 -0700 Subject: [PATCH] Make the ordering API use the commmon services --- .../Controllers/HomeController.cs | 10 - .../Ordering.API/CustomExtensionsMethods.cs | 79 ++++ .../Ordering/Ordering.API/GlobalUsings.cs | 58 +-- .../InternalServerErrorObjectResult.cs | 10 - ...orizationHeaderParameterOperationFilter.cs | 27 -- .../Filters/AuthorizeCheckOperationFilter.cs | 29 -- .../Filters/HttpGlobalExceptionFilter.cs | 60 --- .../Ordering/Ordering.API/Ordering.API.csproj | 6 +- src/Services/Ordering/Ordering.API/Program.cs | 387 ++---------------- .../Properties/launchSettings.json | 20 +- .../Ordering/Ordering.API/appsettings.json | 53 ++- src/Services/Ordering/Ordering.API/web.config | 14 - .../OrderingScenarioBase.cs | 12 - .../OrderingScenarios.cs | 1 + .../Ordering.FunctionalTests/appsettings.json | 5 +- 15 files changed, 168 insertions(+), 603 deletions(-) delete mode 100644 src/Services/Ordering/Ordering.API/Controllers/HomeController.cs create mode 100644 src/Services/Ordering/Ordering.API/CustomExtensionsMethods.cs delete mode 100644 src/Services/Ordering/Ordering.API/Infrastructure/ActionResults/InternalServerErrorObjectResult.cs delete mode 100644 src/Services/Ordering/Ordering.API/Infrastructure/Auth/AuthorizationHeaderParameterOperationFilter.cs delete mode 100644 src/Services/Ordering/Ordering.API/Infrastructure/Filters/AuthorizeCheckOperationFilter.cs delete mode 100644 src/Services/Ordering/Ordering.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs delete mode 100644 src/Services/Ordering/Ordering.API/web.config diff --git a/src/Services/Ordering/Ordering.API/Controllers/HomeController.cs b/src/Services/Ordering/Ordering.API/Controllers/HomeController.cs deleted file mode 100644 index 601b7ab6d..000000000 --- a/src/Services/Ordering/Ordering.API/Controllers/HomeController.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers; - -public class HomeController : Controller -{ - // GET: // - public IActionResult Index() - { - return new RedirectResult("~/swagger"); - } -} diff --git a/src/Services/Ordering/Ordering.API/CustomExtensionsMethods.cs b/src/Services/Ordering/Ordering.API/CustomExtensionsMethods.cs new file mode 100644 index 000000000..d0849b832 --- /dev/null +++ b/src/Services/Ordering/Ordering.API/CustomExtensionsMethods.cs @@ -0,0 +1,79 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; + +static class CustomExtensionsMethods +{ + public static IServiceCollection AddHealthChecks(this IServiceCollection services, IConfiguration configuration) + { + var hcBuilder = services.AddHealthChecks(); + + hcBuilder + .AddSqlServer(_ => + configuration.GetRequiredConnectionString("OrderingDB"), + name: "OrderingDB-check", + tags: new string[] { "live", "ready" }); + + return services; + } + + public static IServiceCollection AddDbContexts(this IServiceCollection services, IConfiguration configuration) + { + static void ConfigureSqlOptions(SqlServerDbContextOptionsBuilder sqlOptions) + { + sqlOptions.MigrationsAssembly(typeof(Program).Assembly.FullName); + + // 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.GetRequiredConnectionString("OrderingDB"), ConfigureSqlOptions); + }); + + services.AddDbContext(options => + { + options.UseSqlServer(configuration.GetRequiredConnectionString("OrderingDB"), ConfigureSqlOptions); + }); + + return services; + } + + public static IServiceCollection AddCustomIntegrations(this IServiceCollection services, IConfiguration configuration) + { + services.AddTransient(); + services.AddTransient>( + sp => (DbConnection c) => new IntegrationEventLogService(c)); + + services.AddTransient(); + + return services; + } + + public static IServiceCollection AddCustomConfiguration(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; + } + + private static string GetRequiredConnectionString(this IConfiguration configuration, string name) => + configuration.GetConnectionString(name) ?? throw new InvalidOperationException($"Configuration missing value for: {(configuration is IConfigurationSection s ? s.Path + ":ConnectionStrings:" + name : "ConnectionStrings:" + name)}"); +} diff --git a/src/Services/Ordering/Ordering.API/GlobalUsings.cs b/src/Services/Ordering/Ordering.API/GlobalUsings.cs index e52b97941..be14d664d 100644 --- a/src/Services/Ordering/Ordering.API/GlobalUsings.cs +++ b/src/Services/Ordering/Ordering.API/GlobalUsings.cs @@ -1,77 +1,57 @@ -global using ApiModels = Microsoft.eShopOnContainers.Services.Ordering.API.Application.Models; -global using AppCommand = Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; -global using Azure.Core; +global using System; +global using System.Collections.Generic; +global using System.Data.Common; +global using System.Data.SqlClient; +global using System.IO; +global using System.Linq; +global using System.Net; +global using System.Runtime.Serialization; +global using System.Threading; +global using System.Threading.Tasks; global using Azure.Identity; global using Dapper; global using FluentValidation; global using Google.Protobuf.Collections; global using Grpc.Core; -global using HealthChecks.UI.Client; global using MediatR; global using Microsoft.AspNetCore.Authorization; global using Microsoft.AspNetCore.Builder; -global using Microsoft.AspNetCore.Diagnostics.HealthChecks; global using Microsoft.AspNetCore.Hosting; global using Microsoft.AspNetCore.Http; -global using Microsoft.AspNetCore.Mvc.Authorization; -global using Microsoft.AspNetCore.Mvc.Filters; global using Microsoft.AspNetCore.Mvc; -global using Microsoft.AspNetCore.Server.Kestrel.Core; -global using Microsoft.AspNetCore; -global using Microsoft.EntityFrameworkCore.Design; global using Microsoft.EntityFrameworkCore; +global using Microsoft.EntityFrameworkCore.Design; global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Extensions; -global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus; -global using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ; -global using Microsoft.eShopOnContainers.BuildingBlocks.EventBusServiceBus; -global using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services; global using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF; +global using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services; +global using Microsoft.eShopOnContainers.Services.Ordering.API; global using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Behaviors; global using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; -global using Microsoft.eShopOnContainers.Services.Ordering.API.Application.DomainEventHandlers; global using Microsoft.eShopOnContainers.Services.Ordering.API.Application.IntegrationEvents; -global using Microsoft.eShopOnContainers.Services.Ordering.API.Application.IntegrationEvents.Events; global using Microsoft.eShopOnContainers.Services.Ordering.API.Application.IntegrationEvents.EventHandling; +global using Microsoft.eShopOnContainers.Services.Ordering.API.Application.IntegrationEvents.Events; global using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Models; global using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Queries; global using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Validations; -global using Microsoft.eShopOnContainers.Services.Ordering.API.Controllers; global using Microsoft.eShopOnContainers.Services.Ordering.API.Extensions; -global using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.ActionResults; -global using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Filters; -global using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services; global using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure; -global using Microsoft.eShopOnContainers.Services.Ordering.API; +global using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services; global using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate; global using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; global using Microsoft.eShopOnContainers.Services.Ordering.Domain.Events; global using Microsoft.eShopOnContainers.Services.Ordering.Domain.Exceptions; global using Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork; +global using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure; global using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency; global using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Repositories; -global using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure; global using Microsoft.Extensions.Configuration; global using Microsoft.Extensions.DependencyInjection; -global using Microsoft.Extensions.Diagnostics.HealthChecks; -global using Microsoft.Extensions.Hosting; global using Microsoft.Extensions.Logging; global using Microsoft.Extensions.Options; -global using Microsoft.OpenApi.Models; -global using Polly.Retry; global using Polly; -global using RabbitMQ.Client; +global using Polly.Retry; global using Swashbuckle.AspNetCore.SwaggerGen; -global using System.Collections.Generic; -global using System.Data.Common; -global using System.Data.SqlClient; -global using System.IdentityModel.Tokens.Jwt; -global using System.IO; -global using System.Linq; -global using System.Net; -global using System.Reflection; -global using System.Runtime.Serialization; -global using System.Threading.Tasks; -global using System.Threading; -global using System; +global using AppCommand = Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; +global using ApiModels = Microsoft.eShopOnContainers.Services.Ordering.API.Application.Models; diff --git a/src/Services/Ordering/Ordering.API/Infrastructure/ActionResults/InternalServerErrorObjectResult.cs b/src/Services/Ordering/Ordering.API/Infrastructure/ActionResults/InternalServerErrorObjectResult.cs deleted file mode 100644 index d886bf371..000000000 --- a/src/Services/Ordering/Ordering.API/Infrastructure/ActionResults/InternalServerErrorObjectResult.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.ActionResults; - -public class InternalServerErrorObjectResult : ObjectResult -{ - public InternalServerErrorObjectResult(object error) - : base(error) - { - StatusCode = StatusCodes.Status500InternalServerError; - } -} diff --git a/src/Services/Ordering/Ordering.API/Infrastructure/Auth/AuthorizationHeaderParameterOperationFilter.cs b/src/Services/Ordering/Ordering.API/Infrastructure/Auth/AuthorizationHeaderParameterOperationFilter.cs deleted file mode 100644 index dbf8cf97c..000000000 --- a/src/Services/Ordering/Ordering.API/Infrastructure/Auth/AuthorizationHeaderParameterOperationFilter.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Auth; - -public class AuthorizationHeaderParameterOperationFilter : IOperationFilter -{ - public void Apply(OpenApiOperation operation, OperationFilterContext context) - { - var filterPipeline = context.ApiDescription.ActionDescriptor.FilterDescriptors; - var isAuthorized = filterPipeline.Select(filterInfo => filterInfo.Filter).Any(filter => filter is AuthorizeFilter); - var allowAnonymous = filterPipeline.Select(filterInfo => filterInfo.Filter).Any(filter => filter is IAllowAnonymousFilter); - - if (isAuthorized && !allowAnonymous) - { - if (operation.Parameters == null) - operation.Parameters = new List(); - - - operation.Parameters.Add(new OpenApiParameter - { - Name = "Authorization", - In = ParameterLocation.Header, - Description = "access token", - Required = true - }); - } - } - -} diff --git a/src/Services/Ordering/Ordering.API/Infrastructure/Filters/AuthorizeCheckOperationFilter.cs b/src/Services/Ordering/Ordering.API/Infrastructure/Filters/AuthorizeCheckOperationFilter.cs deleted file mode 100644 index b4689bf9a..000000000 --- a/src/Services/Ordering/Ordering.API/Infrastructure/Filters/AuthorizeCheckOperationFilter.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Filters; - -public class AuthorizeCheckOperationFilter : IOperationFilter -{ - public void Apply(OpenApiOperation operation, OperationFilterContext context) - { - // Check for authorize attribute - var hasAuthorize = context.MethodInfo.DeclaringType.GetCustomAttributes(true).OfType().Any() || - context.MethodInfo.GetCustomAttributes(true).OfType().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 - { - new() - { - [ oAuthScheme ] = new [] { "orderingapi" } - } - }; - } -} diff --git a/src/Services/Ordering/Ordering.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs b/src/Services/Ordering/Ordering.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs deleted file mode 100644 index eef48c502..000000000 --- a/src/Services/Ordering/Ordering.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs +++ /dev/null @@ -1,60 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Filters; - -public class HttpGlobalExceptionFilter : IExceptionFilter -{ - private readonly IWebHostEnvironment env; - private readonly ILogger logger; - - public HttpGlobalExceptionFilter(IWebHostEnvironment env, ILogger logger) - { - this.env = env; - this.logger = logger; - } - - public void OnException(ExceptionContext context) - { - logger.LogError(new EventId(context.Exception.HResult), - context.Exception, - context.Exception.Message); - - if (context.Exception.GetType() == typeof(OrderingDomainException)) - { - var problemDetails = new ValidationProblemDetails() - { - Instance = context.HttpContext.Request.Path, - Status = StatusCodes.Status400BadRequest, - Detail = "Please refer to the errors property for additional details." - }; - - problemDetails.Errors.Add("DomainValidations", new string[] { context.Exception.Message.ToString() }); - - context.Result = new BadRequestObjectResult(problemDetails); - context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest; - } - else - { - var json = new JsonErrorResponse - { - Messages = new[] { "An error occur.Try it again." } - }; - - if (env.IsDevelopment()) - { - json.DeveloperMessage = context.Exception; - } - - // Result asigned to a result object but in destiny the response is empty. This is a known bug of .net core 1.1 - // It will be fixed in .net core 1.1.2. See https://github.com/aspnet/Mvc/issues/5594 for more information - context.Result = new InternalServerErrorObjectResult(json); - context.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError; - } - context.ExceptionHandled = true; - } - - private class JsonErrorResponse - { - public string[] Messages { get; set; } - - public object DeveloperMessage { get; set; } - } -} diff --git a/src/Services/Ordering/Ordering.API/Ordering.API.csproj b/src/Services/Ordering/Ordering.API/Ordering.API.csproj index a3abadcab..dc8bf637b 100644 --- a/src/Services/Ordering/Ordering.API/Ordering.API.csproj +++ b/src/Services/Ordering/Ordering.API/Ordering.API.csproj @@ -11,9 +11,6 @@ - - PreserveNewest - PreserveNewest @@ -34,7 +31,8 @@ - + + diff --git a/src/Services/Ordering/Ordering.API/Program.cs b/src/Services/Ordering/Ordering.API/Program.cs index 71b202f6a..862914e08 100644 --- a/src/Services/Ordering/Ordering.API/Program.cs +++ b/src/Services/Ordering/Ordering.API/Program.cs @@ -1,49 +1,23 @@ -var builder = WebApplication.CreateBuilder(args); +using Services.Common; -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); -} +var builder = WebApplication.CreateBuilder(args); -builder.WebHost.ConfigureKestrel(options => -{ - var httpPort = builder.Configuration.GetValue("PORT", 80); - options.Listen(IPAddress.Any, httpPort, listenOptions => - { - listenOptions.Protocols = HttpProtocols.Http1AndHttp2; - }); +builder.AddServiceDefaults(); - var grpcPort = builder.Configuration.GetValue("GRPC_PORT", 5001); - options.Listen(IPAddress.Any, grpcPort, listenOptions => - { - listenOptions.Protocols = HttpProtocols.Http2; - }); -}); +builder.Services.AddGrpc(); +builder.Services.AddControllers(); -builder.Services.AddGrpc(options => options.EnableDetailedErrors = true); -builder.Services.AddApplicationInsightsTelemetry(builder.Configuration); -builder.Services.AddApplicationInsightsKubernetesEnricher(); -builder.Services - .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.Services.AddHealthChecks(builder.Configuration); +builder.Services.AddDbContexts(builder.Configuration); +builder.Services.AddCustomIntegrations(builder.Configuration); +builder.Services.AddCustomConfiguration(builder.Configuration); var services = builder.Services; services.AddMediatR(cfg => { cfg.RegisterServicesFromAssemblyContaining(typeof(Program)); - + cfg.AddOpenBehavior(typeof(LoggingBehavior<,>)); cfg.AddOpenBehavior(typeof(ValidatorBehavior<,>)); cfg.AddOpenBehavior(typeof(TransactionBehavior<,>)); @@ -55,9 +29,7 @@ services.AddSingleton, CreateOrderCommandValidato services.AddSingleton>, IdentifiedCommandValidator>(); services.AddSingleton, ShipOrderCommandValidator>(); -var queriesConnectionString = builder.Configuration["ConnectionString"]; - -services.AddScoped(sp => new OrderQueries(queriesConnectionString)); +services.AddScoped(sp => new OrderQueries(builder.Configuration.GetConnectionString("OrderingDB"))); services.AddScoped(); services.AddScoped(); services.AddScoped(); @@ -71,55 +43,27 @@ services.AddSingleton, UserCheckoutAcceptedIntegrationEventHandler>(); var app = builder.Build(); -if (!app.Environment.IsDevelopment()) -{ - app.UseExceptionHandler("/Home/Error"); -} -var pathBase = app.Configuration["PATH_BASE"]; -if (!string.IsNullOrEmpty(pathBase)) +if (!await app.CheckHealthAsync()) { - app.UsePathBase(pathBase); + return; } -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.UseServiceDefaults(); + +app.MapGet("/", () => Results.Redirect("/swagger")); -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); + +var eventBus = app.Services.GetRequiredService(); + +eventBus.Subscribe>(); +eventBus.Subscribe>(); +eventBus.Subscribe>(); +eventBus.Subscribe>(); +eventBus.Subscribe>(); +eventBus.Subscribe>(); using (var scope = app.Services.CreateScope()) { @@ -135,284 +79,3 @@ using (var scope = app.Services.CreateScope()) } await app.RunAsync(); - -void ConfigureEventBus(IApplicationBuilder app) -{ - var eventBus = app.ApplicationServices.GetRequiredService(); - - eventBus.Subscribe>(); - eventBus.Subscribe>(); - eventBus.Subscribe>(); - eventBus.Subscribe>(); - eventBus.Subscribe>(); - eventBus.Subscribe>(); -} - -static class CustomExtensionsMethods -{ - 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) - { - return services.AddSwaggerGen(options => - { - options.SwaggerDoc("v1", new OpenApiInfo - { - Title = "eShopOnContainers - Ordering HTTP API", - Version = "v1", - Description = "The Ordering Service HTTP API" - }); - - var identityUrl = configuration.GetSection("Identity")["ExternalUrl"]; - - options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme - { - Type = SecuritySchemeType.OAuth2, - Flows = new OpenApiOAuthFlows() - { - Implicit = new OpenApiOAuthFlow() - { - AuthorizationUrl = new Uri($"{identityUrl}/connect/authorize"), - TokenUrl = new Uri($"{identityUrl}/connect/token"), - Scopes = new Dictionary() - { - { "orders", "Ordering API" } - } - } - } - }); - - options.OperationFilter(); - }); - } - - 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.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 eventBusSubscriptionsManager = sp.GetRequiredService(); - string subscriptionName = configuration["SubscriptionClientName"]; - - return new EventBusServiceBus(serviceBusPersisterConnection, logger, eventBusSubscriptionsManager, sp, subscriptionName); - }); - } - else - { - services.AddSingleton(sp => - { - var subscriptionClientName = configuration["SubscriptionClientName"]; - var rabbitMQPersistentConnection = sp.GetRequiredService(); - var logger = sp.GetRequiredService>(); - var eventBusSubscriptionsManager = sp.GetRequiredService(); - - if (!int.TryParse(configuration["EventBusRetryCount"], out var retryCount)) - { - retryCount = 5; - } - - return new EventBusRabbitMQ(rabbitMQPersistentConnection, logger, sp, eventBusSubscriptionsManager, 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/Properties/launchSettings.json b/src/Services/Ordering/Ordering.API/Properties/launchSettings.json index 9d9a76490..f05bc8e5c 100644 --- a/src/Services/Ordering/Ordering.API/Properties/launchSettings.json +++ b/src/Services/Ordering/Ordering.API/Properties/launchSettings.json @@ -1,25 +1,9 @@ { - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:55102/", - "sslPort": 0 - } - }, "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "/swagger", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Microsoft.eShopOnContainers.Services.Ordering.API": { + "Ordering.API": { "commandName": "Project", "launchBrowser": true, - "launchUrl": "http://localhost:55102/", + "applicationUrl": "http://localhost:5228/", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/src/Services/Ordering/Ordering.API/appsettings.json b/src/Services/Ordering/Ordering.API/appsettings.json index d920b2d86..3f54fb3ce 100644 --- a/src/Services/Ordering/Ordering.API/appsettings.json +++ b/src/Services/Ordering/Ordering.API/appsettings.json @@ -1,24 +1,43 @@ { - "ConnectionString": "Server=tcp:127.0.0.1,5433;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word;TrustServerCertificate=true", + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "OpenApi": { + "Endpoint": { + "Name": "Ordering.API V1" + }, + "Document": { + "Description": "The Ordering Service HTTP API", + "Title": "eShopOnContainers - Ordering HTTP API", + "Version": "v1" + }, + "Auth": { + "ClientId": "orderingswaggerui", + "AppName": "Ordering Swagger UI" + } + }, + "ConnectionStrings": { + "EventBus": "localhost" + }, + "EventBus": { + "SubscriptionClientName": "Ordering", + "RetryCount": 5 + }, + "ApplicationInsights": { + "InstrumentationKey": "" + }, "Identity": { "Url": "http://localhost:5105", "ExternalUrl": "http://localhost:5105", - "Audience": "orders" + "Audience": "orders", + "Scopes": { + "orders": "Ordering API" + } }, "UseCustomizationData": false, - "AzureServiceBusEnabled": false, - "SubscriptionClientName": "Ordering", "GracePeriodTime": "1", - "CheckUpdateTime": "30000", - "ApplicationInsights": { - "InstrumentationKey": "" - }, - "EventBusRetryCount": 5, - "EventBusConnection": "localhost", - "UseVault": false, - "Vault": { - "Name": "eshop", - "ClientId": "your-client-id", - "ClientSecret": "your-client-secret" - } - } + "CheckUpdateTime": "30000" +} diff --git a/src/Services/Ordering/Ordering.API/web.config b/src/Services/Ordering/Ordering.API/web.config deleted file mode 100644 index 3d49211e5..000000000 --- a/src/Services/Ordering/Ordering.API/web.config +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - \ 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 2bf493a4d..2d25c78f1 100644 --- a/src/Services/Ordering/Ordering.FunctionalTests/OrderingScenarioBase.cs +++ b/src/Services/Ordering/Ordering.FunctionalTests/OrderingScenarioBase.cs @@ -9,18 +9,6 @@ public class OrderingScenarioBase { public TestServer CreateServer() { - Services.MigrateDbContext((context, services) => - { - var env = services.GetService(); - var settings = services.GetService>(); - var logger = services.GetService>(); - - new OrderingContextSeed() - .SeedAsync(context, env, settings, logger) - .Wait(); - }) - .MigrateDbContext((_, __) => { }); - return Server; } diff --git a/src/Services/Ordering/Ordering.FunctionalTests/OrderingScenarios.cs b/src/Services/Ordering/Ordering.FunctionalTests/OrderingScenarios.cs index 4ed21ba1b..85dd4ab3d 100644 --- a/src/Services/Ordering/Ordering.FunctionalTests/OrderingScenarios.cs +++ b/src/Services/Ordering/Ordering.FunctionalTests/OrderingScenarios.cs @@ -30,6 +30,7 @@ namespace Ordering.FunctionalTests var response = await server.CreateClient() .PutAsync(Put.CancelOrder, content); + var s = await response.Content.ReadAsStringAsync(); Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); } diff --git a/src/Services/Ordering/Ordering.FunctionalTests/appsettings.json b/src/Services/Ordering/Ordering.FunctionalTests/appsettings.json index fa0fae4af..69307ec18 100644 --- a/src/Services/Ordering/Ordering.FunctionalTests/appsettings.json +++ b/src/Services/Ordering/Ordering.FunctionalTests/appsettings.json @@ -1,6 +1,9 @@ { + "ConnectionStrings": { + "OrderingDb": "Server=tcp:127.0.0.1,5433;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word;Encrypt=False;TrustServerCertificate=true", + "EventBus": "localhost" + }, "CheckUpdateTime": "30000", - "ConnectionString": "Server=tcp:127.0.0.1,5433;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word;Encrypt=False;TrustServerCertificate=true", "EventBusConnection": "localhost", "ExternalCatalogBaseUrl": "http://localhost:5101", "GracePeriodTime": "1",