From fda92f6928feded848302cc40beb6e2441c64aa0 Mon Sep 17 00:00:00 2001 From: espent1004 Date: Sat, 8 Feb 2020 23:39:19 +0100 Subject: [PATCH] Adding a second microservice for TenantA --- docker-compose.override.yml | 10 +- docker-compose.yml | 6 + eShopOnContainers-ServicesAndWebApps.sln | 51 +++ .../TenantACustomisations.csproj | 1 + .../TenantAShippingInformation/Dockerfile | 59 +++ .../AutofacModules/ApplicationModule.cs | 28 ++ .../AutofacModules/MediatorModule.cs | 12 + .../Filters/AuthorizeCheckOperationFilter.cs | 33 ++ .../Filters/HttpGlobalExceptionFilter.cs | 69 +++ ...aitingValidationIntegrationEventHandler.cs | 34 ++ ...gedToAwaitingValidationIntegrationEvent.cs | 30 ++ .../TenantAShippingInformation/Program.cs | 87 ++++ .../Properties/launchSettings.json | 27 ++ .../TenantAShippingInformation/Startup.cs | 411 ++++++++++++++++++ .../TenantAShippingInformation.csproj | 53 +++ .../appsettings.Development.json | 9 + .../appsettings.json | 29 ++ 17 files changed, 948 insertions(+), 1 deletion(-) create mode 100644 src/Services/TenantCustomisations/TenantAShippingInformation/Dockerfile create mode 100644 src/Services/TenantCustomisations/TenantAShippingInformation/Infrastructure/AutofacModules/ApplicationModule.cs create mode 100644 src/Services/TenantCustomisations/TenantAShippingInformation/Infrastructure/AutofacModules/MediatorModule.cs create mode 100644 src/Services/TenantCustomisations/TenantAShippingInformation/Infrastructure/Filters/AuthorizeCheckOperationFilter.cs create mode 100644 src/Services/TenantCustomisations/TenantAShippingInformation/Infrastructure/Filters/HttpGlobalExceptionFilter.cs create mode 100644 src/Services/TenantCustomisations/TenantAShippingInformation/IntegrationEvents/EventHandling/OrderStatusChangedToAwaitingValidationIntegrationEventHandler.cs create mode 100644 src/Services/TenantCustomisations/TenantAShippingInformation/IntegrationEvents/Events/OrderStatusChangedToAwaitingValidationIntegrationEvent.cs create mode 100644 src/Services/TenantCustomisations/TenantAShippingInformation/Program.cs create mode 100644 src/Services/TenantCustomisations/TenantAShippingInformation/Properties/launchSettings.json create mode 100644 src/Services/TenantCustomisations/TenantAShippingInformation/Startup.cs create mode 100644 src/Services/TenantCustomisations/TenantAShippingInformation/TenantAShippingInformation.csproj create mode 100644 src/Services/TenantCustomisations/TenantAShippingInformation/appsettings.Development.json create mode 100644 src/Services/TenantCustomisations/TenantAShippingInformation/appsettings.json diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 37c19004a..1ee9cd03e 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -69,10 +69,18 @@ services: - EventBusPassword=${ESHOP_SERVICE_BUS_PASSWORD} - AzureServiceBusEnabled=False - ConnectionString=${ESHOP_AZURE_CATALOG_DB:-Server=sql.data;Database=Microsoft.eShopOnContainers.Services.TenantADb;User Id=sa;Password=Pass@word} - ports: - "5116:80" + tenantashippinginformation: + environment: + - EventBusConnection=${ESHOP_AZURE_SERVICE_BUS:-rabbitmq} + - EventBusUserName=${ESHOP_SERVICE_BUS_USERNAME} + - EventBusPassword=${ESHOP_SERVICE_BUS_PASSWORD} + - AzureServiceBusEnabled=False + ports: + - "5117:80" + basket.api: environment: - ASPNETCORE_ENVIRONMENT=Development diff --git a/docker-compose.yml b/docker-compose.yml index 4feb44ae0..45401646e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -50,6 +50,12 @@ services: dockerfile: src/Services/TenantCustomisations/TenantACustomisations/Dockerfile depends_on: - sql.data + + tenantashippinginformation: + image: ${REGISTRY:-eshop}/tenantashippinginformation:${PLATFORM:-linux}-${TAG:-latest} + build: + context: . + dockerfile: src/Services/TenantCustomisations/TenantAShippingInformation/Dockerfile catalog.api: image: ${REGISTRY:-eshop}/catalog.api:${PLATFORM:-linux}-${TAG:-latest} diff --git a/eShopOnContainers-ServicesAndWebApps.sln b/eShopOnContainers-ServicesAndWebApps.sln index 4a671c9d9..7202f9703 100644 --- a/eShopOnContainers-ServicesAndWebApps.sln +++ b/eShopOnContainers-ServicesAndWebApps.sln @@ -158,6 +158,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TenantCustomisations", "Ten EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TenantACustomisations", "src\Services\TenantCustomisations\TenantACustomisations\TenantACustomisations.csproj", "{76651DAE-FF27-44A4-AF84-34689E2FFDF2}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TenantAShippingInformation", "src\Services\TenantCustomisations\TenantAShippingInformation\TenantAShippingInformation.csproj", "{6228E4B8-4672-4253-9F9F-2F75BD40623B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Ad-Hoc|Any CPU = Ad-Hoc|Any CPU @@ -1900,6 +1902,54 @@ Global {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.Release|x64.Build.0 = Release|Any CPU {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.Release|x86.ActiveCfg = Release|Any CPU {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.Release|x86.Build.0 = Release|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.Ad-Hoc|x64.Build.0 = Debug|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.Ad-Hoc|x86.Build.0 = Debug|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.AppStore|ARM.ActiveCfg = Debug|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.AppStore|ARM.Build.0 = Debug|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.AppStore|iPhone.Build.0 = Debug|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.AppStore|x64.ActiveCfg = Debug|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.AppStore|x64.Build.0 = Debug|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.AppStore|x86.ActiveCfg = Debug|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.AppStore|x86.Build.0 = Debug|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.Debug|ARM.ActiveCfg = Debug|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.Debug|ARM.Build.0 = Debug|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.Debug|iPhone.Build.0 = Debug|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.Debug|x64.ActiveCfg = Debug|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.Debug|x64.Build.0 = Debug|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.Debug|x86.ActiveCfg = Debug|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.Debug|x86.Build.0 = Debug|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.Release|Any CPU.Build.0 = Release|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.Release|ARM.ActiveCfg = Release|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.Release|ARM.Build.0 = Release|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.Release|iPhone.ActiveCfg = Release|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.Release|iPhone.Build.0 = Release|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.Release|x64.ActiveCfg = Release|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.Release|x64.Build.0 = Release|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.Release|x86.ActiveCfg = Release|Any CPU + {6228E4B8-4672-4253-9F9F-2F75BD40623B}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1970,6 +2020,7 @@ Global {9C101827-119D-44EE-B0F0-94E7ABA8AE6A} = {71587505-2945-4286-BF0B-BA4C004B0BA7} {773A0C2A-CA6F-4D4A-860B-C518EFA6FACB} = {91CF7717-08AB-4E65-B10E-0B426F01E2E8} {76651DAE-FF27-44A4-AF84-34689E2FFDF2} = {773A0C2A-CA6F-4D4A-860B-C518EFA6FACB} + {6228E4B8-4672-4253-9F9F-2F75BD40623B} = {773A0C2A-CA6F-4D4A-860B-C518EFA6FACB} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {25728519-5F0F-4973-8A64-0A81EB4EA8D9} diff --git a/src/Services/TenantCustomisations/TenantACustomisations/TenantACustomisations.csproj b/src/Services/TenantCustomisations/TenantACustomisations/TenantACustomisations.csproj index 637a02603..855195623 100644 --- a/src/Services/TenantCustomisations/TenantACustomisations/TenantACustomisations.csproj +++ b/src/Services/TenantCustomisations/TenantACustomisations/TenantACustomisations.csproj @@ -44,6 +44,7 @@ + diff --git a/src/Services/TenantCustomisations/TenantAShippingInformation/Dockerfile b/src/Services/TenantCustomisations/TenantAShippingInformation/Dockerfile new file mode 100644 index 000000000..cb6d18737 --- /dev/null +++ b/src/Services/TenantCustomisations/TenantAShippingInformation/Dockerfile @@ -0,0 +1,59 @@ +FROM mcr.microsoft.com/dotnet/core/aspnet:2.2-stretch-slim AS base +WORKDIR /app +EXPOSE 80 + +FROM mcr.microsoft.com/dotnet/core/sdk:2.2-stretch AS build +WORKDIR /src + +# Keep the project list and command dotnet restore identical in all Dockfiles to maximize image cache utilization +COPY eShopOnContainers-ServicesAndWebApps.sln . +COPY docker-compose.dcproj /src/ +COPY src/ApiGateways/ApiGw-Base/OcelotApiGw.csproj src/ApiGateways/ApiGw-Base/ +COPY src/ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj src/ApiGateways/Mobile.Bff.Shopping/aggregator/ +COPY src/ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj src/ApiGateways/Web.Bff.Shopping/aggregator/ +COPY src/BuildingBlocks/Devspaces.Support/Devspaces.Support.csproj src/BuildingBlocks/Devspaces.Support/ +COPY src/BuildingBlocks/EventBus/EventBus/EventBus.csproj src/BuildingBlocks/EventBus/EventBus/ +COPY src/BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj src/BuildingBlocks/EventBus/EventBus.Tests/ +COPY src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj src/BuildingBlocks/EventBus/EventBusRabbitMQ/ +COPY src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.csproj src/BuildingBlocks/EventBus/EventBusServiceBus/ +COPY src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEF.csproj src/BuildingBlocks/EventBus/IntegrationEventLogEF/ +COPY src/BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHost.Customization.csproj src/BuildingBlocks/WebHostCustomization/WebHost.Customization/ +COPY src/Services/Basket/Basket.API/Basket.API.csproj src/Services/Basket/Basket.API/ +COPY src/Services/Basket/Basket.FunctionalTests/Basket.FunctionalTests.csproj src/Services/Basket/Basket.FunctionalTests/ +COPY src/Services/Basket/Basket.UnitTests/Basket.UnitTests.csproj src/Services/Basket/Basket.UnitTests/ +COPY src/Services/Catalog/Catalog.API/Catalog.API.csproj src/Services/Catalog/Catalog.API/ +COPY src/Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj src/Services/Catalog/Catalog.FunctionalTests/ +COPY src/Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj src/Services/Catalog/Catalog.UnitTests/ +COPY src/Services/Identity/Identity.API/Identity.API.csproj src/Services/Identity/Identity.API/ +COPY src/Services/Location/Locations.API/Locations.API.csproj src/Services/Location/Locations.API/ +COPY src/Services/Location/Locations.FunctionalTests/Locations.FunctionalTests.csproj src/Services/Location/Locations.FunctionalTests/ +COPY src/Services/Marketing/Marketing.API/Marketing.API.csproj src/Services/Marketing/Marketing.API/ +COPY src/Services/Marketing/Marketing.FunctionalTests/Marketing.FunctionalTests.csproj src/Services/Marketing/Marketing.FunctionalTests/ +COPY src/Services/Ordering/Ordering.API/Ordering.API.csproj src/Services/Ordering/Ordering.API/ +COPY src/Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj src/Services/Ordering/Ordering.BackgroundTasks/ +COPY src/Services/Ordering/Ordering.Domain/Ordering.Domain.csproj src/Services/Ordering/Ordering.Domain/ +COPY src/Services/Ordering/Ordering.FunctionalTests/Ordering.FunctionalTests.csproj src/Services/Ordering/Ordering.FunctionalTests/ +COPY src/Services/Ordering/Ordering.Infrastructure/Ordering.Infrastructure.csproj src/Services/Ordering/Ordering.Infrastructure/ +COPY src/Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj src/Services/Ordering/Ordering.SignalrHub/ +COPY src/Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj src/Services/Ordering/Ordering.UnitTests/ +COPY src/Services/Payment/Payment.API/Payment.API.csproj src/Services/Payment/Payment.API/ +COPY src/Services/Webhooks/Webhooks.API/Webhooks.API.csproj src/Services/Webhooks/Webhooks.API/ +COPY src/Web/WebhookClient/WebhookClient.csproj src/Web/WebhookClient/ +COPY src/Web/WebMVC/WebMVC.csproj src/Web/WebMVC/ +COPY src/Web/WebSPA/WebSPA.csproj src/Web/WebSPA/ +COPY src/Web/WebStatus/WebStatus.csproj src/Web/WebStatus/ +COPY test/ServicesTests/Application.FunctionalTests/Application.FunctionalTests.csproj test/ServicesTests/Application.FunctionalTests/ +COPY test/ServicesTests/LoadTest/LoadTest.csproj test/ServicesTests/LoadTest/ + +RUN dotnet restore eShopOnContainers-ServicesAndWebApps.sln + +COPY . . +WORKDIR /src/src/Services/TenantCustomisations/TenantAShippingInformation +RUN dotnet publish --no-restore -c Release -o /app + +FROM build AS publish + +FROM base AS final +WORKDIR /app +COPY --from=publish /app . +ENTRYPOINT ["dotnet", "TenantAShippingInformation.dll"] diff --git a/src/Services/TenantCustomisations/TenantAShippingInformation/Infrastructure/AutofacModules/ApplicationModule.cs b/src/Services/TenantCustomisations/TenantAShippingInformation/Infrastructure/AutofacModules/ApplicationModule.cs new file mode 100644 index 000000000..54fc370be --- /dev/null +++ b/src/Services/TenantCustomisations/TenantAShippingInformation/Infrastructure/AutofacModules/ApplicationModule.cs @@ -0,0 +1,28 @@ +using Autofac; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; +using System.Reflection; +using TenantAShippingInformation.IntegrationEvents.EventHandling; + +namespace TenantAShippingInformation.Infrastructure.AutofacModules +{ + + public class ApplicationModule + :Autofac.Module + { + + public string QueriesConnectionString { get; } + + public ApplicationModule(string qconstr) + { + QueriesConnectionString = qconstr; + + } + + protected override void Load(ContainerBuilder builder) + { + builder.RegisterAssemblyTypes(typeof(OrderStatusChangedToAwaitingValidationIntegrationEventHandler).GetTypeInfo().Assembly) + .AsClosedTypesOf(typeof(IIntegrationEventHandler<>)); + + } + } +} diff --git a/src/Services/TenantCustomisations/TenantAShippingInformation/Infrastructure/AutofacModules/MediatorModule.cs b/src/Services/TenantCustomisations/TenantAShippingInformation/Infrastructure/AutofacModules/MediatorModule.cs new file mode 100644 index 000000000..8f8fe2da2 --- /dev/null +++ b/src/Services/TenantCustomisations/TenantAShippingInformation/Infrastructure/AutofacModules/MediatorModule.cs @@ -0,0 +1,12 @@ +using Autofac; + +namespace TenantAShippingInformation.Infrastructure.AutofacModules +{ + public class MediatorModule : Autofac.Module + { + protected override void Load(ContainerBuilder builder) + { + //TODO + } + } +} diff --git a/src/Services/TenantCustomisations/TenantAShippingInformation/Infrastructure/Filters/AuthorizeCheckOperationFilter.cs b/src/Services/TenantCustomisations/TenantAShippingInformation/Infrastructure/Filters/AuthorizeCheckOperationFilter.cs new file mode 100644 index 000000000..2c694dfbd --- /dev/null +++ b/src/Services/TenantCustomisations/TenantAShippingInformation/Infrastructure/Filters/AuthorizeCheckOperationFilter.cs @@ -0,0 +1,33 @@ +using Microsoft.AspNetCore.Authorization; +using Swashbuckle.AspNetCore.Swagger; +using Swashbuckle.AspNetCore.SwaggerGen; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace TenantAShippingInformation.Infrastructure.Filters +{ + public class AuthorizeCheckOperationFilter : IOperationFilter + { + public void Apply(Operation 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 Response { Description = "Unauthorized" }); + operation.Responses.TryAdd("403", new Response { Description = "Forbidden" }); + + operation.Security = new List>> + { + new Dictionary> + { + { "oauth2", new [] { "orderingapi" } } + } + }; + } + } +} \ No newline at end of file diff --git a/src/Services/TenantCustomisations/TenantAShippingInformation/Infrastructure/Filters/HttpGlobalExceptionFilter.cs b/src/Services/TenantCustomisations/TenantAShippingInformation/Infrastructure/Filters/HttpGlobalExceptionFilter.cs new file mode 100644 index 000000000..d1b84d2fd --- /dev/null +++ b/src/Services/TenantCustomisations/TenantAShippingInformation/Infrastructure/Filters/HttpGlobalExceptionFilter.cs @@ -0,0 +1,69 @@ +namespace TenantAShippingInformation.Infrastructure.Filters +{ + using Microsoft.AspNetCore.Hosting; + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Mvc; + using Microsoft.AspNetCore.Mvc.Filters; + using Microsoft.Extensions.Logging; + using System.Net; + + public class HttpGlobalExceptionFilter : IExceptionFilter + { + private readonly IHostingEnvironment env; + private readonly ILogger logger; + + public HttpGlobalExceptionFilter(IHostingEnvironment 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 (1==2)//TODO + { + 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 + //TODO + //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/TenantCustomisations/TenantAShippingInformation/IntegrationEvents/EventHandling/OrderStatusChangedToAwaitingValidationIntegrationEventHandler.cs b/src/Services/TenantCustomisations/TenantAShippingInformation/IntegrationEvents/EventHandling/OrderStatusChangedToAwaitingValidationIntegrationEventHandler.cs new file mode 100644 index 000000000..37a0c3c01 --- /dev/null +++ b/src/Services/TenantCustomisations/TenantAShippingInformation/IntegrationEvents/EventHandling/OrderStatusChangedToAwaitingValidationIntegrationEventHandler.cs @@ -0,0 +1,34 @@ +using Microsoft.AspNetCore.SignalR; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; +using Microsoft.Extensions.Logging; +using Serilog.Context; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using TenantAShippingInformation.IntegrationEvents.Events; + +namespace TenantAShippingInformation.IntegrationEvents.EventHandling +{ + public class OrderStatusChangedToAwaitingValidationIntegrationEventHandler : + IIntegrationEventHandler + { + private readonly ILogger _logger; + + public OrderStatusChangedToAwaitingValidationIntegrationEventHandler(ILogger logger) + { + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public async Task Handle(OrderStatusChangedToAwaitingValidationIntegrationEvent @event) + { + using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}- TenantA")) + { + //TODO + Debug.WriteLine(@event); + + } + } + } +} \ No newline at end of file diff --git a/src/Services/TenantCustomisations/TenantAShippingInformation/IntegrationEvents/Events/OrderStatusChangedToAwaitingValidationIntegrationEvent.cs b/src/Services/TenantCustomisations/TenantAShippingInformation/IntegrationEvents/Events/OrderStatusChangedToAwaitingValidationIntegrationEvent.cs new file mode 100644 index 000000000..bbaf49a85 --- /dev/null +++ b/src/Services/TenantCustomisations/TenantAShippingInformation/IntegrationEvents/Events/OrderStatusChangedToAwaitingValidationIntegrationEvent.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; + +namespace TenantAShippingInformation.IntegrationEvents.Events +{ + public class OrderStatusChangedToAwaitingValidationIntegrationEvent : IntegrationEvent + { + public int OrderId { get; } + public IEnumerable OrderStockItems { get; } + + public OrderStatusChangedToAwaitingValidationIntegrationEvent(int orderId, + IEnumerable orderStockItems) + { + OrderId = orderId; + OrderStockItems = orderStockItems; + } + } + + public class OrderStockItem + { + public int ProductId { get; } + public int Units { get; } + + public OrderStockItem(int productId, int units) + { + ProductId = productId; + Units = units; + } + } +} \ No newline at end of file diff --git a/src/Services/TenantCustomisations/TenantAShippingInformation/Program.cs b/src/Services/TenantCustomisations/TenantAShippingInformation/Program.cs new file mode 100644 index 000000000..77fff7100 --- /dev/null +++ b/src/Services/TenantCustomisations/TenantAShippingInformation/Program.cs @@ -0,0 +1,87 @@ +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Serilog; +using System; +using System.IO; + +namespace TenantAShippingInformation +{ + public class Program + { + public static readonly string Namespace = typeof(Program).Namespace; + public static readonly string AppName = Namespace; + + public static int Main(string[] args) + { + var configuration = GetConfiguration(); + + Log.Logger = CreateSerilogLogger(configuration); + + try + { + Log.Information("Configuring web host ({ApplicationContext})...", AppName); + var host = BuildWebHost(configuration, args); + + Log.Information("Starting web host ({ApplicationContext})...", AppName); + host.Run(); + + return 0; + } + catch (Exception ex) + { + Log.Fatal(ex, "Program terminated unexpectedly ({ApplicationContext})!", AppName); + return 1; + } + finally + { + Log.CloseAndFlush(); + } + } + + private static IWebHost BuildWebHost(IConfiguration configuration, string[] args) => + WebHost.CreateDefaultBuilder(args) + .CaptureStartupErrors(false) + .UseStartup() + .UseApplicationInsights() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseConfiguration(configuration) + .UseSerilog() + .Build(); + + private static Serilog.ILogger CreateSerilogLogger(IConfiguration configuration) + { + var seqServerUrl = configuration["Serilog:SeqServerUrl"]; + var logstashUrl = configuration["Serilog:LogstashgUrl"]; + return new LoggerConfiguration() + .MinimumLevel.Verbose() + .Enrich.WithProperty("ApplicationContext", AppName) + .Enrich.FromLogContext() + .WriteTo.Console() + .WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl) + .WriteTo.Http(string.IsNullOrWhiteSpace(logstashUrl) ? "http://logstash:8080" : logstashUrl) + .ReadFrom.Configuration(configuration) + .CreateLogger(); + } + + private static 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)) + { + builder.AddAzureKeyVault( + $"https://{config["Vault:Name"]}.vault.azure.net/", + config["Vault:ClientId"], + config["Vault:ClientSecret"]); + } + + return builder.Build(); + } + } +} \ No newline at end of file diff --git a/src/Services/TenantCustomisations/TenantAShippingInformation/Properties/launchSettings.json b/src/Services/TenantCustomisations/TenantAShippingInformation/Properties/launchSettings.json new file mode 100644 index 000000000..e58a67af1 --- /dev/null +++ b/src/Services/TenantCustomisations/TenantAShippingInformation/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:59299", + "sslPort": 44393 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "TenantAShippingInformation": { + "commandName": "Project", + "launchBrowser": true, + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/src/Services/TenantCustomisations/TenantAShippingInformation/Startup.cs b/src/Services/TenantCustomisations/TenantAShippingInformation/Startup.cs new file mode 100644 index 000000000..09b3a0aeb --- /dev/null +++ b/src/Services/TenantCustomisations/TenantAShippingInformation/Startup.cs @@ -0,0 +1,411 @@ +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.IdentityModel.Tokens.Jwt; +using System.Reflection; +using Autofac; +using Autofac.Extensions.DependencyInjection; +using HealthChecks.UI.Client; +using Microsoft.ApplicationInsights.Extensibility; +using Microsoft.ApplicationInsights.ServiceFabric; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Azure.ServiceBus; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBusServiceBus; +using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF; +using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Logging; +using RabbitMQ.Client; +using Swashbuckle.AspNetCore.Swagger; +using TenantAShippingInformation.Infrastructure.AutofacModules; +using TenantAShippingInformation.Infrastructure.Filters; + +namespace TenantAShippingInformation +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + public IServiceProvider ConfigureServices(IServiceCollection services) + { + services.AddApplicationInsights(Configuration) + .AddCustomMvc() + .AddHealthChecks(Configuration) + .AddCustomDbContext(Configuration) + .AddCustomSwagger(Configuration) + .AddCustomIntegrations(Configuration) + .AddCustomConfiguration(Configuration) + .AddEventBus(Configuration) + .AddCustomAuthentication(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, 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.UseCors("CorsPolicy"); + + app.UseHealthChecks("/liveness", new HealthCheckOptions + { + Predicate = r => r.Name.Contains("self") + }); + + app.UseHealthChecks("/hc", new HealthCheckOptions() + { + Predicate = _ => true, + ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse + }); + + ConfigureAuth(app); + + app.UseMvcWithDefaultRoute(); + + 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"); + }); + + ConfigureEventBus(app); + //using (var serviceScope = app.ApplicationServices.GetService().CreateScope()) + //{ + // var context = serviceScope.ServiceProvider.GetRequiredService(); + // context.Database.EnsureCreated(); + //} + } + + + private void ConfigureEventBus(IApplicationBuilder app) + { + var eventBus = app.ApplicationServices.GetRequiredService(); + + //eventBus.Subscribe>(); + //eventBus.Subscribe(); + + } + + protected virtual void ConfigureAuth(IApplicationBuilder app) + { + if (Configuration.GetValue("UseLoadTest")) + { + //app.UseMiddleware(); + //TODO + } + + app.UseAuthentication(); + } + } + + static class CustomExtensionsMethods + { + public static IServiceCollection AddApplicationInsights(this IServiceCollection services, IConfiguration configuration) + { + services.AddApplicationInsightsTelemetry(configuration); + var orchestratorType = configuration.GetValue("OrchestratorType"); + + if (orchestratorType?.ToUpper() == "K8S") + { + // Enable K8s telemetry initializer + services.AddApplicationInsightsKubernetesEnricher(); + } + if (orchestratorType?.ToUpper() == "SF") + { + // Enable SF telemetry initializer + services.AddSingleton((serviceProvider) => + new FabricTelemetryInitializer()); + } + + return services; + } + + public static IServiceCollection AddCustomMvc(this IServiceCollection services) + { + // Add framework services. + services.AddMvc(options => + { + options.Filters.Add(typeof(HttpGlobalExceptionFilter)); + }) + .SetCompatibilityVersion(CompatibilityVersion.Version_2_2) + .AddControllersAsServices(); //Injecting Controllers themselves thru DI + //For further info see: http://docs.autofac.org/en/latest/integration/aspnetcore.html#controllers-as-services + + 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"])); + + 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: 10, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null); + }); + }); + + return services; + } + + public static IServiceCollection AddCustomSwagger(this IServiceCollection services, IConfiguration configuration) + { + services.AddSwaggerGen(options => + { + options.DescribeAllEnumsAsStrings(); + options.SwaggerDoc("v1", new Swashbuckle.AspNetCore.Swagger.Info + { + Title = "Ordering HTTP API", + Version = "v1", + Description = "The Ordering Service HTTP API", + TermsOfService = "Terms Of Service" + }); + + options.AddSecurityDefinition("oauth2", new OAuth2Scheme + { + Type = "oauth2", + Flow = "implicit", + AuthorizationUrl = $"{configuration.GetValue("IdentityUrlExternal")}/connect/authorize", + TokenUrl = $"{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 logger = sp.GetRequiredService>(); + + var serviceBusConnectionString = configuration["EventBusConnection"]; + var serviceBusConnection = new ServiceBusConnectionStringBuilder(serviceBusConnectionString); + + return new DefaultServiceBusPersisterConnection(serviceBusConnection, logger); + }); + } + 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"]; + } + + factory.VirtualHost = "TenantA"; + + 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) + { + var subscriptionClientName = configuration["SubscriptionClientName"]; + +/* if (configuration.GetValue("AzureServiceBusEnabled")) + { + services.AddSingleton(sp => + { + var serviceBusPersisterConnection = sp.GetRequiredService(); + var iLifetimeScope = sp.GetRequiredService(); + var logger = sp.GetRequiredService>(); + var eventBusSubcriptionsManager = sp.GetRequiredService(); + + return new EventBusServiceBus(serviceBusPersisterConnection, logger, + eventBusSubcriptionsManager, subscriptionClientName, iLifetimeScope); + }); + } + else*/ + { + services.AddSingleton(sp => + { + var rabbitMQPersistentConnection = sp.GetRequiredService(); + var iLifetimeScope = 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, iLifetimeScope, eventBusSubcriptionsManager, "TenantA", subscriptionClientName, retryCount); + }); + } + + services.AddSingleton(); + //services.AddTransient(); + + 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(options => + { + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + + }).AddJwtBearer(options => + { + options.Authority = identityUrl; + options.RequireHttpsMetadata = false; + options.Audience = "orders"; + }); + + return services; + } + } +} diff --git a/src/Services/TenantCustomisations/TenantAShippingInformation/TenantAShippingInformation.csproj b/src/Services/TenantCustomisations/TenantAShippingInformation/TenantAShippingInformation.csproj new file mode 100644 index 000000000..b3c7da36b --- /dev/null +++ b/src/Services/TenantCustomisations/TenantAShippingInformation/TenantAShippingInformation.csproj @@ -0,0 +1,53 @@ + + + + netcoreapp2.2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Services/TenantCustomisations/TenantAShippingInformation/appsettings.Development.json b/src/Services/TenantCustomisations/TenantAShippingInformation/appsettings.Development.json new file mode 100644 index 000000000..8983e0fc1 --- /dev/null +++ b/src/Services/TenantCustomisations/TenantAShippingInformation/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/src/Services/TenantCustomisations/TenantAShippingInformation/appsettings.json b/src/Services/TenantCustomisations/TenantAShippingInformation/appsettings.json new file mode 100644 index 000000000..6dc768b13 --- /dev/null +++ b/src/Services/TenantCustomisations/TenantAShippingInformation/appsettings.json @@ -0,0 +1,29 @@ +{ + "Serilog": { + "SeqServerUrl": null, + "LogstashgUrl": null, + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Warning", + "Microsoft.eShopOnContainers": "Information", + "System": "Warning" + } + } + }, + "IdentityUrl": "http://localhost:5105", + //"ConnectionString": "127.0.0.1", + "ConnectionString": "Server=tcp:127.0.0.1,5433;Database=Microsoft.eShopOnContainers.Services.TenantADb;User Id=sa;Password=Pass@word;", + "AzureServiceBusEnabled": false, + "SubscriptionClientName": "TenantAShippingInformation", + "ApplicationInsights": { + "InstrumentationKey": "" + }, + "EventBusRetryCount": 5, + "UseVault": false, + "Vault": { + "Name": "eshop", + "ClientId": "your-clien-id", + "ClientSecret": "your-client-secret" + } +} \ No newline at end of file