Browse Source

Merge pull request #952 from dotnet-architecture/features/add-seq-sink

Add Seq sink for Serilog and sample traces
pull/953/head
Miguel Veloso 6 years ago
committed by GitHub
parent
commit
23992ed324
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
111 changed files with 1829 additions and 807 deletions
  1. +1
    -1
      .gitignore
  2. +6
    -0
      docker-compose.override.yml
  3. +3
    -0
      docker-compose.yml
  4. +1
    -1
      src/ApiGateways/Mobile.Bff.Shopping/aggregator/Startup.cs
  5. +1
    -1
      src/ApiGateways/Web.Bff.Shopping/aggregator/Startup.cs
  6. +2
    -2
      src/BuildingBlocks/EventBus/EventBusRabbitMQ/DefaultRabbitMQPersisterConnection.cs
  7. +1
    -1
      src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs
  8. +3
    -3
      src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.cs
  9. +3
    -3
      src/BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHostExtensions.cs
  10. +10
    -7
      src/Services/Basket/Basket.API/Basket.API.csproj
  11. +22
    -3
      src/Services/Basket/Basket.API/Controllers/BasketController.cs
  12. +14
    -2
      src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/OrderStartedIntegrationEventHandler.cs
  13. +22
    -9
      src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedIntegrationEventHandler.cs
  14. +67
    -39
      src/Services/Basket/Basket.API/Program.cs
  15. +5
    -4
      src/Services/Basket/Basket.API/Startup.cs
  16. +9
    -6
      src/Services/Basket/Basket.API/appsettings.json
  17. +22
    -6
      src/Services/Basket/Basket.UnitTests/Application/BasketWebApiTest.cs
  18. +5
    -4
      src/Services/Catalog/Catalog.API/Catalog.API.csproj
  19. +7
    -7
      src/Services/Catalog/Catalog.API/Infrastructure/CatalogContextSeed.cs
  20. +23
    -10
      src/Services/Catalog/Catalog.API/IntegrationEvents/CatalogIntegrationEventService.cs
  21. +28
    -16
      src/Services/Catalog/Catalog.API/IntegrationEvents/EventHandling/OrderStatusChangedToAwaitingValidationIntegrationEventHandler.cs
  22. +20
    -8
      src/Services/Catalog/Catalog.API/IntegrationEvents/EventHandling/OrderStatusChangedToPaidIntegrationEventHandler.cs
  23. +70
    -39
      src/Services/Catalog/Catalog.API/Program.cs
  24. +4
    -1
      src/Services/Catalog/Catalog.API/Properties/launchSettings.json
  25. +4
    -3
      src/Services/Catalog/Catalog.API/Startup.cs
  26. +9
    -6
      src/Services/Catalog/Catalog.API/appsettings.json
  27. +4
    -2
      src/Services/Catalog/Catalog.API/web.config
  28. +6
    -6
      src/Services/Identity/Identity.API/Data/ApplicationDbContextSeed.cs
  29. +12
    -9
      src/Services/Identity/Identity.API/Identity.API.csproj
  30. +86
    -57
      src/Services/Identity/Identity.API/Program.cs
  31. +24
    -21
      src/Services/Identity/Identity.API/Startup.cs
  32. +9
    -6
      src/Services/Identity/Identity.API/appsettings.json
  33. +15
    -5
      src/Services/Location/Locations.API/Infrastructure/Services/LocationsService.cs
  34. +8
    -4
      src/Services/Location/Locations.API/Locations.API.csproj
  35. +67
    -37
      src/Services/Location/Locations.API/Program.cs
  36. +3
    -2
      src/Services/Location/Locations.API/Startup.cs
  37. +9
    -6
      src/Services/Location/Locations.API/appsettings.json
  38. +1
    -1
      src/Services/Marketing/Marketing.API/Infrastructure/MarketingContextSeed.cs
  39. +17
    -6
      src/Services/Marketing/Marketing.API/IntegrationEvents/Handlers/UserLocationUpdatedIntegrationEventHandler.cs
  40. +7
    -4
      src/Services/Marketing/Marketing.API/Marketing.API.csproj
  41. +70
    -40
      src/Services/Marketing/Marketing.API/Program.cs
  42. +2
    -1
      src/Services/Marketing/Marketing.API/Startup.cs
  43. +9
    -4
      src/Services/Marketing/Marketing.API/appsettings.json
  44. +29
    -0
      src/Services/Ordering/Ordering.API/Application/Behaviors/BehaviorsHelperExtensions.cs
  45. +5
    -3
      src/Services/Ordering/Ordering.API/Application/Behaviors/LoggingBehavior.cs
  46. +20
    -13
      src/Services/Ordering/Ordering.API/Application/Behaviors/TransactionBehaviour.cs
  47. +17
    -6
      src/Services/Ordering/Ordering.API/Application/Behaviors/ValidatorBehavior.cs
  48. +6
    -1
      src/Services/Ordering/Ordering.API/Application/Commands/CancelOrderCommandHandler.cs
  49. +16
    -6
      src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommandHandler.cs
  50. +64
    -11
      src/Services/Ordering/Ordering.API/Application/Commands/IdentifiedCommandHandler.cs
  51. +6
    -1
      src/Services/Ordering/Ordering.API/Application/Commands/SetAwaitingValidationOrderStatusCommandHandler.cs
  52. +6
    -1
      src/Services/Ordering/Ordering.API/Application/Commands/SetPaidOrderStatusCommandHandler.cs
  53. +6
    -1
      src/Services/Ordering/Ordering.API/Application/Commands/SetStockConfirmedOrderStatusCommandHandler.cs
  54. +6
    -1
      src/Services/Ordering/Ordering.API/Application/Commands/SetStockRejectedOrderStatusCommandHandler.cs
  55. +8
    -3
      src/Services/Ordering/Ordering.API/Application/Commands/ShipOrderCommandHandler.cs
  56. +10
    -9
      src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/BuyerAndPaymentMethodVerified/UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler.cs
  57. +3
    -3
      src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderCancelled/OrderCancelledDomainEventHandler.cs
  58. +3
    -3
      src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderGracePeriodConfirmed/OrderStatusChangedToAwaitingValidationDomainEventHandler.cs
  59. +3
    -3
      src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderPaid/OrderStatusChangedToPaidDomainEventHandler.cs
  60. +3
    -3
      src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderShipped/OrderShippedDomainEventHandler.cs
  61. +3
    -1
      src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler.cs
  62. +3
    -3
      src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStockConfirmed/OrderStatusChangedToStockConfirmedDomainEventHandler.cs
  63. +24
    -3
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/GracePeriodConfirmedIntegrationEventHandler.cs
  64. +24
    -3
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentFailedIntegrationEventHandler.cs
  65. +24
    -3
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentSuccededIntegrationEventHandler.cs
  66. +25
    -4
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockConfirmedIntegrationEventHandler.cs
  67. +28
    -7
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockRejectedIntegrationEventHandler.cs
  68. +50
    -21
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/UserCheckoutAcceptedIntegrationEventHandler.cs
  69. +16
    -4
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/OrderingIntegrationEventService.cs
  70. +32
    -1
      src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs
  71. +0
    -1
      src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs
  72. +5
    -5
      src/Services/Ordering/Ordering.API/Infrastructure/OrderingContextSeed.cs
  73. +11
    -7
      src/Services/Ordering/Ordering.API/Ordering.API.csproj
  74. +70
    -39
      src/Services/Ordering/Ordering.API/Program.cs
  75. +6
    -6
      src/Services/Ordering/Ordering.API/Startup.cs
  76. +9
    -6
      src/Services/Ordering/Ordering.API/appsettings.json
  77. +3
    -0
      src/Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj
  78. +59
    -17
      src/Services/Ordering/Ordering.BackgroundTasks/Program.cs
  79. +12
    -9
      src/Services/Ordering/Ordering.BackgroundTasks/Tasks/GracePeriodManagerTask.cs
  80. +8
    -10
      src/Services/Ordering/Ordering.BackgroundTasks/appsettings.json
  81. +16
    -7
      src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs
  82. +15
    -4
      src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToCancelledIntegrationEventHandler.cs
  83. +15
    -4
      src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToPaidIntegrationEventHandler.cs
  84. +15
    -4
      src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToShippedIntegrationEventHandler.cs
  85. +15
    -4
      src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToStockConfirmedIntegrationEventHandler.cs
  86. +15
    -4
      src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToSubmittedIntegrationEventHandler.cs
  87. +15
    -4
      src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/orderStatusChangedToAwaitingValidationIntegrationEventHandler.cs
  88. +9
    -7
      src/Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj
  89. +56
    -17
      src/Services/Ordering/Ordering.SignalrHub/Program.cs
  90. +9
    -6
      src/Services/Ordering/Ordering.SignalrHub/Startup.cs
  91. +9
    -6
      src/Services/Ordering/Ordering.SignalrHub/appsettings.json
  92. +5
    -2
      src/Services/Ordering/Ordering.UnitTests/Application/IdentifiedCommandHandlerTest.cs
  93. +3
    -1
      src/Services/Ordering/Ordering.UnitTests/Application/NewOrderCommandHandlerTest.cs
  94. +10
    -7
      src/Services/Ordering/Ordering.UnitTests/Application/OrdersWebApiTest.cs
  95. +33
    -20
      src/Services/Payment/Payment.API/IntegrationEvents/EventHandling/OrderStatusChangedToStockConfirmedIntegrationEventHandler.cs
  96. +6
    -3
      src/Services/Payment/Payment.API/Payment.API.csproj
  97. +68
    -23
      src/Services/Payment/Payment.API/Program.cs
  98. +2
    -1
      src/Services/Payment/Payment.API/Startup.cs
  99. +9
    -4
      src/Services/Payment/Payment.API/appsettings.json
  100. +6
    -6
      src/Web/WebMVC/Infrastructure/WebContextSeed.cs

+ 1
- 1
.gitignore View File

@ -26,7 +26,7 @@ bld/
# Visual Studio 2015 cache/options directory
.vs/
# Files created by bundling and minification on startup
# .js files created on build:
src/Web/WebMVC/wwwroot/js/site*
# Uncomment if you have tasks that create the project's static files in wwwroot


+ 6
- 0
docker-compose.override.yml View File

@ -7,6 +7,12 @@ version: '3.4'
# An external IP or DNS name has to be used (instead localhost and the 10.0.75.1 IP) when testing the Web apps and the Xamarin apps from remote machines/devices using the same WiFi, for instance.
services:
seq:
environment:
- ACCEPT_EULA=Y
ports:
- "5340:80"
sql.data:
environment:
- SA_PASSWORD=Pass@word


+ 3
- 0
docker-compose.yml View File

@ -1,6 +1,9 @@
version: '3.4'
services:
seq:
image: datalust/seq:latest
sql.data:
image: microsoft/mssql-server-linux:2017-latest


+ 1
- 1
src/ApiGateways/Mobile.Bff.Shopping/aggregator/Startup.cs View File

@ -57,7 +57,7 @@ namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator
if (!string.IsNullOrEmpty(pathBase))
{
loggerFactory.CreateLogger("init").LogDebug($"Using PATH BASE '{pathBase}'");
loggerFactory.CreateLogger<Startup>().LogDebug("Using PATH BASE '{pathBase}'", pathBase);
app.UsePathBase(pathBase);
}


+ 1
- 1
src/ApiGateways/Web.Bff.Shopping/aggregator/Startup.cs View File

@ -57,7 +57,7 @@ namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator
var pathBase = Configuration["PATH_BASE"];
if (!string.IsNullOrEmpty(pathBase))
{
loggerFactory.CreateLogger("init").LogDebug($"Using PATH BASE '{pathBase}'");
loggerFactory.CreateLogger<Startup>().LogDebug("Using PATH BASE '{pathBase}'", pathBase);
app.UsePathBase(pathBase);
}


+ 2
- 2
src/BuildingBlocks/EventBus/EventBusRabbitMQ/DefaultRabbitMQPersisterConnection.cs View File

@ -72,7 +72,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
.Or<BrokerUnreachableException>()
.WaitAndRetry(_retryCount, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (ex, time) =>
{
_logger.LogWarning(ex.ToString());
_logger.LogWarning(ex, "RabbitMQ Client could not connect after {TimeOut}s ({ExceptionMessage})", $"{time.TotalSeconds:n1}", ex.Message);
}
);
@ -88,7 +88,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
_connection.CallbackException += OnCallbackException;
_connection.ConnectionBlocked += OnConnectionBlocked;
_logger.LogInformation($"RabbitMQ persistent connection acquired a connection {_connection.Endpoint.HostName} and is subscribed to failure events");
_logger.LogInformation("RabbitMQ Client acquired a persistent connection to '{HostName}' and is subscribed to failure events", _connection.Endpoint.HostName);
return true;
}


+ 1
- 1
src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs View File

@ -76,7 +76,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
.Or<SocketException>()
.WaitAndRetry(_retryCount, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (ex, time) =>
{
_logger.LogWarning(ex.ToString());
_logger.LogWarning(ex, "Could not publish event: {EventId} after {Timeout}s ({ExceptionMessage})", @event.Id, $"{time.TotalSeconds:n1}", ex.Message);
});
using (var channel = _persistentConnection.CreateModel())


+ 3
- 3
src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.cs View File

@ -83,7 +83,7 @@
}
catch (ServiceBusException)
{
_logger.LogInformation($"The messaging entity {eventName} already exists.");
_logger.LogWarning("The messaging entity {eventName} already exists.", eventName);
}
}
@ -105,7 +105,7 @@
}
catch (MessagingEntityNotFoundException)
{
_logger.LogInformation($"The messaging entity {eventName} Could not be found.");
_logger.LogWarning("The messaging entity {eventName} Could not be found.", eventName);
}
_subsManager.RemoveSubscription<T, TH>();
@ -194,7 +194,7 @@
}
catch (MessagingEntityNotFoundException)
{
_logger.LogInformation($"The messaging entity { RuleDescription.DefaultRuleName } Could not be found.");
_logger.LogWarning("The messaging entity {DefaultRuleName} Could not be found.", RuleDescription.DefaultRuleName);
}
}
}

+ 3
- 3
src/BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHostExtensions.cs View File

@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.Hosting
try
{
logger.LogInformation($"Migrating database associated with context {typeof(TContext).Name}");
logger.LogInformation("Migrating database associated with context {DbContextName}", typeof(TContext).Name);
if (underK8s)
{
@ -55,11 +55,11 @@ namespace Microsoft.AspNetCore.Hosting
retry.Execute(() => InvokeSeeder(seeder, context, services));
}
logger.LogInformation($"Migrated database associated with context {typeof(TContext).Name}");
logger.LogInformation("Migrated database associated with context {DbContextName}", typeof(TContext).Name);
}
catch (Exception ex)
{
logger.LogError(ex, $"An error occurred while migrating the database used on context {typeof(TContext).Name}");
logger.LogError(ex, "An error occurred while migrating the database used on context {DbContextName}", typeof(TContext).Name);
if (underK8s)
{
throw; // Rethrow under k8s because we rely on k8s to re-run the pod


+ 10
- 7
src/Services/Basket/Basket.API/Basket.API.csproj View File

@ -13,25 +13,28 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="2.2.2" />
<PackageReference Include="AspNetCore.HealthChecks.Redis" Version="2.2.0" />
<PackageReference Include="AspNetCore.HealthChecks.AzureServiceBus" Version="2.2.0" />
<PackageReference Include="AspNetCore.HealthChecks.Rabbitmq" Version="2.2.0" />
<PackageReference Include="AspNetCore.HealthChecks.Redis" Version="2.2.0" />
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="2.2.2" />
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="4.2.1" />
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.2.1" />
<PackageReference Include="Microsoft.ApplicationInsights.DependencyCollector" Version="2.6.1" />
<PackageReference Include="Microsoft.ApplicationInsights.Kubernetes" Version="1.0.2" />
<PackageReference Include="Microsoft.ApplicationInsights.ServiceFabric" Version="2.2.2" />
<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.HealthChecks" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.HealthChecks" Version="1.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.AzureKeyVault" Version="2.1.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.AzureKeyVault" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.2.0" />
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="4.2.1" />
<PackageReference Include="Microsoft.Extensions.Logging.AzureAppServices" Version="2.2.0" />
<PackageReference Include="StackExchange.Redis.StrongName" Version="1.2.6" />
<PackageReference Include="Microsoft.Extensions.Logging.AzureAppServices" Version="2.2.0" />
<PackageReference Include="Serilog.AspNetCore" Version="2.1.1" />
<PackageReference Include="Serilog.Enrichers.Environment" Version="2.1.2" />
<PackageReference Include="Serilog.Settings.Configuration" Version="3.0.1" />
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
<PackageReference Include="Serilog.Sinks.Seq" Version="4.0.0" />
<PackageReference Include="StackExchange.Redis.StrongName" Version="1.2.6" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="3.0.0" />
<PackageReference Include="Microsoft.AspNetCore.HealthChecks" Version="1.0.0" />
</ItemGroup>
<ItemGroup>


+ 22
- 3
src/Services/Basket/Basket.API/Controllers/BasketController.cs View File

@ -5,6 +5,8 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.Services.Basket.API.Model;
using Microsoft.eShopOnContainers.Services.Basket.API.Services;
using Microsoft.Extensions.Logging;
using Serilog.Context;
using System;
using System.Net;
using System.Threading.Tasks;
@ -19,9 +21,15 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers
private readonly IBasketRepository _repository;
private readonly IIdentityService _identityService;
private readonly IEventBus _eventBus;
private readonly ILogger<BasketController> _logger;
public BasketController(IBasketRepository repository, IIdentityService identityService, IEventBus eventBus)
public BasketController(
ILogger<BasketController> logger,
IBasketRepository repository,
IIdentityService identityService,
IEventBus eventBus)
{
_logger = logger;
_repository = repository;
_identityService = identityService;
_eventBus = eventBus;
@ -50,7 +58,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers
public async Task<ActionResult> CheckoutAsync([FromBody]BasketCheckout basketCheckout, [FromHeader(Name = "x-requestid")] string requestId)
{
var userId = _identityService.GetUserIdentity();
basketCheckout.RequestId = (Guid.TryParse(requestId, out Guid guid) && guid != Guid.Empty) ?
guid : basketCheckout.RequestId;
@ -70,7 +78,18 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers
// Once basket is checkout, sends an integration event to
// ordering.api to convert basket to order and proceeds with
// order creation process
_eventBus.Publish(eventMessage);
try
{
_logger.LogInformation("----- Publishing integration event: {IntegrationEventId} from {AppName} - ({@IntegrationEvent})", eventMessage.Id, Program.AppName, eventMessage);
_eventBus.Publish(eventMessage);
}
catch (Exception ex)
{
_logger.LogError(ex, "ERROR Publishing integration event: {IntegrationEventId} from {AppName}", eventMessage.Id, Program.AppName);
throw;
}
return Accepted();
}


+ 14
- 2
src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/OrderStartedIntegrationEventHandler.cs View File

@ -1,6 +1,9 @@
using Basket.API.IntegrationEvents.Events;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.Services.Basket.API;
using Microsoft.eShopOnContainers.Services.Basket.API.Model;
using Microsoft.Extensions.Logging;
using Serilog.Context;
using System;
using System.Threading.Tasks;
@ -9,15 +12,24 @@ namespace Basket.API.IntegrationEvents.EventHandling
public class OrderStartedIntegrationEventHandler : IIntegrationEventHandler<OrderStartedIntegrationEvent>
{
private readonly IBasketRepository _repository;
private readonly ILogger<OrderStartedIntegrationEventHandler> _logger;
public OrderStartedIntegrationEventHandler(IBasketRepository repository)
public OrderStartedIntegrationEventHandler(
IBasketRepository repository,
ILogger<OrderStartedIntegrationEventHandler> logger)
{
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task Handle(OrderStartedIntegrationEvent @event)
{
await _repository.DeleteBasketAsync(@event.UserId.ToString());
using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}"))
{
_logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event);
await _repository.DeleteBasketAsync(@event.UserId.ToString());
}
}
}
}


+ 22
- 9
src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedIntegrationEventHandler.cs View File

@ -1,6 +1,8 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.Events;
using Microsoft.eShopOnContainers.Services.Basket.API.Model;
using Microsoft.Extensions.Logging;
using Serilog.Context;
using System;
using System.Linq;
using System.Threading.Tasks;
@ -9,22 +11,31 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.Even
{
public class ProductPriceChangedIntegrationEventHandler : IIntegrationEventHandler<ProductPriceChangedIntegrationEvent>
{
private readonly ILogger<ProductPriceChangedIntegrationEventHandler> _logger;
private readonly IBasketRepository _repository;
public ProductPriceChangedIntegrationEventHandler(IBasketRepository repository)
public ProductPriceChangedIntegrationEventHandler(
ILogger<ProductPriceChangedIntegrationEventHandler> logger,
IBasketRepository repository)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
}
public async Task Handle(ProductPriceChangedIntegrationEvent @event)
{
var userIds = _repository.GetUsers();
foreach (var id in userIds)
using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}"))
{
var basket = await _repository.GetBasketAsync(id);
_logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event);
await UpdatePriceInBasketItems(@event.ProductId, @event.NewPrice, @event.OldPrice, basket);
var userIds = _repository.GetUsers();
foreach (var id in userIds)
{
var basket = await _repository.GetBasketAsync(id);
await UpdatePriceInBasketItems(@event.ProductId, @event.NewPrice, @event.OldPrice, basket);
}
}
}
@ -35,17 +46,19 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.Even
if (itemsToUpdate != null)
{
_logger.LogInformation("----- ProductPriceChangedIntegrationEventHandler - Updating items in basket for user: {BuyerId} ({@Items})", basket.BuyerId, itemsToUpdate);
foreach (var item in itemsToUpdate)
{
if(item.UnitPrice == oldPrice)
{
if (item.UnitPrice == oldPrice)
{
var originalPrice = item.UnitPrice;
item.UnitPrice = newPrice;
item.OldUnitPrice = originalPrice;
}
}
await _repository.UpdateBasketAsync(basket);
}
}
}
}
}


+ 67
- 39
src/Services/Basket/Basket.API/Program.cs View File

@ -12,52 +12,80 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
{
public class Program
{
public static void Main(string[] args)
public static readonly string Namespace = typeof(Program).Namespace;
public static readonly string AppName = Namespace.Substring(Namespace.LastIndexOf('.', Namespace.LastIndexOf('.') - 1) + 1);
public static int Main(string[] args)
{
BuildWebHost(args).Run();
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();
}
}
public static IWebHost BuildWebHost(string[] args) =>
private static IWebHost BuildWebHost(IConfiguration configuration, string[] args) =>
WebHost.CreateDefaultBuilder(args)
.CaptureStartupErrors(false)
.UseFailing(options =>
{
options.ConfigPath = "/Failing";
})
.UseContentRoot(Directory.GetCurrentDirectory())
options.ConfigPath = "/Failing")
.UseStartup<Startup>()
.ConfigureAppConfiguration((builderContext, config) =>
{
var builtConfig = config.Build();
var configurationBuilder = new ConfigurationBuilder();
if (Convert.ToBoolean(builtConfig["UseVault"]))
{
configurationBuilder.AddAzureKeyVault(
$"https://{builtConfig["Vault:Name"]}.vault.azure.net/",
builtConfig["Vault:ClientId"],
builtConfig["Vault:ClientSecret"]);
}
configurationBuilder.AddEnvironmentVariables();
config.AddConfiguration(configurationBuilder.Build());
})
.ConfigureLogging((hostingContext, builder) =>
{
builder.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
builder.AddConsole();
builder.AddDebug();
builder.AddAzureWebAppDiagnostics();
})
.UseSerilog((builderContext, config) =>
{
config
.MinimumLevel.Information()
.Enrich.FromLogContext()
.WriteTo.Console();
})
.UseApplicationInsights()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseConfiguration(configuration)
.UseSerilog()
.Build();
private static Serilog.ILogger CreateSerilogLogger(IConfiguration configuration)
{
var seqServerUrl = configuration["Serilog:SeqServerUrl"];
return new LoggerConfiguration()
.MinimumLevel.Verbose()
.Enrich.WithProperty("ApplicationContext", AppName)
.Enrich.FromLogContext()
.WriteTo.Console()
.WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl)
.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<bool>("UseVault", false))
{
builder.AddAzureKeyVault(
$"https://{config["Vault:Name"]}.vault.azure.net/",
config["Vault:ClientId"],
config["Vault:ClientSecret"]);
}
return builder.Build();
}
}
}

+ 5
- 4
src/Services/Basket/Basket.API/Startup.cs View File

@ -50,7 +50,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
// This method gets called by the runtime. Use this method to add services to the container.
public IServiceProvider ConfigureServices(IServiceCollection services)
{
RegisterAppInsights(services);
RegisterAppInsights(services);
// Add framework services.
services.AddMvc(options =>
@ -66,7 +66,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
services.AddCustomHealthCheck(Configuration);
services.Configure<BasketSettings>(Configuration);
services.Configure<BasketSettings>(Configuration);
//By connecting here we are making sure that our service
//cannot start until redis is ready. This might slow down startup,
@ -179,9 +179,10 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace);
//loggerFactory.AddAzureWebAppDiagnostics();
//loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace);
var pathBase = Configuration["PATH_BASE"];
if (!string.IsNullOrEmpty(pathBase))


+ 9
- 6
src/Services/Basket/Basket.API/appsettings.json View File

@ -1,10 +1,13 @@
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
"Serilog": {
"SeqServerUrl": null,
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"Microsoft.eShopOnContainers": "Information",
"System": "Warning"
}
}
},
"IdentityUrl": "http://localhost:5105",


+ 22
- 6
src/Services/Basket/Basket.UnitTests/Application/BasketWebApiTest.cs View File

@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.Services.Basket.API.Controllers;
using Microsoft.eShopOnContainers.Services.Basket.API.Model;
using Microsoft.Extensions.Logging;
using Moq;
using System;
using System.Collections.Generic;
@ -20,12 +21,14 @@ namespace UnitTest.Basket.Application
private readonly Mock<IBasketRepository> _basketRepositoryMock;
private readonly Mock<IBasketIdentityService> _identityServiceMock;
private readonly Mock<IEventBus> _serviceBusMock;
private readonly Mock<ILogger<BasketController>> _loggerMock;
public BasketWebApiTest()
{
_basketRepositoryMock = new Mock<IBasketRepository>();
_identityServiceMock = new Mock<IBasketIdentityService>();
_serviceBusMock = new Mock<IEventBus>();
_loggerMock = new Mock<ILogger<BasketController>>();
}
[Fact]
@ -43,7 +46,11 @@ namespace UnitTest.Basket.Application
//Act
var basketController = new BasketController(
_basketRepositoryMock.Object, _identityServiceMock.Object, _serviceBusMock.Object);
_loggerMock.Object,
_basketRepositoryMock.Object,
_identityServiceMock.Object,
_serviceBusMock.Object);
var actionResult = await basketController.GetBasketByIdAsync(fakeCustomerId);
//Assert
@ -65,14 +72,17 @@ namespace UnitTest.Basket.Application
//Act
var basketController = new BasketController(
_basketRepositoryMock.Object, _identityServiceMock.Object, _serviceBusMock.Object);
_loggerMock.Object,
_basketRepositoryMock.Object,
_identityServiceMock.Object,
_serviceBusMock.Object);
var actionResult = await basketController.UpdateBasketAsync(fakeCustomerBasket);
//Assert
Assert.Equal((actionResult.Result as OkObjectResult).StatusCode, (int)System.Net.HttpStatusCode.OK);
Assert.Equal(((CustomerBasket)actionResult.Value).BuyerId, fakeCustomerId);
}
}
[Fact]
public async Task Doing_Checkout_Without_Basket_Should_Return_Bad_Request()
@ -84,7 +94,10 @@ namespace UnitTest.Basket.Application
//Act
var basketController = new BasketController(
_basketRepositoryMock.Object, _identityServiceMock.Object, _serviceBusMock.Object);
_loggerMock.Object,
_basketRepositoryMock.Object,
_identityServiceMock.Object,
_serviceBusMock.Object);
var result = await basketController.CheckoutAsync(new BasketCheckout(), Guid.NewGuid().ToString()) as BadRequestResult;
Assert.NotNull(result);
@ -102,7 +115,10 @@ namespace UnitTest.Basket.Application
_identityServiceMock.Setup(x => x.GetUserIdentity()).Returns(fakeCustomerId);
var basketController = new BasketController(
_basketRepositoryMock.Object, _identityServiceMock.Object, _serviceBusMock.Object);
_loggerMock.Object,
_basketRepositoryMock.Object,
_identityServiceMock.Object,
_serviceBusMock.Object);
basketController.ControllerContext = new ControllerContext()
{
@ -122,7 +138,7 @@ namespace UnitTest.Basket.Application
}
private CustomerBasket GetCustomerBasketFake(string fakeCustomerId)
{
{
return new CustomerBasket(fakeCustomerId)
{
Items = new List<BasketItem>()


+ 5
- 4
src/Services/Catalog/Catalog.API/Catalog.API.csproj View File

@ -34,24 +34,25 @@
<ItemGroup>
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="2.2.2" />
<PackageReference Include="AspNetCore.HealthChecks.AzureServiceBus" Version="2.2.0" />
<PackageReference Include="AspNetCore.HealthChecks.AzureStorage" Version="2.2.0" />
<PackageReference Include="AspNetCore.HealthChecks.Rabbitmq" Version="2.2.0" />
<PackageReference Include="AspNetCore.HealthChecks.SqlServer" Version="2.2.0" />
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="2.2.2" />
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="4.2.1" />
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.2.1" />
<PackageReference Include="Microsoft.ApplicationInsights.DependencyCollector" Version="2.6.1" />
<PackageReference Include="Microsoft.ApplicationInsights.Kubernetes" Version="1.0.2" />
<PackageReference Include="Microsoft.ApplicationInsights.ServiceFabric" Version="2.2.2" />
<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.AspNetCore.HealthChecks" Version="1.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.AzureKeyVault" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Logging.AzureAppServices" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.AzureKeyVault" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Logging.AzureAppServices" Version="2.2.0" />
<PackageReference Include="Serilog.AspNetCore" Version="2.1.1" />
<PackageReference Include="Serilog.Enrichers.Environment" Version="2.1.2" />
<PackageReference Include="Serilog.Settings.Configuration" Version="3.0.1" />
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
<PackageReference Include="Serilog.Sinks.Seq" Version="4.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="3.0.0" />
<PackageReference Include="System.IO.Compression.ZipFile" Version="4.3.0" />
</ItemGroup>


+ 7
- 7
src/Services/Catalog/Catalog.API/Infrastructure/CatalogContextSeed.cs View File

@ -76,14 +76,14 @@
}
catch (Exception ex)
{
logger.LogError(ex.Message);
logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message);
return GetPreconfiguredCatalogBrands();
}
return File.ReadAllLines(csvFileCatalogBrands)
.Skip(1) // skip header row
.SelectTry(x => CreateCatalogBrand(x))
.OnCaughtException(ex => { logger.LogError(ex.Message); return null; })
.OnCaughtException(ex => { logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); return null; })
.Where(x => x != null);
}
@ -131,14 +131,14 @@
}
catch (Exception ex)
{
logger.LogError(ex.Message);
logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message);
return GetPreconfiguredCatalogTypes();
}
return File.ReadAllLines(csvFileCatalogTypes)
.Skip(1) // skip header row
.SelectTry(x => CreateCatalogType(x))
.OnCaughtException(ex => { logger.LogError(ex.Message); return null; })
.OnCaughtException(ex => { logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); return null; })
.Where(x => x != null);
}
@ -186,7 +186,7 @@
}
catch (Exception ex)
{
logger.LogError(ex.Message);
logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message);
return GetPreconfiguredItems();
}
@ -197,7 +197,7 @@
.Skip(1) // skip header row
.Select(row => Regex.Split(row, ",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)") )
.SelectTry(column => CreateCatalogItem(column, csvheaders, catalogTypeIdLookup, catalogBrandIdLookup))
.OnCaughtException(ex => { logger.LogError(ex.Message); return null; })
.OnCaughtException(ex => { logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); return null; })
.Where(x => x != null);
}
@ -377,7 +377,7 @@
sleepDurationProvider: retry => TimeSpan.FromSeconds(5),
onRetry: (exception, timeSpan, retry, ctx) =>
{
logger.LogTrace($"[{prefix}] Exception {exception.GetType().Name} with message ${exception.Message} detected on attempt {retry} of {retries}");
logger.LogWarning(exception, "[{prefix}] Exception {ExceptionType} with message {Message} detected on attempt {retry} of {retries}", prefix, exception.GetType().Name, exception.Message, retry, retries);
}
);
}


+ 23
- 10
src/Services/Catalog/Catalog.API/IntegrationEvents/CatalogIntegrationEventService.cs View File

@ -4,7 +4,10 @@ using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services;
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Utilities;
using Microsoft.eShopOnContainers.Services.Catalog.API;
using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure;
using Microsoft.Extensions.Logging;
using Serilog.Context;
using System;
using System.Data.Common;
using System.Threading.Tasks;
@ -17,10 +20,15 @@ namespace Catalog.API.IntegrationEvents
private readonly IEventBus _eventBus;
private readonly CatalogContext _catalogContext;
private readonly IIntegrationEventLogService _eventLogService;
private readonly ILogger<CatalogIntegrationEventService> _logger;
public CatalogIntegrationEventService(IEventBus eventBus, CatalogContext catalogContext,
Func<DbConnection, IIntegrationEventLogService> integrationEventLogServiceFactory)
public CatalogIntegrationEventService(
ILogger<CatalogIntegrationEventService> logger,
IEventBus eventBus,
CatalogContext catalogContext,
Func<DbConnection, IIntegrationEventLogService> integrationEventLogServiceFactory)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_catalogContext = catalogContext ?? throw new ArgumentNullException(nameof(catalogContext));
_integrationEventLogServiceFactory = integrationEventLogServiceFactory ?? throw new ArgumentNullException(nameof(integrationEventLogServiceFactory));
_eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus));
@ -31,26 +39,31 @@ namespace Catalog.API.IntegrationEvents
{
try
{
_logger.LogInformation("----- Publishing integration event: {IntegrationEventId_published} from {AppName} - ({@IntegrationEvent})", evt.Id, Program.AppName, evt);
await _eventLogService.MarkEventAsInProgressAsync(evt.Id);
_eventBus.Publish(evt);
await _eventLogService.MarkEventAsPublishedAsync(evt.Id);
}
catch (Exception)
catch (Exception ex)
{
_logger.LogError(ex, "ERROR Publishing integration event: {IntegrationEventId} from {AppName} - ({@IntegrationEvent})", evt.Id, Program.AppName, evt);
await _eventLogService.MarkEventAsFailedAsync(evt.Id);
}
}
}
public async Task SaveEventAndCatalogContextChangesAsync(IntegrationEvent evt)
{
_logger.LogInformation("----- CatalogIntegrationEventService - Saving changes and integrationEvent: {IntegrationEventId}", evt.Id);
//Use of an EF Core resiliency strategy when using multiple DbContexts within an explicit BeginTransaction():
//See: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency
await ResilientTransaction.New(_catalogContext)
.ExecuteAsync(async () => {
// Achieving atomicity between original catalog database operation and the IntegrationEventLog thanks to a local transaction
await _catalogContext.SaveChangesAsync();
await _eventLogService.SaveEventAsync(evt, _catalogContext.Database.CurrentTransaction.GetDbTransaction());
});
await ResilientTransaction.New(_catalogContext).ExecuteAsync(async () =>
{
// Achieving atomicity between original catalog database operation and the IntegrationEventLog thanks to a local transaction
await _catalogContext.SaveChangesAsync();
await _eventLogService.SaveEventAsync(evt, _catalogContext.Database.CurrentTransaction.GetDbTransaction());
});
}
}
}

+ 28
- 16
src/Services/Catalog/Catalog.API/IntegrationEvents/EventHandling/OrderStatusChangedToAwaitingValidationIntegrationEventHandler.cs View File

@ -8,39 +8,51 @@
using System.Linq;
using global::Catalog.API.IntegrationEvents;
using IntegrationEvents.Events;
using Serilog.Context;
using Microsoft.Extensions.Logging;
public class OrderStatusChangedToAwaitingValidationIntegrationEventHandler :
IIntegrationEventHandler<OrderStatusChangedToAwaitingValidationIntegrationEvent>
{
private readonly CatalogContext _catalogContext;
private readonly ICatalogIntegrationEventService _catalogIntegrationEventService;
private readonly ILogger<OrderStatusChangedToAwaitingValidationIntegrationEventHandler> _logger;
public OrderStatusChangedToAwaitingValidationIntegrationEventHandler(CatalogContext catalogContext,
ICatalogIntegrationEventService catalogIntegrationEventService)
public OrderStatusChangedToAwaitingValidationIntegrationEventHandler(
CatalogContext catalogContext,
ICatalogIntegrationEventService catalogIntegrationEventService,
ILogger<OrderStatusChangedToAwaitingValidationIntegrationEventHandler> logger)
{
_catalogContext = catalogContext;
_catalogIntegrationEventService = catalogIntegrationEventService;
_logger = logger ?? throw new System.ArgumentNullException(nameof(logger));
}
public async Task Handle(OrderStatusChangedToAwaitingValidationIntegrationEvent command)
public async Task Handle(OrderStatusChangedToAwaitingValidationIntegrationEvent @event)
{
var confirmedOrderStockItems = new List<ConfirmedOrderStockItem>();
foreach (var orderStockItem in command.OrderStockItems)
using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}"))
{
var catalogItem = _catalogContext.CatalogItems.Find(orderStockItem.ProductId);
var hasStock = catalogItem.AvailableStock >= orderStockItem.Units;
var confirmedOrderStockItem = new ConfirmedOrderStockItem(catalogItem.Id, hasStock);
_logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event);
confirmedOrderStockItems.Add(confirmedOrderStockItem);
}
var confirmedOrderStockItems = new List<ConfirmedOrderStockItem>();
foreach (var orderStockItem in @event.OrderStockItems)
{
var catalogItem = _catalogContext.CatalogItems.Find(orderStockItem.ProductId);
var hasStock = catalogItem.AvailableStock >= orderStockItem.Units;
var confirmedOrderStockItem = new ConfirmedOrderStockItem(catalogItem.Id, hasStock);
var confirmedIntegrationEvent = confirmedOrderStockItems.Any(c => !c.HasStock)
? (IntegrationEvent) new OrderStockRejectedIntegrationEvent(command.OrderId, confirmedOrderStockItems)
: new OrderStockConfirmedIntegrationEvent(command.OrderId);
confirmedOrderStockItems.Add(confirmedOrderStockItem);
}
await _catalogIntegrationEventService.SaveEventAndCatalogContextChangesAsync(confirmedIntegrationEvent);
await _catalogIntegrationEventService.PublishThroughEventBusAsync(confirmedIntegrationEvent);
var confirmedIntegrationEvent = confirmedOrderStockItems.Any(c => !c.HasStock)
? (IntegrationEvent)new OrderStockRejectedIntegrationEvent(@event.OrderId, confirmedOrderStockItems)
: new OrderStockConfirmedIntegrationEvent(@event.OrderId);
await _catalogIntegrationEventService.SaveEventAndCatalogContextChangesAsync(confirmedIntegrationEvent);
await _catalogIntegrationEventService.PublishThroughEventBusAsync(confirmedIntegrationEvent);
}
}
}
}

+ 20
- 8
src/Services/Catalog/Catalog.API/IntegrationEvents/EventHandling/OrderStatusChangedToPaidIntegrationEventHandler.cs View File

@ -4,28 +4,40 @@
using System.Threading.Tasks;
using Infrastructure;
using Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents.Events;
using Microsoft.Extensions.Logging;
using Serilog.Context;
public class OrderStatusChangedToPaidIntegrationEventHandler :
IIntegrationEventHandler<OrderStatusChangedToPaidIntegrationEvent>
{
private readonly CatalogContext _catalogContext;
private readonly ILogger<OrderStatusChangedToPaidIntegrationEventHandler> _logger;
public OrderStatusChangedToPaidIntegrationEventHandler(CatalogContext catalogContext)
public OrderStatusChangedToPaidIntegrationEventHandler(
CatalogContext catalogContext,
ILogger<OrderStatusChangedToPaidIntegrationEventHandler> logger)
{
_catalogContext = catalogContext;
_logger = logger ?? throw new System.ArgumentNullException(nameof(logger));
}
public async Task Handle(OrderStatusChangedToPaidIntegrationEvent command)
public async Task Handle(OrderStatusChangedToPaidIntegrationEvent @event)
{
//we're not blocking stock/inventory
foreach (var orderStockItem in command.OrderStockItems)
using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}"))
{
var catalogItem = _catalogContext.CatalogItems.Find(orderStockItem.ProductId);
_logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event);
catalogItem.RemoveStock(orderStockItem.Units);
}
//we're not blocking stock/inventory
foreach (var orderStockItem in @event.OrderStockItems)
{
var catalogItem = _catalogContext.CatalogItems.Find(orderStockItem.ProductId);
catalogItem.RemoveStock(orderStockItem.Units);
}
await _catalogContext.SaveChangesAsync();
await _catalogContext.SaveChangesAsync();
}
}
}
}

+ 70
- 39
src/Services/Catalog/Catalog.API/Program.cs View File

@ -9,66 +9,97 @@ using Microsoft.Extensions.Options;
using Serilog;
using System;
using System.IO;
namespace Microsoft.eShopOnContainers.Services.Catalog.API
{
public class Program
{
public static void Main(string[] args)
public static readonly string Namespace = typeof(Program).Namespace;
public static readonly string AppName = Namespace.Substring(Namespace.LastIndexOf('.', Namespace.LastIndexOf('.') - 1) + 1);
public static int Main(string[] args)
{
BuildWebHost(args)
.MigrateDbContext<CatalogContext>((context,services)=>
var configuration = GetConfiguration();
Log.Logger = CreateSerilogLogger(configuration);
try
{
Log.Information("Configuring web host ({ApplicationContext})...", AppName);
var host = BuildWebHost(configuration, args);
Log.Information("Applying migrations ({ApplicationContext})...", AppName);
host.MigrateDbContext<CatalogContext>((context, services) =>
{
var env = services.GetService<IHostingEnvironment>();
var settings = services.GetService<IOptions<CatalogSettings>>();
var logger = services.GetService<ILogger<CatalogContextSeed>>();
new CatalogContextSeed()
.SeedAsync(context,env,settings,logger)
.Wait();
.SeedAsync(context, env, settings, logger)
.Wait();
})
.MigrateDbContext<IntegrationEventLogContext>((_,__)=> { })
.Run();
.MigrateDbContext<IntegrationEventLogContext>((_, __) => { });
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();
}
}
public static IWebHost BuildWebHost(string[] args) =>
private static IWebHost BuildWebHost(IConfiguration configuration, string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.CaptureStartupErrors(false)
.UseStartup<Startup>()
.UseApplicationInsights()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseWebRoot("Pics")
.ConfigureAppConfiguration((builderContext, config) =>
{
var builtConfig = config.Build();
.UseConfiguration(configuration)
.UseSerilog()
.Build();
private static Serilog.ILogger CreateSerilogLogger(IConfiguration configuration)
{
var seqServerUrl = configuration["Serilog:SeqServerUrl"];
var configurationBuilder = new ConfigurationBuilder();
return new LoggerConfiguration()
.MinimumLevel.Verbose()
.Enrich.WithProperty("ApplicationContext", AppName)
.Enrich.FromLogContext()
.WriteTo.Console()
.WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl)
.ReadFrom.Configuration(configuration)
.CreateLogger();
}
if (Convert.ToBoolean(builtConfig["UseVault"]))
{
configurationBuilder.AddAzureKeyVault(
$"https://{builtConfig["Vault:Name"]}.vault.azure.net/",
builtConfig["Vault:ClientId"],
builtConfig["Vault:ClientSecret"]);
}
private static IConfiguration GetConfiguration()
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables();
configurationBuilder.AddEnvironmentVariables();
var config = builder.Build();
config.AddConfiguration(configurationBuilder.Build());
})
.ConfigureLogging((hostingContext, builder) =>
{
builder.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
builder.AddConsole();
builder.AddDebug();
builder.AddAzureWebAppDiagnostics();
})
.UseSerilog((builderContext, config) =>
{
config
.MinimumLevel.Information()
.Enrich.FromLogContext()
.WriteTo.Console();
})
.Build();
if (config.GetValue<bool>("UseVault", false))
{
builder.AddAzureKeyVault(
$"https://{config["Vault:Name"]}.vault.azure.net/",
config["Vault:ClientId"],
config["Vault:ClientSecret"]);
}
return builder.Build();
}
}
}

+ 4
- 1
src/Services/Catalog/Catalog.API/Properties/launchSettings.json View File

@ -13,7 +13,10 @@
"launchBrowser": true,
"launchUrl": "/swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
"ConnectionString": "server=localhost,5433;Database=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word",
"ASPNETCORE_ENVIRONMENT": "Development",
"EventBusConnection": "localhost",
"Serilog:SeqServerUrl": "http://locahost:5340"
}
},
"Microsoft.eShopOnContainers.Services.Catalog.API": {


+ 4
- 3
src/Services/Catalog/Catalog.API/Startup.cs View File

@ -60,17 +60,18 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API
}
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
//Configure logs
loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace);
//loggerFactory.AddAzureWebAppDiagnostics();
//loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace);
var pathBase = Configuration["PATH_BASE"];
if (!string.IsNullOrEmpty(pathBase))
{
loggerFactory.CreateLogger("init").LogDebug($"Using PATH BASE '{pathBase}'");
loggerFactory.CreateLogger<Startup>().LogDebug("Using PATH BASE '{pathBase}'", pathBase);
app.UsePathBase(pathBase);
}


+ 9
- 6
src/Services/Catalog/Catalog.API/appsettings.json View File

@ -2,12 +2,15 @@
"ConnectionString": "Server=tcp:127.0.0.1,5433;Initial Catalog=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word",
"PicBaseUrl": "http://localhost:5101/api/v1/catalog/items/[0]/pic/",
"UseCustomizationData": false,
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Trace",
"System": "Information",
"Microsoft": "Information"
"Serilog": {
"SeqServerUrl": null,
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"Microsoft.eShopOnContainers": "Information",
"System": "Warning"
}
}
},
"AzureServiceBusEnabled": false,


+ 4
- 2
src/Services/Catalog/Catalog.API/web.config View File

@ -2,8 +2,10 @@
<configuration>
<system.webServer>
<handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified" />
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
</handlers>
<aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" forwardWindowsAuthToken="false" stdoutLogEnabled="false" />
<aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" forwardWindowsAuthToken="false" stdoutLogEnabled="false">
<environmentVariables />
</aspNetCore>
</system.webServer>
</configuration>

+ 6
- 6
src/Services/Identity/Identity.API/Data/ApplicationDbContextSeed.cs View File

@ -51,7 +51,7 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Data
{
retryForAvaiability++;
logger.LogError(ex.Message,$"There is an error migrating data for ApplicationDbContext");
logger.LogError(ex, "EXCEPTION ERROR while migrating {DbContextName}", nameof(ApplicationDbContext));
await SeedAsync(context,env,logger,settings, retryForAvaiability);
}
@ -80,7 +80,7 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Data
}
catch (Exception ex)
{
logger.LogError(ex.Message);
logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message);
return GetDefaultUser();
}
@ -89,7 +89,7 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Data
.Skip(1) // skip header column
.Select(row => Regex.Split(row, ",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)") )
.SelectTry(column => CreateApplicationUser(column, csvheaders))
.OnCaughtException(ex => { logger.LogError(ex.Message); return null; })
.OnCaughtException(ex => { logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); return null; })
.Where(x => x != null)
.ToList();
@ -199,7 +199,7 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Data
string imagesZipFile = Path.Combine(contentRootPath, "Setup", "images.zip");
if (!File.Exists(imagesZipFile))
{
logger.LogError($" zip file '{imagesZipFile}' does not exists.");
logger.LogError("Zip file '{ZipFileName}' does not exists.", imagesZipFile);
return;
}
@ -221,14 +221,14 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Data
}
else
{
logger.LogWarning($"Skip file '{entry.Name}' in zipfile '{imagesZipFile}'");
logger.LogWarning("Skipped file '{FileName}' in zipfile '{ZipFileName}'", entry.Name, imagesZipFile);
}
}
}
}
catch (Exception ex)
{
logger.LogError($"Exception in method GetPreconfiguredImages WebMVC. Exception Message={ex.Message}");
logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); ;
}
}
}


+ 12
- 9
src/Services/Identity/Identity.API/Identity.API.csproj View File

@ -13,25 +13,28 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.HealthChecks" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.HealthChecks" Version="1.0.0" />
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="2.2.2" />
<PackageReference Include="AspNetCore.HealthChecks.SqlServer" Version="2.2.0" />
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="2.2.2" />
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="4.2.1" />
<PackageReference Include="IdentityServer4.AspNetIdentity" Version="2.1.0" />
<PackageReference Include="IdentityServer4.EntityFramework" Version="2.1.1" />
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.2.1" />
<PackageReference Include="Microsoft.ApplicationInsights.DependencyCollector" Version="2.6.1" />
<PackageReference Include="Microsoft.ApplicationInsights.Kubernetes" Version="1.0.2" />
<PackageReference Include="Microsoft.ApplicationInsights.ServiceFabric" Version="2.2.2" />
<PackageReference Include="Microsoft.Extensions.Logging.AzureAppServices" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.AspNetCore.DataProtection.Redis" Version="2.2.0-preview2-35157" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.HealthChecks" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.HealthChecks" Version="1.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.AzureKeyVault" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.2.0" />
<PackageReference Include="IdentityServer4.AspNetIdentity" Version="2.1.0" />
<PackageReference Include="IdentityServer4.EntityFramework" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging.AzureAppServices" Version="2.2.0" />
<PackageReference Include="Microsoft.Web.LibraryManager.Build" Version="1.0.172" />
<PackageReference Include="Serilog.AspNetCore" Version="2.1.1" />
<PackageReference Include="Serilog.Enrichers.Environment" Version="2.1.2" />
<PackageReference Include="Serilog.Settings.Configuration" Version="3.0.1" />
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
<PackageReference Include="Microsoft.AspNetCore.DataProtection.Redis" Version="2.2.0-preview2-35157" />
<PackageReference Include="Serilog.Sinks.Seq" Version="4.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="3.0.0" />
<PackageReference Include="Microsoft.Web.LibraryManager.Build" Version="1.0.172" />
</ItemGroup>
<Target Name="PrepublishScript" BeforeTargets="PrepareForPublish">


+ 86
- 57
src/Services/Identity/Identity.API/Program.cs View File

@ -14,70 +14,99 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API
{
public class Program
{
public static void Main(string[] args)
public static readonly string Namespace = typeof(Program).Namespace;
public static readonly string AppName = Namespace.Substring(Namespace.LastIndexOf('.', Namespace.LastIndexOf('.') - 1) + 1);
public static int Main(string[] args)
{
BuildWebHost(args)
.MigrateDbContext<PersistedGrantDbContext>((_, __) => { })
.MigrateDbContext<ApplicationDbContext>((context, services) =>
{
var env = services.GetService<IHostingEnvironment>();
var logger = services.GetService<ILogger<ApplicationDbContextSeed>>();
var settings = services.GetService<IOptions<AppSettings>>();
new ApplicationDbContextSeed()
.SeedAsync(context, env, logger, settings)
.Wait();
})
.MigrateDbContext<ConfigurationDbContext>((context,services)=>
{
var configuration = services.GetService<IConfiguration>();
new ConfigurationDbContextSeed()
.SeedAsync(context, configuration)
.Wait();
}).Run();
}
var configuration = GetConfiguration();
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.ConfigureAppConfiguration((builderContext, config) =>
{
var builtConfig = config.Build();
Log.Logger = CreateSerilogLogger(configuration);
try
{
Log.Information("Configuring web host ({ApplicationContext})...", AppName);
var host = BuildWebHost(configuration, args);
var configurationBuilder = new ConfigurationBuilder();
Log.Information("Applying migrations ({ApplicationContext})...", AppName);
host.MigrateDbContext<PersistedGrantDbContext>((_, __) => { })
.MigrateDbContext<ApplicationDbContext>((context, services) =>
{
var env = services.GetService<IHostingEnvironment>();
var logger = services.GetService<ILogger<ApplicationDbContextSeed>>();
var settings = services.GetService<IOptions<AppSettings>>();
if (Convert.ToBoolean(builtConfig["UseVault"]))
new ApplicationDbContextSeed()
.SeedAsync(context, env, logger, settings)
.Wait();
})
.MigrateDbContext<ConfigurationDbContext>((context, services) =>
{
configurationBuilder.AddAzureKeyVault(
$"https://{builtConfig["Vault:Name"]}.vault.azure.net/",
builtConfig["Vault:ClientId"],
builtConfig["Vault:ClientSecret"]);
}
configurationBuilder.AddEnvironmentVariables();
config.AddConfiguration(configurationBuilder.Build());
})
.ConfigureLogging((hostingContext, builder) =>
{
builder.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
builder.AddConsole();
builder.AddDebug();
builder.AddAzureWebAppDiagnostics();
})
new ConfigurationDbContextSeed()
.SeedAsync(context, configuration)
.Wait();
});
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<Startup>()
.UseApplicationInsights()
.UseSerilog((builderContext, config) =>
{
config
.MinimumLevel.Information()
.Enrich.FromLogContext()
.WriteTo.Console();
})
.UseContentRoot(Directory.GetCurrentDirectory())
.UseConfiguration(configuration)
.UseSerilog()
.Build();
private static Serilog.ILogger CreateSerilogLogger(IConfiguration configuration)
{
var seqServerUrl = configuration["Serilog:SeqServerUrl"];
return new LoggerConfiguration()
.MinimumLevel.Verbose()
.Enrich.WithProperty("ApplicationContext", AppName)
.Enrich.FromLogContext()
.WriteTo.Console()
.WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl)
.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<bool>("UseVault", false))
{
builder.AddAzureKeyVault(
$"https://{config["Vault:Name"]}.vault.azure.net/",
config["Vault:ClientId"],
config["Vault:ClientSecret"]);
}
return builder.Build();
}
}
}

+ 24
- 21
src/Services/Identity/Identity.API/Startup.cs View File

@ -42,12 +42,12 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API
// Add framework services.
services.AddDbContext<ApplicationDbContext>(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);
}));
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);
}));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
@ -90,22 +90,22 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API
.AddConfigurationStore(options =>
{
options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString,
sqlServerOptionsAction: sqlOptions =>
{
sqlOptions.MigrationsAssembly(migrationsAssembly);
//Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency
sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
});
sqlServerOptionsAction: sqlOptions =>
{
sqlOptions.MigrationsAssembly(migrationsAssembly);
//Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency
sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
});
})
.AddOperationalStore(options =>
{
options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString,
sqlServerOptionsAction: sqlOptions =>
{
sqlOptions.MigrationsAssembly(migrationsAssembly);
//Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency
sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
});
sqlServerOptionsAction: sqlOptions =>
{
sqlOptions.MigrationsAssembly(migrationsAssembly);
//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.AddTransient<IProfileService, ProfileService>();
@ -117,8 +117,11 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace);
{
//loggerFactory.AddConsole(Configuration.GetSection("Logging"));
//loggerFactory.AddDebug();
//loggerFactory.AddAzureWebAppDiagnostics();
//loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace);
if (env.IsDevelopment())
{
@ -133,7 +136,7 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API
var pathBase = Configuration["PATH_BASE"];
if (!string.IsNullOrEmpty(pathBase))
{
loggerFactory.CreateLogger("init").LogDebug($"Using PATH BASE '{pathBase}'");
loggerFactory.CreateLogger<Startup>().LogDebug("Using PATH BASE '{pathBase}'", pathBase);
app.UsePathBase(pathBase);
}


+ 9
- 6
src/Services/Identity/Identity.API/appsettings.json View File

@ -5,12 +5,15 @@
"SpaClient": "http://localhost:5104",
"XamarinCallback": "http://localhost:5105/xamarincallback",
"UseCustomizationData": false,
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Trace",
"System": "Information",
"Microsoft": "Information"
"Serilog": {
"SeqServerUrl": null,
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"Microsoft.eShopOnContainers": "Information",
"System": "Warning"
}
}
},
"ApplicationInsights": {


+ 15
- 5
src/Services/Location/Locations.API/Infrastructure/Services/LocationsService.cs View File

@ -6,6 +6,7 @@
using Microsoft.eShopOnContainers.Services.Locations.API.IntegrationEvents.Events;
using Microsoft.eShopOnContainers.Services.Locations.API.Model;
using Microsoft.eShopOnContainers.Services.Locations.API.ViewModel;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
@ -14,11 +15,16 @@
{
private readonly ILocationsRepository _locationsRepository;
private readonly IEventBus _eventBus;
private readonly ILogger<LocationsService> _logger;
public LocationsService(ILocationsRepository locationsRepository, IEventBus eventBus)
public LocationsService(
ILocationsRepository locationsRepository,
IEventBus eventBus,
ILogger<LocationsService> logger)
{
_locationsRepository = locationsRepository ?? throw new ArgumentNullException(nameof(locationsRepository));
_eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task<Locations> GetLocationAsync(int locationId)
@ -37,11 +43,11 @@
}
public async Task<bool> AddOrUpdateUserLocationAsync(string userId, LocationRequest currentPosition)
{
{
// Get the list of ordered regions the user currently is within
var currentUserAreaLocationList = await _locationsRepository.GetCurrentUserRegionsListAsync(currentPosition);
if(currentUserAreaLocationList is null)
if (currentUserAreaLocationList is null)
{
throw new LocationDomainException("User current area not found");
}
@ -66,13 +72,17 @@
{
var newUserLocations = MapUserLocationDetails(newLocations);
var @event = new UserLocationUpdatedIntegrationEvent(userId, newUserLocations);
_logger.LogInformation("----- Publishing integration event: {IntegrationEventId} from {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event);
_eventBus.Publish(@event);
}
private List<UserLocationDetails> MapUserLocationDetails(List<Locations> newLocations)
{
var result = new List<UserLocationDetails>();
newLocations.ForEach(location => {
newLocations.ForEach(location =>
{
result.Add(new UserLocationDetails()
{
LocationId = location.LocationId,


+ 8
- 4
src/Services/Location/Locations.API/Locations.API.csproj View File

@ -6,28 +6,32 @@
<UserSecretsId>aspnet-Locations.API-20161122013619</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="2.2.2" />
<PackageReference Include="AspNetCore.HealthChecks.MongoDb" Version="2.2.0" />
<PackageReference Include="AspNetCore.HealthChecks.AzureServiceBus" Version="2.2.0" />
<PackageReference Include="AspNetCore.HealthChecks.MongoDb" Version="2.2.0" />
<PackageReference Include="AspNetCore.HealthChecks.Rabbitmq" Version="2.2.0" />
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="2.2.2" />
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="4.2.1" />
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.2.1" />
<PackageReference Include="Microsoft.ApplicationInsights.DependencyCollector" Version="2.6.1" />
<PackageReference Include="Microsoft.ApplicationInsights.Kubernetes" Version="1.0.2" />
<PackageReference Include="Microsoft.ApplicationInsights.ServiceFabric" Version="2.2.2" />
<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.HealthChecks" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.HealthChecks" Version="1.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.AzureAppServices" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.AzureKeyVault" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Logging.AzureAppServices" Version="2.2.0" />
<PackageReference Include="mongocsharpdriver" Version="2.5.0" />
<PackageReference Include="MongoDB.Bson" Version="2.5.0" />
<PackageReference Include="MongoDB.Driver" Version="2.5.0" />
<PackageReference Include="MongoDB.Driver.Core" Version="2.5.0" />
<PackageReference Include="Serilog.AspNetCore" Version="2.1.1" />
<PackageReference Include="Serilog.Enrichers.Environment" Version="2.1.2" />
<PackageReference Include="Serilog.Settings.Configuration" Version="3.0.1" />
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
<PackageReference Include="Serilog.Sinks.Seq" Version="4.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="3.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBusRabbitMQ\EventBusRabbitMQ.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBusServiceBus\EventBusServiceBus.csproj" />


+ 67
- 37
src/Services/Location/Locations.API/Program.cs View File

@ -11,48 +11,78 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API
{
public class Program
{
public static void Main(string[] args)
public static readonly string Namespace = typeof(Program).Namespace;
public static readonly string AppName = Namespace.Substring(Namespace.LastIndexOf('.', Namespace.LastIndexOf('.') - 1) + 1);
public static int Main(string[] args)
{
BuildWebHost(args).Run();
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();
}
}
public static IWebHost BuildWebHost(string[] args) =>
private static IWebHost BuildWebHost(IConfiguration configuration, string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseContentRoot(Directory.GetCurrentDirectory())
.CaptureStartupErrors(false)
.UseStartup<Startup>()
.ConfigureAppConfiguration((builderContext, config) =>
{
var builtConfig = config.Build();
var configurationBuilder = new ConfigurationBuilder();
if (Convert.ToBoolean(builtConfig["UseVault"]))
{
configurationBuilder.AddAzureKeyVault(
$"https://{builtConfig["Vault:Name"]}.vault.azure.net/",
builtConfig["Vault:ClientId"],
builtConfig["Vault:ClientSecret"]);
}
configurationBuilder.AddEnvironmentVariables();
config.AddConfiguration(configurationBuilder.Build());
})
.ConfigureLogging((hostingContext, builder) =>
{
builder.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
builder.AddConsole();
builder.AddDebug();
builder.AddAzureWebAppDiagnostics();
})
.UseApplicationInsights()
.UseSerilog((builderContext, config) =>
{
config
.MinimumLevel.Information()
.Enrich.FromLogContext()
.WriteTo.Console();
})
.UseContentRoot(Directory.GetCurrentDirectory())
.UseConfiguration(configuration)
.UseSerilog()
.Build();
private static Serilog.ILogger CreateSerilogLogger(IConfiguration configuration)
{
var seqServerUrl = configuration["Serilog:SeqServerUrl"];
return new LoggerConfiguration()
.MinimumLevel.Verbose()
.Enrich.WithProperty("ApplicationContext", AppName)
.Enrich.FromLogContext()
.WriteTo.Console()
.WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl)
.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<bool>("UseVault", false))
{
builder.AddAzureKeyVault(
$"https://{config["Vault:Name"]}.vault.azure.net/",
config["Vault:ClientId"],
config["Vault:ClientSecret"]);
}
return builder.Build();
}
}
}
}

+ 3
- 2
src/Services/Location/Locations.API/Startup.cs View File

@ -153,9 +153,10 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace);
//loggerFactory.AddAzureWebAppDiagnostics();
//loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace);
var pathBase = Configuration["PATH_BASE"];
if (!string.IsNullOrEmpty(pathBase))


+ 9
- 6
src/Services/Location/Locations.API/appsettings.json View File

@ -2,12 +2,15 @@
"ConnectionString": "mongodb://nosql.data",
"Database": "LocationsDb",
"IdentityUrl": "http://localhost:5105",
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Trace",
"System": "Information",
"Microsoft": "Information"
"Serilog": {
"SeqServerUrl": null,
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"Microsoft.eShopOnContainers": "Information",
"System": "Warning"
}
}
},
"AzureServiceBusEnabled": false,


+ 1
- 1
src/Services/Marketing/Marketing.API/Infrastructure/MarketingContextSeed.cs View File

@ -76,7 +76,7 @@
sleepDurationProvider: retry => TimeSpan.FromSeconds(5),
onRetry: (exception, timeSpan, retry, ctx) =>
{
logger.LogTrace($"[{prefix}] Exception {exception.GetType().Name} with message ${exception.Message} detected on attempt {retry} of {retries}");
logger.LogWarning(exception, "[{prefix}] Exception {ExceptionType} with message {Message} detected on attempt {retry} of {retries}", prefix, exception.GetType().Name, exception.Message, retry, retries);
}
);
}


+ 17
- 6
src/Services/Marketing/Marketing.API/IntegrationEvents/Handlers/UserLocationUpdatedIntegrationEventHandler.cs View File

@ -4,6 +4,8 @@
using Marketing.API.Model;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure.Repositories;
using Microsoft.Extensions.Logging;
using Serilog.Context;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
@ -12,20 +14,29 @@
: IIntegrationEventHandler<UserLocationUpdatedIntegrationEvent>
{
private readonly IMarketingDataRepository _marketingDataRepository;
private readonly ILogger<UserLocationUpdatedIntegrationEventHandler> _logger;
public UserLocationUpdatedIntegrationEventHandler(IMarketingDataRepository repository)
public UserLocationUpdatedIntegrationEventHandler(
IMarketingDataRepository repository,
ILogger<UserLocationUpdatedIntegrationEventHandler> logger)
{
_marketingDataRepository = repository ?? throw new ArgumentNullException(nameof(repository));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task Handle(UserLocationUpdatedIntegrationEvent @event)
{
var userMarketingData = await _marketingDataRepository.GetAsync(@event.UserId);
userMarketingData = userMarketingData ??
new MarketingData() { UserId = @event.UserId };
using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}"))
{
_logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event);
userMarketingData.Locations = MapUpdatedUserLocations(@event.LocationList);
await _marketingDataRepository.UpdateLocationAsync(userMarketingData);
var userMarketingData = await _marketingDataRepository.GetAsync(@event.UserId);
userMarketingData = userMarketingData ??
new MarketingData() { UserId = @event.UserId };
userMarketingData.Locations = MapUpdatedUserLocations(@event.LocationList);
await _marketingDataRepository.UpdateLocationAsync(userMarketingData);
}
}
private List<Location> MapUpdatedUserLocations(List<UserLocationDetails> newUserLocations)


+ 7
- 4
src/Services/Marketing/Marketing.API/Marketing.API.csproj View File

@ -21,27 +21,30 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="AspNetCore.HealthChecks.AzureServiceBus" Version="2.2.0" />
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="2.2.2" />
<PackageReference Include="AspNetCore.HealthChecks.Rabbitmq" Version="2.2.0" />
<PackageReference Include="AspNetCore.HealthChecks.AzureStorage" Version="2.2.0" />
<PackageReference Include="AspNetCore.HealthChecks.MongoDb" Version="2.2.0" />
<PackageReference Include="AspNetCore.HealthChecks.Rabbitmq" Version="2.2.0" />
<PackageReference Include="AspNetCore.HealthChecks.SqlServer" Version="2.2.0" />
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="2.2.2" />
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="4.2.1" />
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.2.1" />
<PackageReference Include="Microsoft.ApplicationInsights.DependencyCollector" Version="2.6.1" />
<PackageReference Include="Microsoft.ApplicationInsights.Kubernetes" Version="1.0.2" />
<PackageReference Include="Microsoft.ApplicationInsights.ServiceFabric" Version="2.2.2" />
<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.HealthChecks" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.HealthChecks" Version="1.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.AzureKeyVault" Version="2.2.0" />
<PackageReference Include="Microsoft.ApplicationInsights.ServiceFabric" Version="2.2.2" />
<PackageReference Include="Microsoft.Extensions.Logging.AzureAppServices" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.2.0" />
<PackageReference Include="mongocsharpdriver" Version="2.5.0" />
<PackageReference Include="MongoDB.Bson" Version="2.5.0" />
<PackageReference Include="MongoDB.Driver" Version="2.5.0" />
<PackageReference Include="MongoDB.Driver.Core" Version="2.5.0" />
<PackageReference Include="Serilog.AspNetCore" Version="2.1.1" />
<PackageReference Include="Serilog.Enrichers.Environment" Version="2.1.2" />
<PackageReference Include="Serilog.Settings.Configuration" Version="3.0.1" />
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
<PackageReference Include="Serilog.Sinks.Seq" Version="4.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="3.0.0" />
</ItemGroup>
<ItemGroup>


+ 70
- 40
src/Services/Marketing/Marketing.API/Program.cs View File

@ -12,59 +12,89 @@
public class Program
{
public static void Main(string[] args)
public static readonly string Namespace = typeof(Program).Namespace;
public static readonly string AppName = Namespace.Substring(Namespace.LastIndexOf('.', Namespace.LastIndexOf('.') - 1) + 1);
public static int Main(string[] args)
{
BuildWebHost(args)
.MigrateDbContext<MarketingContext>((context, services) =>
var configuration = GetConfiguration();
Log.Logger = CreateSerilogLogger(configuration);
try
{
var logger = services.GetService<ILogger<MarketingContextSeed>>();
Log.Information("Configuring web host ({ApplicationContext})...", AppName);
var host = BuildWebHost(configuration, args);
Log.Information("Applying migrations ({ApplicationContext})...", AppName);
host.MigrateDbContext<MarketingContext>((context, services) =>
{
var logger = services.GetService<ILogger<MarketingContextSeed>>();
new MarketingContextSeed()
.SeedAsync(context, logger)
.Wait();
});
new MarketingContextSeed()
.SeedAsync(context,logger)
.Wait();
Log.Information("Starting web host ({ApplicationContext})...", AppName);
host.Run();
}).Run();
return 0;
}
catch (Exception ex)
{
Log.Fatal(ex, "Program terminated unexpectedly ({ApplicationContext})!", AppName);
return 1;
}
finally
{
Log.CloseAndFlush();
}
}
public static IWebHost BuildWebHost(string[] args) =>
private static IWebHost BuildWebHost(IConfiguration configuration, string[] args) =>
WebHost.CreateDefaultBuilder(args)
.CaptureStartupErrors(false)
.UseStartup<Startup>()
.UseApplicationInsights()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>()
.UseWebRoot("Pics")
.ConfigureAppConfiguration((builderContext, config) =>
{
var builtConfig = config.Build();
.UseConfiguration(configuration)
.UseSerilog()
.Build();
var configurationBuilder = new ConfigurationBuilder();
private static Serilog.ILogger CreateSerilogLogger(IConfiguration configuration)
{
var seqServerUrl = configuration["Serilog:SeqServerUrl"];
if (Convert.ToBoolean(builtConfig["UseVault"]))
{
configurationBuilder.AddAzureKeyVault(
$"https://{builtConfig["Vault:Name"]}.vault.azure.net/",
builtConfig["Vault:ClientId"],
builtConfig["Vault:ClientSecret"]);
}
return new LoggerConfiguration()
.MinimumLevel.Verbose()
.Enrich.WithProperty("ApplicationContext", AppName)
.Enrich.FromLogContext()
.WriteTo.Console()
.WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl)
.ReadFrom.Configuration(configuration)
.CreateLogger();
}
configurationBuilder.AddEnvironmentVariables();
private static IConfiguration GetConfiguration()
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables();
config.AddConfiguration(configurationBuilder.Build());
})
.ConfigureLogging((hostingContext, builder) =>
{
builder.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
builder.AddConsole();
builder.AddDebug();
builder.AddAzureWebAppDiagnostics();
})
.UseApplicationInsights()
.UseSerilog((builderContext, config) =>
{
config
.MinimumLevel.Information()
.Enrich.FromLogContext()
.WriteTo.Console();
})
.Build();
var config = builder.Build();
if (config.GetValue<bool>("UseVault", false))
{
builder.AddAzureKeyVault(
$"https://{config["Vault:Name"]}.vault.azure.net/",
config["Vault:ClientId"],
config["Vault:ClientSecret"]);
}
return builder.Build();
}
}
}

+ 2
- 1
src/Services/Marketing/Marketing.API/Startup.cs View File

@ -179,7 +179,8 @@
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env,ILoggerFactory loggerFactory)
{
loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace);
//loggerFactory.AddAzureWebAppDiagnostics();
//loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace);
var pathBase = Configuration["PATH_BASE"];


+ 9
- 4
src/Services/Marketing/Marketing.API/appsettings.json View File

@ -1,8 +1,13 @@
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Trace"
"Serilog": {
"SeqServerUrl": null,
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"Microsoft.eShopOnContainers": "Information",
"System": "Warning"
}
}
},
"ConnectionString": "Server=tcp:127.0.0.1,5433;Initial Catalog=Microsoft.eShopOnContainers.Services.MarketingDb;User Id=sa;Password=Pass@word",


+ 29
- 0
src/Services/Ordering/Ordering.API/Application/Behaviors/BehaviorsHelperExtensions.cs View File

@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Ordering.API.Application.Behaviors
{
internal static class BehaviorsHelperExtensions
{
internal static string GetGenericTypeName(this object @object)
{
var typeName = string.Empty;
var type = @object.GetType();
if (type.IsGenericType)
{
var genericTypes = string.Join(",", type.GetGenericArguments().Select(t => t.Name).ToArray());
typeName = $"{type.Name.Remove(type.Name.IndexOf('`'))}<{genericTypes}>";
}
else
{
typeName = type.Name;
}
return typeName;
}
}
}

+ 5
- 3
src/Services/Ordering/Ordering.API/Application/Behaviors/LoggingBehavior.cs View File

@ -1,9 +1,10 @@
using MediatR;
using Microsoft.Extensions.Logging;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Ordering.API.Infrastructure.Behaviors
namespace Ordering.API.Application.Behaviors
{
public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
@ -12,9 +13,10 @@ namespace Ordering.API.Infrastructure.Behaviors
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
_logger.LogInformation($"Handling {typeof(TRequest).Name}");
_logger.LogInformation("----- Handling command {CommandName} ({@Command})", request.GetGenericTypeName(), request);
var response = await next();
_logger.LogInformation($"Handled {typeof(TResponse).Name}");
_logger.LogInformation("----- Command {CommandName} handled - response: {@Response}", request.GetGenericTypeName(), response);
return response;
}
}


+ 20
- 13
src/Services/Ordering/Ordering.API/Application/Behaviors/TransactionBehaviour.cs View File

@ -3,9 +3,8 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure;
using Microsoft.Extensions.Logging;
using Ordering.API.Application.IntegrationEvents;
using Serilog.Context;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@ -28,33 +27,41 @@ namespace Ordering.API.Application.Behaviors
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
TResponse response = default(TResponse);
var response = default(TResponse);
var typeName = request.GetGenericTypeName();
try
{
var strategy = _dbContext.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync(async () =>
if (_dbContext.HasActiveTransaction)
{
_logger.LogInformation($"Begin transaction {typeof(TRequest).Name}");
return await next();
}
var strategy = _dbContext.Database.CreateExecutionStrategy();
await _dbContext.BeginTransactionAsync();
await strategy.ExecuteAsync(async () =>
{
using (var transaction = await _dbContext.BeginTransactionAsync())
using (LogContext.PushProperty("TransactionContext", transaction.TransactionId))
{
_logger.LogInformation("----- Begin transaction {TransactionId} for {CommandName} ({@Command})", transaction.TransactionId, typeName, request);
response = await next();
response = await next();
await _dbContext.CommitTransactionAsync();
_logger.LogInformation("----- Commit transaction {TransactionId} for {CommandName}", transaction.TransactionId, typeName);
_logger.LogInformation($"Committed transaction {typeof(TRequest).Name}");
await _dbContext.CommitTransactionAsync(transaction);
}
await _orderingIntegrationEventService.PublishEventsThroughEventBusAsync();
});
return response;
}
catch (Exception)
catch (Exception ex)
{
_logger.LogInformation($"Rollback transaction executed {typeof(TRequest).Name}");
_logger.LogError(ex, "ERROR Handling transaction for {CommandName} ({@Command})", typeName, request);
_dbContext.RollbackTransaction();
throw;
}
}


+ 17
- 6
src/Services/Ordering/Ordering.API/Application/Behaviors/ValidatorBehavior.cs View File

@ -1,20 +1,30 @@
using FluentValidation;
using MediatR;
using Microsoft.Extensions.Logging;
using Ordering.Domain.Exceptions;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Ordering.API.Infrastructure.Behaviors
namespace Ordering.API.Application.Behaviors
{
public class ValidatorBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
private readonly ILogger<ValidatorBehavior<TRequest, TResponse>> _logger;
private readonly IValidator<TRequest>[] _validators;
public ValidatorBehavior(IValidator<TRequest>[] validators) => _validators = validators;
public ValidatorBehavior(IValidator<TRequest>[] validators, ILogger<ValidatorBehavior<TRequest, TResponse>> logger)
{
_validators = validators;
_logger = logger;
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
var typeName = request.GetGenericTypeName();
_logger.LogInformation("----- Validating command {CommandType}", typeName);
var failures = _validators
.Select(v => v.Validate(request))
.SelectMany(result => result.Errors)
@ -23,12 +33,13 @@ namespace Ordering.API.Infrastructure.Behaviors
if (failures.Any())
{
_logger.LogWarning("Validation errors - {CommandType} - Command: {@Command} - Errors: {@ValidationErrors}", typeName, request, failures);
throw new OrderingDomainException(
$"Command Validation Errors for type {typeof(TRequest).Name}", new ValidationException("Validation exception", failures));
}
var response = await next();
return response;
return await next();
}
}
}
}

+ 6
- 1
src/Services/Ordering/Ordering.API/Application/Commands/CancelOrderCommandHandler.cs View File

@ -2,6 +2,7 @@
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency;
using Microsoft.Extensions.Logging;
using System.Threading;
using System.Threading.Tasks;
@ -40,7 +41,11 @@ namespace Ordering.API.Application.Commands
// Use for Idempotency in Command process
public class CancelOrderIdentifiedCommandHandler : IdentifiedCommandHandler<CancelOrderCommand, bool>
{
public CancelOrderIdentifiedCommandHandler(IMediator mediator, IRequestManager requestManager) : base(mediator, requestManager)
public CancelOrderIdentifiedCommandHandler(
IMediator mediator,
IRequestManager requestManager,
ILogger<IdentifiedCommandHandler<CancelOrderCommand, bool>> logger)
: base(mediator, requestManager, logger)
{
}


+ 16
- 6
src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommandHandler.cs View File

@ -6,6 +6,7 @@
using MediatR;
using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services;
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency;
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;
@ -18,17 +19,20 @@
private readonly IIdentityService _identityService;
private readonly IMediator _mediator;
private readonly IOrderingIntegrationEventService _orderingIntegrationEventService;
private readonly ILogger<CreateOrderCommandHandler> _logger;
// Using DI to inject infrastructure persistence Repositories
public CreateOrderCommandHandler(IMediator mediator,
IOrderingIntegrationEventService orderingIntegrationEventService,
IOrderRepository orderRepository,
IIdentityService identityService)
IOrderRepository orderRepository,
IIdentityService identityService,
ILogger<CreateOrderCommandHandler> logger)
{
_orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository));
_identityService = identityService ?? throw new ArgumentNullException(nameof(identityService));
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
_orderingIntegrationEventService = orderingIntegrationEventService ?? throw new ArgumentNullException(nameof(orderingIntegrationEventService));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task<bool> Handle(CreateOrderCommand message, CancellationToken cancellationToken)
@ -36,20 +40,22 @@
// Add Integration event to clean the basket
var orderStartedIntegrationEvent = new OrderStartedIntegrationEvent(message.UserId);
await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStartedIntegrationEvent);
// Add/Update the Buyer AggregateRoot
// DDD patterns comment: Add child entities and value-objects through the Order Aggregate-Root
// methods and constructor so validations, invariants and business logic
// make sure that consistency is preserved across the whole aggregate
var address = new Address(message.Street, message.City, message.State, message.Country, message.ZipCode);
var order = new Order(message.UserId, message.UserName, address, message.CardTypeId, message.CardNumber, message.CardSecurityNumber, message.CardHolderName, message.CardExpiration);
foreach (var item in message.OrderItems)
{
order.AddOrderItem(item.ProductId, item.ProductName, item.UnitPrice, item.Discount, item.PictureUrl, item.Units);
}
_orderRepository.Add(order);
_logger.LogInformation("----- Creating Order - Order: {@Order}", order);
_orderRepository.Add(order);
return await _orderRepository.UnitOfWork
.SaveEntitiesAsync();
@ -60,7 +66,11 @@
// Use for Idempotency in Command process
public class CreateOrderIdentifiedCommandHandler : IdentifiedCommandHandler<CreateOrderCommand, bool>
{
public CreateOrderIdentifiedCommandHandler(IMediator mediator, IRequestManager requestManager) : base(mediator, requestManager)
public CreateOrderIdentifiedCommandHandler(
IMediator mediator,
IRequestManager requestManager,
ILogger<IdentifiedCommandHandler<CreateOrderCommand, bool>> logger)
: base(mediator, requestManager, logger)
{
}


+ 64
- 11
src/Services/Ordering/Ordering.API/Application/Commands/IdentifiedCommandHandler.cs View File

@ -1,5 +1,9 @@
using MediatR;
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency;
using Microsoft.Extensions.Logging;
using Ordering.API.Application.Behaviors;
using Ordering.API.Application.Commands;
using System;
using System.Threading;
using System.Threading.Tasks;
@ -16,11 +20,16 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands
{
private readonly IMediator _mediator;
private readonly IRequestManager _requestManager;
private readonly ILogger<IdentifiedCommandHandler<T, R>> _logger;
public IdentifiedCommandHandler(IMediator mediator, IRequestManager requestManager)
public IdentifiedCommandHandler(
IMediator mediator,
IRequestManager requestManager,
ILogger<IdentifiedCommandHandler<T, R>> logger)
{
_mediator = mediator;
_requestManager = requestManager;
_logger = logger ?? throw new System.ArgumentNullException(nameof(logger));
}
/// <summary>
@ -48,16 +57,60 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands
else
{
await _requestManager.CreateRequestForCommandAsync<T>(message.Id);
try
{
// Send the embeded business command to mediator so it runs its related CommandHandler
var result = await _mediator.Send(message.Command);
return result;
}
catch
{
return default(R);
}
try
{
var command = message.Command;
var commandName = command.GetGenericTypeName();
var idProperty = string.Empty;
var commandId = string.Empty;
switch (command)
{
case CreateOrderCommand createOrderCommand:
idProperty = nameof(createOrderCommand.UserId);
commandId = createOrderCommand.UserId;
break;
case CancelOrderCommand cancelOrderCommand:
idProperty = nameof(cancelOrderCommand.OrderNumber);
commandId = $"{cancelOrderCommand.OrderNumber}";
break;
case ShipOrderCommand shipOrderCommand:
idProperty = nameof(shipOrderCommand.OrderNumber);
commandId = $"{shipOrderCommand.OrderNumber}";
break;
default:
idProperty = "Id?";
commandId = "n/a";
break;
}
_logger.LogInformation(
"----- Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})",
commandName,
idProperty,
commandId,
command);
// Send the embeded business command to mediator so it runs its related CommandHandler
var result = await _mediator.Send(command);
_logger.LogInformation(
"----- Command result: {@Result} - {CommandName} - {IdProperty}: {CommandId} ({@Command})",
result,
commandName,
idProperty,
commandId,
command);
return result;
}
catch
{
return default(R);
}
}
}
}

+ 6
- 1
src/Services/Ordering/Ordering.API/Application/Commands/SetAwaitingValidationOrderStatusCommandHandler.cs View File

@ -2,6 +2,7 @@
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency;
using Microsoft.Extensions.Logging;
using System.Threading;
using System.Threading.Tasks;
@ -40,7 +41,11 @@ namespace Ordering.API.Application.Commands
// Use for Idempotency in Command process
public class SetAwaitingValidationIdentifiedOrderStatusCommandHandler : IdentifiedCommandHandler<SetAwaitingValidationOrderStatusCommand, bool>
{
public SetAwaitingValidationIdentifiedOrderStatusCommandHandler(IMediator mediator, IRequestManager requestManager) : base(mediator, requestManager)
public SetAwaitingValidationIdentifiedOrderStatusCommandHandler(
IMediator mediator,
IRequestManager requestManager,
ILogger<IdentifiedCommandHandler<SetAwaitingValidationOrderStatusCommand, bool>> logger)
: base(mediator, requestManager, logger)
{
}


+ 6
- 1
src/Services/Ordering/Ordering.API/Application/Commands/SetPaidOrderStatusCommandHandler.cs View File

@ -2,6 +2,7 @@
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency;
using Microsoft.Extensions.Logging;
using System.Threading;
using System.Threading.Tasks;
@ -43,7 +44,11 @@ namespace Ordering.API.Application.Commands
// Use for Idempotency in Command process
public class SetPaidIdentifiedOrderStatusCommandHandler : IdentifiedCommandHandler<SetPaidOrderStatusCommand, bool>
{
public SetPaidIdentifiedOrderStatusCommandHandler(IMediator mediator, IRequestManager requestManager) : base(mediator, requestManager)
public SetPaidIdentifiedOrderStatusCommandHandler(
IMediator mediator,
IRequestManager requestManager,
ILogger<IdentifiedCommandHandler<SetPaidOrderStatusCommand, bool>> logger)
: base(mediator, requestManager, logger)
{
}


+ 6
- 1
src/Services/Ordering/Ordering.API/Application/Commands/SetStockConfirmedOrderStatusCommandHandler.cs View File

@ -2,6 +2,7 @@
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency;
using Microsoft.Extensions.Logging;
using System.Threading;
using System.Threading.Tasks;
@ -43,7 +44,11 @@ namespace Ordering.API.Application.Commands
// Use for Idempotency in Command process
public class SetStockConfirmedOrderStatusIdenfifiedCommandHandler : IdentifiedCommandHandler<SetStockConfirmedOrderStatusCommand, bool>
{
public SetStockConfirmedOrderStatusIdenfifiedCommandHandler(IMediator mediator, IRequestManager requestManager) : base(mediator, requestManager)
public SetStockConfirmedOrderStatusIdenfifiedCommandHandler(
IMediator mediator,
IRequestManager requestManager,
ILogger<IdentifiedCommandHandler<SetStockConfirmedOrderStatusCommand, bool>> logger)
: base(mediator, requestManager, logger)
{
}


+ 6
- 1
src/Services/Ordering/Ordering.API/Application/Commands/SetStockRejectedOrderStatusCommandHandler.cs View File

@ -2,6 +2,7 @@
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency;
using Microsoft.Extensions.Logging;
using System.Threading;
using System.Threading.Tasks;
@ -44,7 +45,11 @@ namespace Ordering.API.Application.Commands
// Use for Idempotency in Command process
public class SetStockRejectedOrderStatusIdenfifiedCommandHandler : IdentifiedCommandHandler<SetStockRejectedOrderStatusCommand, bool>
{
public SetStockRejectedOrderStatusIdenfifiedCommandHandler(IMediator mediator, IRequestManager requestManager) : base(mediator, requestManager)
public SetStockRejectedOrderStatusIdenfifiedCommandHandler(
IMediator mediator,
IRequestManager requestManager,
ILogger<IdentifiedCommandHandler<SetStockRejectedOrderStatusCommand, bool>> logger)
: base(mediator, requestManager, logger)
{
}


+ 8
- 3
src/Services/Ordering/Ordering.API/Application/Commands/ShipOrderCommandHandler.cs View File

@ -2,6 +2,7 @@
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency;
using Microsoft.Extensions.Logging;
using System.Threading;
using System.Threading.Tasks;
@ -9,7 +10,7 @@ namespace Ordering.API.Application.Commands
{
// Regular CommandHandler
public class ShipOrderCommandHandler : IRequestHandler<ShipOrderCommand, bool>
{
{
private readonly IOrderRepository _orderRepository;
public ShipOrderCommandHandler(IOrderRepository orderRepository)
@ -26,7 +27,7 @@ namespace Ordering.API.Application.Commands
public async Task<bool> Handle(ShipOrderCommand command, CancellationToken cancellationToken)
{
var orderToUpdate = await _orderRepository.GetAsync(command.OrderNumber);
if(orderToUpdate == null)
if (orderToUpdate == null)
{
return false;
}
@ -40,7 +41,11 @@ namespace Ordering.API.Application.Commands
// Use for Idempotency in Command process
public class ShipOrderIdentifiedCommandHandler : IdentifiedCommandHandler<ShipOrderCommand, bool>
{
public ShipOrderIdentifiedCommandHandler(IMediator mediator, IRequestManager requestManager) : base(mediator, requestManager)
public ShipOrderIdentifiedCommandHandler(
IMediator mediator,
IRequestManager requestManager,
ILogger<IdentifiedCommandHandler<ShipOrderCommand, bool>> logger)
: base(mediator, requestManager, logger)
{
}


+ 10
- 9
src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/BuyerAndPaymentMethodVerified/UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler.cs View File

@ -8,16 +8,16 @@ using System.Threading.Tasks;
namespace Ordering.API.Application.DomainEventHandlers.BuyerAndPaymentMethodVerified
{
public class UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler
public class UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler
: INotificationHandler<BuyerAndPaymentMethodVerifiedDomainEvent>
{
private readonly IOrderRepository _orderRepository;
private readonly ILoggerFactory _logger;
private readonly IOrderRepository _orderRepository;
private readonly ILoggerFactory _logger;
public UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler(
IOrderRepository orderRepository, ILoggerFactory logger)
IOrderRepository orderRepository, ILoggerFactory logger)
{
_orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository));
_orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
@ -28,10 +28,11 @@ namespace Ordering.API.Application.DomainEventHandlers.BuyerAndPaymentMethodVeri
{
var orderToUpdate = await _orderRepository.GetAsync(buyerPaymentMethodVerifiedEvent.OrderId);
orderToUpdate.SetBuyerId(buyerPaymentMethodVerifiedEvent.Buyer.Id);
orderToUpdate.SetPaymentId(buyerPaymentMethodVerifiedEvent.Payment.Id);
orderToUpdate.SetPaymentId(buyerPaymentMethodVerifiedEvent.Payment.Id);
_logger.CreateLogger(nameof(UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler))
.LogTrace($"Order with Id: {buyerPaymentMethodVerifiedEvent.OrderId} has been successfully updated with a payment method id: { buyerPaymentMethodVerifiedEvent.Payment.Id }");
_logger.CreateLogger<UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler>()
.LogTrace("Order with Id: {OrderId} has been successfully updated with a payment method {PaymentMethod} ({Id})",
buyerPaymentMethodVerifiedEvent.OrderId, nameof(buyerPaymentMethodVerifiedEvent.Payment), buyerPaymentMethodVerifiedEvent.Payment.Id);
}
}
}
}

+ 3
- 3
src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderCancelled/OrderCancelledDomainEventHandler.cs View File

@ -33,9 +33,9 @@ namespace Ordering.API.Application.DomainEventHandlers.OrderCancelled
public async Task Handle(OrderCancelledDomainEvent orderCancelledDomainEvent, CancellationToken cancellationToken)
{
_logger.CreateLogger(nameof(OrderCancelledDomainEvent))
.LogTrace($"Order with Id: {orderCancelledDomainEvent.Order.Id} has been successfully updated with " +
$"a status order id: {OrderStatus.Shipped.Id}");
_logger.CreateLogger<OrderCancelledDomainEvent>()
.LogTrace("Order with Id: {OrderId} has been successfully updated to status {Status} ({Id})",
orderCancelledDomainEvent.Order.Id, nameof(OrderStatus.Cancelled), OrderStatus.Cancelled.Id);
var order = await _orderRepository.GetAsync(orderCancelledDomainEvent.Order.Id);
var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value.ToString());


+ 3
- 3
src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderGracePeriodConfirmed/OrderStatusChangedToAwaitingValidationDomainEventHandler.cs View File

@ -33,9 +33,9 @@
public async Task Handle(OrderStatusChangedToAwaitingValidationDomainEvent orderStatusChangedToAwaitingValidationDomainEvent, CancellationToken cancellationToken)
{
_logger.CreateLogger(nameof(OrderStatusChangedToAwaitingValidationDomainEvent))
.LogTrace($"Order with Id: {orderStatusChangedToAwaitingValidationDomainEvent.OrderId} has been successfully updated with " +
$"a status order id: {OrderStatus.AwaitingValidation.Id}");
_logger.CreateLogger<OrderStatusChangedToAwaitingValidationDomainEvent>()
.LogTrace("Order with Id: {OrderId} has been successfully updated to status {Status} ({Id})",
orderStatusChangedToAwaitingValidationDomainEvent.OrderId, nameof(OrderStatus.AwaitingValidation), OrderStatus.AwaitingValidation.Id);
var order = await _orderRepository.GetAsync(orderStatusChangedToAwaitingValidationDomainEvent.OrderId);


+ 3
- 3
src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderPaid/OrderStatusChangedToPaidDomainEventHandler.cs View File

@ -35,9 +35,9 @@
public async Task Handle(OrderStatusChangedToPaidDomainEvent orderStatusChangedToPaidDomainEvent, CancellationToken cancellationToken)
{
_logger.CreateLogger(nameof(OrderStatusChangedToPaidDomainEventHandler))
.LogTrace($"Order with Id: {orderStatusChangedToPaidDomainEvent.OrderId} has been successfully updated with " +
$"a status order id: {OrderStatus.Paid.Id}");
_logger.CreateLogger<OrderStatusChangedToPaidDomainEventHandler>()
.LogTrace("Order with Id: {OrderId} has been successfully updated to status {Status} ({Id})",
orderStatusChangedToPaidDomainEvent.OrderId, nameof(OrderStatus.Paid), OrderStatus.Paid.Id);
var order = await _orderRepository.GetAsync(orderStatusChangedToPaidDomainEvent.OrderId);
var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value.ToString());


+ 3
- 3
src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderShipped/OrderShippedDomainEventHandler.cs View File

@ -33,9 +33,9 @@ namespace Ordering.API.Application.DomainEventHandlers.OrderShipped
public async Task Handle(OrderShippedDomainEvent orderShippedDomainEvent, CancellationToken cancellationToken)
{
_logger.CreateLogger(nameof(OrderShippedDomainEvent))
.LogTrace($"Order with Id: {orderShippedDomainEvent.Order.Id} has been successfully updated with " +
$"a status order id: {OrderStatus.Shipped.Id}");
_logger.CreateLogger<OrderShippedDomainEvent>()
.LogTrace("Order with Id: {OrderId} has been successfully updated to status {Status} ({Id})",
orderShippedDomainEvent.Order.Id, nameof(OrderStatus.Shipped), OrderStatus.Shipped.Id);
var order = await _orderRepository.GetAsync(orderShippedDomainEvent.Order.Id);
var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value.ToString());


+ 3
- 1
src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler.cs View File

@ -61,7 +61,9 @@ namespace Ordering.API.Application.DomainEventHandlers.OrderStartedEvent
var orderStatusChangedTosubmittedIntegrationEvent = new OrderStatusChangedToSubmittedIntegrationEvent(orderStartedEvent.Order.Id, orderStartedEvent.Order.OrderStatus.Name, buyer.Name);
await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedTosubmittedIntegrationEvent);
_logger.CreateLogger(nameof(ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler)).LogTrace($"Buyer {buyerUpdated.Id} and related payment method were validated or updated for orderId: {orderStartedEvent.Order.Id}.");
_logger.CreateLogger<ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler>()
.LogTrace("Buyer {BuyerId} and related payment method were validated or updated for orderId: {OrderId}.",
buyerUpdated.Id, orderStartedEvent.Order.Id);
}
}
}

+ 3
- 3
src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStockConfirmed/OrderStatusChangedToStockConfirmedDomainEventHandler.cs View File

@ -33,9 +33,9 @@
public async Task Handle(OrderStatusChangedToStockConfirmedDomainEvent orderStatusChangedToStockConfirmedDomainEvent, CancellationToken cancellationToken)
{
_logger.CreateLogger(nameof(OrderStatusChangedToStockConfirmedDomainEventHandler))
.LogTrace($"Order with Id: {orderStatusChangedToStockConfirmedDomainEvent.OrderId} has been successfully updated with " +
$"a status order id: {OrderStatus.StockConfirmed.Id}");
_logger.CreateLogger<OrderStatusChangedToStockConfirmedDomainEventHandler>()
.LogTrace("Order with Id: {OrderId} has been successfully updated to status {Status} ({Id})",
orderStatusChangedToStockConfirmedDomainEvent.OrderId, nameof(OrderStatus.StockConfirmed), OrderStatus.StockConfirmed.Id);
var order = await _orderRepository.GetAsync(orderStatusChangedToStockConfirmedDomainEvent.OrderId);
var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value.ToString());


+ 24
- 3
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/GracePeriodConfirmedIntegrationEventHandler.cs View File

@ -1,8 +1,12 @@
using MediatR;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.Services.Ordering.API;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Microsoft.Extensions.Logging;
using Ordering.API.Application.Behaviors;
using Ordering.API.Application.Commands;
using Ordering.API.Application.IntegrationEvents.Events;
using Serilog.Context;
using System.Threading.Tasks;
namespace Ordering.API.Application.IntegrationEvents.EventHandling
@ -10,10 +14,14 @@ namespace Ordering.API.Application.IntegrationEvents.EventHandling
public class GracePeriodConfirmedIntegrationEventHandler : IIntegrationEventHandler<GracePeriodConfirmedIntegrationEvent>
{
private readonly IMediator _mediator;
private readonly ILogger<GracePeriodConfirmedIntegrationEventHandler> _logger;
public GracePeriodConfirmedIntegrationEventHandler(IMediator mediator)
public GracePeriodConfirmedIntegrationEventHandler(
IMediator mediator,
ILogger<GracePeriodConfirmedIntegrationEventHandler> logger)
{
_mediator = mediator;
_logger = logger ?? throw new System.ArgumentNullException(nameof(logger));
}
/// <summary>
@ -26,8 +34,21 @@ namespace Ordering.API.Application.IntegrationEvents.EventHandling
/// <returns></returns>
public async Task Handle(GracePeriodConfirmedIntegrationEvent @event)
{
var command = new SetAwaitingValidationOrderStatusCommand(@event.OrderId);
await _mediator.Send(command);
using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}"))
{
_logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event);
var command = new SetAwaitingValidationOrderStatusCommand(@event.OrderId);
_logger.LogInformation(
"----- Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})",
command.GetGenericTypeName(),
nameof(command.OrderNumber),
command.OrderNumber,
command);
await _mediator.Send(command);
}
}
}
}

+ 24
- 3
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentFailedIntegrationEventHandler.cs View File

@ -2,9 +2,13 @@
{
using MediatR;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.Services.Ordering.API;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Microsoft.Extensions.Logging;
using Ordering.API.Application.Behaviors;
using Ordering.API.Application.Commands;
using Ordering.API.Application.IntegrationEvents.Events;
using Serilog.Context;
using System;
using System.Threading.Tasks;
@ -12,16 +16,33 @@
IIntegrationEventHandler<OrderPaymentFailedIntegrationEvent>
{
private readonly IMediator _mediator;
private readonly ILogger<OrderPaymentFailedIntegrationEventHandler> _logger;
public OrderPaymentFailedIntegrationEventHandler(IMediator mediator)
public OrderPaymentFailedIntegrationEventHandler(
IMediator mediator,
ILogger<OrderPaymentFailedIntegrationEventHandler> logger)
{
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task Handle(OrderPaymentFailedIntegrationEvent @event)
{
var command = new CancelOrderCommand(@event.OrderId);
await _mediator.Send(command);
using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}"))
{
_logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event);
var command = new CancelOrderCommand(@event.OrderId);
_logger.LogInformation(
"----- Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})",
command.GetGenericTypeName(),
nameof(command.OrderNumber),
command.OrderNumber,
command);
await _mediator.Send(command);
}
}
}
}

+ 24
- 3
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentSuccededIntegrationEventHandler.cs View File

@ -2,9 +2,13 @@
{
using MediatR;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.Services.Ordering.API;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Microsoft.Extensions.Logging;
using Ordering.API.Application.Behaviors;
using Ordering.API.Application.Commands;
using Ordering.API.Application.IntegrationEvents.Events;
using Serilog.Context;
using System;
using System.Threading.Tasks;
@ -12,16 +16,33 @@
IIntegrationEventHandler<OrderPaymentSuccededIntegrationEvent>
{
private readonly IMediator _mediator;
private readonly ILogger<OrderPaymentSuccededIntegrationEventHandler> _logger;
public OrderPaymentSuccededIntegrationEventHandler(IMediator mediator)
public OrderPaymentSuccededIntegrationEventHandler(
IMediator mediator,
ILogger<OrderPaymentSuccededIntegrationEventHandler> logger)
{
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task Handle(OrderPaymentSuccededIntegrationEvent @event)
{
var command = new SetPaidOrderStatusCommand(@event.OrderId);
await _mediator.Send(command);
using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}"))
{
_logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event);
var command = new SetPaidOrderStatusCommand(@event.OrderId);
_logger.LogInformation(
"----- Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})",
command.GetGenericTypeName(),
nameof(command.OrderNumber),
command.OrderNumber,
command);
await _mediator.Send(command);
}
}
}
}

+ 25
- 4
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockConfirmedIntegrationEventHandler.cs View File

@ -7,21 +7,42 @@
using MediatR;
using System;
using Ordering.API.Application.Commands;
using Microsoft.Extensions.Logging;
using Serilog.Context;
using Microsoft.eShopOnContainers.Services.Ordering.API;
using Ordering.API.Application.Behaviors;
public class OrderStockConfirmedIntegrationEventHandler :
public class OrderStockConfirmedIntegrationEventHandler :
IIntegrationEventHandler<OrderStockConfirmedIntegrationEvent>
{
private readonly IMediator _mediator;
private readonly ILogger<OrderStockConfirmedIntegrationEventHandler> _logger;
public OrderStockConfirmedIntegrationEventHandler(IMediator mediator)
public OrderStockConfirmedIntegrationEventHandler(
IMediator mediator,
ILogger<OrderStockConfirmedIntegrationEventHandler> logger)
{
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task Handle(OrderStockConfirmedIntegrationEvent @event)
{
var command = new SetStockConfirmedOrderStatusCommand(@event.OrderId);
await _mediator.Send(command);
using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}"))
{
_logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event);
var command = new SetStockConfirmedOrderStatusCommand(@event.OrderId);
_logger.LogInformation(
"----- Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})",
command.GetGenericTypeName(),
nameof(command.OrderNumber),
command.OrderNumber,
command);
await _mediator.Send(command);
}
}
}
}

+ 28
- 7
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockRejectedIntegrationEventHandler.cs View File

@ -7,25 +7,46 @@
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using MediatR;
using Ordering.API.Application.Commands;
using Microsoft.Extensions.Logging;
using Serilog.Context;
using Microsoft.eShopOnContainers.Services.Ordering.API;
using Ordering.API.Application.Behaviors;
public class OrderStockRejectedIntegrationEventHandler : IIntegrationEventHandler<OrderStockRejectedIntegrationEvent>
{
private readonly IMediator _mediator;
private readonly ILogger<OrderStockRejectedIntegrationEventHandler> _logger;
public OrderStockRejectedIntegrationEventHandler(IMediator mediator)
public OrderStockRejectedIntegrationEventHandler(
IMediator mediator,
ILogger<OrderStockRejectedIntegrationEventHandler> logger)
{
_mediator = mediator;
_logger = logger ?? throw new System.ArgumentNullException(nameof(logger));
}
public async Task Handle(OrderStockRejectedIntegrationEvent @event)
{
var orderStockRejectedItems = @event.OrderStockItems
.FindAll(c => !c.HasStock)
.Select(c => c.ProductId)
.ToList();
using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}"))
{
_logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event);
var command = new SetStockRejectedOrderStatusCommand(@event.OrderId, orderStockRejectedItems);
await _mediator.Send(command);
var orderStockRejectedItems = @event.OrderStockItems
.FindAll(c => !c.HasStock)
.Select(c => c.ProductId)
.ToList();
var command = new SetStockRejectedOrderStatusCommand(@event.OrderId, orderStockRejectedItems);
_logger.LogInformation(
"----- Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})",
command.GetGenericTypeName(),
nameof(command.OrderNumber),
command.OrderNumber,
command);
await _mediator.Send(command);
}
}
}
}

+ 50
- 21
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/UserCheckoutAcceptedIntegrationEventHandler.cs View File

@ -5,48 +5,77 @@ using System.Threading.Tasks;
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands;
using Microsoft.Extensions.Logging;
using Ordering.API.Application.IntegrationEvents.Events;
using Serilog.Context;
using Microsoft.eShopOnContainers.Services.Ordering.API;
using Ordering.API.Application.Behaviors;
namespace Ordering.API.Application.IntegrationEvents.EventHandling
{
public class UserCheckoutAcceptedIntegrationEventHandler : IIntegrationEventHandler<UserCheckoutAcceptedIntegrationEvent>
{
private readonly IMediator _mediator;
private readonly ILoggerFactory _logger;
private readonly ILogger<UserCheckoutAcceptedIntegrationEventHandler> _logger;
public UserCheckoutAcceptedIntegrationEventHandler(IMediator mediator,
ILoggerFactory logger)
public UserCheckoutAcceptedIntegrationEventHandler(
IMediator mediator,
ILogger<UserCheckoutAcceptedIntegrationEventHandler> logger)
{
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
/// <summary>
/// Integration event handler which starts the create order process
/// </summary>
/// <param name="eventMsg">
/// <param name="@event">
/// Integration event message which is sent by the
/// basket.api once it has successfully process the
/// order items.
/// </param>
/// <returns></returns>
public async Task Handle(UserCheckoutAcceptedIntegrationEvent eventMsg)
public async Task Handle(UserCheckoutAcceptedIntegrationEvent @event)
{
var result = false;
if (eventMsg.RequestId != Guid.Empty)
using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}"))
{
var createOrderCommand = new CreateOrderCommand(eventMsg.Basket.Items, eventMsg.UserId, eventMsg.UserName, eventMsg.City, eventMsg.Street,
eventMsg.State, eventMsg.Country, eventMsg.ZipCode,
eventMsg.CardNumber, eventMsg.CardHolderName, eventMsg.CardExpiration,
eventMsg.CardSecurityNumber, eventMsg.CardTypeId);
var requestCreateOrder = new IdentifiedCommand<CreateOrderCommand, bool>(createOrderCommand, eventMsg.RequestId);
result = await _mediator.Send(requestCreateOrder);
}
_logger.CreateLogger(nameof(UserCheckoutAcceptedIntegrationEventHandler))
.LogTrace(result ? $"UserCheckoutAccepted integration event has been received and a create new order process is started with requestId: {eventMsg.RequestId}" :
$"UserCheckoutAccepted integration event has been received but a new order process has failed with requestId: {eventMsg.RequestId}");
_logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event);
var result = false;
if (@event.RequestId != Guid.Empty)
{
using (LogContext.PushProperty("IdentifiedCommandId", @event.RequestId))
{
var createOrderCommand = new CreateOrderCommand(@event.Basket.Items, @event.UserId, @event.UserName, @event.City, @event.Street,
@event.State, @event.Country, @event.ZipCode,
@event.CardNumber, @event.CardHolderName, @event.CardExpiration,
@event.CardSecurityNumber, @event.CardTypeId);
var requestCreateOrder = new IdentifiedCommand<CreateOrderCommand, bool>(createOrderCommand, @event.RequestId);
_logger.LogInformation(
"----- Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})",
requestCreateOrder.GetGenericTypeName(),
nameof(requestCreateOrder.Id),
requestCreateOrder.Id,
requestCreateOrder);
result = await _mediator.Send(requestCreateOrder);
if (result)
{
_logger.LogInformation("----- CreateOrderCommand suceeded - RequestId: {RequestId}", @event.RequestId);
}
else
{
_logger.LogWarning("CreateOrderCommand failed - RequestId: {RequestId}", @event.RequestId);
}
}
}
else
{
_logger.LogWarning("Invalid IntegrationEvent - RequestId is missing - {@IntegrationEvent}", @event);
}
}
}
}
}

+ 16
- 4
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/OrderingIntegrationEventService.cs View File

@ -5,7 +5,9 @@ using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF;
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services;
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Utilities;
using Microsoft.eShopOnContainers.Services.Ordering.API;
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure;
using Microsoft.Extensions.Logging;
using System;
using System.Data.Common;
using System.Diagnostics;
@ -21,39 +23,49 @@ namespace Ordering.API.Application.IntegrationEvents
private readonly OrderingContext _orderingContext;
private readonly IntegrationEventLogContext _eventLogContext;
private readonly IIntegrationEventLogService _eventLogService;
private readonly ILogger<OrderingIntegrationEventService> _logger;
public OrderingIntegrationEventService(IEventBus eventBus,
public OrderingIntegrationEventService(IEventBus eventBus,
OrderingContext orderingContext,
IntegrationEventLogContext eventLogContext,
Func<DbConnection, IIntegrationEventLogService> integrationEventLogServiceFactory)
Func<DbConnection, IIntegrationEventLogService> integrationEventLogServiceFactory,
ILogger<OrderingIntegrationEventService> logger)
{
_orderingContext = orderingContext ?? throw new ArgumentNullException(nameof(orderingContext));
_eventLogContext = eventLogContext ?? throw new ArgumentNullException(nameof(eventLogContext));
_integrationEventLogServiceFactory = integrationEventLogServiceFactory ?? throw new ArgumentNullException(nameof(integrationEventLogServiceFactory));
_eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus));
_eventLogService = _integrationEventLogServiceFactory(_orderingContext.Database.GetDbConnection());
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task PublishEventsThroughEventBusAsync()
{
var pendindLogEvents = await _eventLogService.RetrieveEventLogsPendingToPublishAsync();
foreach (var logEvt in pendindLogEvents)
{
_logger.LogInformation("----- Publishing integration event: {IntegrationEventId} from {AppName} - ({@IntegrationEvent})", logEvt.EventId, Program.AppName, logEvt.IntegrationEvent);
try
{
await _eventLogService.MarkEventAsInProgressAsync(logEvt.EventId);
_eventBus.Publish(logEvt.IntegrationEvent);
await _eventLogService.MarkEventAsPublishedAsync(logEvt.EventId);
}
catch (Exception)
catch (Exception ex)
{
_logger.LogError(ex, "ERROR publishing integration event: {IntegrationEventId} from {AppName}", logEvt.EventId, Program.AppName);
await _eventLogService.MarkEventAsFailedAsync(logEvt.EventId);
}
}
}
}
public async Task AddAndSaveEventAsync(IntegrationEvent evt)
{
_logger.LogInformation("----- Enqueuing integration event {IntegrationEventId} to repository ({@IntegrationEvent})", evt.Id, evt);
await _eventLogService.SaveEventAsync(evt, _orderingContext.GetCurrentTransaction.GetDbTransaction());
}
}


+ 32
- 1
src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs View File

@ -4,6 +4,8 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands;
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Queries;
using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services;
using Microsoft.Extensions.Logging;
using Ordering.API.Application.Behaviors;
using Ordering.API.Application.Commands;
using System;
using System.Collections.Generic;
@ -20,12 +22,18 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers
private readonly IMediator _mediator;
private readonly IOrderQueries _orderQueries;
private readonly IIdentityService _identityService;
private readonly ILogger<OrdersController> _logger;
public OrdersController(IMediator mediator, IOrderQueries orderQueries, IIdentityService identityService)
public OrdersController(
IMediator mediator,
IOrderQueries orderQueries,
IIdentityService identityService,
ILogger<OrdersController> logger)
{
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
_orderQueries = orderQueries ?? throw new ArgumentNullException(nameof(orderQueries));
_identityService = identityService ?? throw new ArgumentNullException(nameof(identityService));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
[Route("cancel")]
@ -39,6 +47,14 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers
if (Guid.TryParse(requestId, out Guid guid) && guid != Guid.Empty)
{
var requestCancelOrder = new IdentifiedCommand<CancelOrderCommand, bool>(command, guid);
_logger.LogInformation(
"----- Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})",
requestCancelOrder.GetGenericTypeName(),
nameof(requestCancelOrder.Command.OrderNumber),
requestCancelOrder.Command.OrderNumber,
requestCancelOrder);
commandResult = await _mediator.Send(requestCancelOrder);
}
@ -61,6 +77,14 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers
if (Guid.TryParse(requestId, out Guid guid) && guid != Guid.Empty)
{
var requestShipOrder = new IdentifiedCommand<ShipOrderCommand, bool>(command, guid);
_logger.LogInformation(
"----- Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})",
requestShipOrder.GetGenericTypeName(),
nameof(requestShipOrder.Command.OrderNumber),
requestShipOrder.Command.OrderNumber,
requestShipOrder);
commandResult = await _mediator.Send(requestShipOrder);
}
@ -114,6 +138,13 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers
[HttpPost]
public async Task<ActionResult<OrderDraftDTO>> CreateOrderDraftFromBasketDataAsync([FromBody] CreateOrderDraftCommand createOrderDraftCommand)
{
_logger.LogInformation(
"----- Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})",
createOrderDraftCommand.GetGenericTypeName(),
nameof(createOrderDraftCommand.BuyerId),
createOrderDraftCommand.BuyerId,
createOrderDraftCommand);
return await _mediator.Send(createOrderDraftCommand);
}
}

+ 0
- 1
src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs View File

@ -7,7 +7,6 @@ using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands;
using Ordering.API.Application.Behaviors;
using Ordering.API.Application.DomainEventHandlers.OrderStartedEvent;
using Ordering.API.Application.Validations;
using Ordering.API.Infrastructure.Behaviors;
namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.AutofacModules
{


+ 5
- 5
src/Services/Ordering/Ordering.API/Infrastructure/OrderingContextSeed.cs View File

@ -74,7 +74,7 @@
}
catch (Exception ex)
{
log.LogError(ex.Message);
log.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message);
return GetPredefinedCardTypes();
}
@ -82,7 +82,7 @@
return File.ReadAllLines(csvFileCardTypes)
.Skip(1) // skip header column
.SelectTry(x => CreateCardType(x, ref id))
.OnCaughtException(ex => { log.LogError(ex.Message); return null; })
.OnCaughtException(ex => { log.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); return null; })
.Where(x => x != null);
}
@ -118,7 +118,7 @@
}
catch (Exception ex)
{
log.LogError(ex.Message);
log.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message);
return GetPredefinedOrderStatus();
}
@ -126,7 +126,7 @@
return File.ReadAllLines(csvFileOrderStatus)
.Skip(1) // skip header row
.SelectTry(x => CreateOrderStatus(x, ref id))
.OnCaughtException(ex => { log.LogError(ex.Message); return null; })
.OnCaughtException(ex => { log.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); return null; })
.Where(x => x != null);
}
@ -182,7 +182,7 @@
sleepDurationProvider: retry => TimeSpan.FromSeconds(5),
onRetry: (exception, timeSpan, retry, ctx) =>
{
logger.LogTrace($"[{prefix}] Exception {exception.GetType().Name} with message ${exception.Message} detected on attempt {retry} of {retries}");
logger.LogWarning(exception, "[{prefix}] Exception {ExceptionType} with message {Message} detected on attempt {retry} of {retries}", prefix, exception.GetType().Name, exception.Message, retry, retries);
}
);
}


+ 11
- 7
src/Services/Ordering/Ordering.API/Ordering.API.csproj View File

@ -5,6 +5,7 @@
<UserSecretsId>aspnet-Ordering.API-20161122013547</UserSecretsId>
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback>
<DockerComposeProjectPath>..\..\..\..\docker-compose.dcproj</DockerComposeProjectPath>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
@ -27,30 +28,33 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="AspNetCore.HealthChecks.SqlServer" Version="2.2.0" />
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="2.2.2" />
<PackageReference Include="AspNetCore.HealthChecks.AzureServiceBus" Version="2.2.0" />
<PackageReference Include="AspNetCore.HealthChecks.Rabbitmq" Version="2.2.0" />
<PackageReference Include="AspNetCore.HealthChecks.SqlServer" Version="2.2.0" />
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="2.2.2" />
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="4.2.1" />
<PackageReference Include="Dapper" Version="1.50.7" />
<PackageReference Include="FluentValidation.AspNetCore" Version="7.5.0" />
<PackageReference Include="MediatR" Version="5.1.0" />
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="5.1.0" />
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="4.2.1" />
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.2.1" />
<PackageReference Include="Microsoft.ApplicationInsights.DependencyCollector" Version="2.6.1" />
<PackageReference Include="Microsoft.ApplicationInsights.Kubernetes" Version="1.0.2" />
<PackageReference Include="Microsoft.ApplicationInsights.ServiceFabric" Version="2.2.2" />
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.HealthChecks" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.HealthChecks" Version="1.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.AzureKeyVault" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Logging.AzureAppServices" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.2.0" />
<PackageReference Include="MediatR" Version="5.1.0" />
<PackageReference Include="Polly" Version="6.0.1" />
<PackageReference Include="Serilog.AspNetCore" Version="2.1.1" />
<PackageReference Include="Serilog.Enrichers.Environment" Version="2.1.2" />
<PackageReference Include="Serilog.Settings.Configuration" Version="3.0.1" />
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
<PackageReference Include="Serilog.Sinks.Seq" Version="4.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="3.0.0" />
<PackageReference Include="System.Reflection" Version="4.3.0" />
<PackageReference Include="Dapper" Version="1.50.7" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="Polly" Version="6.0.1" />
</ItemGroup>
<ItemGroup>
<None Update="Setup\*">


+ 70
- 39
src/Services/Ordering/Ordering.API/Program.cs View File

@ -15,10 +15,22 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API
{
public class Program
{
public static void Main(string[] args)
public static readonly string Namespace = typeof(Program).Namespace;
public static readonly string AppName = Namespace.Substring(Namespace.LastIndexOf('.', Namespace.LastIndexOf('.') - 1) + 1);
public static int Main(string[] args)
{
BuildWebHost(args)
.MigrateDbContext<OrderingContext>((context, services) =>
var configuration = GetConfiguration();
Log.Logger = CreateSerilogLogger(configuration);
try
{
Log.Information("Configuring web host ({ApplicationContext})...", AppName);
var host = BuildWebHost(configuration, args);
Log.Information("Applying migrations ({ApplicationContext})...", AppName);
host.MigrateDbContext<OrderingContext>((context, services) =>
{
var env = services.GetService<IHostingEnvironment>();
var settings = services.GetService<IOptions<OrderingSettings>>();
@ -28,47 +40,66 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API
.SeedAsync(context, env, settings, logger)
.Wait();
})
.MigrateDbContext<IntegrationEventLogContext>((_,__)=>{})
.Run();
.MigrateDbContext<IntegrationEventLogContext>((_, __) => { });
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();
}
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
private static IWebHost BuildWebHost(IConfiguration configuration, string[] args) =>
WebHost.CreateDefaultBuilder(args)
.CaptureStartupErrors(false)
.UseStartup<Startup>()
.UseApplicationInsights()
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureAppConfiguration((builderContext, config) =>
{
var builtConfig = config.Build();
.UseConfiguration(configuration)
.UseSerilog()
.Build();
var configurationBuilder = new ConfigurationBuilder();
if (Convert.ToBoolean(builtConfig["UseVault"]))
{
configurationBuilder.AddAzureKeyVault(
$"https://{builtConfig["Vault:Name"]}.vault.azure.net/",
builtConfig["Vault:ClientId"],
builtConfig["Vault:ClientSecret"]);
}
private static Serilog.ILogger CreateSerilogLogger(IConfiguration configuration)
{
var seqServerUrl = configuration["Serilog:SeqServerUrl"];
configurationBuilder.AddEnvironmentVariables();
return new LoggerConfiguration()
.MinimumLevel.Verbose()
.Enrich.WithProperty("ApplicationContext", AppName)
.Enrich.FromLogContext()
.WriteTo.Console()
.WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl)
.ReadFrom.Configuration(configuration)
.CreateLogger();
}
config.AddConfiguration(configurationBuilder.Build());
})
.ConfigureLogging((hostingContext, builder) =>
{
builder.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
builder.AddConsole();
builder.AddDebug();
builder.AddAzureWebAppDiagnostics();
})
.UseApplicationInsights()
.UseSerilog((builderContext, config) =>
{
config
.MinimumLevel.Information()
.Enrich.FromLogContext()
.WriteTo.Console();
})
.Build();
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<bool>("UseVault", false))
{
builder.AddAzureKeyVault(
$"https://{config["Vault:Name"]}.vault.azure.net/",
config["Vault:ClientId"],
config["Vault:ClientSecret"]);
}
return builder.Build();
}
}
}
}

+ 6
- 6
src/Services/Ordering/Ordering.API/Startup.cs View File

@ -74,14 +74,15 @@
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace);
//loggerFactory.AddAzureWebAppDiagnostics();
//loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace);
var pathBase = Configuration["PATH_BASE"];
if (!string.IsNullOrEmpty(pathBase))
{
loggerFactory.CreateLogger("init").LogDebug($"Using PATH BASE '{pathBase}'");
loggerFactory.CreateLogger<Startup>().LogDebug("Using PATH BASE '{pathBase}'", pathBase);
app.UsePathBase(pathBase);
}
}
app.UseCors("CorsPolicy");
@ -121,10 +122,9 @@
eventBus.Subscribe<OrderStockConfirmedIntegrationEvent, IIntegrationEventHandler<OrderStockConfirmedIntegrationEvent>>();
eventBus.Subscribe<OrderStockRejectedIntegrationEvent, IIntegrationEventHandler<OrderStockRejectedIntegrationEvent>>();
eventBus.Subscribe<OrderPaymentFailedIntegrationEvent, IIntegrationEventHandler<OrderPaymentFailedIntegrationEvent>>();
eventBus.Subscribe<OrderPaymentSuccededIntegrationEvent, IIntegrationEventHandler<OrderPaymentSuccededIntegrationEvent>>();
eventBus.Subscribe<OrderPaymentSuccededIntegrationEvent, IIntegrationEventHandler<OrderPaymentSuccededIntegrationEvent>>();
}
protected virtual void ConfigureAuth(IApplicationBuilder app)
{
if (Configuration.GetValue<bool>("UseLoadTest"))
@ -167,7 +167,7 @@
})
.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
//For further info see: http://docs.autofac.org/en/latest/integration/aspnetcore.html#controllers-as-services
services.AddCors(options =>
{


+ 9
- 6
src/Services/Ordering/Ordering.API/appsettings.json View File

@ -2,12 +2,15 @@
"ConnectionString": "Server=tcp:127.0.0.1,5433;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word;",
"IdentityUrl": "http://localhost:5105",
"UseCustomizationData": false,
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Trace",
"System": "Information",
"Microsoft": "Information"
"Serilog": {
"SeqServerUrl": null,
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"Microsoft.eShopOnContainers": "Information",
"System": "Warning"
}
}
},
"AzureServiceBusEnabled": false,


+ 3
- 0
src/Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj View File

@ -24,7 +24,10 @@
<PackageReference Include="Microsoft.AspNetCore.HealthChecks" Version="1.0.0" />
<PackageReference Include="AspNetCore.HealthChecks.SqlServer" Version="2.2.0" />
<PackageReference Include="Serilog.AspNetCore" Version="2.1.1" />
<PackageReference Include="Serilog.Enrichers.Environment" Version="2.1.2" />
<PackageReference Include="Serilog.Settings.Configuration" Version="3.0.1" />
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
<PackageReference Include="Serilog.Sinks.Seq" Version="4.0.0" />
</ItemGroup>
<ItemGroup>


+ 59
- 17
src/Services/Ordering/Ordering.BackgroundTasks/Program.cs View File

@ -1,33 +1,75 @@
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Serilog;
using System;
using System.IO;
namespace Ordering.BackgroundTasks
{
public class Program
{
public static void Main(string[] args)
public static readonly string Namespace = typeof(Program).Namespace;
public static readonly string AppName = Namespace;
public static int Main(string[] args)
{
BuildWebHost(args).Run();
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();
}
}
public static IWebHost BuildWebHost(string[] args) =>
private static IWebHost BuildWebHost(IConfiguration configuration, string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.ConfigureLogging((hostingContext, builder) =>
{
builder.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
builder.AddDebug();
builder.AddConsole();
})
.UseSerilog((builderContext, config) =>
{
config
.MinimumLevel.Information()
.Enrich.FromLogContext()
.WriteTo.Console();
})
.CaptureStartupErrors(false)
.UseStartup<Startup>()
.UseConfiguration(configuration)
.UseSerilog()
.Build();
private static Serilog.ILogger CreateSerilogLogger(IConfiguration configuration)
{
var seqServerUrl = configuration["Serilog:SeqServerUrl"];
return new LoggerConfiguration()
.MinimumLevel.Verbose()
.Enrich.WithProperty("ApplicationContext", AppName)
.Enrich.FromLogContext()
.WriteTo.Console()
.WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl)
.ReadFrom.Configuration(configuration)
.CreateLogger();
}
private static IConfiguration GetConfiguration()
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables();
return builder.Build();
}
}
}

+ 12
- 9
src/Services/Ordering/Ordering.BackgroundTasks/Tasks/GracePeriodManagerTask.cs View File

@ -20,9 +20,10 @@ namespace Ordering.BackgroundTasks.Tasks
private readonly BackgroundTaskSettings _settings;
private readonly IEventBus _eventBus;
public GracePeriodManagerService(IOptions<BackgroundTaskSettings> settings,
IEventBus eventBus,
ILogger<GracePeriodManagerService> logger)
public GracePeriodManagerService(
IOptions<BackgroundTaskSettings> settings,
IEventBus eventBus,
ILogger<GracePeriodManagerService> logger)
{
_settings = settings?.Value ?? throw new ArgumentNullException(nameof(settings));
_eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus));
@ -32,27 +33,27 @@ namespace Ordering.BackgroundTasks.Tasks
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogDebug($"GracePeriodManagerService is starting.");
_logger.LogDebug("GracePeriodManagerService is starting.");
stoppingToken.Register(() => _logger.LogDebug($"#1 GracePeriodManagerService background task is stopping."));
stoppingToken.Register(() => _logger.LogDebug("#1 GracePeriodManagerService background task is stopping."));
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogDebug($"GracePeriodManagerService background task is doing background work.");
_logger.LogDebug("GracePeriodManagerService background task is doing background work.");
CheckConfirmedGracePeriodOrders();
await Task.Delay(_settings.CheckUpdateTime, stoppingToken);
}
_logger.LogDebug($"GracePeriodManagerService background task is stopping.");
_logger.LogDebug("GracePeriodManagerService background task is stopping.");
await Task.CompletedTask;
}
private void CheckConfirmedGracePeriodOrders()
{
_logger.LogDebug($"Checking confirmed grace period orders");
_logger.LogDebug("Checking confirmed grace period orders");
var orderIds = GetConfirmedGracePeriodOrders();
@ -60,6 +61,8 @@ namespace Ordering.BackgroundTasks.Tasks
{
var confirmGracePeriodEvent = new GracePeriodConfirmedIntegrationEvent(orderId);
_logger.LogInformation("----- Publishing integration event: {IntegrationEventId} from {AppName} - ({@IntegrationEvent})", confirmGracePeriodEvent.Id, Program.AppName, confirmGracePeriodEvent);
_eventBus.Publish(confirmGracePeriodEvent);
}
}
@ -81,7 +84,7 @@ namespace Ordering.BackgroundTasks.Tasks
}
catch (SqlException exception)
{
_logger.LogCritical($"FATAL ERROR: Database connections could not be opened: {exception.Message}");
_logger.LogCritical(exception, "FATAL ERROR: Database connections could not be opened: {Message}", exception.Message);
}
}


+ 8
- 10
src/Services/Ordering/Ordering.BackgroundTasks/appsettings.json View File

@ -1,15 +1,13 @@
{
"ConnectionString": "Server=tcp:127.0.0.1,5433;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word;",
"Logging": {
"IncludeScopes": false,
"Debug": {
"LogLevel": {
"Default": "Debug"
}
},
"Console": {
"LogLevel": {
"Default": "Debug"
"Serilog": {
"SeqServerUrl": null,
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"Microsoft.eShopOnContainers": "Information",
"System": "Warning"
}
}
},


+ 16
- 7
src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs View File

@ -27,10 +27,12 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
private readonly IMediator _mediator;
private IDbContextTransaction _currentTransaction;
private OrderingContext(DbContextOptions<OrderingContext> options) : base (options) { }
private OrderingContext(DbContextOptions<OrderingContext> options) : base(options) { }
public IDbContextTransaction GetCurrentTransaction => _currentTransaction;
public bool HasActiveTransaction => _currentTransaction != null;
public OrderingContext(DbContextOptions<OrderingContext> options, IMediator mediator) : base(options)
{
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
@ -47,7 +49,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
modelBuilder.ApplyConfiguration(new OrderItemEntityTypeConfiguration());
modelBuilder.ApplyConfiguration(new CardTypeEntityTypeConfiguration());
modelBuilder.ApplyConfiguration(new OrderStatusEntityTypeConfiguration());
modelBuilder.ApplyConfiguration(new BuyerEntityTypeConfiguration());
modelBuilder.ApplyConfiguration(new BuyerEntityTypeConfiguration());
}
public async Task<bool> SaveEntitiesAsync(CancellationToken cancellationToken = default(CancellationToken))
@ -67,17 +69,24 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
return true;
}
public async Task BeginTransactionAsync()
public async Task<IDbContextTransaction> BeginTransactionAsync()
{
_currentTransaction = _currentTransaction ?? await Database.BeginTransactionAsync(IsolationLevel.ReadCommitted);
if (_currentTransaction != null) return null;
_currentTransaction = await Database.BeginTransactionAsync(IsolationLevel.ReadCommitted);
return _currentTransaction;
}
public async Task CommitTransactionAsync()
public async Task CommitTransactionAsync(IDbContextTransaction transaction)
{
if (transaction == null) throw new ArgumentNullException(nameof(transaction));
if (transaction != _currentTransaction) throw new InvalidOperationException($"Transaction {transaction.TransactionId} is not current");
try
{
await SaveChangesAsync();
_currentTransaction?.Commit();
transaction.Commit();
}
catch
{
@ -118,7 +127,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
var optionsBuilder = new DbContextOptionsBuilder<OrderingContext>()
.UseSqlServer("Server=.;Initial Catalog=Microsoft.eShopOnContainers.Services.OrderingDb;Integrated Security=true");
return new OrderingContext(optionsBuilder.Options,new NoMediator());
return new OrderingContext(optionsBuilder.Options, new NoMediator());
}
class NoMediator : IMediator


+ 15
- 4
src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToCancelledIntegrationEventHandler.cs View File

@ -1,6 +1,8 @@
using Microsoft.AspNetCore.SignalR;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.Extensions.Logging;
using Ordering.SignalrHub.IntegrationEvents.Events;
using Serilog.Context;
using System;
using System.Collections.Generic;
using System.Linq;
@ -11,18 +13,27 @@ namespace Ordering.SignalrHub.IntegrationEvents.EventHandling
public class OrderStatusChangedToCancelledIntegrationEventHandler : IIntegrationEventHandler<OrderStatusChangedToCancelledIntegrationEvent>
{
private readonly IHubContext<NotificationsHub> _hubContext;
private readonly ILogger<OrderStatusChangedToCancelledIntegrationEventHandler> _logger;
public OrderStatusChangedToCancelledIntegrationEventHandler(IHubContext<NotificationsHub> hubContext)
public OrderStatusChangedToCancelledIntegrationEventHandler(
IHubContext<NotificationsHub> hubContext,
ILogger<OrderStatusChangedToCancelledIntegrationEventHandler> logger)
{
_hubContext = hubContext ?? throw new ArgumentNullException(nameof(hubContext));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task Handle(OrderStatusChangedToCancelledIntegrationEvent @event)
{
await _hubContext.Clients
.Group(@event.BuyerName)
.SendAsync("UpdatedOrderState", new { OrderId = @event.OrderId, Status = @event.OrderStatus });
using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}"))
{
_logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event);
await _hubContext.Clients
.Group(@event.BuyerName)
.SendAsync("UpdatedOrderState", new { OrderId = @event.OrderId, Status = @event.OrderStatus });
}
}
}
}

+ 15
- 4
src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToPaidIntegrationEventHandler.cs View File

@ -1,6 +1,8 @@
using Microsoft.AspNetCore.SignalR;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.Extensions.Logging;
using Ordering.SignalrHub.IntegrationEvents.Events;
using Serilog.Context;
using System;
using System.Threading.Tasks;
@ -9,18 +11,27 @@ namespace Ordering.SignalrHub.IntegrationEvents.EventHandling
public class OrderStatusChangedToPaidIntegrationEventHandler : IIntegrationEventHandler<OrderStatusChangedToPaidIntegrationEvent>
{
private readonly IHubContext<NotificationsHub> _hubContext;
private readonly ILogger<OrderStatusChangedToPaidIntegrationEventHandler> _logger;
public OrderStatusChangedToPaidIntegrationEventHandler(IHubContext<NotificationsHub> hubContext)
public OrderStatusChangedToPaidIntegrationEventHandler(
IHubContext<NotificationsHub> hubContext,
ILogger<OrderStatusChangedToPaidIntegrationEventHandler> logger)
{
_hubContext = hubContext ?? throw new ArgumentNullException(nameof(hubContext));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task Handle(OrderStatusChangedToPaidIntegrationEvent @event)
{
await _hubContext.Clients
.Group(@event.BuyerName)
.SendAsync("UpdatedOrderState", new { OrderId = @event.OrderId, Status = @event.OrderStatus });
using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}"))
{
_logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event);
await _hubContext.Clients
.Group(@event.BuyerName)
.SendAsync("UpdatedOrderState", new { OrderId = @event.OrderId, Status = @event.OrderStatus });
}
}
}
}

+ 15
- 4
src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToShippedIntegrationEventHandler.cs View File

@ -1,6 +1,8 @@
using Microsoft.AspNetCore.SignalR;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.Extensions.Logging;
using Ordering.SignalrHub.IntegrationEvents.Events;
using Serilog.Context;
using System;
using System.Collections.Generic;
using System.Linq;
@ -11,18 +13,27 @@ namespace Ordering.SignalrHub.IntegrationEvents.EventHandling
public class OrderStatusChangedToShippedIntegrationEventHandler : IIntegrationEventHandler<OrderStatusChangedToShippedIntegrationEvent>
{
private readonly IHubContext<NotificationsHub> _hubContext;
private readonly ILogger<OrderStatusChangedToShippedIntegrationEventHandler> _logger;
public OrderStatusChangedToShippedIntegrationEventHandler(IHubContext<NotificationsHub> hubContext)
public OrderStatusChangedToShippedIntegrationEventHandler(
IHubContext<NotificationsHub> hubContext,
ILogger<OrderStatusChangedToShippedIntegrationEventHandler> logger)
{
_hubContext = hubContext ?? throw new ArgumentNullException(nameof(hubContext));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task Handle(OrderStatusChangedToShippedIntegrationEvent @event)
{
await _hubContext.Clients
.Group(@event.BuyerName)
.SendAsync("UpdatedOrderState", new { OrderId = @event.OrderId, Status = @event.OrderStatus });
using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}"))
{
_logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event);
await _hubContext.Clients
.Group(@event.BuyerName)
.SendAsync("UpdatedOrderState", new { OrderId = @event.OrderId, Status = @event.OrderStatus });
}
}
}
}

+ 15
- 4
src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToStockConfirmedIntegrationEventHandler.cs View File

@ -1,6 +1,8 @@
using Microsoft.AspNetCore.SignalR;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.Extensions.Logging;
using Ordering.SignalrHub.IntegrationEvents.Events;
using Serilog.Context;
using System;
using System.Collections.Generic;
using System.Linq;
@ -12,18 +14,27 @@ namespace Ordering.SignalrHub.IntegrationEvents.EventHandling
IIntegrationEventHandler<OrderStatusChangedToStockConfirmedIntegrationEvent>
{
private readonly IHubContext<NotificationsHub> _hubContext;
private readonly ILogger<OrderStatusChangedToStockConfirmedIntegrationEventHandler> _logger;
public OrderStatusChangedToStockConfirmedIntegrationEventHandler(IHubContext<NotificationsHub> hubContext)
public OrderStatusChangedToStockConfirmedIntegrationEventHandler(
IHubContext<NotificationsHub> hubContext,
ILogger<OrderStatusChangedToStockConfirmedIntegrationEventHandler> logger)
{
_hubContext = hubContext ?? throw new ArgumentNullException(nameof(hubContext));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task Handle(OrderStatusChangedToStockConfirmedIntegrationEvent @event)
{
await _hubContext.Clients
.Group(@event.BuyerName)
.SendAsync("UpdatedOrderState", new { OrderId = @event.OrderId, Status = @event.OrderStatus });
using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}"))
{
_logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event);
await _hubContext.Clients
.Group(@event.BuyerName)
.SendAsync("UpdatedOrderState", new { OrderId = @event.OrderId, Status = @event.OrderStatus });
}
}
}
}

+ 15
- 4
src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToSubmittedIntegrationEventHandler.cs View File

@ -1,6 +1,8 @@
using Microsoft.AspNetCore.SignalR;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.Extensions.Logging;
using Ordering.SignalrHub.IntegrationEvents.Events;
using Serilog.Context;
using System;
using System.Collections.Generic;
using System.Linq;
@ -12,18 +14,27 @@ namespace Ordering.SignalrHub.IntegrationEvents.EventHandling
IIntegrationEventHandler<OrderStatusChangedToSubmittedIntegrationEvent>
{
private readonly IHubContext<NotificationsHub> _hubContext;
private readonly ILogger<OrderStatusChangedToSubmittedIntegrationEventHandler> _logger;
public OrderStatusChangedToSubmittedIntegrationEventHandler(IHubContext<NotificationsHub> hubContext)
public OrderStatusChangedToSubmittedIntegrationEventHandler(
IHubContext<NotificationsHub> hubContext,
ILogger<OrderStatusChangedToSubmittedIntegrationEventHandler> logger)
{
_hubContext = hubContext ?? throw new ArgumentNullException(nameof(hubContext));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task Handle(OrderStatusChangedToSubmittedIntegrationEvent @event)
{
await _hubContext.Clients
.Group(@event.BuyerName)
.SendAsync("UpdatedOrderState", new { OrderId = @event.OrderId, Status = @event.OrderStatus });
using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}"))
{
_logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event);
await _hubContext.Clients
.Group(@event.BuyerName)
.SendAsync("UpdatedOrderState", new { OrderId = @event.OrderId, Status = @event.OrderStatus });
}
}
}
}

+ 15
- 4
src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/orderStatusChangedToAwaitingValidationIntegrationEventHandler.cs View File

@ -1,5 +1,7 @@
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.Linq;
@ -10,18 +12,27 @@ namespace Ordering.SignalrHub.IntegrationEvents
public class OrderStatusChangedToAwaitingValidationIntegrationEventHandler : IIntegrationEventHandler<OrderStatusChangedToAwaitingValidationIntegrationEvent>
{
private readonly IHubContext<NotificationsHub> _hubContext;
private readonly ILogger<OrderStatusChangedToAwaitingValidationIntegrationEventHandler> _logger;
public OrderStatusChangedToAwaitingValidationIntegrationEventHandler(IHubContext<NotificationsHub> hubContext)
public OrderStatusChangedToAwaitingValidationIntegrationEventHandler(
IHubContext<NotificationsHub> hubContext,
ILogger<OrderStatusChangedToAwaitingValidationIntegrationEventHandler> logger)
{
_hubContext = hubContext ?? throw new ArgumentNullException(nameof(hubContext));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task Handle(OrderStatusChangedToAwaitingValidationIntegrationEvent @event)
{
await _hubContext.Clients
.Group(@event.BuyerName)
.SendAsync("UpdatedOrderState", new { OrderId = @event.OrderId, Status = @event.OrderStatus });
using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}"))
{
_logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event);
await _hubContext.Clients
.Group(@event.BuyerName)
.SendAsync("UpdatedOrderState", new { OrderId = @event.OrderId, Status = @event.OrderStatus });
}
}
}
}

+ 9
- 7
src/Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj View File

@ -11,23 +11,25 @@
<ItemGroup>
<PackageReference Include="AspNetCore.HealthChecks.AzureServiceBus" Version="2.2.0" />
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="2.2.2" />
<PackageReference Include="AspNetCore.HealthChecks.Rabbitmq" Version="2.2.0" />
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="2.2.2" />
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="4.2.1" />
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.2.0" />
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.2.1" />
<PackageReference Include="Microsoft.ApplicationInsights.DependencyCollector" Version="2.6.1" />
<PackageReference Include="Microsoft.ApplicationInsights.Kubernetes" Version="1.0.2" />
<PackageReference Include="Microsoft.ApplicationInsights.ServiceFabric" Version="2.2.2" />
<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.HealthChecks" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.HealthChecks" Version="1.0.0" />
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.1.0" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Core" Version="1.1.0" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Redis" Version="1.1.0" />
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.2.1" />
<PackageReference Include="Microsoft.ApplicationInsights.DependencyCollector" Version="2.6.1" />
<PackageReference Include="Microsoft.ApplicationInsights.Kubernetes" Version="1.0.2" />
<PackageReference Include="Microsoft.ApplicationInsights.ServiceFabric" Version="2.2.2" />
<PackageReference Include="Microsoft.Extensions.Logging.AzureAppServices" Version="2.2.0-preview3-35497" />
<PackageReference Include="Microsoft.Extensions.Logging.AzureAppServices" Version="2.2.0" />
<PackageReference Include="Serilog.AspNetCore" Version="2.1.1" />
<PackageReference Include="Serilog.Enrichers.Environment" Version="2.1.2" />
<PackageReference Include="Serilog.Settings.Configuration" Version="3.0.1" />
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
<PackageReference Include="Serilog.Sinks.Seq" Version="4.0.0" />
</ItemGroup>
<ItemGroup>


+ 56
- 17
src/Services/Ordering/Ordering.SignalrHub/Program.cs View File

@ -13,28 +13,67 @@ namespace Ordering.SignalrHub
{
public class Program
{
public static void Main(string[] args)
public static readonly string Namespace = typeof(Program).Namespace;
public static readonly string AppName = Namespace;
public static int Main(string[] args)
{
BuildWebHost(args).Run();
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();
}
}
public static IWebHost BuildWebHost(string[] args) =>
private static IWebHost BuildWebHost(IConfiguration configuration, string[] args) =>
WebHost.CreateDefaultBuilder(args)
.CaptureStartupErrors(false)
.UseStartup<Startup>()
.ConfigureLogging((hostingContext, builder) =>
{
builder.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
builder.AddConsole();
builder.AddDebug();
builder.AddAzureWebAppDiagnostics();
})
.UseSerilog((builderContext, config) =>
{
config
.MinimumLevel.Information()
.Enrich.FromLogContext()
.WriteTo.Console();
})
.UseConfiguration(configuration)
.UseSerilog()
.Build();
private static Serilog.ILogger CreateSerilogLogger(IConfiguration configuration)
{
var seqServerUrl = configuration["Serilog:SeqServerUrl"];
return new LoggerConfiguration()
.MinimumLevel.Verbose()
.Enrich.WithProperty("ApplicationContext", AppName)
.Enrich.FromLogContext()
.WriteTo.Console()
.WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl)
.ReadFrom.Configuration(configuration)
.CreateLogger();
}
private static IConfiguration GetConfiguration()
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables();
return builder.Build();
}
}
}

+ 9
- 6
src/Services/Ordering/Ordering.SignalrHub/Startup.cs View File

@ -30,6 +30,8 @@ namespace Ordering.SignalrHub
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public IServiceProvider ConfigureServices(IServiceCollection services)
@ -115,17 +117,18 @@ namespace Ordering.SignalrHub
return new AutofacServiceProvider(container.Build());
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace);
//loggerFactory.AddConsole(Configuration.GetSection("Logging"));
//loggerFactory.AddDebug();
//loggerFactory.AddAzureWebAppDiagnostics();
//loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace);
var pathBase = Configuration["PATH_BASE"];
if (!string.IsNullOrEmpty(pathBase))
{
loggerFactory.CreateLogger("init").LogDebug($"Using PATH BASE '{pathBase}'");
loggerFactory.CreateLogger<Startup>().LogDebug("Using PATH BASE '{pathBase}'", pathBase);
app.UsePathBase(pathBase);
}
@ -156,13 +159,13 @@ namespace Ordering.SignalrHub
private void ConfigureEventBus(IApplicationBuilder app)
{
var eventBus = app.ApplicationServices.GetRequiredService<IEventBus>();
eventBus.Subscribe<OrderStatusChangedToAwaitingValidationIntegrationEvent, OrderStatusChangedToAwaitingValidationIntegrationEventHandler>();
eventBus.Subscribe<OrderStatusChangedToPaidIntegrationEvent, OrderStatusChangedToPaidIntegrationEventHandler>();
eventBus.Subscribe<OrderStatusChangedToStockConfirmedIntegrationEvent, OrderStatusChangedToStockConfirmedIntegrationEventHandler>();
eventBus.Subscribe<OrderStatusChangedToShippedIntegrationEvent, OrderStatusChangedToShippedIntegrationEventHandler>();
eventBus.Subscribe<OrderStatusChangedToCancelledIntegrationEvent, OrderStatusChangedToCancelledIntegrationEventHandler>();
eventBus.Subscribe<OrderStatusChangedToSubmittedIntegrationEvent, OrderStatusChangedToSubmittedIntegrationEventHandler>();
eventBus.Subscribe<OrderStatusChangedToSubmittedIntegrationEvent, OrderStatusChangedToSubmittedIntegrationEventHandler>();
}
private void ConfigureAuthService(IServiceCollection services)


+ 9
- 6
src/Services/Ordering/Ordering.SignalrHub/appsettings.json View File

@ -1,11 +1,14 @@
{
"IdentityUrl": "http://localhost:5105",
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Trace",
"System": "Information",
"Microsoft": "Information"
"Serilog": {
"SeqServerUrl": null,
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"Microsoft.eShopOnContainers": "Information",
"System": "Warning"
}
}
},
"AzureServiceBusEnabled": false,


+ 5
- 2
src/Services/Ordering/Ordering.UnitTests/Application/IdentifiedCommandHandlerTest.cs View File

@ -8,6 +8,7 @@ namespace UnitTest.Ordering.Application
using MediatR;
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands;
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency;
using Microsoft.Extensions.Logging;
using Moq;
using System.Collections;
using System.Collections.Generic;
@ -17,11 +18,13 @@ namespace UnitTest.Ordering.Application
{
private readonly Mock<IRequestManager> _requestManager;
private readonly Mock<IMediator> _mediator;
private readonly Mock<ILogger<IdentifiedCommandHandler<CreateOrderCommand, bool>>> _loggerMock;
public IdentifiedCommandHandlerTest()
{
_requestManager = new Mock<IRequestManager>();
_mediator = new Mock<IMediator>();
_loggerMock = new Mock<ILogger<IdentifiedCommandHandler<CreateOrderCommand, bool>>>();
}
[Fact]
@ -38,7 +41,7 @@ namespace UnitTest.Ordering.Application
.Returns(Task.FromResult(true));
//Act
var handler = new IdentifiedCommandHandler<CreateOrderCommand, bool>(_mediator.Object, _requestManager.Object);
var handler = new IdentifiedCommandHandler<CreateOrderCommand, bool>(_mediator.Object, _requestManager.Object, _loggerMock.Object);
var cltToken = new System.Threading.CancellationToken();
var result = await handler.Handle(fakeOrderCmd, cltToken);
@ -61,7 +64,7 @@ namespace UnitTest.Ordering.Application
.Returns(Task.FromResult(true));
//Act
var handler = new IdentifiedCommandHandler<CreateOrderCommand, bool>(_mediator.Object, _requestManager.Object);
var handler = new IdentifiedCommandHandler<CreateOrderCommand, bool>(_mediator.Object, _requestManager.Object, _loggerMock.Object);
var cltToken = new System.Threading.CancellationToken();
var result = await handler.Handle(fakeOrderCmd, cltToken);


+ 3
- 1
src/Services/Ordering/Ordering.UnitTests/Application/NewOrderCommandHandlerTest.cs View File

@ -13,6 +13,7 @@ namespace UnitTest.Ordering.Application
using global::Ordering.API.Application.IntegrationEvents;
using global::Ordering.API.Application.Models;
using MediatR;
using Microsoft.Extensions.Logging;
using System.Collections;
using System.Collections.Generic;
using Xunit;
@ -50,8 +51,9 @@ namespace UnitTest.Ordering.Application
_identityServiceMock.Setup(svc => svc.GetUserIdentity()).Returns(buyerId);
var LoggerMock = new Mock<ILogger<CreateOrderCommandHandler>>();
//Act
var handler = new CreateOrderCommandHandler(_mediator.Object, _orderingIntegrationEventService.Object, _orderRepositoryMock.Object, _identityServiceMock.Object);
var handler = new CreateOrderCommandHandler(_mediator.Object, _orderingIntegrationEventService.Object, _orderRepositoryMock.Object, _identityServiceMock.Object, LoggerMock.Object);
var cltToken = new System.Threading.CancellationToken();
var result = await handler.Handle(fakeOrderCmd, cltToken);


+ 10
- 7
src/Services/Ordering/Ordering.UnitTests/Application/OrdersWebApiTest.cs View File

@ -4,6 +4,7 @@ using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands;
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Queries;
using Microsoft.eShopOnContainers.Services.Ordering.API.Controllers;
using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services;
using Microsoft.Extensions.Logging;
using Moq;
using Ordering.API.Application.Commands;
using System;
@ -19,12 +20,14 @@ namespace UnitTest.Ordering.Application
private readonly Mock<IMediator> _mediatorMock;
private readonly Mock<IOrderQueries> _orderQueriesMock;
private readonly Mock<IIdentityService> _identityServiceMock;
private readonly Mock<ILogger<OrdersController>> _loggerMock;
public OrdersWebApiTest()
{
_mediatorMock = new Mock<IMediator>();
_orderQueriesMock = new Mock<IOrderQueries>();
_identityServiceMock = new Mock<IIdentityService>();
_loggerMock = new Mock<ILogger<OrdersController>>();
}
[Fact]
@ -35,7 +38,7 @@ namespace UnitTest.Ordering.Application
.Returns(Task.FromResult(true));
//Act
var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object);
var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object, _loggerMock.Object);
var actionResult = await orderController.CancelOrderAsync(new CancelOrderCommand(1), Guid.NewGuid().ToString()) as OkResult;
//Assert
@ -51,7 +54,7 @@ namespace UnitTest.Ordering.Application
.Returns(Task.FromResult(true));
//Act
var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object);
var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object, _loggerMock.Object);
var actionResult = await orderController.CancelOrderAsync(new CancelOrderCommand(1), String.Empty) as BadRequestResult;
//Assert
@ -66,7 +69,7 @@ namespace UnitTest.Ordering.Application
.Returns(Task.FromResult(true));
//Act
var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object);
var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object, _loggerMock.Object);
var actionResult = await orderController.ShipOrderAsync(new ShipOrderCommand(1), Guid.NewGuid().ToString()) as OkResult;
//Assert
@ -82,7 +85,7 @@ namespace UnitTest.Ordering.Application
.Returns(Task.FromResult(true));
//Act
var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object);
var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object, _loggerMock.Object);
var actionResult = await orderController.ShipOrderAsync(new ShipOrderCommand(1), String.Empty) as BadRequestResult;
//Assert
@ -102,7 +105,7 @@ namespace UnitTest.Ordering.Application
.Returns(Task.FromResult(fakeDynamicResult));
//Act
var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object);
var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object, _loggerMock.Object);
var actionResult = await orderController.GetOrdersAsync();
//Assert
@ -119,7 +122,7 @@ namespace UnitTest.Ordering.Application
.Returns(Task.FromResult(fakeDynamicResult));
//Act
var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object);
var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object, _loggerMock.Object);
var actionResult = await orderController.GetOrderAsync(fakeOrderId) as OkObjectResult;
//Assert
@ -135,7 +138,7 @@ namespace UnitTest.Ordering.Application
.Returns(Task.FromResult(fakeDynamicResult));
//Act
var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object);
var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object, _loggerMock.Object);
var actionResult = await orderController.GetCardTypesAsync();
//Assert


+ 33
- 20
src/Services/Payment/Payment.API/IntegrationEvents/EventHandling/OrderStatusChangedToStockConfirmedIntegrationEventHandler.cs View File

@ -2,45 +2,58 @@
{
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Payment.API.IntegrationEvents.Events;
using Serilog.Context;
using System.Threading.Tasks;
public class OrderStatusChangedToStockConfirmedIntegrationEventHandler :
public class OrderStatusChangedToStockConfirmedIntegrationEventHandler :
IIntegrationEventHandler<OrderStatusChangedToStockConfirmedIntegrationEvent>
{
private readonly IEventBus _eventBus;
private readonly PaymentSettings _settings;
private readonly ILogger<OrderStatusChangedToStockConfirmedIntegrationEventHandler> _logger;
public OrderStatusChangedToStockConfirmedIntegrationEventHandler(IEventBus eventBus,
IOptionsSnapshot<PaymentSettings> settings)
public OrderStatusChangedToStockConfirmedIntegrationEventHandler(
IEventBus eventBus,
IOptionsSnapshot<PaymentSettings> settings,
ILogger<OrderStatusChangedToStockConfirmedIntegrationEventHandler> logger)
{
_eventBus = eventBus;
_settings = settings.Value;
}
_logger = logger ?? throw new System.ArgumentNullException(nameof(logger));
}
public async Task Handle(OrderStatusChangedToStockConfirmedIntegrationEvent @event)
{
IntegrationEvent orderPaymentIntegrationEvent;
using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}"))
{
_logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event);
//Business feature comment:
// When OrderStatusChangedToStockConfirmed Integration Event is handled.
// Here we're simulating that we'd be performing the payment against any payment gateway
// Instead of a real payment we just take the env. var to simulate the payment
// The payment can be successful or it can fail
IntegrationEvent orderPaymentIntegrationEvent;
if (_settings.PaymentSucceded)
{
orderPaymentIntegrationEvent = new OrderPaymentSuccededIntegrationEvent(@event.OrderId);
}
else
{
orderPaymentIntegrationEvent = new OrderPaymentFailedIntegrationEvent(@event.OrderId);
}
//Business feature comment:
// When OrderStatusChangedToStockConfirmed Integration Event is handled.
// Here we're simulating that we'd be performing the payment against any payment gateway
// Instead of a real payment we just take the env. var to simulate the payment
// The payment can be successful or it can fail
if (_settings.PaymentSucceded)
{
orderPaymentIntegrationEvent = new OrderPaymentSuccededIntegrationEvent(@event.OrderId);
}
else
{
orderPaymentIntegrationEvent = new OrderPaymentFailedIntegrationEvent(@event.OrderId);
}
_eventBus.Publish(orderPaymentIntegrationEvent);
_logger.LogInformation("----- Publishing integration event: {IntegrationEventId} from {AppName} - ({@IntegrationEvent})", orderPaymentIntegrationEvent.Id, Program.AppName, orderPaymentIntegrationEvent);
await Task.CompletedTask;
_eventBus.Publish(orderPaymentIntegrationEvent);
await Task.CompletedTask;
}
}
}
}

+ 6
- 3
src/Services/Payment/Payment.API/Payment.API.csproj View File

@ -7,20 +7,23 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="2.2.2" />
<PackageReference Include="AspNetCore.HealthChecks.AzureServiceBus" Version="2.2.0" />
<PackageReference Include="AspNetCore.HealthChecks.Rabbitmq" Version="2.2.0" />
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="2.2.2" />
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="4.2.1" />
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.2.1" />
<PackageReference Include="Microsoft.ApplicationInsights.DependencyCollector" Version="2.6.1" />
<PackageReference Include="Microsoft.ApplicationInsights.Kubernetes" Version="1.0.2" />
<PackageReference Include="Microsoft.ApplicationInsights.ServiceFabric" Version="2.2.2" />
<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.HealthChecks" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Logging.AzureAppServices" Version="2.2.0-preview3-35497" />
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.AzureKeyVault" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Logging.AzureAppServices" Version="2.2.0" />
<PackageReference Include="Serilog.AspNetCore" Version="2.1.1" />
<PackageReference Include="Serilog.Enrichers.Environment" Version="2.1.2" />
<PackageReference Include="Serilog.Settings.Configuration" Version="3.0.1" />
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
<PackageReference Include="Serilog.Sinks.Seq" Version="4.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBusRabbitMQ\EventBusRabbitMQ.csproj" />


+ 68
- 23
src/Services/Payment/Payment.API/Program.cs View File

@ -4,40 +4,85 @@ using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Serilog;
using System;
using System.IO;
namespace Payment.API
{
public class Program
{
public static void Main(string[] args)
public static readonly string Namespace = typeof(Program).Namespace;
public static readonly string AppName = Namespace;
public static int Main(string[] args)
{
BuildWebHost(args).Run();
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();
}
}
public static IWebHost BuildWebHost(string[] args) =>
private static IWebHost BuildWebHost(IConfiguration configuration, string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseContentRoot(Directory.GetCurrentDirectory())
.CaptureStartupErrors(false)
.UseStartup<Startup>()
.ConfigureAppConfiguration((builderContext, config) =>
{
config.AddEnvironmentVariables();
})
.ConfigureLogging((hostingContext, builder) =>
{
builder.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
builder.AddConsole();
builder.AddDebug();
builder.AddAzureWebAppDiagnostics();
})
.UseApplicationInsights()
.UseSerilog((builderContext, config) =>
{
config
.MinimumLevel.Information()
.Enrich.FromLogContext()
.WriteTo.Console();
})
.UseContentRoot(Directory.GetCurrentDirectory())
.UseConfiguration(configuration)
.UseSerilog()
.Build();
private static Serilog.ILogger CreateSerilogLogger(IConfiguration configuration)
{
var seqServerUrl = configuration["Serilog:SeqServerUrl"];
return new LoggerConfiguration()
.MinimumLevel.Verbose()
.Enrich.WithProperty("ApplicationContext", AppName)
.Enrich.FromLogContext()
.WriteTo.Console()
.WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl)
.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<bool>("UseVault", false))
{
builder.AddAzureKeyVault(
$"https://{config["Vault:Name"]}.vault.azure.net/",
config["Vault:ClientId"],
config["Vault:ClientSecret"]);
}
return builder.Build();
}
}
}
}

+ 2
- 1
src/Services/Payment/Payment.API/Startup.cs View File

@ -91,7 +91,8 @@ namespace Payment.API
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace);
//loggerFactory.AddAzureWebAppDiagnostics();
//loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace);
var pathBase = Configuration["PATH_BASE"];
if (!string.IsNullOrEmpty(pathBase))


+ 9
- 4
src/Services/Payment/Payment.API/appsettings.json View File

@ -1,8 +1,13 @@
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
"Serilog": {
"SeqServerUrl": null,
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"Microsoft.eShopOnContainers": "Information",
"System": "Warning"
}
}
},
"PaymentSucceded": true,


+ 6
- 6
src/Web/WebMVC/Infrastructure/WebContextSeed.cs View File

@ -15,7 +15,7 @@ namespace WebMVC.Infrastructure
{
public static void Seed(IApplicationBuilder applicationBuilder, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
var log = loggerFactory.CreateLogger("WebMVC seed");
var log = loggerFactory.CreateLogger<WebContextSeed>();
var settings = (AppSettings)applicationBuilder
.ApplicationServices.GetRequiredService<IOptions<AppSettings>>().Value;
@ -39,7 +39,7 @@ namespace WebMVC.Infrastructure
string overrideCssFile = Path.Combine(contentRootPath, "Setup", "override.css");
if (!File.Exists(overrideCssFile))
{
log.LogError($" override css file '{overrideCssFile}' does not exists.");
log.LogError("Override css file '{FileName}' does not exists.", overrideCssFile);
return;
}
@ -48,7 +48,7 @@ namespace WebMVC.Infrastructure
}
catch (Exception ex)
{
log.LogError($"Exception in method GetPreconfiguredCSS WebMVC. Exception Message={ex.Message}");
log.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message);
}
}
@ -59,7 +59,7 @@ namespace WebMVC.Infrastructure
string imagesZipFile = Path.Combine(contentRootPath, "Setup", "images.zip");
if (!File.Exists(imagesZipFile))
{
log.LogError($" zip file '{imagesZipFile}' does not exists.");
log.LogError("Zip file '{ZipFileName}' does not exists.", imagesZipFile);
return;
}
@ -81,14 +81,14 @@ namespace WebMVC.Infrastructure
}
else
{
log.LogWarning($"Skip file '{entry.Name}' in zipfile '{imagesZipFile}'");
log.LogWarning("Skipped file '{FileName}' in zipfile '{ZipFileName}'", entry.Name, imagesZipFile);
}
}
}
}
catch ( Exception ex )
{
log.LogError($"Exception in method GetPreconfiguredImages WebMVC. Exception Message={ex.Message}");
log.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message);
}
}


Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save