Moved all usings to globalusing file
This commit is contained in:
		
							parent
							
								
									818995d8b8
								
							
						
					
					
						commit
						666fba815f
					
				| @ -1,23 +1,16 @@ | |||||||
| using MediatR; | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Behaviors; | ||||||
| using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Extensions; | public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> | ||||||
| using Microsoft.Extensions.Logging; |  | ||||||
| using System.Threading; |  | ||||||
| using System.Threading.Tasks; |  | ||||||
| 
 |  | ||||||
| namespace Ordering.API.Application.Behaviors |  | ||||||
| { | { | ||||||
|     public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> |     private readonly ILogger<LoggingBehavior<TRequest, TResponse>> _logger; | ||||||
|  |     public LoggingBehavior(ILogger<LoggingBehavior<TRequest, TResponse>> logger) => _logger = logger; | ||||||
|  | 
 | ||||||
|  |     public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next) | ||||||
|     { |     { | ||||||
|         private readonly ILogger<LoggingBehavior<TRequest, TResponse>> _logger; |         _logger.LogInformation("----- Handling command {CommandName} ({@Command})", request.GetGenericTypeName(), request); | ||||||
|         public LoggingBehavior(ILogger<LoggingBehavior<TRequest, TResponse>> logger) => _logger = logger; |         var response = await next(); | ||||||
|  |         _logger.LogInformation("----- Command {CommandName} handled - response: {@Response}", request.GetGenericTypeName(), response); | ||||||
| 
 | 
 | ||||||
|         public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next) |         return response; | ||||||
|         { |  | ||||||
|             _logger.LogInformation("----- Handling command {CommandName} ({@Command})", request.GetGenericTypeName(), request); |  | ||||||
|             var response = await next(); |  | ||||||
|             _logger.LogInformation("----- Command {CommandName} handled - response: {@Response}", request.GetGenericTypeName(), response); |  | ||||||
| 
 |  | ||||||
|             return response; |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | |||||||
| @ -1,74 +1,64 @@ | |||||||
| using MediatR; | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Behaviors; | ||||||
| using Microsoft.EntityFrameworkCore; | 
 | ||||||
| using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Extensions; |  | ||||||
| using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure; |  | ||||||
| using Microsoft.Extensions.Logging; | using Microsoft.Extensions.Logging; | ||||||
| using Ordering.API.Application.IntegrationEvents; |  | ||||||
| using Serilog.Context; |  | ||||||
| using System; |  | ||||||
| using System.Threading; |  | ||||||
| using System.Threading.Tasks; |  | ||||||
| 
 | 
 | ||||||
| namespace Ordering.API.Application.Behaviors | public class TransactionBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> | ||||||
| { | { | ||||||
|     public class TransactionBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> |     private readonly ILogger<TransactionBehaviour<TRequest, TResponse>> _logger; | ||||||
|  |     private readonly OrderingContext _dbContext; | ||||||
|  |     private readonly IOrderingIntegrationEventService _orderingIntegrationEventService; | ||||||
|  | 
 | ||||||
|  |     public TransactionBehaviour(OrderingContext dbContext, | ||||||
|  |         IOrderingIntegrationEventService orderingIntegrationEventService, | ||||||
|  |         ILogger<TransactionBehaviour<TRequest, TResponse>> logger) | ||||||
|     { |     { | ||||||
|         private readonly ILogger<TransactionBehaviour<TRequest, TResponse>> _logger; |         _dbContext = dbContext ?? throw new ArgumentException(nameof(OrderingContext)); | ||||||
|         private readonly OrderingContext _dbContext; |         _orderingIntegrationEventService = orderingIntegrationEventService ?? throw new ArgumentException(nameof(orderingIntegrationEventService)); | ||||||
|         private readonly IOrderingIntegrationEventService _orderingIntegrationEventService; |         _logger = logger ?? throw new ArgumentException(nameof(ILogger)); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|         public TransactionBehaviour(OrderingContext dbContext, |     public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next) | ||||||
|             IOrderingIntegrationEventService orderingIntegrationEventService, |     { | ||||||
|             ILogger<TransactionBehaviour<TRequest, TResponse>> logger) |         var response = default(TResponse); | ||||||
|  |         var typeName = request.GetGenericTypeName(); | ||||||
|  | 
 | ||||||
|  |         try | ||||||
|         { |         { | ||||||
|             _dbContext = dbContext ?? throw new ArgumentException(nameof(OrderingContext)); |             if (_dbContext.HasActiveTransaction) | ||||||
|             _orderingIntegrationEventService = orderingIntegrationEventService ?? throw new ArgumentException(nameof(orderingIntegrationEventService)); |  | ||||||
|             _logger = logger ?? throw new ArgumentException(nameof(ILogger)); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next) |  | ||||||
|         { |  | ||||||
|             var response = default(TResponse); |  | ||||||
|             var typeName = request.GetGenericTypeName(); |  | ||||||
| 
 |  | ||||||
|             try |  | ||||||
|             { |             { | ||||||
|                 if (_dbContext.HasActiveTransaction) |                 return await next(); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             var strategy = _dbContext.Database.CreateExecutionStrategy(); | ||||||
|  | 
 | ||||||
|  |             await strategy.ExecuteAsync(async () => | ||||||
|  |             { | ||||||
|  |                 Guid transactionId; | ||||||
|  | 
 | ||||||
|  |                 using (var transaction = await _dbContext.BeginTransactionAsync()) | ||||||
|  |                 using (LogContext.PushProperty("TransactionContext", transaction.TransactionId)) | ||||||
|                 { |                 { | ||||||
|                     return await next(); |                     _logger.LogInformation("----- Begin transaction {TransactionId} for {CommandName} ({@Command})", transaction.TransactionId, typeName, request); | ||||||
|  | 
 | ||||||
|  |                     response = await next(); | ||||||
|  | 
 | ||||||
|  |                     _logger.LogInformation("----- Commit transaction {TransactionId} for {CommandName}", transaction.TransactionId, typeName); | ||||||
|  | 
 | ||||||
|  |                     await _dbContext.CommitTransactionAsync(transaction); | ||||||
|  | 
 | ||||||
|  |                     transactionId = transaction.TransactionId; | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 var strategy = _dbContext.Database.CreateExecutionStrategy(); |                 await _orderingIntegrationEventService.PublishEventsThroughEventBusAsync(transactionId); | ||||||
|  |             }); | ||||||
| 
 | 
 | ||||||
|                 await strategy.ExecuteAsync(async () => |             return response; | ||||||
|                 { |         } | ||||||
|                     Guid transactionId; |         catch (Exception ex) | ||||||
|  |         { | ||||||
|  |             _logger.LogError(ex, "ERROR Handling transaction for {CommandName} ({@Command})", typeName, request); | ||||||
| 
 | 
 | ||||||
|                     using (var transaction = await _dbContext.BeginTransactionAsync()) |             throw; | ||||||
|                     using (LogContext.PushProperty("TransactionContext", transaction.TransactionId)) |  | ||||||
|                     { |  | ||||||
|                         _logger.LogInformation("----- Begin transaction {TransactionId} for {CommandName} ({@Command})", transaction.TransactionId, typeName, request); |  | ||||||
| 
 |  | ||||||
|                         response = await next(); |  | ||||||
| 
 |  | ||||||
|                         _logger.LogInformation("----- Commit transaction {TransactionId} for {CommandName}", transaction.TransactionId, typeName); |  | ||||||
| 
 |  | ||||||
|                         await _dbContext.CommitTransactionAsync(transaction); |  | ||||||
| 
 |  | ||||||
|                         transactionId = transaction.TransactionId; |  | ||||||
|                     } |  | ||||||
| 
 |  | ||||||
|                     await _orderingIntegrationEventService.PublishEventsThroughEventBusAsync(transactionId); |  | ||||||
|                 }); |  | ||||||
| 
 |  | ||||||
|                 return response; |  | ||||||
|             } |  | ||||||
|             catch (Exception ex) |  | ||||||
|             { |  | ||||||
|                 _logger.LogError(ex, "ERROR Handling transaction for {CommandName} ({@Command})", typeName, request); |  | ||||||
| 
 |  | ||||||
|                 throw; |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,46 +1,36 @@ | |||||||
| using FluentValidation; | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Behaviors; | ||||||
| using MediatR; |  | ||||||
| using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Extensions; |  | ||||||
| using Microsoft.Extensions.Logging; |  | ||||||
| using Ordering.Domain.Exceptions; |  | ||||||
| using System.Linq; |  | ||||||
| using System.Threading; |  | ||||||
| using System.Threading.Tasks; |  | ||||||
| 
 | 
 | ||||||
| namespace Ordering.API.Application.Behaviors | public class ValidatorBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> | ||||||
| { | { | ||||||
|     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, ILogger<ValidatorBehavior<TRequest, TResponse>> logger) | ||||||
|     { |     { | ||||||
|         private readonly ILogger<ValidatorBehavior<TRequest, TResponse>> _logger; |         _validators = validators; | ||||||
|         private readonly IValidator<TRequest>[] _validators; |         _logger = logger; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|         public ValidatorBehavior(IValidator<TRequest>[] validators, ILogger<ValidatorBehavior<TRequest, TResponse>> 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) | ||||||
|  |             .Where(error => error != null) | ||||||
|  |             .ToList(); | ||||||
|  | 
 | ||||||
|  |         if (failures.Any()) | ||||||
|         { |         { | ||||||
|             _validators = validators; |             _logger.LogWarning("Validation errors - {CommandType} - Command: {@Command} - Errors: {@ValidationErrors}", typeName, request, failures); | ||||||
|             _logger = logger; | 
 | ||||||
|  |             throw new OrderingDomainException( | ||||||
|  |                 $"Command Validation Errors for type {typeof(TRequest).Name}", new ValidationException("Validation exception", failures)); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next) |         return await next(); | ||||||
|         { |  | ||||||
|             var typeName = request.GetGenericTypeName(); |  | ||||||
| 
 |  | ||||||
|             _logger.LogInformation("----- Validating command {CommandType}", typeName); |  | ||||||
| 
 |  | ||||||
|             var failures = _validators |  | ||||||
|                 .Select(v => v.Validate(request)) |  | ||||||
|                 .SelectMany(result => result.Errors) |  | ||||||
|                 .Where(error => error != null) |  | ||||||
|                 .ToList(); |  | ||||||
| 
 |  | ||||||
|             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)); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             return await next(); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -1,20 +1,16 @@ | |||||||
| using MediatR; | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; | ||||||
| using System.Runtime.Serialization; |  | ||||||
| 
 | 
 | ||||||
| namespace Ordering.API.Application.Commands | public class CancelOrderCommand : IRequest<bool> | ||||||
| { | { | ||||||
|     public class CancelOrderCommand : IRequest<bool> | 
 | ||||||
|  |     [DataMember] | ||||||
|  |     public int OrderNumber { get; set; } | ||||||
|  |     public CancelOrderCommand() | ||||||
|     { |     { | ||||||
| 
 | 
 | ||||||
|         [DataMember] |     } | ||||||
|         public int OrderNumber { get; set; } |     public CancelOrderCommand(int orderNumber) | ||||||
|         public CancelOrderCommand() |     { | ||||||
|         { |         OrderNumber = orderNumber; | ||||||
| 
 |  | ||||||
|         } |  | ||||||
|         public CancelOrderCommand(int orderNumber) |  | ||||||
|         { |  | ||||||
|             OrderNumber = orderNumber; |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,57 +1,48 @@ | |||||||
| using MediatR; | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; | ||||||
| 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; |  | ||||||
| 
 | 
 | ||||||
| namespace Ordering.API.Application.Commands | // Regular CommandHandler | ||||||
|  | public class CancelOrderCommandHandler : IRequestHandler<CancelOrderCommand, bool> | ||||||
| { | { | ||||||
|     // Regular CommandHandler |     private readonly IOrderRepository _orderRepository; | ||||||
|     public class CancelOrderCommandHandler : IRequestHandler<CancelOrderCommand, bool> | 
 | ||||||
|  |     public CancelOrderCommandHandler(IOrderRepository orderRepository) | ||||||
|     { |     { | ||||||
|         private readonly IOrderRepository _orderRepository; |         _orderRepository = orderRepository; | ||||||
| 
 |  | ||||||
|         public CancelOrderCommandHandler(IOrderRepository orderRepository) |  | ||||||
|         { |  | ||||||
|             _orderRepository = orderRepository; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         /// <summary> |  | ||||||
|         /// Handler which processes the command when |  | ||||||
|         /// customer executes cancel order from app |  | ||||||
|         /// </summary> |  | ||||||
|         /// <param name="command"></param> |  | ||||||
|         /// <returns></returns> |  | ||||||
|         public async Task<bool> Handle(CancelOrderCommand command, CancellationToken cancellationToken) |  | ||||||
|         { |  | ||||||
|             var orderToUpdate = await _orderRepository.GetAsync(command.OrderNumber); |  | ||||||
|             if (orderToUpdate == null) |  | ||||||
|             { |  | ||||||
|                 return false; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             orderToUpdate.SetCancelledStatus(); |  | ||||||
|             return await _orderRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 |     /// <summary> | ||||||
|     // Use for Idempotency in Command process |     /// Handler which processes the command when | ||||||
|     public class CancelOrderIdentifiedCommandHandler : IdentifiedCommandHandler<CancelOrderCommand, bool> |     /// customer executes cancel order from app | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="command"></param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     public async Task<bool> Handle(CancelOrderCommand command, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         public CancelOrderIdentifiedCommandHandler( |         var orderToUpdate = await _orderRepository.GetAsync(command.OrderNumber); | ||||||
|             IMediator mediator, |         if (orderToUpdate == null) | ||||||
|             IRequestManager requestManager, |  | ||||||
|             ILogger<IdentifiedCommandHandler<CancelOrderCommand, bool>> logger) |  | ||||||
|             : base(mediator, requestManager, logger) |  | ||||||
|         { |         { | ||||||
|  |             return false; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         protected override bool CreateResultForDuplicateRequest() |         orderToUpdate.SetCancelledStatus(); | ||||||
|         { |         return await _orderRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken); | ||||||
|             return true;                // Ignore duplicate requests for processing order. |     } | ||||||
|         } | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | // Use for Idempotency in Command process | ||||||
|  | public class CancelOrderIdentifiedCommandHandler : IdentifiedCommandHandler<CancelOrderCommand, bool> | ||||||
|  | { | ||||||
|  |     public CancelOrderIdentifiedCommandHandler( | ||||||
|  |         IMediator mediator, | ||||||
|  |         IRequestManager requestManager, | ||||||
|  |         ILogger<IdentifiedCommandHandler<CancelOrderCommand, bool>> logger) | ||||||
|  |         : base(mediator, requestManager, logger) | ||||||
|  |     { | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     protected override bool CreateResultForDuplicateRequest() | ||||||
|  |     { | ||||||
|  |         return true;                // Ignore duplicate requests for processing order. | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,106 +1,101 @@ | |||||||
| using MediatR; | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; | ||||||
| using Ordering.API.Application.Models; |  | ||||||
| using System; |  | ||||||
| using System.Collections.Generic; |  | ||||||
| using System.Linq; |  | ||||||
| using System.Runtime.Serialization; |  | ||||||
| 
 | 
 | ||||||
| namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands | // DDD and CQRS patterns comment: Note that it is recommended to implement immutable Commands | ||||||
|  | // In this case, its immutability is achieved by having all the setters as private | ||||||
|  | // plus only being able to update the data just once, when creating the object through its constructor. | ||||||
|  | // References on Immutable Commands:   | ||||||
|  | // http://cqrs.nu/Faq | ||||||
|  | // https://docs.spine3.org/motivation/immutability.html  | ||||||
|  | // http://blog.gauffin.org/2012/06/griffin-container-introducing-command-support/ | ||||||
|  | // https://docs.microsoft.com/dotnet/csharp/programming-guide/classes-and-structs/how-to-implement-a-lightweight-class-with-auto-implemented-properties | ||||||
|  | 
 | ||||||
|  | using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Models; | ||||||
|  | 
 | ||||||
|  | [DataContract] | ||||||
|  | public class CreateOrderCommand | ||||||
|  |     : IRequest<bool> | ||||||
| { | { | ||||||
|     // DDD and CQRS patterns comment: Note that it is recommended to implement immutable Commands |     [DataMember] | ||||||
|     // In this case, its immutability is achieved by having all the setters as private |     private readonly List<OrderItemDTO> _orderItems; | ||||||
|     // plus only being able to update the data just once, when creating the object through its constructor. |  | ||||||
|     // References on Immutable Commands:   |  | ||||||
|     // http://cqrs.nu/Faq |  | ||||||
|     // https://docs.spine3.org/motivation/immutability.html  |  | ||||||
|     // http://blog.gauffin.org/2012/06/griffin-container-introducing-command-support/ |  | ||||||
|     // https://docs.microsoft.com/dotnet/csharp/programming-guide/classes-and-structs/how-to-implement-a-lightweight-class-with-auto-implemented-properties |  | ||||||
| 
 | 
 | ||||||
|     [DataContract] |     [DataMember] | ||||||
|     public class CreateOrderCommand |     public string UserId { get; private set; } | ||||||
|         : IRequest<bool> | 
 | ||||||
|  |     [DataMember] | ||||||
|  |     public string UserName { get; private set; } | ||||||
|  | 
 | ||||||
|  |     [DataMember] | ||||||
|  |     public string City { get; private set; } | ||||||
|  | 
 | ||||||
|  |     [DataMember] | ||||||
|  |     public string Street { get; private set; } | ||||||
|  | 
 | ||||||
|  |     [DataMember] | ||||||
|  |     public string State { get; private set; } | ||||||
|  | 
 | ||||||
|  |     [DataMember] | ||||||
|  |     public string Country { get; private set; } | ||||||
|  | 
 | ||||||
|  |     [DataMember] | ||||||
|  |     public string ZipCode { get; private set; } | ||||||
|  | 
 | ||||||
|  |     [DataMember] | ||||||
|  |     public string CardNumber { get; private set; } | ||||||
|  | 
 | ||||||
|  |     [DataMember] | ||||||
|  |     public string CardHolderName { get; private set; } | ||||||
|  | 
 | ||||||
|  |     [DataMember] | ||||||
|  |     public DateTime CardExpiration { get; private set; } | ||||||
|  | 
 | ||||||
|  |     [DataMember] | ||||||
|  |     public string CardSecurityNumber { get; private set; } | ||||||
|  | 
 | ||||||
|  |     [DataMember] | ||||||
|  |     public int CardTypeId { get; private set; } | ||||||
|  | 
 | ||||||
|  |     [DataMember] | ||||||
|  |     public IEnumerable<OrderItemDTO> OrderItems => _orderItems; | ||||||
|  | 
 | ||||||
|  |     public CreateOrderCommand() | ||||||
|     { |     { | ||||||
|         [DataMember] |         _orderItems = new List<OrderItemDTO>(); | ||||||
|         private readonly List<OrderItemDTO> _orderItems; |     } | ||||||
| 
 | 
 | ||||||
|         [DataMember] |     public CreateOrderCommand(List<BasketItem> basketItems, string userId, string userName, string city, string street, string state, string country, string zipcode, | ||||||
|         public string UserId { get; private set; } |         string cardNumber, string cardHolderName, DateTime cardExpiration, | ||||||
| 
 |         string cardSecurityNumber, int cardTypeId) : this() | ||||||
|         [DataMember] |     { | ||||||
|         public string UserName { get; private set; } |         _orderItems = basketItems.ToOrderItemsDTO().ToList(); | ||||||
| 
 |         UserId = userId; | ||||||
|         [DataMember] |         UserName = userName; | ||||||
|         public string City { get; private set; } |         City = city; | ||||||
| 
 |         Street = street; | ||||||
|         [DataMember] |         State = state; | ||||||
|         public string Street { get; private set; } |         Country = country; | ||||||
| 
 |         ZipCode = zipcode; | ||||||
|         [DataMember] |         CardNumber = cardNumber; | ||||||
|         public string State { get; private set; } |         CardHolderName = cardHolderName; | ||||||
| 
 |         CardExpiration = cardExpiration; | ||||||
|         [DataMember] |         CardSecurityNumber = cardSecurityNumber; | ||||||
|         public string Country { get; private set; } |         CardTypeId = cardTypeId; | ||||||
| 
 |         CardExpiration = cardExpiration; | ||||||
|         [DataMember] |     } | ||||||
|         public string ZipCode { get; private set; } |  | ||||||
| 
 |  | ||||||
|         [DataMember] |  | ||||||
|         public string CardNumber { get; private set; } |  | ||||||
| 
 |  | ||||||
|         [DataMember] |  | ||||||
|         public string CardHolderName { get; private set; } |  | ||||||
| 
 |  | ||||||
|         [DataMember] |  | ||||||
|         public DateTime CardExpiration { get; private set; } |  | ||||||
| 
 |  | ||||||
|         [DataMember] |  | ||||||
|         public string CardSecurityNumber { get; private set; } |  | ||||||
| 
 |  | ||||||
|         [DataMember] |  | ||||||
|         public int CardTypeId { get; private set; } |  | ||||||
| 
 |  | ||||||
|         [DataMember] |  | ||||||
|         public IEnumerable<OrderItemDTO> OrderItems => _orderItems; |  | ||||||
| 
 |  | ||||||
|         public CreateOrderCommand() |  | ||||||
|         { |  | ||||||
|             _orderItems = new List<OrderItemDTO>(); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         public CreateOrderCommand(List<BasketItem> basketItems, string userId, string userName, string city, string street, string state, string country, string zipcode, |  | ||||||
|             string cardNumber, string cardHolderName, DateTime cardExpiration, |  | ||||||
|             string cardSecurityNumber, int cardTypeId) : this() |  | ||||||
|         { |  | ||||||
|             _orderItems = basketItems.ToOrderItemsDTO().ToList(); |  | ||||||
|             UserId = userId; |  | ||||||
|             UserName = userName; |  | ||||||
|             City = city; |  | ||||||
|             Street = street; |  | ||||||
|             State = state; |  | ||||||
|             Country = country; |  | ||||||
|             ZipCode = zipcode; |  | ||||||
|             CardNumber = cardNumber; |  | ||||||
|             CardHolderName = cardHolderName; |  | ||||||
|             CardExpiration = cardExpiration; |  | ||||||
|             CardSecurityNumber = cardSecurityNumber; |  | ||||||
|             CardTypeId = cardTypeId; |  | ||||||
|             CardExpiration = cardExpiration; |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|         public record OrderItemDTO |     public record OrderItemDTO | ||||||
|         { |     { | ||||||
|             public int ProductId { get; init; } |         public int ProductId { get; init; } | ||||||
| 
 | 
 | ||||||
|             public string ProductName { get; init; } |         public string ProductName { get; init; } | ||||||
| 
 | 
 | ||||||
|             public decimal UnitPrice { get; init; } |         public decimal UnitPrice { get; init; } | ||||||
| 
 | 
 | ||||||
|             public decimal Discount { get; init; } |         public decimal Discount { get; init; } | ||||||
| 
 | 
 | ||||||
|             public int Units { get; init; } |         public int Units { get; init; } | ||||||
| 
 | 
 | ||||||
|             public string PictureUrl { get; init; } |         public string PictureUrl { get; init; } | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | |||||||
| @ -1,82 +1,72 @@ | |||||||
| namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; | ||||||
|  | 
 | ||||||
|  | using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; | ||||||
|  | 
 | ||||||
|  | // Regular CommandHandler | ||||||
|  | public class CreateOrderCommandHandler | ||||||
|  |     : IRequestHandler<CreateOrderCommand, bool> | ||||||
| { | { | ||||||
|     using Domain.AggregatesModel.OrderAggregate; |     private readonly IOrderRepository _orderRepository; | ||||||
|     using global::Ordering.API.Application.IntegrationEvents; |     private readonly IIdentityService _identityService; | ||||||
|     using global::Ordering.API.Application.IntegrationEvents.Events; |     private readonly IMediator _mediator; | ||||||
|     using MediatR; |     private readonly IOrderingIntegrationEventService _orderingIntegrationEventService; | ||||||
|     using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services; |     private readonly ILogger<CreateOrderCommandHandler> _logger; | ||||||
|     using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency; |  | ||||||
|     using Microsoft.Extensions.Logging; |  | ||||||
|     using System; |  | ||||||
|     using System.Threading; |  | ||||||
|     using System.Threading.Tasks; |  | ||||||
| 
 | 
 | ||||||
|     // Regular CommandHandler |     // Using DI to inject infrastructure persistence Repositories | ||||||
|     public class CreateOrderCommandHandler |     public CreateOrderCommandHandler(IMediator mediator, | ||||||
|         : IRequestHandler<CreateOrderCommand, bool> |         IOrderingIntegrationEventService orderingIntegrationEventService, | ||||||
|  |         IOrderRepository orderRepository, | ||||||
|  |         IIdentityService identityService, | ||||||
|  |         ILogger<CreateOrderCommandHandler> logger) | ||||||
|     { |     { | ||||||
|         private readonly IOrderRepository _orderRepository; |         _orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository)); | ||||||
|         private readonly IIdentityService _identityService; |         _identityService = identityService ?? throw new ArgumentNullException(nameof(identityService)); | ||||||
|         private readonly IMediator _mediator; |         _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); | ||||||
|         private readonly IOrderingIntegrationEventService _orderingIntegrationEventService; |         _orderingIntegrationEventService = orderingIntegrationEventService ?? throw new ArgumentNullException(nameof(orderingIntegrationEventService)); | ||||||
|         private readonly ILogger<CreateOrderCommandHandler> _logger; |         _logger = logger ?? throw new ArgumentNullException(nameof(logger)); | ||||||
| 
 |  | ||||||
|         // Using DI to inject infrastructure persistence Repositories |  | ||||||
|         public CreateOrderCommandHandler(IMediator mediator, |  | ||||||
|             IOrderingIntegrationEventService orderingIntegrationEventService, |  | ||||||
|             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) |  | ||||||
|         { |  | ||||||
|             // 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); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             _logger.LogInformation("----- Creating Order - Order: {@Order}", order); |  | ||||||
| 
 |  | ||||||
|             _orderRepository.Add(order); |  | ||||||
| 
 |  | ||||||
|             return await _orderRepository.UnitOfWork |  | ||||||
|                 .SaveEntitiesAsync(cancellationToken); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 |     public async Task<bool> Handle(CreateOrderCommand message, CancellationToken cancellationToken) | ||||||
|     // Use for Idempotency in Command process |  | ||||||
|     public class CreateOrderIdentifiedCommandHandler : IdentifiedCommandHandler<CreateOrderCommand, bool> |  | ||||||
|     { |     { | ||||||
|         public CreateOrderIdentifiedCommandHandler( |         // Add Integration event to clean the basket | ||||||
|             IMediator mediator, |         var orderStartedIntegrationEvent = new OrderStartedIntegrationEvent(message.UserId); | ||||||
|             IRequestManager requestManager, |         await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStartedIntegrationEvent); | ||||||
|             ILogger<IdentifiedCommandHandler<CreateOrderCommand, bool>> logger) | 
 | ||||||
|             : base(mediator, requestManager, logger) |         // 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); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         protected override bool CreateResultForDuplicateRequest() |         _logger.LogInformation("----- Creating Order - Order: {@Order}", order); | ||||||
|         { | 
 | ||||||
|             return true;                // Ignore duplicate requests for creating order. |         _orderRepository.Add(order); | ||||||
|         } | 
 | ||||||
|  |         return await _orderRepository.UnitOfWork | ||||||
|  |             .SaveEntitiesAsync(cancellationToken); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | // Use for Idempotency in Command process | ||||||
|  | public class CreateOrderIdentifiedCommandHandler : IdentifiedCommandHandler<CreateOrderCommand, bool> | ||||||
|  | { | ||||||
|  |     public CreateOrderIdentifiedCommandHandler( | ||||||
|  |         IMediator mediator, | ||||||
|  |         IRequestManager requestManager, | ||||||
|  |         ILogger<IdentifiedCommandHandler<CreateOrderCommand, bool>> logger) | ||||||
|  |         : base(mediator, requestManager, logger) | ||||||
|  |     { | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     protected override bool CreateResultForDuplicateRequest() | ||||||
|  |     { | ||||||
|  |         return true;                // Ignore duplicate requests for creating order. | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,21 +1,16 @@ | |||||||
| using MediatR; | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; | ||||||
| using Ordering.API.Application.Models; | using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Models; | ||||||
| using System.Collections.Generic; |  | ||||||
| 
 | 
 | ||||||
| namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands | public class CreateOrderDraftCommand : IRequest<OrderDraftDTO> | ||||||
| { | { | ||||||
|     public class CreateOrderDraftCommand : IRequest<OrderDraftDTO> | 
 | ||||||
|  |     public string BuyerId { get; private set; } | ||||||
|  | 
 | ||||||
|  |     public IEnumerable<BasketItem> Items { get; private set; } | ||||||
|  | 
 | ||||||
|  |     public CreateOrderDraftCommand(string buyerId, IEnumerable<BasketItem> items) | ||||||
|     { |     { | ||||||
| 
 |         BuyerId = buyerId; | ||||||
|         public string BuyerId { get; private set; } |         Items = items; | ||||||
| 
 |  | ||||||
|         public IEnumerable<BasketItem> Items { get; private set; } |  | ||||||
| 
 |  | ||||||
|         public CreateOrderDraftCommand(string buyerId, IEnumerable<BasketItem> items) |  | ||||||
|         { |  | ||||||
|             BuyerId = buyerId; |  | ||||||
|             Items = items; |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,71 +1,58 @@ | |||||||
| namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; | ||||||
|  | 
 | ||||||
|  | using static Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands.CreateOrderCommand; | ||||||
|  | using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; | ||||||
|  | 
 | ||||||
|  | // Regular CommandHandler | ||||||
|  | public class CreateOrderDraftCommandHandler | ||||||
|  |     : IRequestHandler<CreateOrderDraftCommand, OrderDraftDTO> | ||||||
| { | { | ||||||
|     using Domain.AggregatesModel.OrderAggregate; |     private readonly IOrderRepository _orderRepository; | ||||||
|     using global::Ordering.API.Application.Models; |     private readonly IIdentityService _identityService; | ||||||
|     using MediatR; |     private readonly IMediator _mediator; | ||||||
|     using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services; |  | ||||||
|     using System; |  | ||||||
|     using System.Collections.Generic; |  | ||||||
|     using System.Linq; |  | ||||||
|     using System.Threading; |  | ||||||
|     using System.Threading.Tasks; |  | ||||||
|     using static Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands.CreateOrderCommand; |  | ||||||
| 
 | 
 | ||||||
|     // Regular CommandHandler |     // Using DI to inject infrastructure persistence Repositories | ||||||
|     public class CreateOrderDraftCommandHandler |     public CreateOrderDraftCommandHandler(IMediator mediator, IIdentityService identityService) | ||||||
|         : IRequestHandler<CreateOrderDraftCommand, OrderDraftDTO> |  | ||||||
|     { |     { | ||||||
|         private readonly IOrderRepository _orderRepository; |         _identityService = identityService ?? throw new ArgumentNullException(nameof(identityService)); | ||||||
|         private readonly IIdentityService _identityService; |         _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); | ||||||
|         private readonly IMediator _mediator; |  | ||||||
| 
 |  | ||||||
|         // Using DI to inject infrastructure persistence Repositories |  | ||||||
|         public CreateOrderDraftCommandHandler(IMediator mediator, IIdentityService identityService) |  | ||||||
|         { |  | ||||||
|             _identityService = identityService ?? throw new ArgumentNullException(nameof(identityService)); |  | ||||||
|             _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         public Task<OrderDraftDTO> Handle(CreateOrderDraftCommand message, CancellationToken cancellationToken) |  | ||||||
|         { |  | ||||||
| 
 |  | ||||||
|             var order = Order.NewDraft(); |  | ||||||
|             var orderItems = message.Items.Select(i => i.ToOrderItemDTO()); |  | ||||||
|             foreach (var item in orderItems) |  | ||||||
|             { |  | ||||||
|                 order.AddOrderItem(item.ProductId, item.ProductName, item.UnitPrice, item.Discount, item.PictureUrl, item.Units); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             return Task.FromResult(OrderDraftDTO.FromOrder(order)); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 |     public Task<OrderDraftDTO> Handle(CreateOrderDraftCommand message, CancellationToken cancellationToken) | ||||||
|     public record OrderDraftDTO |  | ||||||
|     { |     { | ||||||
|         public IEnumerable<OrderItemDTO> OrderItems { get; init; } |  | ||||||
|         public decimal Total { get; init; } |  | ||||||
| 
 | 
 | ||||||
|         public static OrderDraftDTO FromOrder(Order order) |         var order = Order.NewDraft(); | ||||||
|  |         var orderItems = message.Items.Select(i => i.ToOrderItemDTO()); | ||||||
|  |         foreach (var item in orderItems) | ||||||
|         { |         { | ||||||
|             return new OrderDraftDTO() |             order.AddOrderItem(item.ProductId, item.ProductName, item.UnitPrice, item.Discount, item.PictureUrl, item.Units); | ||||||
|             { |  | ||||||
|                 OrderItems = order.OrderItems.Select(oi => new OrderItemDTO |  | ||||||
|                 { |  | ||||||
|                     Discount = oi.GetCurrentDiscount(), |  | ||||||
|                     ProductId = oi.ProductId, |  | ||||||
|                     UnitPrice = oi.GetUnitPrice(), |  | ||||||
|                     PictureUrl = oi.GetPictureUri(), |  | ||||||
|                     Units = oi.GetUnits(), |  | ||||||
|                     ProductName = oi.GetOrderItemProductName() |  | ||||||
|                 }), |  | ||||||
|                 Total = order.GetTotal() |  | ||||||
|             }; |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         return Task.FromResult(OrderDraftDTO.FromOrder(order)); | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | public record OrderDraftDTO | ||||||
|  | { | ||||||
|  |     public IEnumerable<OrderItemDTO> OrderItems { get; init; } | ||||||
|  |     public decimal Total { get; init; } | ||||||
|  | 
 | ||||||
|  |     public static OrderDraftDTO FromOrder(Order order) | ||||||
|  |     { | ||||||
|  |         return new OrderDraftDTO() | ||||||
|  |         { | ||||||
|  |             OrderItems = order.OrderItems.Select(oi => new OrderItemDTO | ||||||
|  |             { | ||||||
|  |                 Discount = oi.GetCurrentDiscount(), | ||||||
|  |                 ProductId = oi.ProductId, | ||||||
|  |                 UnitPrice = oi.GetUnitPrice(), | ||||||
|  |                 PictureUrl = oi.GetPictureUri(), | ||||||
|  |                 Units = oi.GetUnits(), | ||||||
|  |                 ProductName = oi.GetOrderItemProductName() | ||||||
|  |             }), | ||||||
|  |             Total = order.GetTotal() | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -1,17 +1,13 @@ | |||||||
| using MediatR; | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; | ||||||
| using System; |  | ||||||
| 
 | 
 | ||||||
| namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands | public class IdentifiedCommand<T, R> : IRequest<R> | ||||||
|  |     where T : IRequest<R> | ||||||
| { | { | ||||||
|     public class IdentifiedCommand<T, R> : IRequest<R> |     public T Command { get; } | ||||||
|         where T : IRequest<R> |     public Guid Id { get; } | ||||||
|  |     public IdentifiedCommand(T command, Guid id) | ||||||
|     { |     { | ||||||
|         public T Command { get; } |         Command = command; | ||||||
|         public Guid Id { get; } |         Id = id; | ||||||
|         public IdentifiedCommand(T command, Guid id) |  | ||||||
|         { |  | ||||||
|             Command = command; |  | ||||||
|             Id = id; |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,116 +1,107 @@ | |||||||
| using MediatR; | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; | ||||||
| using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Extensions; |  | ||||||
| using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency; |  | ||||||
| using Microsoft.Extensions.Logging; |  | ||||||
| using Ordering.API.Application.Commands; |  | ||||||
| using System.Threading; |  | ||||||
| using System.Threading.Tasks; |  | ||||||
| 
 | 
 | ||||||
| namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands | /// <summary> | ||||||
|  | /// Provides a base implementation for handling duplicate request and ensuring idempotent updates, in the cases where | ||||||
|  | /// a requestid sent by client is used to detect duplicate requests. | ||||||
|  | /// </summary> | ||||||
|  | /// <typeparam name="T">Type of the command handler that performs the operation if request is not duplicated</typeparam> | ||||||
|  | /// <typeparam name="R">Return value of the inner command handler</typeparam> | ||||||
|  | public class IdentifiedCommandHandler<T, R> : IRequestHandler<IdentifiedCommand<T, R>, R> | ||||||
|  |     where T : IRequest<R> | ||||||
| { | { | ||||||
|     /// <summary> |     private readonly IMediator _mediator; | ||||||
|     /// Provides a base implementation for handling duplicate request and ensuring idempotent updates, in the cases where |     private readonly IRequestManager _requestManager; | ||||||
|     /// a requestid sent by client is used to detect duplicate requests. |     private readonly ILogger<IdentifiedCommandHandler<T, R>> _logger; | ||||||
|     /// </summary> | 
 | ||||||
|     /// <typeparam name="T">Type of the command handler that performs the operation if request is not duplicated</typeparam> |     public IdentifiedCommandHandler( | ||||||
|     /// <typeparam name="R">Return value of the inner command handler</typeparam> |         IMediator mediator, | ||||||
|     public class IdentifiedCommandHandler<T, R> : IRequestHandler<IdentifiedCommand<T, R>, R> |         IRequestManager requestManager, | ||||||
|         where T : IRequest<R> |         ILogger<IdentifiedCommandHandler<T, R>> logger) | ||||||
|     { |     { | ||||||
|         private readonly IMediator _mediator; |         _mediator = mediator; | ||||||
|         private readonly IRequestManager _requestManager; |         _requestManager = requestManager; | ||||||
|         private readonly ILogger<IdentifiedCommandHandler<T, R>> _logger; |         _logger = logger ?? throw new System.ArgumentNullException(nameof(logger)); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|         public IdentifiedCommandHandler( |     /// <summary> | ||||||
|             IMediator mediator, |     /// Creates the result value to return if a previous request was found | ||||||
|             IRequestManager requestManager, |     /// </summary> | ||||||
|             ILogger<IdentifiedCommandHandler<T, R>> logger) |     /// <returns></returns> | ||||||
|  |     protected virtual R CreateResultForDuplicateRequest() | ||||||
|  |     { | ||||||
|  |         return default(R); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// <summary> | ||||||
|  |     /// This method handles the command. It just ensures that no other request exists with the same ID, and if this is the case | ||||||
|  |     /// just enqueues the original inner command. | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="message">IdentifiedCommand which contains both original command & request ID</param> | ||||||
|  |     /// <returns>Return value of inner command or default value if request same ID was found</returns> | ||||||
|  |     public async Task<R> Handle(IdentifiedCommand<T, R> message, CancellationToken cancellationToken) | ||||||
|  |     { | ||||||
|  |         var alreadyExists = await _requestManager.ExistAsync(message.Id); | ||||||
|  |         if (alreadyExists) | ||||||
|         { |         { | ||||||
|             _mediator = mediator; |             return CreateResultForDuplicateRequest(); | ||||||
|             _requestManager = requestManager; |  | ||||||
|             _logger = logger ?? throw new System.ArgumentNullException(nameof(logger)); |  | ||||||
|         } |         } | ||||||
| 
 |         else | ||||||
|         /// <summary> |  | ||||||
|         /// Creates the result value to return if a previous request was found |  | ||||||
|         /// </summary> |  | ||||||
|         /// <returns></returns> |  | ||||||
|         protected virtual R CreateResultForDuplicateRequest() |  | ||||||
|         { |         { | ||||||
|             return default(R); |             await _requestManager.CreateRequestForCommandAsync<T>(message.Id); | ||||||
|         } |             try | ||||||
| 
 |  | ||||||
|         /// <summary> |  | ||||||
|         /// This method handles the command. It just ensures that no other request exists with the same ID, and if this is the case |  | ||||||
|         /// just enqueues the original inner command. |  | ||||||
|         /// </summary> |  | ||||||
|         /// <param name="message">IdentifiedCommand which contains both original command & request ID</param> |  | ||||||
|         /// <returns>Return value of inner command or default value if request same ID was found</returns> |  | ||||||
|         public async Task<R> Handle(IdentifiedCommand<T, R> message, CancellationToken cancellationToken) |  | ||||||
|         { |  | ||||||
|             var alreadyExists = await _requestManager.ExistAsync(message.Id); |  | ||||||
|             if (alreadyExists) |  | ||||||
|             { |             { | ||||||
|                 return CreateResultForDuplicateRequest(); |                 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, cancellationToken); | ||||||
|  | 
 | ||||||
|  |                 _logger.LogInformation( | ||||||
|  |                     "----- Command result: {@Result} - {CommandName} - {IdProperty}: {CommandId} ({@Command})", | ||||||
|  |                     result, | ||||||
|  |                     commandName, | ||||||
|  |                     idProperty, | ||||||
|  |                     commandId, | ||||||
|  |                     command); | ||||||
|  | 
 | ||||||
|  |                 return result; | ||||||
|             } |             } | ||||||
|             else |             catch | ||||||
|             { |             { | ||||||
|                 await _requestManager.CreateRequestForCommandAsync<T>(message.Id); |                 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, cancellationToken); |  | ||||||
| 
 |  | ||||||
|                     _logger.LogInformation( |  | ||||||
|                         "----- Command result: {@Result} - {CommandName} - {IdProperty}: {CommandId} ({@Command})", |  | ||||||
|                         result, |  | ||||||
|                         commandName, |  | ||||||
|                         idProperty, |  | ||||||
|                         commandId, |  | ||||||
|                         command); |  | ||||||
| 
 |  | ||||||
|                     return result; |  | ||||||
|                 } |  | ||||||
|                 catch |  | ||||||
|                 { |  | ||||||
|                     return default(R); |  | ||||||
|                 } |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,17 +1,13 @@ | |||||||
| using MediatR; | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; | ||||||
| using System.Runtime.Serialization; |  | ||||||
| 
 | 
 | ||||||
| namespace Ordering.API.Application.Commands | public class SetAwaitingValidationOrderStatusCommand : IRequest<bool> | ||||||
| { | { | ||||||
|     public class SetAwaitingValidationOrderStatusCommand : IRequest<bool> | 
 | ||||||
|  |     [DataMember] | ||||||
|  |     public int OrderNumber { get; private set; } | ||||||
|  | 
 | ||||||
|  |     public SetAwaitingValidationOrderStatusCommand(int orderNumber) | ||||||
|     { |     { | ||||||
| 
 |         OrderNumber = orderNumber; | ||||||
|         [DataMember] |  | ||||||
|         public int OrderNumber { get; private set; } |  | ||||||
| 
 |  | ||||||
|         public SetAwaitingValidationOrderStatusCommand(int orderNumber) |  | ||||||
|         { |  | ||||||
|             OrderNumber = orderNumber; |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,57 +1,48 @@ | |||||||
| using MediatR; | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; | ||||||
| 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; |  | ||||||
| 
 | 
 | ||||||
| namespace Ordering.API.Application.Commands | // Regular CommandHandler | ||||||
|  | public class SetAwaitingValidationOrderStatusCommandHandler : IRequestHandler<SetAwaitingValidationOrderStatusCommand, bool> | ||||||
| { | { | ||||||
|     // Regular CommandHandler |     private readonly IOrderRepository _orderRepository; | ||||||
|     public class SetAwaitingValidationOrderStatusCommandHandler : IRequestHandler<SetAwaitingValidationOrderStatusCommand, bool> | 
 | ||||||
|  |     public SetAwaitingValidationOrderStatusCommandHandler(IOrderRepository orderRepository) | ||||||
|     { |     { | ||||||
|         private readonly IOrderRepository _orderRepository; |         _orderRepository = orderRepository; | ||||||
| 
 |  | ||||||
|         public SetAwaitingValidationOrderStatusCommandHandler(IOrderRepository orderRepository) |  | ||||||
|         { |  | ||||||
|             _orderRepository = orderRepository; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         /// <summary> |  | ||||||
|         /// Handler which processes the command when |  | ||||||
|         /// graceperiod has finished |  | ||||||
|         /// </summary> |  | ||||||
|         /// <param name="command"></param> |  | ||||||
|         /// <returns></returns> |  | ||||||
|         public async Task<bool> Handle(SetAwaitingValidationOrderStatusCommand command, CancellationToken cancellationToken) |  | ||||||
|         { |  | ||||||
|             var orderToUpdate = await _orderRepository.GetAsync(command.OrderNumber); |  | ||||||
|             if (orderToUpdate == null) |  | ||||||
|             { |  | ||||||
|                 return false; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             orderToUpdate.SetAwaitingValidationStatus(); |  | ||||||
|             return await _orderRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 |     /// <summary> | ||||||
|     // Use for Idempotency in Command process |     /// Handler which processes the command when | ||||||
|     public class SetAwaitingValidationIdentifiedOrderStatusCommandHandler : IdentifiedCommandHandler<SetAwaitingValidationOrderStatusCommand, bool> |     /// graceperiod has finished | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="command"></param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     public async Task<bool> Handle(SetAwaitingValidationOrderStatusCommand command, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         public SetAwaitingValidationIdentifiedOrderStatusCommandHandler( |         var orderToUpdate = await _orderRepository.GetAsync(command.OrderNumber); | ||||||
|             IMediator mediator, |         if (orderToUpdate == null) | ||||||
|             IRequestManager requestManager, |  | ||||||
|             ILogger<IdentifiedCommandHandler<SetAwaitingValidationOrderStatusCommand, bool>> logger) |  | ||||||
|             : base(mediator, requestManager, logger) |  | ||||||
|         { |         { | ||||||
|  |             return false; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         protected override bool CreateResultForDuplicateRequest() |         orderToUpdate.SetAwaitingValidationStatus(); | ||||||
|         { |         return await _orderRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken); | ||||||
|             return true;                // Ignore duplicate requests for processing order. |     } | ||||||
|         } | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | // Use for Idempotency in Command process | ||||||
|  | public class SetAwaitingValidationIdentifiedOrderStatusCommandHandler : IdentifiedCommandHandler<SetAwaitingValidationOrderStatusCommand, bool> | ||||||
|  | { | ||||||
|  |     public SetAwaitingValidationIdentifiedOrderStatusCommandHandler( | ||||||
|  |         IMediator mediator, | ||||||
|  |         IRequestManager requestManager, | ||||||
|  |         ILogger<IdentifiedCommandHandler<SetAwaitingValidationOrderStatusCommand, bool>> logger) | ||||||
|  |         : base(mediator, requestManager, logger) | ||||||
|  |     { | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     protected override bool CreateResultForDuplicateRequest() | ||||||
|  |     { | ||||||
|  |         return true;                // Ignore duplicate requests for processing order. | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,17 +1,13 @@ | |||||||
| using MediatR; | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; | ||||||
| using System.Runtime.Serialization; |  | ||||||
| 
 | 
 | ||||||
| namespace Ordering.API.Application.Commands | public class SetPaidOrderStatusCommand : IRequest<bool> | ||||||
| { | { | ||||||
|     public class SetPaidOrderStatusCommand : IRequest<bool> | 
 | ||||||
|  |     [DataMember] | ||||||
|  |     public int OrderNumber { get; private set; } | ||||||
|  | 
 | ||||||
|  |     public SetPaidOrderStatusCommand(int orderNumber) | ||||||
|     { |     { | ||||||
| 
 |         OrderNumber = orderNumber; | ||||||
|         [DataMember] |  | ||||||
|         public int OrderNumber { get; private set; } |  | ||||||
| 
 |  | ||||||
|         public SetPaidOrderStatusCommand(int orderNumber) |  | ||||||
|         { |  | ||||||
|             OrderNumber = orderNumber; |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,60 +1,51 @@ | |||||||
| using MediatR; | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; | ||||||
| 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; |  | ||||||
| 
 | 
 | ||||||
| namespace Ordering.API.Application.Commands | // Regular CommandHandler | ||||||
|  | public class SetPaidOrderStatusCommandHandler : IRequestHandler<SetPaidOrderStatusCommand, bool> | ||||||
| { | { | ||||||
|     // Regular CommandHandler |     private readonly IOrderRepository _orderRepository; | ||||||
|     public class SetPaidOrderStatusCommandHandler : IRequestHandler<SetPaidOrderStatusCommand, bool> | 
 | ||||||
|  |     public SetPaidOrderStatusCommandHandler(IOrderRepository orderRepository) | ||||||
|     { |     { | ||||||
|         private readonly IOrderRepository _orderRepository; |         _orderRepository = orderRepository; | ||||||
| 
 |  | ||||||
|         public SetPaidOrderStatusCommandHandler(IOrderRepository orderRepository) |  | ||||||
|         { |  | ||||||
|             _orderRepository = orderRepository; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         /// <summary> |  | ||||||
|         /// Handler which processes the command when |  | ||||||
|         /// Shipment service confirms the payment |  | ||||||
|         /// </summary> |  | ||||||
|         /// <param name="command"></param> |  | ||||||
|         /// <returns></returns> |  | ||||||
|         public async Task<bool> Handle(SetPaidOrderStatusCommand command, CancellationToken cancellationToken) |  | ||||||
|         { |  | ||||||
|             // Simulate a work time for validating the payment |  | ||||||
|             await Task.Delay(10000, cancellationToken); |  | ||||||
| 
 |  | ||||||
|             var orderToUpdate = await _orderRepository.GetAsync(command.OrderNumber); |  | ||||||
|             if (orderToUpdate == null) |  | ||||||
|             { |  | ||||||
|                 return false; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             orderToUpdate.SetPaidStatus(); |  | ||||||
|             return await _orderRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 |     /// <summary> | ||||||
|     // Use for Idempotency in Command process |     /// Handler which processes the command when | ||||||
|     public class SetPaidIdentifiedOrderStatusCommandHandler : IdentifiedCommandHandler<SetPaidOrderStatusCommand, bool> |     /// Shipment service confirms the payment | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="command"></param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     public async Task<bool> Handle(SetPaidOrderStatusCommand command, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         public SetPaidIdentifiedOrderStatusCommandHandler( |         // Simulate a work time for validating the payment | ||||||
|             IMediator mediator, |         await Task.Delay(10000, cancellationToken); | ||||||
|             IRequestManager requestManager, | 
 | ||||||
|             ILogger<IdentifiedCommandHandler<SetPaidOrderStatusCommand, bool>> logger) |         var orderToUpdate = await _orderRepository.GetAsync(command.OrderNumber); | ||||||
|             : base(mediator, requestManager, logger) |         if (orderToUpdate == null) | ||||||
|         { |         { | ||||||
|  |             return false; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         protected override bool CreateResultForDuplicateRequest() |         orderToUpdate.SetPaidStatus(); | ||||||
|         { |         return await _orderRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken); | ||||||
|             return true;                // Ignore duplicate requests for processing order. |     } | ||||||
|         } | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | // Use for Idempotency in Command process | ||||||
|  | public class SetPaidIdentifiedOrderStatusCommandHandler : IdentifiedCommandHandler<SetPaidOrderStatusCommand, bool> | ||||||
|  | { | ||||||
|  |     public SetPaidIdentifiedOrderStatusCommandHandler( | ||||||
|  |         IMediator mediator, | ||||||
|  |         IRequestManager requestManager, | ||||||
|  |         ILogger<IdentifiedCommandHandler<SetPaidOrderStatusCommand, bool>> logger) | ||||||
|  |         : base(mediator, requestManager, logger) | ||||||
|  |     { | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     protected override bool CreateResultForDuplicateRequest() | ||||||
|  |     { | ||||||
|  |         return true;                // Ignore duplicate requests for processing order. | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,17 +1,13 @@ | |||||||
| using MediatR; | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; | ||||||
| using System.Runtime.Serialization; |  | ||||||
| 
 | 
 | ||||||
| namespace Ordering.API.Application.Commands | public class SetStockConfirmedOrderStatusCommand : IRequest<bool> | ||||||
| { | { | ||||||
|     public class SetStockConfirmedOrderStatusCommand : IRequest<bool> | 
 | ||||||
|  |     [DataMember] | ||||||
|  |     public int OrderNumber { get; private set; } | ||||||
|  | 
 | ||||||
|  |     public SetStockConfirmedOrderStatusCommand(int orderNumber) | ||||||
|     { |     { | ||||||
| 
 |         OrderNumber = orderNumber; | ||||||
|         [DataMember] |  | ||||||
|         public int OrderNumber { get; private set; } |  | ||||||
| 
 |  | ||||||
|         public SetStockConfirmedOrderStatusCommand(int orderNumber) |  | ||||||
|         { |  | ||||||
|             OrderNumber = orderNumber; |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,60 +1,51 @@ | |||||||
| using MediatR; | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; | ||||||
| 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; |  | ||||||
| 
 | 
 | ||||||
| namespace Ordering.API.Application.Commands | // Regular CommandHandler | ||||||
|  | public class SetStockConfirmedOrderStatusCommandHandler : IRequestHandler<SetStockConfirmedOrderStatusCommand, bool> | ||||||
| { | { | ||||||
|     // Regular CommandHandler |     private readonly IOrderRepository _orderRepository; | ||||||
|     public class SetStockConfirmedOrderStatusCommandHandler : IRequestHandler<SetStockConfirmedOrderStatusCommand, bool> | 
 | ||||||
|  |     public SetStockConfirmedOrderStatusCommandHandler(IOrderRepository orderRepository) | ||||||
|     { |     { | ||||||
|         private readonly IOrderRepository _orderRepository; |         _orderRepository = orderRepository; | ||||||
| 
 |  | ||||||
|         public SetStockConfirmedOrderStatusCommandHandler(IOrderRepository orderRepository) |  | ||||||
|         { |  | ||||||
|             _orderRepository = orderRepository; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         /// <summary> |  | ||||||
|         /// Handler which processes the command when |  | ||||||
|         /// Stock service confirms the request |  | ||||||
|         /// </summary> |  | ||||||
|         /// <param name="command"></param> |  | ||||||
|         /// <returns></returns> |  | ||||||
|         public async Task<bool> Handle(SetStockConfirmedOrderStatusCommand command, CancellationToken cancellationToken) |  | ||||||
|         { |  | ||||||
|             // Simulate a work time for confirming the stock |  | ||||||
|             await Task.Delay(10000, cancellationToken); |  | ||||||
| 
 |  | ||||||
|             var orderToUpdate = await _orderRepository.GetAsync(command.OrderNumber); |  | ||||||
|             if (orderToUpdate == null) |  | ||||||
|             { |  | ||||||
|                 return false; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             orderToUpdate.SetStockConfirmedStatus(); |  | ||||||
|             return await _orderRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 |     /// <summary> | ||||||
|     // Use for Idempotency in Command process |     /// Handler which processes the command when | ||||||
|     public class SetStockConfirmedOrderStatusIdenfifiedCommandHandler : IdentifiedCommandHandler<SetStockConfirmedOrderStatusCommand, bool> |     /// Stock service confirms the request | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="command"></param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     public async Task<bool> Handle(SetStockConfirmedOrderStatusCommand command, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         public SetStockConfirmedOrderStatusIdenfifiedCommandHandler( |         // Simulate a work time for confirming the stock | ||||||
|             IMediator mediator, |         await Task.Delay(10000, cancellationToken); | ||||||
|             IRequestManager requestManager, | 
 | ||||||
|             ILogger<IdentifiedCommandHandler<SetStockConfirmedOrderStatusCommand, bool>> logger) |         var orderToUpdate = await _orderRepository.GetAsync(command.OrderNumber); | ||||||
|             : base(mediator, requestManager, logger) |         if (orderToUpdate == null) | ||||||
|         { |         { | ||||||
|  |             return false; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         protected override bool CreateResultForDuplicateRequest() |         orderToUpdate.SetStockConfirmedStatus(); | ||||||
|         { |         return await _orderRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken); | ||||||
|             return true;                // Ignore duplicate requests for processing order. |     } | ||||||
|         } | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | // Use for Idempotency in Command process | ||||||
|  | public class SetStockConfirmedOrderStatusIdenfifiedCommandHandler : IdentifiedCommandHandler<SetStockConfirmedOrderStatusCommand, bool> | ||||||
|  | { | ||||||
|  |     public SetStockConfirmedOrderStatusIdenfifiedCommandHandler( | ||||||
|  |         IMediator mediator, | ||||||
|  |         IRequestManager requestManager, | ||||||
|  |         ILogger<IdentifiedCommandHandler<SetStockConfirmedOrderStatusCommand, bool>> logger) | ||||||
|  |         : base(mediator, requestManager, logger) | ||||||
|  |     { | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     protected override bool CreateResultForDuplicateRequest() | ||||||
|  |     { | ||||||
|  |         return true;                // Ignore duplicate requests for processing order. | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,22 +1,17 @@ | |||||||
| using MediatR; | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; | ||||||
| using System.Collections.Generic; |  | ||||||
| using System.Runtime.Serialization; |  | ||||||
| 
 | 
 | ||||||
| namespace Ordering.API.Application.Commands | public class SetStockRejectedOrderStatusCommand : IRequest<bool> | ||||||
| { | { | ||||||
|     public class SetStockRejectedOrderStatusCommand : IRequest<bool> | 
 | ||||||
|  |     [DataMember] | ||||||
|  |     public int OrderNumber { get; private set; } | ||||||
|  | 
 | ||||||
|  |     [DataMember] | ||||||
|  |     public List<int> OrderStockItems { get; private set; } | ||||||
|  | 
 | ||||||
|  |     public SetStockRejectedOrderStatusCommand(int orderNumber, List<int> orderStockItems) | ||||||
|     { |     { | ||||||
| 
 |         OrderNumber = orderNumber; | ||||||
|         [DataMember] |         OrderStockItems = orderStockItems; | ||||||
|         public int OrderNumber { get; private set; } |  | ||||||
| 
 |  | ||||||
|         [DataMember] |  | ||||||
|         public List<int> OrderStockItems { get; private set; } |  | ||||||
| 
 |  | ||||||
|         public SetStockRejectedOrderStatusCommand(int orderNumber, List<int> orderStockItems) |  | ||||||
|         { |  | ||||||
|             OrderNumber = orderNumber; |  | ||||||
|             OrderStockItems = orderStockItems; |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,61 +1,52 @@ | |||||||
| using MediatR; | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; | ||||||
| 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; |  | ||||||
| 
 | 
 | ||||||
| namespace Ordering.API.Application.Commands | // Regular CommandHandler | ||||||
|  | public class SetStockRejectedOrderStatusCommandHandler : IRequestHandler<SetStockRejectedOrderStatusCommand, bool> | ||||||
| { | { | ||||||
|     // Regular CommandHandler |     private readonly IOrderRepository _orderRepository; | ||||||
|     public class SetStockRejectedOrderStatusCommandHandler : IRequestHandler<SetStockRejectedOrderStatusCommand, bool> | 
 | ||||||
|  |     public SetStockRejectedOrderStatusCommandHandler(IOrderRepository orderRepository) | ||||||
|     { |     { | ||||||
|         private readonly IOrderRepository _orderRepository; |         _orderRepository = orderRepository; | ||||||
| 
 |  | ||||||
|         public SetStockRejectedOrderStatusCommandHandler(IOrderRepository orderRepository) |  | ||||||
|         { |  | ||||||
|             _orderRepository = orderRepository; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         /// <summary> |  | ||||||
|         /// Handler which processes the command when |  | ||||||
|         /// Stock service rejects the request |  | ||||||
|         /// </summary> |  | ||||||
|         /// <param name="command"></param> |  | ||||||
|         /// <returns></returns> |  | ||||||
|         public async Task<bool> Handle(SetStockRejectedOrderStatusCommand command, CancellationToken cancellationToken) |  | ||||||
|         { |  | ||||||
|             // Simulate a work time for rejecting the stock |  | ||||||
|             await Task.Delay(10000, cancellationToken); |  | ||||||
| 
 |  | ||||||
|             var orderToUpdate = await _orderRepository.GetAsync(command.OrderNumber); |  | ||||||
|             if (orderToUpdate == null) |  | ||||||
|             { |  | ||||||
|                 return false; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             orderToUpdate.SetCancelledStatusWhenStockIsRejected(command.OrderStockItems); |  | ||||||
| 
 |  | ||||||
|             return await _orderRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 |     /// <summary> | ||||||
|     // Use for Idempotency in Command process |     /// Handler which processes the command when | ||||||
|     public class SetStockRejectedOrderStatusIdenfifiedCommandHandler : IdentifiedCommandHandler<SetStockRejectedOrderStatusCommand, bool> |     /// Stock service rejects the request | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="command"></param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     public async Task<bool> Handle(SetStockRejectedOrderStatusCommand command, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         public SetStockRejectedOrderStatusIdenfifiedCommandHandler( |         // Simulate a work time for rejecting the stock | ||||||
|             IMediator mediator, |         await Task.Delay(10000, cancellationToken); | ||||||
|             IRequestManager requestManager, | 
 | ||||||
|             ILogger<IdentifiedCommandHandler<SetStockRejectedOrderStatusCommand, bool>> logger) |         var orderToUpdate = await _orderRepository.GetAsync(command.OrderNumber); | ||||||
|             : base(mediator, requestManager, logger) |         if (orderToUpdate == null) | ||||||
|         { |         { | ||||||
|  |             return false; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         protected override bool CreateResultForDuplicateRequest() |         orderToUpdate.SetCancelledStatusWhenStockIsRejected(command.OrderStockItems); | ||||||
|         { | 
 | ||||||
|             return true;                // Ignore duplicate requests for processing order. |         return await _orderRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken); | ||||||
|         } |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | // Use for Idempotency in Command process | ||||||
|  | public class SetStockRejectedOrderStatusIdenfifiedCommandHandler : IdentifiedCommandHandler<SetStockRejectedOrderStatusCommand, bool> | ||||||
|  | { | ||||||
|  |     public SetStockRejectedOrderStatusIdenfifiedCommandHandler( | ||||||
|  |         IMediator mediator, | ||||||
|  |         IRequestManager requestManager, | ||||||
|  |         ILogger<IdentifiedCommandHandler<SetStockRejectedOrderStatusCommand, bool>> logger) | ||||||
|  |         : base(mediator, requestManager, logger) | ||||||
|  |     { | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     protected override bool CreateResultForDuplicateRequest() | ||||||
|  |     { | ||||||
|  |         return true;                // Ignore duplicate requests for processing order. | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,17 +1,13 @@ | |||||||
| using MediatR; | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; | ||||||
| using System.Runtime.Serialization; |  | ||||||
| 
 | 
 | ||||||
| namespace Ordering.API.Application.Commands | public class ShipOrderCommand : IRequest<bool> | ||||||
| { | { | ||||||
|     public class ShipOrderCommand : IRequest<bool> | 
 | ||||||
|  |     [DataMember] | ||||||
|  |     public int OrderNumber { get; private set; } | ||||||
|  | 
 | ||||||
|  |     public ShipOrderCommand(int orderNumber) | ||||||
|     { |     { | ||||||
| 
 |         OrderNumber = orderNumber; | ||||||
|         [DataMember] |  | ||||||
|         public int OrderNumber { get; private set; } |  | ||||||
| 
 |  | ||||||
|         public ShipOrderCommand(int orderNumber) |  | ||||||
|         { |  | ||||||
|             OrderNumber = orderNumber; |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,57 +1,48 @@ | |||||||
| using MediatR; | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; | ||||||
| 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; |  | ||||||
| 
 | 
 | ||||||
| namespace Ordering.API.Application.Commands | // Regular CommandHandler | ||||||
|  | public class ShipOrderCommandHandler : IRequestHandler<ShipOrderCommand, bool> | ||||||
| { | { | ||||||
|     // Regular CommandHandler |     private readonly IOrderRepository _orderRepository; | ||||||
|     public class ShipOrderCommandHandler : IRequestHandler<ShipOrderCommand, bool> | 
 | ||||||
|  |     public ShipOrderCommandHandler(IOrderRepository orderRepository) | ||||||
|     { |     { | ||||||
|         private readonly IOrderRepository _orderRepository; |         _orderRepository = orderRepository; | ||||||
| 
 |  | ||||||
|         public ShipOrderCommandHandler(IOrderRepository orderRepository) |  | ||||||
|         { |  | ||||||
|             _orderRepository = orderRepository; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         /// <summary> |  | ||||||
|         /// Handler which processes the command when |  | ||||||
|         /// administrator executes ship order from app |  | ||||||
|         /// </summary> |  | ||||||
|         /// <param name="command"></param> |  | ||||||
|         /// <returns></returns> |  | ||||||
|         public async Task<bool> Handle(ShipOrderCommand command, CancellationToken cancellationToken) |  | ||||||
|         { |  | ||||||
|             var orderToUpdate = await _orderRepository.GetAsync(command.OrderNumber); |  | ||||||
|             if (orderToUpdate == null) |  | ||||||
|             { |  | ||||||
|                 return false; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             orderToUpdate.SetShippedStatus(); |  | ||||||
|             return await _orderRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 |     /// <summary> | ||||||
|     // Use for Idempotency in Command process |     /// Handler which processes the command when | ||||||
|     public class ShipOrderIdentifiedCommandHandler : IdentifiedCommandHandler<ShipOrderCommand, bool> |     /// administrator executes ship order from app | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="command"></param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     public async Task<bool> Handle(ShipOrderCommand command, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         public ShipOrderIdentifiedCommandHandler( |         var orderToUpdate = await _orderRepository.GetAsync(command.OrderNumber); | ||||||
|             IMediator mediator, |         if (orderToUpdate == null) | ||||||
|             IRequestManager requestManager, |  | ||||||
|             ILogger<IdentifiedCommandHandler<ShipOrderCommand, bool>> logger) |  | ||||||
|             : base(mediator, requestManager, logger) |  | ||||||
|         { |         { | ||||||
|  |             return false; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         protected override bool CreateResultForDuplicateRequest() |         orderToUpdate.SetShippedStatus(); | ||||||
|         { |         return await _orderRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken); | ||||||
|             return true;                // Ignore duplicate requests for processing order. |     } | ||||||
|         } | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | // Use for Idempotency in Command process | ||||||
|  | public class ShipOrderIdentifiedCommandHandler : IdentifiedCommandHandler<ShipOrderCommand, bool> | ||||||
|  | { | ||||||
|  |     public ShipOrderIdentifiedCommandHandler( | ||||||
|  |         IMediator mediator, | ||||||
|  |         IRequestManager requestManager, | ||||||
|  |         ILogger<IdentifiedCommandHandler<ShipOrderCommand, bool>> logger) | ||||||
|  |         : base(mediator, requestManager, logger) | ||||||
|  |     { | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     protected override bool CreateResultForDuplicateRequest() | ||||||
|  |     { | ||||||
|  |         return true;                // Ignore duplicate requests for processing order. | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,38 +1,29 @@ | |||||||
| using MediatR; | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.DomainEventHandlers.BuyerAndPaymentMethodVerified; | ||||||
| using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; |  | ||||||
| using Microsoft.Extensions.Logging; |  | ||||||
| using Ordering.Domain.Events; |  | ||||||
| using System; |  | ||||||
| using System.Threading; |  | ||||||
| using System.Threading.Tasks; |  | ||||||
| 
 | 
 | ||||||
| namespace Ordering.API.Application.DomainEventHandlers.BuyerAndPaymentMethodVerified | public class UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler | ||||||
|  |                 : INotificationHandler<BuyerAndPaymentMethodVerifiedDomainEvent> | ||||||
| { | { | ||||||
|     public class UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler |     private readonly IOrderRepository _orderRepository; | ||||||
|                    : INotificationHandler<BuyerAndPaymentMethodVerifiedDomainEvent> |     private readonly ILoggerFactory _logger; | ||||||
|  | 
 | ||||||
|  |     public UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler( | ||||||
|  |         IOrderRepository orderRepository, ILoggerFactory logger) | ||||||
|     { |     { | ||||||
|         private readonly IOrderRepository _orderRepository; |         _orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository)); | ||||||
|         private readonly ILoggerFactory _logger; |         _logger = logger ?? throw new ArgumentNullException(nameof(logger)); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|         public UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler( |     // Domain Logic comment: | ||||||
|             IOrderRepository orderRepository, ILoggerFactory logger) |     // When the Buyer and Buyer's payment method have been created or verified that they existed,  | ||||||
|         { |     // then we can update the original Order with the BuyerId and PaymentId (foreign keys) | ||||||
|             _orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository)); |     public async Task Handle(BuyerAndPaymentMethodVerifiedDomainEvent buyerPaymentMethodVerifiedEvent, CancellationToken cancellationToken) | ||||||
|             _logger = logger ?? throw new ArgumentNullException(nameof(logger)); |     { | ||||||
|         } |         var orderToUpdate = await _orderRepository.GetAsync(buyerPaymentMethodVerifiedEvent.OrderId); | ||||||
|  |         orderToUpdate.SetBuyerId(buyerPaymentMethodVerifiedEvent.Buyer.Id); | ||||||
|  |         orderToUpdate.SetPaymentId(buyerPaymentMethodVerifiedEvent.Payment.Id); | ||||||
| 
 | 
 | ||||||
|         // Domain Logic comment: |         _logger.CreateLogger<UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler>() | ||||||
|         // When the Buyer and Buyer's payment method have been created or verified that they existed,  |             .LogTrace("Order with Id: {OrderId} has been successfully updated with a payment method {PaymentMethod} ({Id})", | ||||||
|         // then we can update the original Order with the BuyerId and PaymentId (foreign keys) |                 buyerPaymentMethodVerifiedEvent.OrderId, nameof(buyerPaymentMethodVerifiedEvent.Payment), buyerPaymentMethodVerifiedEvent.Payment.Id); | ||||||
|         public async Task Handle(BuyerAndPaymentMethodVerifiedDomainEvent buyerPaymentMethodVerifiedEvent, CancellationToken cancellationToken) |  | ||||||
|         { |  | ||||||
|             var orderToUpdate = await _orderRepository.GetAsync(buyerPaymentMethodVerifiedEvent.OrderId); |  | ||||||
|             orderToUpdate.SetBuyerId(buyerPaymentMethodVerifiedEvent.Buyer.Id); |  | ||||||
|             orderToUpdate.SetPaymentId(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); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,47 +1,37 @@ | |||||||
| using MediatR; | using Microsoft.eShopOnContainers.Services.Ordering.Domain.Events; | ||||||
| using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate; |  | ||||||
| using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; |  | ||||||
| using Microsoft.Extensions.Logging; |  | ||||||
| using Ordering.API.Application.IntegrationEvents; |  | ||||||
| using Ordering.API.Application.IntegrationEvents.Events; |  | ||||||
| using Ordering.Domain.Events; |  | ||||||
| using System; |  | ||||||
| using System.Threading; |  | ||||||
| using System.Threading.Tasks; |  | ||||||
| 
 | 
 | ||||||
| namespace Ordering.API.Application.DomainEventHandlers.OrderCancelled | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.DomainEventHandlers.OrderCancelled; | ||||||
|  | 
 | ||||||
|  | public class OrderCancelledDomainEventHandler | ||||||
|  |                 : INotificationHandler<OrderCancelledDomainEvent> | ||||||
| { | { | ||||||
|     public class OrderCancelledDomainEventHandler |     private readonly IOrderRepository _orderRepository; | ||||||
|                    : INotificationHandler<OrderCancelledDomainEvent> |     private readonly IBuyerRepository _buyerRepository; | ||||||
|  |     private readonly ILoggerFactory _logger; | ||||||
|  |     private readonly IOrderingIntegrationEventService _orderingIntegrationEventService; | ||||||
|  | 
 | ||||||
|  |     public OrderCancelledDomainEventHandler( | ||||||
|  |         IOrderRepository orderRepository, | ||||||
|  |         ILoggerFactory logger, | ||||||
|  |         IBuyerRepository buyerRepository, | ||||||
|  |         IOrderingIntegrationEventService orderingIntegrationEventService) | ||||||
|     { |     { | ||||||
|         private readonly IOrderRepository _orderRepository; |         _orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository)); | ||||||
|         private readonly IBuyerRepository _buyerRepository; |         _logger = logger ?? throw new ArgumentNullException(nameof(logger)); | ||||||
|         private readonly ILoggerFactory _logger; |         _buyerRepository = buyerRepository ?? throw new ArgumentNullException(nameof(buyerRepository)); | ||||||
|         private readonly IOrderingIntegrationEventService _orderingIntegrationEventService; |         _orderingIntegrationEventService = orderingIntegrationEventService; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|         public OrderCancelledDomainEventHandler( |     public async Task Handle(OrderCancelledDomainEvent orderCancelledDomainEvent, CancellationToken cancellationToken) | ||||||
|             IOrderRepository orderRepository, |     { | ||||||
|             ILoggerFactory logger, |         _logger.CreateLogger<OrderCancelledDomainEvent>() | ||||||
|             IBuyerRepository buyerRepository, |             .LogTrace("Order with Id: {OrderId} has been successfully updated to status {Status} ({Id})", | ||||||
|             IOrderingIntegrationEventService orderingIntegrationEventService) |                 orderCancelledDomainEvent.Order.Id, nameof(OrderStatus.Cancelled), OrderStatus.Cancelled.Id); | ||||||
|         { |  | ||||||
|             _orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository)); |  | ||||||
|             _logger = logger ?? throw new ArgumentNullException(nameof(logger)); |  | ||||||
|             _buyerRepository = buyerRepository ?? throw new ArgumentNullException(nameof(buyerRepository)); |  | ||||||
|             _orderingIntegrationEventService = orderingIntegrationEventService; |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         public async Task Handle(OrderCancelledDomainEvent orderCancelledDomainEvent, CancellationToken cancellationToken) |         var order = await _orderRepository.GetAsync(orderCancelledDomainEvent.Order.Id); | ||||||
|         { |         var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value.ToString()); | ||||||
|             _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 orderStatusChangedToCancelledIntegrationEvent = new OrderStatusChangedToCancelledIntegrationEvent(order.Id, order.OrderStatus.Name, buyer.Name); | ||||||
|             var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value.ToString()); |         await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedToCancelledIntegrationEvent); | ||||||
| 
 |  | ||||||
|             var orderStatusChangedToCancelledIntegrationEvent = new OrderStatusChangedToCancelledIntegrationEvent(order.Id, order.OrderStatus.Name, buyer.Name); |  | ||||||
|             await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedToCancelledIntegrationEvent); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,52 +1,39 @@ | |||||||
| namespace Ordering.API.Application.DomainEventHandlers.OrderGracePeriodConfirmed | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.DomainEventHandlers.OrderGracePeriodConfirmed; | ||||||
|  |      | ||||||
|  | public class OrderStatusChangedToAwaitingValidationDomainEventHandler | ||||||
|  |                 : INotificationHandler<OrderStatusChangedToAwaitingValidationDomainEvent> | ||||||
| { | { | ||||||
|     using Domain.Events; |     private readonly IOrderRepository _orderRepository; | ||||||
|     using MediatR; |     private readonly ILoggerFactory _logger; | ||||||
|     using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate; |     private readonly IBuyerRepository _buyerRepository; | ||||||
|     using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; |     private readonly IOrderingIntegrationEventService _orderingIntegrationEventService; | ||||||
|     using Microsoft.Extensions.Logging; |  | ||||||
|     using Ordering.API.Application.IntegrationEvents; |  | ||||||
|     using Ordering.API.Application.IntegrationEvents.Events; |  | ||||||
|     using System; |  | ||||||
|     using System.Linq; |  | ||||||
|     using System.Threading; |  | ||||||
|     using System.Threading.Tasks; |  | ||||||
| 
 | 
 | ||||||
|     public class OrderStatusChangedToAwaitingValidationDomainEventHandler |     public OrderStatusChangedToAwaitingValidationDomainEventHandler( | ||||||
|                    : INotificationHandler<OrderStatusChangedToAwaitingValidationDomainEvent> |         IOrderRepository orderRepository, ILoggerFactory logger, | ||||||
|  |         IBuyerRepository buyerRepository, | ||||||
|  |         IOrderingIntegrationEventService orderingIntegrationEventService) | ||||||
|     { |     { | ||||||
|         private readonly IOrderRepository _orderRepository; |         _orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository)); | ||||||
|         private readonly ILoggerFactory _logger; |         _logger = logger ?? throw new ArgumentNullException(nameof(logger)); | ||||||
|         private readonly IBuyerRepository _buyerRepository; |         _buyerRepository = buyerRepository; | ||||||
|         private readonly IOrderingIntegrationEventService _orderingIntegrationEventService; |         _orderingIntegrationEventService = orderingIntegrationEventService; | ||||||
| 
 |  | ||||||
|         public OrderStatusChangedToAwaitingValidationDomainEventHandler( |  | ||||||
|             IOrderRepository orderRepository, ILoggerFactory logger, |  | ||||||
|             IBuyerRepository buyerRepository, |  | ||||||
|             IOrderingIntegrationEventService orderingIntegrationEventService) |  | ||||||
|         { |  | ||||||
|             _orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository)); |  | ||||||
|             _logger = logger ?? throw new ArgumentNullException(nameof(logger)); |  | ||||||
|             _buyerRepository = buyerRepository; |  | ||||||
|             _orderingIntegrationEventService = orderingIntegrationEventService; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         public async Task Handle(OrderStatusChangedToAwaitingValidationDomainEvent orderStatusChangedToAwaitingValidationDomainEvent, CancellationToken cancellationToken) |  | ||||||
|         { |  | ||||||
|             _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); |  | ||||||
| 
 |  | ||||||
|             var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value.ToString()); |  | ||||||
| 
 |  | ||||||
|             var orderStockList = orderStatusChangedToAwaitingValidationDomainEvent.OrderItems |  | ||||||
|                 .Select(orderItem => new OrderStockItem(orderItem.ProductId, orderItem.GetUnits())); |  | ||||||
| 
 |  | ||||||
|             var orderStatusChangedToAwaitingValidationIntegrationEvent = new OrderStatusChangedToAwaitingValidationIntegrationEvent( |  | ||||||
|                 order.Id, order.OrderStatus.Name, buyer.Name, orderStockList); |  | ||||||
|             await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedToAwaitingValidationIntegrationEvent); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | 
 | ||||||
|  |     public async Task Handle(OrderStatusChangedToAwaitingValidationDomainEvent orderStatusChangedToAwaitingValidationDomainEvent, CancellationToken cancellationToken) | ||||||
|  |     { | ||||||
|  |         _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); | ||||||
|  | 
 | ||||||
|  |         var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value.ToString()); | ||||||
|  | 
 | ||||||
|  |         var orderStockList = orderStatusChangedToAwaitingValidationDomainEvent.OrderItems | ||||||
|  |             .Select(orderItem => new OrderStockItem(orderItem.ProductId, orderItem.GetUnits())); | ||||||
|  | 
 | ||||||
|  |         var orderStatusChangedToAwaitingValidationIntegrationEvent = new OrderStatusChangedToAwaitingValidationIntegrationEvent( | ||||||
|  |             order.Id, order.OrderStatus.Name, buyer.Name, orderStockList); | ||||||
|  |         await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedToAwaitingValidationIntegrationEvent); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | |||||||
| @ -1,57 +1,44 @@ | |||||||
| namespace Ordering.API.Application.DomainEventHandlers.OrderPaid | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.DomainEventHandlers.OrderPaid; | ||||||
|  |      | ||||||
|  | public class OrderStatusChangedToPaidDomainEventHandler | ||||||
|  |                 : INotificationHandler<OrderStatusChangedToPaidDomainEvent> | ||||||
| { | { | ||||||
|     using Domain.Events; |     private readonly IOrderRepository _orderRepository; | ||||||
|     using MediatR; |     private readonly ILoggerFactory _logger; | ||||||
|     using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate; |     private readonly IBuyerRepository _buyerRepository; | ||||||
|     using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; |     private readonly IOrderingIntegrationEventService _orderingIntegrationEventService; | ||||||
|     using Microsoft.Extensions.Logging; |  | ||||||
|     using Ordering.API.Application.IntegrationEvents; |  | ||||||
|     using Ordering.API.Application.IntegrationEvents.Events; |  | ||||||
|     using System; |  | ||||||
|     using System.Linq; |  | ||||||
|     using System.Threading; |  | ||||||
|     using System.Threading.Tasks; |  | ||||||
| 
 | 
 | ||||||
|     public class OrderStatusChangedToPaidDomainEventHandler | 
 | ||||||
|                    : INotificationHandler<OrderStatusChangedToPaidDomainEvent> |     public OrderStatusChangedToPaidDomainEventHandler( | ||||||
|  |         IOrderRepository orderRepository, ILoggerFactory logger, | ||||||
|  |         IBuyerRepository buyerRepository, | ||||||
|  |         IOrderingIntegrationEventService orderingIntegrationEventService | ||||||
|  |         ) | ||||||
|     { |     { | ||||||
|         private readonly IOrderRepository _orderRepository; |         _orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository)); | ||||||
|         private readonly ILoggerFactory _logger; |         _logger = logger ?? throw new ArgumentNullException(nameof(logger)); | ||||||
|         private readonly IBuyerRepository _buyerRepository; |         _buyerRepository = buyerRepository ?? throw new ArgumentNullException(nameof(buyerRepository)); | ||||||
|         private readonly IOrderingIntegrationEventService _orderingIntegrationEventService; |         _orderingIntegrationEventService = orderingIntegrationEventService ?? throw new ArgumentNullException(nameof(orderingIntegrationEventService)); | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|         public OrderStatusChangedToPaidDomainEventHandler( |  | ||||||
|             IOrderRepository orderRepository, ILoggerFactory logger, |  | ||||||
|             IBuyerRepository buyerRepository, |  | ||||||
|             IOrderingIntegrationEventService orderingIntegrationEventService |  | ||||||
|             ) |  | ||||||
|         { |  | ||||||
|             _orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository)); |  | ||||||
|             _logger = logger ?? throw new ArgumentNullException(nameof(logger)); |  | ||||||
|             _buyerRepository = buyerRepository ?? throw new ArgumentNullException(nameof(buyerRepository)); |  | ||||||
|             _orderingIntegrationEventService = orderingIntegrationEventService ?? throw new ArgumentNullException(nameof(orderingIntegrationEventService)); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         public async Task Handle(OrderStatusChangedToPaidDomainEvent orderStatusChangedToPaidDomainEvent, CancellationToken cancellationToken) |  | ||||||
|         { |  | ||||||
|             _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()); |  | ||||||
| 
 |  | ||||||
|             var orderStockList = orderStatusChangedToPaidDomainEvent.OrderItems |  | ||||||
|                 .Select(orderItem => new OrderStockItem(orderItem.ProductId, orderItem.GetUnits())); |  | ||||||
| 
 |  | ||||||
|             var orderStatusChangedToPaidIntegrationEvent = new OrderStatusChangedToPaidIntegrationEvent( |  | ||||||
|                 orderStatusChangedToPaidDomainEvent.OrderId, |  | ||||||
|                 order.OrderStatus.Name, |  | ||||||
|                 buyer.Name, |  | ||||||
|                 orderStockList); |  | ||||||
| 
 |  | ||||||
|             await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedToPaidIntegrationEvent); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | 
 | ||||||
|  |     public async Task Handle(OrderStatusChangedToPaidDomainEvent orderStatusChangedToPaidDomainEvent, CancellationToken cancellationToken) | ||||||
|  |     { | ||||||
|  |         _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()); | ||||||
|  | 
 | ||||||
|  |         var orderStockList = orderStatusChangedToPaidDomainEvent.OrderItems | ||||||
|  |             .Select(orderItem => new OrderStockItem(orderItem.ProductId, orderItem.GetUnits())); | ||||||
|  | 
 | ||||||
|  |         var orderStatusChangedToPaidIntegrationEvent = new OrderStatusChangedToPaidIntegrationEvent( | ||||||
|  |             orderStatusChangedToPaidDomainEvent.OrderId, | ||||||
|  |             order.OrderStatus.Name, | ||||||
|  |             buyer.Name, | ||||||
|  |             orderStockList); | ||||||
|  | 
 | ||||||
|  |         await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedToPaidIntegrationEvent); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | |||||||
| @ -1,47 +1,35 @@ | |||||||
| using MediatR; | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.DomainEventHandlers.OrderShipped; | ||||||
| using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate; |  | ||||||
| using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; |  | ||||||
| using Microsoft.Extensions.Logging; |  | ||||||
| using Ordering.API.Application.IntegrationEvents; |  | ||||||
| using Ordering.API.Application.IntegrationEvents.Events; |  | ||||||
| using Ordering.Domain.Events; |  | ||||||
| using System; |  | ||||||
| using System.Threading; |  | ||||||
| using System.Threading.Tasks; |  | ||||||
| 
 | 
 | ||||||
| namespace Ordering.API.Application.DomainEventHandlers.OrderShipped | public class OrderShippedDomainEventHandler | ||||||
|  |                 : INotificationHandler<OrderShippedDomainEvent> | ||||||
| { | { | ||||||
|     public class OrderShippedDomainEventHandler |     private readonly IOrderRepository _orderRepository; | ||||||
|                    : INotificationHandler<OrderShippedDomainEvent> |     private readonly IBuyerRepository _buyerRepository; | ||||||
|  |     private readonly IOrderingIntegrationEventService _orderingIntegrationEventService; | ||||||
|  |     private readonly ILoggerFactory _logger; | ||||||
|  | 
 | ||||||
|  |     public OrderShippedDomainEventHandler( | ||||||
|  |         IOrderRepository orderRepository, | ||||||
|  |         ILoggerFactory logger, | ||||||
|  |         IBuyerRepository buyerRepository, | ||||||
|  |         IOrderingIntegrationEventService orderingIntegrationEventService) | ||||||
|     { |     { | ||||||
|         private readonly IOrderRepository _orderRepository; |         _orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository)); | ||||||
|         private readonly IBuyerRepository _buyerRepository; |         _logger = logger ?? throw new ArgumentNullException(nameof(logger)); | ||||||
|         private readonly IOrderingIntegrationEventService _orderingIntegrationEventService; |         _buyerRepository = buyerRepository ?? throw new ArgumentNullException(nameof(buyerRepository)); | ||||||
|         private readonly ILoggerFactory _logger; |         _orderingIntegrationEventService = orderingIntegrationEventService; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|         public OrderShippedDomainEventHandler( |     public async Task Handle(OrderShippedDomainEvent orderShippedDomainEvent, CancellationToken cancellationToken) | ||||||
|             IOrderRepository orderRepository, |     { | ||||||
|             ILoggerFactory logger, |         _logger.CreateLogger<OrderShippedDomainEvent>() | ||||||
|             IBuyerRepository buyerRepository, |             .LogTrace("Order with Id: {OrderId} has been successfully updated to status {Status} ({Id})", | ||||||
|             IOrderingIntegrationEventService orderingIntegrationEventService) |                 orderShippedDomainEvent.Order.Id, nameof(OrderStatus.Shipped), OrderStatus.Shipped.Id); | ||||||
|         { |  | ||||||
|             _orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository)); |  | ||||||
|             _logger = logger ?? throw new ArgumentNullException(nameof(logger)); |  | ||||||
|             _buyerRepository = buyerRepository ?? throw new ArgumentNullException(nameof(buyerRepository)); |  | ||||||
|             _orderingIntegrationEventService = orderingIntegrationEventService; |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         public async Task Handle(OrderShippedDomainEvent orderShippedDomainEvent, CancellationToken cancellationToken) |         var order = await _orderRepository.GetAsync(orderShippedDomainEvent.Order.Id); | ||||||
|         { |         var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value.ToString()); | ||||||
|             _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 orderStatusChangedToShippedIntegrationEvent = new OrderStatusChangedToShippedIntegrationEvent(order.Id, order.OrderStatus.Name, buyer.Name); | ||||||
|             var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value.ToString()); |         await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedToShippedIntegrationEvent); | ||||||
| 
 |  | ||||||
|             var orderStatusChangedToShippedIntegrationEvent = new OrderStatusChangedToShippedIntegrationEvent(order.Id, order.OrderStatus.Name, buyer.Name); |  | ||||||
|             await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedToShippedIntegrationEvent); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,16 +1,15 @@ | |||||||
| namespace Ordering.API.Application.DomainEventHandlers.OrderStartedEvent | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.DomainEventHandlers.OrderStartedEvent; | ||||||
|  | 
 | ||||||
|  | public class SendEmailToCustomerWhenOrderStartedDomainEventHandler | ||||||
|  | //: IAsyncNotificationHandler<OrderStartedDomainEvent> | ||||||
| { | { | ||||||
|     public class SendEmailToCustomerWhenOrderStartedDomainEventHandler |     public SendEmailToCustomerWhenOrderStartedDomainEventHandler() | ||||||
|     //: IAsyncNotificationHandler<OrderStartedDomainEvent> |  | ||||||
|     { |     { | ||||||
|         public SendEmailToCustomerWhenOrderStartedDomainEventHandler() |  | ||||||
|         { |  | ||||||
| 
 | 
 | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         //public async Task Handle(OrderStartedDomainEvent orderNotification) |  | ||||||
|         //{ |  | ||||||
|         //    //TBD - Send email logic |  | ||||||
|         //} |  | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     //public async Task Handle(OrderStartedDomainEvent orderNotification) | ||||||
|  |     //{ | ||||||
|  |     //    //TBD - Send email logic | ||||||
|  |     //} | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,67 +1,55 @@ | |||||||
| using MediatR; | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.DomainEventHandlers.OrderStartedEvent; | ||||||
| using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services; |  | ||||||
| using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate; |  | ||||||
| using Microsoft.Extensions.Logging; |  | ||||||
| using Ordering.API.Application.IntegrationEvents; |  | ||||||
| using Ordering.API.Application.IntegrationEvents.Events; |  | ||||||
| using Ordering.Domain.Events; |  | ||||||
| using System; |  | ||||||
| using System.Threading; |  | ||||||
| using System.Threading.Tasks; |  | ||||||
| 
 | 
 | ||||||
| namespace Ordering.API.Application.DomainEventHandlers.OrderStartedEvent | public class ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler | ||||||
|  |                     : INotificationHandler<OrderStartedDomainEvent> | ||||||
| { | { | ||||||
|     public class ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler |     private readonly ILoggerFactory _logger; | ||||||
|                         : INotificationHandler<OrderStartedDomainEvent> |     private readonly IBuyerRepository _buyerRepository; | ||||||
|  |     private readonly IIdentityService _identityService; | ||||||
|  |     private readonly IOrderingIntegrationEventService _orderingIntegrationEventService; | ||||||
|  | 
 | ||||||
|  |     public ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler( | ||||||
|  |         ILoggerFactory logger, | ||||||
|  |         IBuyerRepository buyerRepository, | ||||||
|  |         IIdentityService identityService, | ||||||
|  |         IOrderingIntegrationEventService orderingIntegrationEventService) | ||||||
|     { |     { | ||||||
|         private readonly ILoggerFactory _logger; |         _buyerRepository = buyerRepository ?? throw new ArgumentNullException(nameof(buyerRepository)); | ||||||
|         private readonly IBuyerRepository _buyerRepository; |         _identityService = identityService ?? throw new ArgumentNullException(nameof(identityService)); | ||||||
|         private readonly IIdentityService _identityService; |         _orderingIntegrationEventService = orderingIntegrationEventService ?? throw new ArgumentNullException(nameof(orderingIntegrationEventService)); | ||||||
|         private readonly IOrderingIntegrationEventService _orderingIntegrationEventService; |         _logger = logger ?? throw new ArgumentNullException(nameof(logger)); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|         public ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler( |     public async Task Handle(OrderStartedDomainEvent orderStartedEvent, CancellationToken cancellationToken) | ||||||
|             ILoggerFactory logger, |     { | ||||||
|             IBuyerRepository buyerRepository, |         var cardTypeId = (orderStartedEvent.CardTypeId != 0) ? orderStartedEvent.CardTypeId : 1; | ||||||
|             IIdentityService identityService, |         var buyer = await _buyerRepository.FindAsync(orderStartedEvent.UserId); | ||||||
|             IOrderingIntegrationEventService orderingIntegrationEventService) |         bool buyerOriginallyExisted = (buyer == null) ? false : true; | ||||||
|  | 
 | ||||||
|  |         if (!buyerOriginallyExisted) | ||||||
|         { |         { | ||||||
|             _buyerRepository = buyerRepository ?? throw new ArgumentNullException(nameof(buyerRepository)); |             buyer = new Buyer(orderStartedEvent.UserId, orderStartedEvent.UserName); | ||||||
|             _identityService = identityService ?? throw new ArgumentNullException(nameof(identityService)); |  | ||||||
|             _orderingIntegrationEventService = orderingIntegrationEventService ?? throw new ArgumentNullException(nameof(orderingIntegrationEventService)); |  | ||||||
|             _logger = logger ?? throw new ArgumentNullException(nameof(logger)); |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         public async Task Handle(OrderStartedDomainEvent orderStartedEvent, CancellationToken cancellationToken) |         buyer.VerifyOrAddPaymentMethod(cardTypeId, | ||||||
|         { |                                         $"Payment Method on {DateTime.UtcNow}", | ||||||
|             var cardTypeId = (orderStartedEvent.CardTypeId != 0) ? orderStartedEvent.CardTypeId : 1; |                                         orderStartedEvent.CardNumber, | ||||||
|             var buyer = await _buyerRepository.FindAsync(orderStartedEvent.UserId); |                                         orderStartedEvent.CardSecurityNumber, | ||||||
|             bool buyerOriginallyExisted = (buyer == null) ? false : true; |                                         orderStartedEvent.CardHolderName, | ||||||
|  |                                         orderStartedEvent.CardExpiration, | ||||||
|  |                                         orderStartedEvent.Order.Id); | ||||||
| 
 | 
 | ||||||
|             if (!buyerOriginallyExisted) |         var buyerUpdated = buyerOriginallyExisted ? | ||||||
|             { |             _buyerRepository.Update(buyer) : | ||||||
|                 buyer = new Buyer(orderStartedEvent.UserId, orderStartedEvent.UserName); |             _buyerRepository.Add(buyer); | ||||||
|             } |  | ||||||
| 
 | 
 | ||||||
|             buyer.VerifyOrAddPaymentMethod(cardTypeId, |         await _buyerRepository.UnitOfWork | ||||||
|                                            $"Payment Method on {DateTime.UtcNow}", |             .SaveEntitiesAsync(cancellationToken); | ||||||
|                                            orderStartedEvent.CardNumber, |  | ||||||
|                                            orderStartedEvent.CardSecurityNumber, |  | ||||||
|                                            orderStartedEvent.CardHolderName, |  | ||||||
|                                            orderStartedEvent.CardExpiration, |  | ||||||
|                                            orderStartedEvent.Order.Id); |  | ||||||
| 
 | 
 | ||||||
|             var buyerUpdated = buyerOriginallyExisted ? |         var orderStatusChangedTosubmittedIntegrationEvent = new OrderStatusChangedToSubmittedIntegrationEvent(orderStartedEvent.Order.Id, orderStartedEvent.Order.OrderStatus.Name, buyer.Name); | ||||||
|                 _buyerRepository.Update(buyer) : |         await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedTosubmittedIntegrationEvent); | ||||||
|                 _buyerRepository.Add(buyer); |         _logger.CreateLogger<ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler>() | ||||||
| 
 |             .LogTrace("Buyer {BuyerId} and related payment method were validated or updated for orderId: {OrderId}.", | ||||||
|             await _buyerRepository.UnitOfWork |                 buyerUpdated.Id, orderStartedEvent.Order.Id); | ||||||
|                 .SaveEntitiesAsync(cancellationToken); |  | ||||||
| 
 |  | ||||||
|             var orderStatusChangedTosubmittedIntegrationEvent = new OrderStatusChangedToSubmittedIntegrationEvent(orderStartedEvent.Order.Id, orderStartedEvent.Order.OrderStatus.Name, buyer.Name); |  | ||||||
|             await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedTosubmittedIntegrationEvent); |  | ||||||
|             _logger.CreateLogger<ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler>() |  | ||||||
|                 .LogTrace("Buyer {BuyerId} and related payment method were validated or updated for orderId: {OrderId}.", |  | ||||||
|                     buyerUpdated.Id, orderStartedEvent.Order.Id); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,47 +1,35 @@ | |||||||
| namespace Ordering.API.Application.DomainEventHandlers.OrderStockConfirmed | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.DomainEventHandlers.OrderStockConfirmed; | ||||||
|  | 
 | ||||||
|  | public class OrderStatusChangedToStockConfirmedDomainEventHandler | ||||||
|  |                 : INotificationHandler<OrderStatusChangedToStockConfirmedDomainEvent> | ||||||
| { | { | ||||||
|     using Domain.Events; |     private readonly IOrderRepository _orderRepository; | ||||||
|     using MediatR; |     private readonly IBuyerRepository _buyerRepository; | ||||||
|     using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate; |     private readonly ILoggerFactory _logger; | ||||||
|     using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; |     private readonly IOrderingIntegrationEventService _orderingIntegrationEventService; | ||||||
|     using Microsoft.Extensions.Logging; |  | ||||||
|     using Ordering.API.Application.IntegrationEvents; |  | ||||||
|     using Ordering.API.Application.IntegrationEvents.Events; |  | ||||||
|     using System; |  | ||||||
|     using System.Threading; |  | ||||||
|     using System.Threading.Tasks; |  | ||||||
| 
 | 
 | ||||||
|     public class OrderStatusChangedToStockConfirmedDomainEventHandler |     public OrderStatusChangedToStockConfirmedDomainEventHandler( | ||||||
|                    : INotificationHandler<OrderStatusChangedToStockConfirmedDomainEvent> |         IOrderRepository orderRepository, | ||||||
|  |         IBuyerRepository buyerRepository, | ||||||
|  |         ILoggerFactory logger, | ||||||
|  |         IOrderingIntegrationEventService orderingIntegrationEventService) | ||||||
|     { |     { | ||||||
|         private readonly IOrderRepository _orderRepository; |         _orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository)); | ||||||
|         private readonly IBuyerRepository _buyerRepository; |         _buyerRepository = buyerRepository ?? throw new ArgumentNullException(nameof(buyerRepository)); | ||||||
|         private readonly ILoggerFactory _logger; |         _logger = logger ?? throw new ArgumentNullException(nameof(logger)); | ||||||
|         private readonly IOrderingIntegrationEventService _orderingIntegrationEventService; |         _orderingIntegrationEventService = orderingIntegrationEventService; | ||||||
| 
 |  | ||||||
|         public OrderStatusChangedToStockConfirmedDomainEventHandler( |  | ||||||
|             IOrderRepository orderRepository, |  | ||||||
|             IBuyerRepository buyerRepository, |  | ||||||
|             ILoggerFactory logger, |  | ||||||
|             IOrderingIntegrationEventService orderingIntegrationEventService) |  | ||||||
|         { |  | ||||||
|             _orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository)); |  | ||||||
|             _buyerRepository = buyerRepository ?? throw new ArgumentNullException(nameof(buyerRepository)); |  | ||||||
|             _logger = logger ?? throw new ArgumentNullException(nameof(logger)); |  | ||||||
|             _orderingIntegrationEventService = orderingIntegrationEventService; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         public async Task Handle(OrderStatusChangedToStockConfirmedDomainEvent orderStatusChangedToStockConfirmedDomainEvent, CancellationToken cancellationToken) |  | ||||||
|         { |  | ||||||
|             _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()); |  | ||||||
| 
 |  | ||||||
|             var orderStatusChangedToStockConfirmedIntegrationEvent = new OrderStatusChangedToStockConfirmedIntegrationEvent(order.Id, order.OrderStatus.Name, buyer.Name); |  | ||||||
|             await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedToStockConfirmedIntegrationEvent); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | 
 | ||||||
|  |     public async Task Handle(OrderStatusChangedToStockConfirmedDomainEvent orderStatusChangedToStockConfirmedDomainEvent, CancellationToken cancellationToken) | ||||||
|  |     { | ||||||
|  |         _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()); | ||||||
|  | 
 | ||||||
|  |         var orderStatusChangedToStockConfirmedIntegrationEvent = new OrderStatusChangedToStockConfirmedIntegrationEvent(order.Id, order.OrderStatus.Name, buyer.Name); | ||||||
|  |         await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedToStockConfirmedIntegrationEvent); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | |||||||
| @ -1,52 +1,42 @@ | |||||||
| using MediatR; | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.IntegrationEvents.EventHandling; | ||||||
| using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; |  | ||||||
| using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Extensions; |  | ||||||
| using Microsoft.Extensions.Logging; |  | ||||||
| using Ordering.API.Application.Commands; |  | ||||||
| using Ordering.API.Application.IntegrationEvents.Events; |  | ||||||
| using Serilog.Context; |  | ||||||
| using System.Threading.Tasks; |  | ||||||
| 
 | 
 | ||||||
| namespace Ordering.API.Application.IntegrationEvents.EventHandling | public class GracePeriodConfirmedIntegrationEventHandler : IIntegrationEventHandler<GracePeriodConfirmedIntegrationEvent> | ||||||
| { | { | ||||||
|     public class GracePeriodConfirmedIntegrationEventHandler : IIntegrationEventHandler<GracePeriodConfirmedIntegrationEvent> |     private readonly IMediator _mediator; | ||||||
|  |     private readonly ILogger<GracePeriodConfirmedIntegrationEventHandler> _logger; | ||||||
|  | 
 | ||||||
|  |     public GracePeriodConfirmedIntegrationEventHandler( | ||||||
|  |         IMediator mediator, | ||||||
|  |         ILogger<GracePeriodConfirmedIntegrationEventHandler> logger) | ||||||
|     { |     { | ||||||
|         private readonly IMediator _mediator; |         _mediator = mediator; | ||||||
|         private readonly ILogger<GracePeriodConfirmedIntegrationEventHandler> _logger; |         _logger = logger ?? throw new System.ArgumentNullException(nameof(logger)); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|         public GracePeriodConfirmedIntegrationEventHandler( |     /// <summary> | ||||||
|             IMediator mediator, |     /// Event handler which confirms that the grace period | ||||||
|             ILogger<GracePeriodConfirmedIntegrationEventHandler> logger) |     /// has been completed and order will not initially be cancelled. | ||||||
|  |     /// Therefore, the order process continues for validation.  | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="event">        | ||||||
|  |     /// </param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     public async Task Handle(GracePeriodConfirmedIntegrationEvent @event) | ||||||
|  |     { | ||||||
|  |         using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) | ||||||
|         { |         { | ||||||
|             _mediator = mediator; |             _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); | ||||||
|             _logger = logger ?? throw new System.ArgumentNullException(nameof(logger)); |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         /// <summary> |             var command = new SetAwaitingValidationOrderStatusCommand(@event.OrderId); | ||||||
|         /// Event handler which confirms that the grace period |  | ||||||
|         /// has been completed and order will not initially be cancelled. |  | ||||||
|         /// Therefore, the order process continues for validation.  |  | ||||||
|         /// </summary> |  | ||||||
|         /// <param name="event">        |  | ||||||
|         /// </param> |  | ||||||
|         /// <returns></returns> |  | ||||||
|         public async Task Handle(GracePeriodConfirmedIntegrationEvent @event) |  | ||||||
|         { |  | ||||||
|             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); | ||||||
| 
 | 
 | ||||||
|                 _logger.LogInformation( |             await _mediator.Send(command); | ||||||
|                     "----- Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})", |  | ||||||
|                     command.GetGenericTypeName(), |  | ||||||
|                     nameof(command.OrderNumber), |  | ||||||
|                     command.OrderNumber, |  | ||||||
|                     command); |  | ||||||
| 
 |  | ||||||
|                 await _mediator.Send(command); |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,46 +1,35 @@ | |||||||
| namespace Ordering.API.Application.IntegrationEvents.EventHandling | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.IntegrationEvents.EventHandling; | ||||||
|  | 
 | ||||||
|  | public class OrderPaymentFailedIntegrationEventHandler : | ||||||
|  |     IIntegrationEventHandler<OrderPaymentFailedIntegrationEvent> | ||||||
| { | { | ||||||
|     using MediatR; |     private readonly IMediator _mediator; | ||||||
|     using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; |     private readonly ILogger<OrderPaymentFailedIntegrationEventHandler> _logger; | ||||||
|     using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Extensions; |  | ||||||
|     using Microsoft.Extensions.Logging; |  | ||||||
|     using Ordering.API.Application.Commands; |  | ||||||
|     using Ordering.API.Application.IntegrationEvents.Events; |  | ||||||
|     using Serilog.Context; |  | ||||||
|     using System; |  | ||||||
|     using System.Threading.Tasks; |  | ||||||
| 
 | 
 | ||||||
|     public class OrderPaymentFailedIntegrationEventHandler : |     public OrderPaymentFailedIntegrationEventHandler( | ||||||
|         IIntegrationEventHandler<OrderPaymentFailedIntegrationEvent> |         IMediator mediator, | ||||||
|  |         ILogger<OrderPaymentFailedIntegrationEventHandler> logger) | ||||||
|     { |     { | ||||||
|         private readonly IMediator _mediator; |         _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); | ||||||
|         private readonly ILogger<OrderPaymentFailedIntegrationEventHandler> _logger; |         _logger = logger ?? throw new ArgumentNullException(nameof(logger)); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|         public OrderPaymentFailedIntegrationEventHandler( |     public async Task Handle(OrderPaymentFailedIntegrationEvent @event) | ||||||
|             IMediator mediator, |     { | ||||||
|             ILogger<OrderPaymentFailedIntegrationEventHandler> logger) |         using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) | ||||||
|         { |         { | ||||||
|             _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); |             _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); | ||||||
|             _logger = logger ?? throw new ArgumentNullException(nameof(logger)); |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         public async Task Handle(OrderPaymentFailedIntegrationEvent @event) |             var command = new CancelOrderCommand(@event.OrderId); | ||||||
|         { |  | ||||||
|             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); | ||||||
| 
 | 
 | ||||||
|                 _logger.LogInformation( |             await _mediator.Send(command); | ||||||
|                     "----- Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})", |  | ||||||
|                     command.GetGenericTypeName(), |  | ||||||
|                     nameof(command.OrderNumber), |  | ||||||
|                     command.OrderNumber, |  | ||||||
|                     command); |  | ||||||
| 
 |  | ||||||
|                 await _mediator.Send(command); |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,46 +1,35 @@ | |||||||
| namespace Ordering.API.Application.IntegrationEvents.EventHandling | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.IntegrationEvents.EventHandling; | ||||||
|  |     | ||||||
|  | public class OrderPaymentSucceededIntegrationEventHandler : | ||||||
|  |     IIntegrationEventHandler<OrderPaymentSucceededIntegrationEvent> | ||||||
| { | { | ||||||
|     using MediatR; |     private readonly IMediator _mediator; | ||||||
|     using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; |     private readonly ILogger<OrderPaymentSucceededIntegrationEventHandler> _logger; | ||||||
|     using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Extensions; |  | ||||||
|     using Microsoft.Extensions.Logging; |  | ||||||
|     using Ordering.API.Application.Commands; |  | ||||||
|     using Ordering.API.Application.IntegrationEvents.Events; |  | ||||||
|     using Serilog.Context; |  | ||||||
|     using System; |  | ||||||
|     using System.Threading.Tasks; |  | ||||||
| 
 | 
 | ||||||
|     public class OrderPaymentSucceededIntegrationEventHandler : |     public OrderPaymentSucceededIntegrationEventHandler( | ||||||
|         IIntegrationEventHandler<OrderPaymentSucceededIntegrationEvent> |         IMediator mediator, | ||||||
|  |         ILogger<OrderPaymentSucceededIntegrationEventHandler> logger) | ||||||
|     { |     { | ||||||
|         private readonly IMediator _mediator; |         _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); | ||||||
|         private readonly ILogger<OrderPaymentSucceededIntegrationEventHandler> _logger; |         _logger = logger ?? throw new ArgumentNullException(nameof(logger)); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|         public OrderPaymentSucceededIntegrationEventHandler( |     public async Task Handle(OrderPaymentSucceededIntegrationEvent @event) | ||||||
|             IMediator mediator, |     { | ||||||
|             ILogger<OrderPaymentSucceededIntegrationEventHandler> logger) |         using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) | ||||||
|         { |         { | ||||||
|             _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); |             _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); | ||||||
|             _logger = logger ?? throw new ArgumentNullException(nameof(logger)); |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         public async Task Handle(OrderPaymentSucceededIntegrationEvent @event) |             var command = new SetPaidOrderStatusCommand(@event.OrderId); | ||||||
|         { |  | ||||||
|             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); | ||||||
| 
 | 
 | ||||||
|                 _logger.LogInformation( |             await _mediator.Send(command); | ||||||
|                     "----- Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})", |  | ||||||
|                     command.GetGenericTypeName(), |  | ||||||
|                     nameof(command.OrderNumber), |  | ||||||
|                     command.OrderNumber, |  | ||||||
|                     command); |  | ||||||
| 
 |  | ||||||
|                 await _mediator.Send(command); |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,46 +1,35 @@ | |||||||
| namespace Ordering.API.Application.IntegrationEvents.EventHandling | namespace Ordering.API.Application.IntegrationEvents.EventHandling; | ||||||
|  | 
 | ||||||
|  | public class OrderStockConfirmedIntegrationEventHandler : | ||||||
|  |     IIntegrationEventHandler<OrderStockConfirmedIntegrationEvent> | ||||||
| { | { | ||||||
|     using Events; |     private readonly IMediator _mediator; | ||||||
|     using MediatR; |     private readonly ILogger<OrderStockConfirmedIntegrationEventHandler> _logger; | ||||||
|     using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; |  | ||||||
|     using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Extensions; |  | ||||||
|     using Microsoft.Extensions.Logging; |  | ||||||
|     using Ordering.API.Application.Commands; |  | ||||||
|     using Serilog.Context; |  | ||||||
|     using System; |  | ||||||
|     using System.Threading.Tasks; |  | ||||||
| 
 | 
 | ||||||
|     public class OrderStockConfirmedIntegrationEventHandler : |     public OrderStockConfirmedIntegrationEventHandler( | ||||||
|         IIntegrationEventHandler<OrderStockConfirmedIntegrationEvent> |         IMediator mediator, | ||||||
|  |         ILogger<OrderStockConfirmedIntegrationEventHandler> logger) | ||||||
|     { |     { | ||||||
|         private readonly IMediator _mediator; |         _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); | ||||||
|         private readonly ILogger<OrderStockConfirmedIntegrationEventHandler> _logger; |         _logger = logger ?? throw new ArgumentNullException(nameof(logger)); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|         public OrderStockConfirmedIntegrationEventHandler( |     public async Task Handle(OrderStockConfirmedIntegrationEvent @event) | ||||||
|             IMediator mediator, |     { | ||||||
|             ILogger<OrderStockConfirmedIntegrationEventHandler> logger) |         using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) | ||||||
|         { |         { | ||||||
|             _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); |             _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); | ||||||
|             _logger = logger ?? throw new ArgumentNullException(nameof(logger)); |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         public async Task Handle(OrderStockConfirmedIntegrationEvent @event) |             var command = new SetStockConfirmedOrderStatusCommand(@event.OrderId); | ||||||
|         { |  | ||||||
|             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); | ||||||
| 
 | 
 | ||||||
|                 _logger.LogInformation( |             await _mediator.Send(command); | ||||||
|                     "----- Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})", |  | ||||||
|                     command.GetGenericTypeName(), |  | ||||||
|                     nameof(command.OrderNumber), |  | ||||||
|                     command.OrderNumber, |  | ||||||
|                     command); |  | ||||||
| 
 |  | ||||||
|                 await _mediator.Send(command); |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,50 +1,38 @@ | |||||||
| namespace Ordering.API.Application.IntegrationEvents.EventHandling | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.IntegrationEvents.EventHandling; | ||||||
|  | public class OrderStockRejectedIntegrationEventHandler : IIntegrationEventHandler<OrderStockRejectedIntegrationEvent> | ||||||
| { | { | ||||||
|     using Events; |     private readonly IMediator _mediator; | ||||||
|     using MediatR; |     private readonly ILogger<OrderStockRejectedIntegrationEventHandler> _logger; | ||||||
|     using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; |  | ||||||
|     using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Extensions; |  | ||||||
|     using Microsoft.Extensions.Logging; |  | ||||||
|     using Ordering.API.Application.Commands; |  | ||||||
|     using Serilog.Context; |  | ||||||
|     using System.Linq; |  | ||||||
|     using System.Threading.Tasks; |  | ||||||
| 
 | 
 | ||||||
|     public class OrderStockRejectedIntegrationEventHandler : IIntegrationEventHandler<OrderStockRejectedIntegrationEvent> |     public OrderStockRejectedIntegrationEventHandler( | ||||||
|  |         IMediator mediator, | ||||||
|  |         ILogger<OrderStockRejectedIntegrationEventHandler> logger) | ||||||
|     { |     { | ||||||
|         private readonly IMediator _mediator; |         _mediator = mediator; | ||||||
|         private readonly ILogger<OrderStockRejectedIntegrationEventHandler> _logger; |         _logger = logger ?? throw new System.ArgumentNullException(nameof(logger)); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|         public OrderStockRejectedIntegrationEventHandler( |     public async Task Handle(OrderStockRejectedIntegrationEvent @event) | ||||||
|             IMediator mediator, |     { | ||||||
|             ILogger<OrderStockRejectedIntegrationEventHandler> logger) |         using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) | ||||||
|         { |         { | ||||||
|             _mediator = mediator; |             _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); | ||||||
|             _logger = logger ?? throw new System.ArgumentNullException(nameof(logger)); |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         public async Task Handle(OrderStockRejectedIntegrationEvent @event) |             var orderStockRejectedItems = @event.OrderStockItems | ||||||
|         { |                 .FindAll(c => !c.HasStock) | ||||||
|             using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) |                 .Select(c => c.ProductId) | ||||||
|             { |                 .ToList(); | ||||||
|                 _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); |  | ||||||
| 
 | 
 | ||||||
|                 var orderStockRejectedItems = @event.OrderStockItems |             var command = new SetStockRejectedOrderStatusCommand(@event.OrderId, orderStockRejectedItems); | ||||||
|                     .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); | ||||||
| 
 | 
 | ||||||
|                 _logger.LogInformation( |             await _mediator.Send(command); | ||||||
|                     "----- Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})", |  | ||||||
|                     command.GetGenericTypeName(), |  | ||||||
|                     nameof(command.OrderNumber), |  | ||||||
|                     command.OrderNumber, |  | ||||||
|                     command); |  | ||||||
| 
 |  | ||||||
|                 await _mediator.Send(command); |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,80 +1,69 @@ | |||||||
| using MediatR; | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.IntegrationEvents.EventHandling; | ||||||
| using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; |  | ||||||
| using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Extensions; |  | ||||||
| using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; |  | ||||||
| using Microsoft.Extensions.Logging; |  | ||||||
| using Ordering.API.Application.IntegrationEvents.Events; |  | ||||||
| using Serilog.Context; |  | ||||||
| using System; |  | ||||||
| using System.Threading.Tasks; |  | ||||||
| 
 | 
 | ||||||
| namespace Ordering.API.Application.IntegrationEvents.EventHandling | public class UserCheckoutAcceptedIntegrationEventHandler : IIntegrationEventHandler<UserCheckoutAcceptedIntegrationEvent> | ||||||
| { | { | ||||||
|     public class UserCheckoutAcceptedIntegrationEventHandler : IIntegrationEventHandler<UserCheckoutAcceptedIntegrationEvent> |     private readonly IMediator _mediator; | ||||||
|  |     private readonly ILogger<UserCheckoutAcceptedIntegrationEventHandler> _logger; | ||||||
|  | 
 | ||||||
|  |     public UserCheckoutAcceptedIntegrationEventHandler( | ||||||
|  |         IMediator mediator, | ||||||
|  |         ILogger<UserCheckoutAcceptedIntegrationEventHandler> logger) | ||||||
|     { |     { | ||||||
|         private readonly IMediator _mediator; |         _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); | ||||||
|         private readonly ILogger<UserCheckoutAcceptedIntegrationEventHandler> _logger; |         _logger = logger ?? throw new ArgumentNullException(nameof(logger)); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|         public UserCheckoutAcceptedIntegrationEventHandler( |     /// <summary> | ||||||
|             IMediator mediator, |     /// Integration event handler which starts the create order process | ||||||
|             ILogger<UserCheckoutAcceptedIntegrationEventHandler> logger) |     /// </summary> | ||||||
|  |     /// <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 @event) | ||||||
|  |     { | ||||||
|  |         using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) | ||||||
|         { |         { | ||||||
|             _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); |             _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); | ||||||
|             _logger = logger ?? throw new ArgumentNullException(nameof(logger)); |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         /// <summary> |             var result = false; | ||||||
|         /// Integration event handler which starts the create order process | 
 | ||||||
|         /// </summary> |             if (@event.RequestId != Guid.Empty) | ||||||
|         /// <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 @event) |  | ||||||
|         { |  | ||||||
|             using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) |  | ||||||
|             { |             { | ||||||
|                 _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); |                 using (LogContext.PushProperty("IdentifiedCommandId", @event.RequestId)) | ||||||
| 
 |  | ||||||
|                 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) | ||||||
|                     { |                     { | ||||||
|                         var createOrderCommand = new CreateOrderCommand(@event.Basket.Items, @event.UserId, @event.UserName, @event.City, @event.Street, |                         _logger.LogInformation("----- CreateOrderCommand suceeded - RequestId: {RequestId}", @event.RequestId); | ||||||
|                             @event.State, @event.Country, @event.ZipCode, |                     } | ||||||
|                             @event.CardNumber, @event.CardHolderName, @event.CardExpiration, |                     else | ||||||
|                             @event.CardSecurityNumber, @event.CardTypeId); |                     { | ||||||
| 
 |                         _logger.LogWarning("CreateOrderCommand failed - RequestId: {RequestId}", @event.RequestId); | ||||||
|                         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 |             } | ||||||
|                 { |             else | ||||||
|                     _logger.LogWarning("Invalid IntegrationEvent - RequestId is missing - {@IntegrationEvent}", @event); |             { | ||||||
|                 } |                 _logger.LogWarning("Invalid IntegrationEvent - RequestId is missing - {@IntegrationEvent}", @event); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,12 +1,10 @@ | |||||||
| namespace Ordering.API.Application.IntegrationEvents.Events | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.IntegrationEvents.Events;     | ||||||
|  | 
 | ||||||
|  | public record GracePeriodConfirmedIntegrationEvent : IntegrationEvent | ||||||
| { | { | ||||||
|     using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; |     public int OrderId { get; } | ||||||
| 
 | 
 | ||||||
|     public record GracePeriodConfirmedIntegrationEvent : IntegrationEvent |     public GracePeriodConfirmedIntegrationEvent(int orderId) => | ||||||
|     { |         OrderId = orderId; | ||||||
|         public int OrderId { get; } |  | ||||||
| 
 |  | ||||||
|         public GracePeriodConfirmedIntegrationEvent(int orderId) => |  | ||||||
|             OrderId = orderId; |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | |||||||
| @ -1,11 +1,8 @@ | |||||||
| namespace Ordering.API.Application.IntegrationEvents.Events | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.IntegrationEvents.Events; | ||||||
|  | 
 | ||||||
|  | public record OrderPaymentFailedIntegrationEvent : IntegrationEvent | ||||||
| { | { | ||||||
|     using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; |     public int OrderId { get; } | ||||||
| 
 | 
 | ||||||
|     public record OrderPaymentFailedIntegrationEvent : IntegrationEvent |     public OrderPaymentFailedIntegrationEvent(int orderId) => OrderId = orderId; | ||||||
|     { | } | ||||||
|         public int OrderId { get; } |  | ||||||
| 
 |  | ||||||
|         public OrderPaymentFailedIntegrationEvent(int orderId) => OrderId = orderId; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | |||||||
| @ -1,11 +1,8 @@ | |||||||
| namespace Ordering.API.Application.IntegrationEvents.Events | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.IntegrationEvents.Events; | ||||||
|  | 
 | ||||||
|  | public record OrderPaymentSucceededIntegrationEvent : IntegrationEvent | ||||||
| { | { | ||||||
|     using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; |     public int OrderId { get; } | ||||||
| 
 | 
 | ||||||
|     public record OrderPaymentSucceededIntegrationEvent : IntegrationEvent |     public OrderPaymentSucceededIntegrationEvent(int orderId) => OrderId = orderId; | ||||||
|     { | } | ||||||
|         public int OrderId { get; } |  | ||||||
| 
 |  | ||||||
|         public OrderPaymentSucceededIntegrationEvent(int orderId) => OrderId = orderId; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | |||||||
| @ -1,15 +1,12 @@ | |||||||
| using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.IntegrationEvents.Events; | ||||||
| 
 | 
 | ||||||
| namespace Ordering.API.Application.IntegrationEvents.Events | // Integration Events notes:  | ||||||
|  | // An Event is “something that has happened in the past”, therefore its name has to be    | ||||||
|  | // An Integration Event is an event that can cause side effects to other microsrvices, Bounded-Contexts or external systems. | ||||||
|  | public record OrderStartedIntegrationEvent : IntegrationEvent | ||||||
| { | { | ||||||
|     // Integration Events notes:  |     public string UserId { get; init; } | ||||||
|     // An Event is “something that has happened in the past”, therefore its name has to be    |  | ||||||
|     // An Integration Event is an event that can cause side effects to other microsrvices, Bounded-Contexts or external systems. |  | ||||||
|     public record OrderStartedIntegrationEvent : IntegrationEvent |  | ||||||
|     { |  | ||||||
|         public string UserId { get; init; } |  | ||||||
| 
 | 
 | ||||||
|         public OrderStartedIntegrationEvent(string userId) |     public OrderStartedIntegrationEvent(string userId) | ||||||
|             => UserId = userId; |         => UserId = userId; | ||||||
|     } | } | ||||||
| } |  | ||||||
|  | |||||||
| @ -1,34 +1,30 @@ | |||||||
| namespace Ordering.API.Application.IntegrationEvents.Events | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.IntegrationEvents.Events; | ||||||
|  |    | ||||||
|  | public record OrderStatusChangedToAwaitingValidationIntegrationEvent : IntegrationEvent | ||||||
| { | { | ||||||
|     using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; |     public int OrderId { get; } | ||||||
|     using System.Collections.Generic; |     public string OrderStatus { get; } | ||||||
|  |     public string BuyerName { get; } | ||||||
|  |     public IEnumerable<OrderStockItem> OrderStockItems { get; } | ||||||
| 
 | 
 | ||||||
|     public record OrderStatusChangedToAwaitingValidationIntegrationEvent : IntegrationEvent |     public OrderStatusChangedToAwaitingValidationIntegrationEvent(int orderId, string orderStatus, string buyerName, | ||||||
|  |         IEnumerable<OrderStockItem> orderStockItems) | ||||||
|     { |     { | ||||||
|         public int OrderId { get; } |         OrderId = orderId; | ||||||
|         public string OrderStatus { get; } |         OrderStockItems = orderStockItems; | ||||||
|         public string BuyerName { get; } |         OrderStatus = orderStatus; | ||||||
|         public IEnumerable<OrderStockItem> OrderStockItems { get; } |         BuyerName = buyerName; | ||||||
| 
 |  | ||||||
|         public OrderStatusChangedToAwaitingValidationIntegrationEvent(int orderId, string orderStatus, string buyerName, |  | ||||||
|             IEnumerable<OrderStockItem> orderStockItems) |  | ||||||
|         { |  | ||||||
|             OrderId = orderId; |  | ||||||
|             OrderStockItems = orderStockItems; |  | ||||||
|             OrderStatus = orderStatus; |  | ||||||
|             BuyerName = buyerName; |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|     public record OrderStockItem | public record OrderStockItem | ||||||
|  | { | ||||||
|  |     public int ProductId { get; } | ||||||
|  |     public int Units { get; } | ||||||
|  | 
 | ||||||
|  |     public OrderStockItem(int productId, int units) | ||||||
|     { |     { | ||||||
|         public int ProductId { get; } |         ProductId = productId; | ||||||
|         public int Units { get; } |         Units = units; | ||||||
| 
 |  | ||||||
|         public OrderStockItem(int productId, int units) |  | ||||||
|         { |  | ||||||
|             ProductId = productId; |  | ||||||
|             Units = units; |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,18 +1,15 @@ | |||||||
| using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.IntegrationEvents.Events; | ||||||
| 
 | 
 | ||||||
| namespace Ordering.API.Application.IntegrationEvents.Events | public record OrderStatusChangedToCancelledIntegrationEvent : IntegrationEvent | ||||||
| { | { | ||||||
|     public record OrderStatusChangedToCancelledIntegrationEvent : IntegrationEvent |     public int OrderId { get; } | ||||||
|     { |     public string OrderStatus { get; } | ||||||
|         public int OrderId { get; } |     public string BuyerName { get; } | ||||||
|         public string OrderStatus { get; } |  | ||||||
|         public string BuyerName { get; } |  | ||||||
| 
 | 
 | ||||||
|         public OrderStatusChangedToCancelledIntegrationEvent(int orderId, string orderStatus, string buyerName) |     public OrderStatusChangedToCancelledIntegrationEvent(int orderId, string orderStatus, string buyerName) | ||||||
|         { |     { | ||||||
|             OrderId = orderId; |         OrderId = orderId; | ||||||
|             OrderStatus = orderStatus; |         OrderStatus = orderStatus; | ||||||
|             BuyerName = buyerName; |         BuyerName = buyerName; | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,24 +1,21 @@ | |||||||
| namespace Ordering.API.Application.IntegrationEvents.Events | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.IntegrationEvents.Events; | ||||||
|  | 
 | ||||||
|  | public record OrderStatusChangedToPaidIntegrationEvent : IntegrationEvent | ||||||
| { | { | ||||||
|     using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; |     public int OrderId { get; } | ||||||
|     using System.Collections.Generic; |     public string OrderStatus { get; } | ||||||
|  |     public string BuyerName { get; } | ||||||
|  |     public IEnumerable<OrderStockItem> OrderStockItems { get; } | ||||||
| 
 | 
 | ||||||
|     public record OrderStatusChangedToPaidIntegrationEvent : IntegrationEvent |     public OrderStatusChangedToPaidIntegrationEvent(int orderId, | ||||||
|  |         string orderStatus, | ||||||
|  |         string buyerName, | ||||||
|  |         IEnumerable<OrderStockItem> orderStockItems) | ||||||
|     { |     { | ||||||
|         public int OrderId { get; } |         OrderId = orderId; | ||||||
|         public string OrderStatus { get; } |         OrderStockItems = orderStockItems; | ||||||
|         public string BuyerName { get; } |         OrderStatus = orderStatus; | ||||||
|         public IEnumerable<OrderStockItem> OrderStockItems { get; } |         BuyerName = buyerName; | ||||||
| 
 |  | ||||||
|         public OrderStatusChangedToPaidIntegrationEvent(int orderId, |  | ||||||
|             string orderStatus, |  | ||||||
|             string buyerName, |  | ||||||
|             IEnumerable<OrderStockItem> orderStockItems) |  | ||||||
|         { |  | ||||||
|             OrderId = orderId; |  | ||||||
|             OrderStockItems = orderStockItems; |  | ||||||
|             OrderStatus = orderStatus; |  | ||||||
|             BuyerName = buyerName; |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | |||||||
| @ -1,18 +1,15 @@ | |||||||
| using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.IntegrationEvents.Events; | ||||||
| 
 | 
 | ||||||
| namespace Ordering.API.Application.IntegrationEvents.Events | public record OrderStatusChangedToShippedIntegrationEvent : IntegrationEvent | ||||||
| { | { | ||||||
|     public record OrderStatusChangedToShippedIntegrationEvent : IntegrationEvent |     public int OrderId { get; } | ||||||
|     { |     public string OrderStatus { get; } | ||||||
|         public int OrderId { get; } |     public string BuyerName { get; } | ||||||
|         public string OrderStatus { get; } |  | ||||||
|         public string BuyerName { get; } |  | ||||||
| 
 | 
 | ||||||
|         public OrderStatusChangedToShippedIntegrationEvent(int orderId, string orderStatus, string buyerName) |     public OrderStatusChangedToShippedIntegrationEvent(int orderId, string orderStatus, string buyerName) | ||||||
|         { |     { | ||||||
|             OrderId = orderId; |         OrderId = orderId; | ||||||
|             OrderStatus = orderStatus; |         OrderStatus = orderStatus; | ||||||
|             BuyerName = buyerName; |         BuyerName = buyerName; | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,18 +1,15 @@ | |||||||
| namespace Ordering.API.Application.IntegrationEvents.Events | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.IntegrationEvents.Events; | ||||||
|  | 
 | ||||||
|  | public record OrderStatusChangedToStockConfirmedIntegrationEvent : IntegrationEvent | ||||||
| { | { | ||||||
|     using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; |     public int OrderId { get; } | ||||||
|  |     public string OrderStatus { get; } | ||||||
|  |     public string BuyerName { get; } | ||||||
| 
 | 
 | ||||||
|     public record OrderStatusChangedToStockConfirmedIntegrationEvent : IntegrationEvent |     public OrderStatusChangedToStockConfirmedIntegrationEvent(int orderId, string orderStatus, string buyerName) | ||||||
|     { |     { | ||||||
|         public int OrderId { get; } |         OrderId = orderId; | ||||||
|         public string OrderStatus { get; } |         OrderStatus = orderStatus; | ||||||
|         public string BuyerName { get; } |         BuyerName = buyerName; | ||||||
| 
 |  | ||||||
|         public OrderStatusChangedToStockConfirmedIntegrationEvent(int orderId, string orderStatus, string buyerName) |  | ||||||
|         { |  | ||||||
|             OrderId = orderId; |  | ||||||
|             OrderStatus = orderStatus; |  | ||||||
|             BuyerName = buyerName; |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,18 +1,15 @@ | |||||||
| using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.IntegrationEvents.Events; | ||||||
| 
 | 
 | ||||||
| namespace Ordering.API.Application.IntegrationEvents.Events | public record OrderStatusChangedToSubmittedIntegrationEvent : IntegrationEvent | ||||||
| { | { | ||||||
|     public record OrderStatusChangedToSubmittedIntegrationEvent : IntegrationEvent |     public int OrderId { get; } | ||||||
|     { |     public string OrderStatus { get; } | ||||||
|         public int OrderId { get; } |     public string BuyerName { get; } | ||||||
|         public string OrderStatus { get; } |  | ||||||
|         public string BuyerName { get; } |  | ||||||
| 
 | 
 | ||||||
|         public OrderStatusChangedToSubmittedIntegrationEvent(int orderId, string orderStatus, string buyerName) |     public OrderStatusChangedToSubmittedIntegrationEvent(int orderId, string orderStatus, string buyerName) | ||||||
|         { |     { | ||||||
|             OrderId = orderId; |         OrderId = orderId; | ||||||
|             OrderStatus = orderStatus; |         OrderStatus = orderStatus; | ||||||
|             BuyerName = buyerName; |         BuyerName = buyerName; | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,11 +1,8 @@ | |||||||
| namespace Ordering.API.Application.IntegrationEvents.Events | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.IntegrationEvents.Events; | ||||||
|  | 
 | ||||||
|  | public record OrderStockConfirmedIntegrationEvent : IntegrationEvent | ||||||
| { | { | ||||||
|     using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; |     public int OrderId { get; } | ||||||
| 
 | 
 | ||||||
|     public record OrderStockConfirmedIntegrationEvent : IntegrationEvent |     public OrderStockConfirmedIntegrationEvent(int orderId) => OrderId = orderId; | ||||||
|     { | } | ||||||
|         public int OrderId { get; } |  | ||||||
| 
 |  | ||||||
|         public OrderStockConfirmedIntegrationEvent(int orderId) => OrderId = orderId; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | |||||||
| @ -1,31 +1,27 @@ | |||||||
| namespace Ordering.API.Application.IntegrationEvents.Events | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.IntegrationEvents.Events;     | ||||||
|  | 
 | ||||||
|  | public record OrderStockRejectedIntegrationEvent : IntegrationEvent | ||||||
| { | { | ||||||
|     using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; |     public int OrderId { get; } | ||||||
|     using System.Collections.Generic; |  | ||||||
| 
 | 
 | ||||||
|     public record OrderStockRejectedIntegrationEvent : IntegrationEvent |     public List<ConfirmedOrderStockItem> OrderStockItems { get; } | ||||||
|  | 
 | ||||||
|  |     public OrderStockRejectedIntegrationEvent(int orderId, | ||||||
|  |         List<ConfirmedOrderStockItem> orderStockItems) | ||||||
|     { |     { | ||||||
|         public int OrderId { get; } |         OrderId = orderId; | ||||||
| 
 |         OrderStockItems = orderStockItems; | ||||||
|         public List<ConfirmedOrderStockItem> OrderStockItems { get; } |  | ||||||
| 
 |  | ||||||
|         public OrderStockRejectedIntegrationEvent(int orderId, |  | ||||||
|             List<ConfirmedOrderStockItem> orderStockItems) |  | ||||||
|         { |  | ||||||
|             OrderId = orderId; |  | ||||||
|             OrderStockItems = orderStockItems; |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|     public record ConfirmedOrderStockItem | public record ConfirmedOrderStockItem | ||||||
|  | { | ||||||
|  |     public int ProductId { get; } | ||||||
|  |     public bool HasStock { get; } | ||||||
|  | 
 | ||||||
|  |     public ConfirmedOrderStockItem(int productId, bool hasStock) | ||||||
|     { |     { | ||||||
|         public int ProductId { get; } |         ProductId = productId; | ||||||
|         public bool HasStock { get; } |         HasStock = hasStock; | ||||||
| 
 |  | ||||||
|         public ConfirmedOrderStockItem(int productId, bool hasStock) |  | ||||||
|         { |  | ||||||
|             ProductId = productId; |  | ||||||
|             HasStock = hasStock; |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,62 +1,57 @@ | |||||||
| using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.IntegrationEvents.Events; | ||||||
| using Ordering.API.Application.Models; |  | ||||||
| using System; |  | ||||||
| 
 | 
 | ||||||
| namespace Ordering.API.Application.IntegrationEvents.Events | public record UserCheckoutAcceptedIntegrationEvent : IntegrationEvent | ||||||
| { | {         | ||||||
|     public record UserCheckoutAcceptedIntegrationEvent : IntegrationEvent |     public string UserId { get; } | ||||||
|     {         |  | ||||||
|         public string UserId { get; } |  | ||||||
|          |          | ||||||
|         public string UserName { get; } |     public string UserName { get; } | ||||||
|          |          | ||||||
|         public string City { get; set; } |     public string City { get; set; } | ||||||
|          |          | ||||||
|         public string Street { get; set; } |     public string Street { get; set; } | ||||||
|          |          | ||||||
|         public string State { get; set; } |     public string State { get; set; } | ||||||
| 
 | 
 | ||||||
|         public string Country { get; set; } |     public string Country { get; set; } | ||||||
| 
 | 
 | ||||||
|         public string ZipCode { get; set; } |     public string ZipCode { get; set; } | ||||||
| 
 | 
 | ||||||
|         public string CardNumber { get; set; } |     public string CardNumber { get; set; } | ||||||
| 
 | 
 | ||||||
|         public string CardHolderName { get; set; } |     public string CardHolderName { get; set; } | ||||||
| 
 | 
 | ||||||
|         public DateTime CardExpiration { get; set; } |     public DateTime CardExpiration { get; set; } | ||||||
| 
 | 
 | ||||||
|         public string CardSecurityNumber { get; set; } |     public string CardSecurityNumber { get; set; } | ||||||
| 
 | 
 | ||||||
|         public int CardTypeId { get; set; } |     public int CardTypeId { get; set; } | ||||||
| 
 | 
 | ||||||
|         public string Buyer { get; set; } |     public string Buyer { get; set; } | ||||||
| 
 | 
 | ||||||
|         public Guid RequestId { get; set; } |     public Guid RequestId { get; set; } | ||||||
| 
 | 
 | ||||||
|         public CustomerBasket Basket { get; } |     public CustomerBasket Basket { get; } | ||||||
| 
 |  | ||||||
|         public UserCheckoutAcceptedIntegrationEvent(string userId, string userName, string city, string street, |  | ||||||
|             string state, string country, string zipCode, string cardNumber, string cardHolderName, |  | ||||||
|             DateTime cardExpiration, string cardSecurityNumber, int cardTypeId, string buyer, Guid requestId, |  | ||||||
|             CustomerBasket basket) |  | ||||||
|         { |  | ||||||
|             UserId = userId; |  | ||||||
|             City = city; |  | ||||||
|             Street = street; |  | ||||||
|             State = state; |  | ||||||
|             Country = country; |  | ||||||
|             ZipCode = zipCode; |  | ||||||
|             CardNumber = cardNumber; |  | ||||||
|             CardHolderName = cardHolderName; |  | ||||||
|             CardExpiration = cardExpiration; |  | ||||||
|             CardSecurityNumber = cardSecurityNumber; |  | ||||||
|             CardTypeId = cardTypeId; |  | ||||||
|             Buyer = buyer; |  | ||||||
|             Basket = basket; |  | ||||||
|             RequestId = requestId; |  | ||||||
|             UserName = userName; |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|  |     public UserCheckoutAcceptedIntegrationEvent(string userId, string userName, string city, string street, | ||||||
|  |         string state, string country, string zipCode, string cardNumber, string cardHolderName, | ||||||
|  |         DateTime cardExpiration, string cardSecurityNumber, int cardTypeId, string buyer, Guid requestId, | ||||||
|  |         CustomerBasket basket) | ||||||
|  |     { | ||||||
|  |         UserId = userId; | ||||||
|  |         City = city; | ||||||
|  |         Street = street; | ||||||
|  |         State = state; | ||||||
|  |         Country = country; | ||||||
|  |         ZipCode = zipCode; | ||||||
|  |         CardNumber = cardNumber; | ||||||
|  |         CardHolderName = cardHolderName; | ||||||
|  |         CardExpiration = cardExpiration; | ||||||
|  |         CardSecurityNumber = cardSecurityNumber; | ||||||
|  |         CardTypeId = cardTypeId; | ||||||
|  |         Buyer = buyer; | ||||||
|  |         Basket = basket; | ||||||
|  |         RequestId = requestId; | ||||||
|  |         UserName = userName; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,12 +1,7 @@ | |||||||
| using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.IntegrationEvents; | ||||||
| using System; |  | ||||||
| using System.Threading.Tasks; |  | ||||||
| 
 | 
 | ||||||
| namespace Ordering.API.Application.IntegrationEvents | public interface IOrderingIntegrationEventService | ||||||
| { | { | ||||||
|     public interface IOrderingIntegrationEventService |     Task PublishEventsThroughEventBusAsync(Guid transactionId); | ||||||
|     { |     Task AddAndSaveEventAsync(IntegrationEvent evt); | ||||||
|         Task PublishEventsThroughEventBusAsync(Guid transactionId); |  | ||||||
|         Task AddAndSaveEventAsync(IntegrationEvent evt); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,65 +1,53 @@ | |||||||
| using Microsoft.EntityFrameworkCore; | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.IntegrationEvents; | ||||||
| using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; |  | ||||||
| using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; |  | ||||||
| using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF; |  | ||||||
| using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services; |  | ||||||
| using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure; |  | ||||||
| using Microsoft.Extensions.Logging; |  | ||||||
| using System; |  | ||||||
| using System.Data.Common; |  | ||||||
| using System.Threading.Tasks; |  | ||||||
| 
 | 
 | ||||||
| namespace Ordering.API.Application.IntegrationEvents | public class OrderingIntegrationEventService : IOrderingIntegrationEventService | ||||||
| { | { | ||||||
|     public class OrderingIntegrationEventService : IOrderingIntegrationEventService |     private readonly Func<DbConnection, IIntegrationEventLogService> _integrationEventLogServiceFactory; | ||||||
|  |     private readonly IEventBus _eventBus; | ||||||
|  |     private readonly OrderingContext _orderingContext; | ||||||
|  |     private readonly IIntegrationEventLogService _eventLogService; | ||||||
|  |     private readonly ILogger<OrderingIntegrationEventService> _logger; | ||||||
|  | 
 | ||||||
|  |     public OrderingIntegrationEventService(IEventBus eventBus, | ||||||
|  |         OrderingContext orderingContext, | ||||||
|  |         IntegrationEventLogContext eventLogContext, | ||||||
|  |         Func<DbConnection, IIntegrationEventLogService> integrationEventLogServiceFactory, | ||||||
|  |         ILogger<OrderingIntegrationEventService> logger) | ||||||
|     { |     { | ||||||
|         private readonly Func<DbConnection, IIntegrationEventLogService> _integrationEventLogServiceFactory; |         _orderingContext = orderingContext ?? throw new ArgumentNullException(nameof(orderingContext)); | ||||||
|         private readonly IEventBus _eventBus; |         _integrationEventLogServiceFactory = integrationEventLogServiceFactory ?? throw new ArgumentNullException(nameof(integrationEventLogServiceFactory)); | ||||||
|         private readonly OrderingContext _orderingContext; |         _eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus)); | ||||||
|         private readonly IIntegrationEventLogService _eventLogService; |         _eventLogService = _integrationEventLogServiceFactory(_orderingContext.Database.GetDbConnection()); | ||||||
|         private readonly ILogger<OrderingIntegrationEventService> _logger; |         _logger = logger ?? throw new ArgumentNullException(nameof(logger)); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|         public OrderingIntegrationEventService(IEventBus eventBus, |     public async Task PublishEventsThroughEventBusAsync(Guid transactionId) | ||||||
|             OrderingContext orderingContext, |     { | ||||||
|             IntegrationEventLogContext eventLogContext, |         var pendingLogEvents = await _eventLogService.RetrieveEventLogsPendingToPublishAsync(transactionId); | ||||||
|             Func<DbConnection, IIntegrationEventLogService> integrationEventLogServiceFactory, | 
 | ||||||
|             ILogger<OrderingIntegrationEventService> logger) |         foreach (var logEvt in pendingLogEvents) | ||||||
|         { |         { | ||||||
|             _orderingContext = orderingContext ?? throw new ArgumentNullException(nameof(orderingContext)); |             _logger.LogInformation("----- Publishing integration event: {IntegrationEventId} from {AppName} - ({@IntegrationEvent})", logEvt.EventId, Program.AppName, logEvt.IntegrationEvent); | ||||||
|             _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(Guid transactionId) |             try | ||||||
|         { |  | ||||||
|             var pendingLogEvents = await _eventLogService.RetrieveEventLogsPendingToPublishAsync(transactionId); |  | ||||||
| 
 |  | ||||||
|             foreach (var logEvt in pendingLogEvents) |  | ||||||
|             { |             { | ||||||
|                 _logger.LogInformation("----- Publishing integration event: {IntegrationEventId} from {AppName} - ({@IntegrationEvent})", logEvt.EventId, Program.AppName, logEvt.IntegrationEvent); |                 await _eventLogService.MarkEventAsInProgressAsync(logEvt.EventId); | ||||||
|  |                 _eventBus.Publish(logEvt.IntegrationEvent); | ||||||
|  |                 await _eventLogService.MarkEventAsPublishedAsync(logEvt.EventId); | ||||||
|  |             } | ||||||
|  |             catch (Exception ex) | ||||||
|  |             { | ||||||
|  |                 _logger.LogError(ex, "ERROR publishing integration event: {IntegrationEventId} from {AppName}", logEvt.EventId, Program.AppName); | ||||||
| 
 | 
 | ||||||
|                 try |                 await _eventLogService.MarkEventAsFailedAsync(logEvt.EventId); | ||||||
|                 { |  | ||||||
|                     await _eventLogService.MarkEventAsInProgressAsync(logEvt.EventId); |  | ||||||
|                     _eventBus.Publish(logEvt.IntegrationEvent); |  | ||||||
|                     await _eventLogService.MarkEventAsPublishedAsync(logEvt.EventId); |  | ||||||
|                 } |  | ||||||
|                 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) |     public async Task AddAndSaveEventAsync(IntegrationEvent evt) | ||||||
|         { |     { | ||||||
|             _logger.LogInformation("----- Enqueuing integration event {IntegrationEventId} to repository ({@IntegrationEvent})", evt.Id, evt); |         _logger.LogInformation("----- Enqueuing integration event {IntegrationEventId} to repository ({@IntegrationEvent})", evt.Id, evt); | ||||||
| 
 | 
 | ||||||
|             await _eventLogService.SaveEventAsync(evt, _orderingContext.GetCurrentTransaction()); |         await _eventLogService.SaveEventAsync(evt, _orderingContext.GetCurrentTransaction()); | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,13 +1,13 @@ | |||||||
| namespace Ordering.API.Application.Models | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Models; | ||||||
|  | 
 | ||||||
|  | public class BasketItem | ||||||
| { | { | ||||||
|     public class BasketItem |     public string Id { get; init; } | ||||||
|     { |     public int ProductId { get; init; } | ||||||
|         public string Id { get; init; } |     public string ProductName { get; init; } | ||||||
|         public int ProductId { get; init; } |     public decimal UnitPrice { get; init; } | ||||||
|         public string ProductName { get; init; } |     public decimal OldUnitPrice { get; init; } | ||||||
|         public decimal UnitPrice { get; init; } |     public int Quantity { get; init; } | ||||||
|         public decimal OldUnitPrice { get; init; } |     public string PictureUrl { get; init; } | ||||||
|         public int Quantity { get; init; } |  | ||||||
|         public string PictureUrl { get; init; } |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | |||||||
| @ -1,16 +1,13 @@ | |||||||
| using System.Collections.Generic; | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Models; | ||||||
| 
 | 
 | ||||||
| namespace Ordering.API.Application.Models | public class CustomerBasket | ||||||
| { | { | ||||||
|     public class CustomerBasket |     public string BuyerId { get; set; } | ||||||
|     { |     public List<BasketItem> Items { get; set; } | ||||||
|         public string BuyerId { get; set; } |  | ||||||
|         public List<BasketItem> Items { get; set; } |  | ||||||
| 
 | 
 | ||||||
|         public CustomerBasket(string buyerId, List<BasketItem> items) |     public CustomerBasket(string buyerId, List<BasketItem> items) | ||||||
|         { |     { | ||||||
|             BuyerId = buyerId; |         BuyerId = buyerId; | ||||||
|             Items = items; |         Items = items; | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,15 +1,10 @@ | |||||||
| namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Queries | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Queries; | ||||||
|  | 
 | ||||||
|  | public interface IOrderQueries | ||||||
| { | { | ||||||
|     using System; |     Task<Order> GetOrderAsync(int id); | ||||||
|     using System.Collections.Generic; |  | ||||||
|     using System.Threading.Tasks; |  | ||||||
| 
 | 
 | ||||||
|     public interface IOrderQueries |     Task<IEnumerable<OrderSummary>> GetOrdersFromUserAsync(Guid userId); | ||||||
|     { |  | ||||||
|         Task<Order> GetOrderAsync(int id); |  | ||||||
| 
 | 
 | ||||||
|         Task<IEnumerable<OrderSummary>> GetOrdersFromUserAsync(Guid userId); |     Task<IEnumerable<CardType>> GetCardTypesAsync(); | ||||||
| 
 |  | ||||||
|         Task<IEnumerable<CardType>> GetCardTypesAsync(); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,105 +1,98 @@ | |||||||
| namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Queries | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Queries; | ||||||
|  |      | ||||||
|  | public class OrderQueries | ||||||
|  |     : IOrderQueries | ||||||
| { | { | ||||||
|     using Dapper; |     private string _connectionString = string.Empty; | ||||||
|     using System; |  | ||||||
|     using System.Collections.Generic; |  | ||||||
|     using System.Data.SqlClient; |  | ||||||
|     using System.Threading.Tasks; |  | ||||||
| 
 | 
 | ||||||
|     public class OrderQueries |     public OrderQueries(string constr) | ||||||
|         : IOrderQueries |  | ||||||
|     { |     { | ||||||
|         private string _connectionString = string.Empty; |         _connectionString = !string.IsNullOrWhiteSpace(constr) ? constr : throw new ArgumentNullException(nameof(constr)); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|         public OrderQueries(string constr) | 
 | ||||||
|  |     public async Task<Order> GetOrderAsync(int id) | ||||||
|  |     { | ||||||
|  |         using (var connection = new SqlConnection(_connectionString)) | ||||||
|         { |         { | ||||||
|             _connectionString = !string.IsNullOrWhiteSpace(constr) ? constr : throw new ArgumentNullException(nameof(constr)); |             connection.Open(); | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|  |             var result = await connection.QueryAsync<dynamic>( | ||||||
|  |                 @"select o.[Id] as ordernumber,o.OrderDate as date, o.Description as description,
 | ||||||
|  |                     o.Address_City as city, o.Address_Country as country, o.Address_State as state, o.Address_Street as street, o.Address_ZipCode as zipcode, | ||||||
|  |                     os.Name as status,  | ||||||
|  |                     oi.ProductName as productname, oi.Units as units, oi.UnitPrice as unitprice, oi.PictureUrl as pictureurl | ||||||
|  |                     FROM ordering.Orders o | ||||||
|  |                     LEFT JOIN ordering.Orderitems oi ON o.Id = oi.orderid  | ||||||
|  |                     LEFT JOIN ordering.orderstatus os on o.OrderStatusId = os.Id | ||||||
|  |                     WHERE o.Id=@id"
 | ||||||
|  |                     , new { id } | ||||||
|  |                 ); | ||||||
| 
 | 
 | ||||||
|         public async Task<Order> GetOrderAsync(int id) |             if (result.AsList().Count == 0) | ||||||
|         { |                 throw new KeyNotFoundException(); | ||||||
|             using (var connection = new SqlConnection(_connectionString)) |  | ||||||
|             { |  | ||||||
|                 connection.Open(); |  | ||||||
| 
 | 
 | ||||||
|                 var result = await connection.QueryAsync<dynamic>( |             return MapOrderItems(result); | ||||||
|                    @"select o.[Id] as ordernumber,o.OrderDate as date, o.Description as description,
 |  | ||||||
|                         o.Address_City as city, o.Address_Country as country, o.Address_State as state, o.Address_Street as street, o.Address_ZipCode as zipcode, |  | ||||||
|                         os.Name as status,  |  | ||||||
|                         oi.ProductName as productname, oi.Units as units, oi.UnitPrice as unitprice, oi.PictureUrl as pictureurl |  | ||||||
|                         FROM ordering.Orders o |  | ||||||
|                         LEFT JOIN ordering.Orderitems oi ON o.Id = oi.orderid  |  | ||||||
|                         LEFT JOIN ordering.orderstatus os on o.OrderStatusId = os.Id |  | ||||||
|                         WHERE o.Id=@id"
 |  | ||||||
|                         , new { id } |  | ||||||
|                     ); |  | ||||||
| 
 |  | ||||||
|                 if (result.AsList().Count == 0) |  | ||||||
|                     throw new KeyNotFoundException(); |  | ||||||
| 
 |  | ||||||
|                 return MapOrderItems(result); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         public async Task<IEnumerable<OrderSummary>> GetOrdersFromUserAsync(Guid userId) |  | ||||||
|         { |  | ||||||
|             using (var connection = new SqlConnection(_connectionString)) |  | ||||||
|             { |  | ||||||
|                 connection.Open(); |  | ||||||
| 
 |  | ||||||
|                 return await connection.QueryAsync<OrderSummary>(@"SELECT o.[Id] as ordernumber,o.[OrderDate] as [date],os.[Name] as [status], SUM(oi.units*oi.unitprice) as total
 |  | ||||||
|                      FROM [ordering].[Orders] o |  | ||||||
|                      LEFT JOIN[ordering].[orderitems] oi ON  o.Id = oi.orderid  |  | ||||||
|                      LEFT JOIN[ordering].[orderstatus] os on o.OrderStatusId = os.Id                      |  | ||||||
|                      LEFT JOIN[ordering].[buyers] ob on o.BuyerId = ob.Id |  | ||||||
|                      WHERE ob.IdentityGuid = @userId |  | ||||||
|                      GROUP BY o.[Id], o.[OrderDate], os.[Name]  |  | ||||||
|                      ORDER BY o.[Id]", new { userId });
 |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         public async Task<IEnumerable<CardType>> GetCardTypesAsync() |  | ||||||
|         { |  | ||||||
|             using (var connection = new SqlConnection(_connectionString)) |  | ||||||
|             { |  | ||||||
|                 connection.Open(); |  | ||||||
| 
 |  | ||||||
|                 return await connection.QueryAsync<CardType>("SELECT * FROM ordering.cardtypes"); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         private Order MapOrderItems(dynamic result) |  | ||||||
|         { |  | ||||||
|             var order = new Order |  | ||||||
|             { |  | ||||||
|                 ordernumber = result[0].ordernumber, |  | ||||||
|                 date = result[0].date, |  | ||||||
|                 status = result[0].status, |  | ||||||
|                 description = result[0].description, |  | ||||||
|                 street = result[0].street, |  | ||||||
|                 city = result[0].city, |  | ||||||
|                 zipcode = result[0].zipcode, |  | ||||||
|                 country = result[0].country, |  | ||||||
|                 orderitems = new List<Orderitem>(), |  | ||||||
|                 total = 0 |  | ||||||
|             }; |  | ||||||
| 
 |  | ||||||
|             foreach (dynamic item in result) |  | ||||||
|             { |  | ||||||
|                 var orderitem = new Orderitem |  | ||||||
|                 { |  | ||||||
|                     productname = item.productname, |  | ||||||
|                     units = item.units, |  | ||||||
|                     unitprice = (double)item.unitprice, |  | ||||||
|                     pictureurl = item.pictureurl |  | ||||||
|                 }; |  | ||||||
| 
 |  | ||||||
|                 order.total += item.units * item.unitprice; |  | ||||||
|                 order.orderitems.Add(orderitem); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             return order; |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     public async Task<IEnumerable<OrderSummary>> GetOrdersFromUserAsync(Guid userId) | ||||||
|  |     { | ||||||
|  |         using (var connection = new SqlConnection(_connectionString)) | ||||||
|  |         { | ||||||
|  |             connection.Open(); | ||||||
|  | 
 | ||||||
|  |             return await connection.QueryAsync<OrderSummary>(@"SELECT o.[Id] as ordernumber,o.[OrderDate] as [date],os.[Name] as [status], SUM(oi.units*oi.unitprice) as total
 | ||||||
|  |                     FROM [ordering].[Orders] o | ||||||
|  |                     LEFT JOIN[ordering].[orderitems] oi ON  o.Id = oi.orderid  | ||||||
|  |                     LEFT JOIN[ordering].[orderstatus] os on o.OrderStatusId = os.Id                      | ||||||
|  |                     LEFT JOIN[ordering].[buyers] ob on o.BuyerId = ob.Id | ||||||
|  |                     WHERE ob.IdentityGuid = @userId | ||||||
|  |                     GROUP BY o.[Id], o.[OrderDate], os.[Name]  | ||||||
|  |                     ORDER BY o.[Id]", new { userId });
 | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public async Task<IEnumerable<CardType>> GetCardTypesAsync() | ||||||
|  |     { | ||||||
|  |         using (var connection = new SqlConnection(_connectionString)) | ||||||
|  |         { | ||||||
|  |             connection.Open(); | ||||||
|  | 
 | ||||||
|  |             return await connection.QueryAsync<CardType>("SELECT * FROM ordering.cardtypes"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private Order MapOrderItems(dynamic result) | ||||||
|  |     { | ||||||
|  |         var order = new Order | ||||||
|  |         { | ||||||
|  |             ordernumber = result[0].ordernumber, | ||||||
|  |             date = result[0].date, | ||||||
|  |             status = result[0].status, | ||||||
|  |             description = result[0].description, | ||||||
|  |             street = result[0].street, | ||||||
|  |             city = result[0].city, | ||||||
|  |             zipcode = result[0].zipcode, | ||||||
|  |             country = result[0].country, | ||||||
|  |             orderitems = new List<Orderitem>(), | ||||||
|  |             total = 0 | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         foreach (dynamic item in result) | ||||||
|  |         { | ||||||
|  |             var orderitem = new Orderitem | ||||||
|  |             { | ||||||
|  |                 productname = item.productname, | ||||||
|  |                 units = item.units, | ||||||
|  |                 unitprice = (double)item.unitprice, | ||||||
|  |                 pictureurl = item.pictureurl | ||||||
|  |             }; | ||||||
|  | 
 | ||||||
|  |             order.total += item.units * item.unitprice; | ||||||
|  |             order.orderitems.Add(orderitem); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return order; | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,41 +1,37 @@ | |||||||
| using System; | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Queries; | ||||||
| using System.Collections.Generic; |  | ||||||
| 
 | 
 | ||||||
| namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Queries | public record Orderitem | ||||||
| { | { | ||||||
|     public record Orderitem |     public string productname { get; init; } | ||||||
|     { |     public int units { get; init; } | ||||||
|         public string productname { get; init; } |     public double unitprice { get; init; } | ||||||
|         public int units { get; init; } |     public string pictureurl { get; init; } | ||||||
|         public double unitprice { get; init; } | } | ||||||
|         public string pictureurl { get; init; } | 
 | ||||||
|     } | public record Order | ||||||
| 
 | { | ||||||
|     public record Order |     public int ordernumber { get; init; } | ||||||
|     { |     public DateTime date { get; init; } | ||||||
|         public int ordernumber { get; init; } |     public string status { get; init; } | ||||||
|         public DateTime date { get; init; } |     public string description { get; init; } | ||||||
|         public string status { get; init; } |     public string street { get; init; } | ||||||
|         public string description { get; init; } |     public string city { get; init; } | ||||||
|         public string street { get; init; } |     public string zipcode { get; init; } | ||||||
|         public string city { get; init; } |     public string country { get; init; } | ||||||
|         public string zipcode { get; init; } |     public List<Orderitem> orderitems { get; set; } | ||||||
|         public string country { get; init; } |     public decimal total { get; set; } | ||||||
|         public List<Orderitem> orderitems { get; set; } | } | ||||||
|         public decimal total { get; set; } | 
 | ||||||
|     } | public record OrderSummary | ||||||
| 
 | { | ||||||
|     public record OrderSummary |     public int ordernumber { get; init; } | ||||||
|     { |     public DateTime date { get; init; } | ||||||
|         public int ordernumber { get; init; } |     public string status { get; init; } | ||||||
|         public DateTime date { get; init; } |     public double total { get; init; } | ||||||
|         public string status { get; init; } | } | ||||||
|         public double total { get; init; } | 
 | ||||||
|     } | public record CardType | ||||||
| 
 | { | ||||||
|     public record CardType |     public int Id { get; init; } | ||||||
|     { |     public string Name { get; init; } | ||||||
|         public int Id { get; init; } |  | ||||||
|         public string Name { get; init; } |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,16 +1,11 @@ | |||||||
| using FluentValidation; | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Validations; | ||||||
| using Microsoft.Extensions.Logging; |  | ||||||
| using Ordering.API.Application.Commands; |  | ||||||
| 
 | 
 | ||||||
| namespace Ordering.API.Application.Validations | public class CancelOrderCommandValidator : AbstractValidator<CancelOrderCommand> | ||||||
| { | { | ||||||
|     public class CancelOrderCommandValidator : AbstractValidator<CancelOrderCommand> |     public CancelOrderCommandValidator(ILogger<CancelOrderCommandValidator> logger) | ||||||
|     { |     { | ||||||
|         public CancelOrderCommandValidator(ILogger<CancelOrderCommandValidator> logger) |         RuleFor(order => order.OrderNumber).NotEmpty().WithMessage("No orderId found"); | ||||||
|         { |  | ||||||
|             RuleFor(order => order.OrderNumber).NotEmpty().WithMessage("No orderId found"); |  | ||||||
| 
 | 
 | ||||||
|             logger.LogTrace("----- INSTANCE CREATED - {ClassName}", GetType().Name); |         logger.LogTrace("----- INSTANCE CREATED - {ClassName}", GetType().Name); | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,40 +1,33 @@ | |||||||
| using FluentValidation; | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Validations; | ||||||
| using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; | 
 | ||||||
| using Microsoft.Extensions.Logging; |  | ||||||
| using System; |  | ||||||
| using System.Collections.Generic; |  | ||||||
| using System.Linq; |  | ||||||
| using static Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands.CreateOrderCommand; | using static Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands.CreateOrderCommand; | ||||||
| 
 | 
 | ||||||
| namespace Ordering.API.Application.Validations | public class CreateOrderCommandValidator : AbstractValidator<CreateOrderCommand> | ||||||
| { | { | ||||||
|     public class CreateOrderCommandValidator : AbstractValidator<CreateOrderCommand> |     public CreateOrderCommandValidator(ILogger<CreateOrderCommandValidator> logger) | ||||||
|     { |     { | ||||||
|         public CreateOrderCommandValidator(ILogger<CreateOrderCommandValidator> logger) |         RuleFor(command => command.City).NotEmpty(); | ||||||
|         { |         RuleFor(command => command.Street).NotEmpty(); | ||||||
|             RuleFor(command => command.City).NotEmpty(); |         RuleFor(command => command.State).NotEmpty(); | ||||||
|             RuleFor(command => command.Street).NotEmpty(); |         RuleFor(command => command.Country).NotEmpty(); | ||||||
|             RuleFor(command => command.State).NotEmpty(); |         RuleFor(command => command.ZipCode).NotEmpty(); | ||||||
|             RuleFor(command => command.Country).NotEmpty(); |         RuleFor(command => command.CardNumber).NotEmpty().Length(12, 19); | ||||||
|             RuleFor(command => command.ZipCode).NotEmpty(); |         RuleFor(command => command.CardHolderName).NotEmpty(); | ||||||
|             RuleFor(command => command.CardNumber).NotEmpty().Length(12, 19); |         RuleFor(command => command.CardExpiration).NotEmpty().Must(BeValidExpirationDate).WithMessage("Please specify a valid card expiration date"); | ||||||
|             RuleFor(command => command.CardHolderName).NotEmpty(); |         RuleFor(command => command.CardSecurityNumber).NotEmpty().Length(3); | ||||||
|             RuleFor(command => command.CardExpiration).NotEmpty().Must(BeValidExpirationDate).WithMessage("Please specify a valid card expiration date"); |         RuleFor(command => command.CardTypeId).NotEmpty(); | ||||||
|             RuleFor(command => command.CardSecurityNumber).NotEmpty().Length(3); |         RuleFor(command => command.OrderItems).Must(ContainOrderItems).WithMessage("No order items found"); | ||||||
|             RuleFor(command => command.CardTypeId).NotEmpty(); |  | ||||||
|             RuleFor(command => command.OrderItems).Must(ContainOrderItems).WithMessage("No order items found"); |  | ||||||
| 
 | 
 | ||||||
|             logger.LogTrace("----- INSTANCE CREATED - {ClassName}", GetType().Name); |         logger.LogTrace("----- INSTANCE CREATED - {ClassName}", GetType().Name); | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         private bool BeValidExpirationDate(DateTime dateTime) |  | ||||||
|         { |  | ||||||
|             return dateTime >= DateTime.UtcNow; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         private bool ContainOrderItems(IEnumerable<OrderItemDTO> orderItems) |  | ||||||
|         { |  | ||||||
|             return orderItems.Any(); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | 
 | ||||||
|  |     private bool BeValidExpirationDate(DateTime dateTime) | ||||||
|  |     { | ||||||
|  |         return dateTime >= DateTime.UtcNow; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private bool ContainOrderItems(IEnumerable<OrderItemDTO> orderItems) | ||||||
|  |     { | ||||||
|  |         return orderItems.Any(); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | |||||||
| @ -1,16 +1,11 @@ | |||||||
| using FluentValidation; | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Validations; | ||||||
| using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; |  | ||||||
| using Microsoft.Extensions.Logging; |  | ||||||
| 
 | 
 | ||||||
| namespace Ordering.API.Application.Validations | public class IdentifiedCommandValidator : AbstractValidator<IdentifiedCommand<CreateOrderCommand, bool>> | ||||||
| { | { | ||||||
|     public class IdentifiedCommandValidator : AbstractValidator<IdentifiedCommand<CreateOrderCommand, bool>> |     public IdentifiedCommandValidator(ILogger<IdentifiedCommandValidator> logger) | ||||||
|     { |     { | ||||||
|         public IdentifiedCommandValidator(ILogger<IdentifiedCommandValidator> logger) |         RuleFor(command => command.Id).NotEmpty(); | ||||||
|         { |  | ||||||
|             RuleFor(command => command.Id).NotEmpty(); |  | ||||||
| 
 | 
 | ||||||
|             logger.LogTrace("----- INSTANCE CREATED - {ClassName}", GetType().Name); |         logger.LogTrace("----- INSTANCE CREATED - {ClassName}", GetType().Name); | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,16 +1,11 @@ | |||||||
| using FluentValidation; | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Validations; | ||||||
| using Microsoft.Extensions.Logging; |  | ||||||
| using Ordering.API.Application.Commands; |  | ||||||
| 
 | 
 | ||||||
| namespace Ordering.API.Application.Validations | public class ShipOrderCommandValidator : AbstractValidator<ShipOrderCommand> | ||||||
| { | { | ||||||
|     public class ShipOrderCommandValidator : AbstractValidator<ShipOrderCommand> |     public ShipOrderCommandValidator(ILogger<ShipOrderCommandValidator> logger) | ||||||
|     { |     { | ||||||
|         public ShipOrderCommandValidator(ILogger<ShipOrderCommandValidator> logger) |         RuleFor(order => order.OrderNumber).NotEmpty().WithMessage("No orderId found"); | ||||||
|         { |  | ||||||
|             RuleFor(order => order.OrderNumber).NotEmpty().WithMessage("No orderId found"); |  | ||||||
| 
 | 
 | ||||||
|             logger.LogTrace("----- INSTANCE CREATED - {ClassName}", GetType().Name); |         logger.LogTrace("----- INSTANCE CREATED - {ClassName}", GetType().Name); | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,13 +1,10 @@ | |||||||
| using Microsoft.AspNetCore.Mvc; | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers; | ||||||
| 
 | 
 | ||||||
| namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers | public class HomeController : Controller | ||||||
| { | { | ||||||
|     public class HomeController : Controller |     // GET: /<controller>/ | ||||||
|  |     public IActionResult Index() | ||||||
|     { |     { | ||||||
|         // GET: /<controller>/ |         return new RedirectResult("~/swagger"); | ||||||
|         public IActionResult Index() |  | ||||||
|         { |  | ||||||
|             return new RedirectResult("~/swagger"); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,153 +1,143 @@ | |||||||
| using MediatR; | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers; | ||||||
| using Microsoft.AspNetCore.Authorization; | 
 | ||||||
| using Microsoft.AspNetCore.Mvc; |  | ||||||
| using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Extensions; | using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Extensions; | ||||||
| using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; | using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; | ||||||
| using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Queries; | using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Queries; | ||||||
| using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services; | using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services; | ||||||
| using Microsoft.Extensions.Logging; |  | ||||||
| using Ordering.API.Application.Commands; |  | ||||||
| using System; |  | ||||||
| using System.Collections.Generic; |  | ||||||
| using System.Net; |  | ||||||
| using System.Threading.Tasks; |  | ||||||
| 
 | 
 | ||||||
| namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers | [Route("api/v1/[controller]")]
 | ||||||
|  | [Authorize] | ||||||
|  | [ApiController] | ||||||
|  | public class OrdersController : ControllerBase | ||||||
| { | { | ||||||
|     [Route("api/v1/[controller]")]
 |     private readonly IMediator _mediator; | ||||||
|     [Authorize] |     private readonly IOrderQueries _orderQueries; | ||||||
|     [ApiController] |     private readonly IIdentityService _identityService; | ||||||
|     public class OrdersController : ControllerBase |     private readonly ILogger<OrdersController> _logger; | ||||||
|  | 
 | ||||||
|  |     public OrdersController( | ||||||
|  |         IMediator mediator, | ||||||
|  |         IOrderQueries orderQueries, | ||||||
|  |         IIdentityService identityService, | ||||||
|  |         ILogger<OrdersController> logger) | ||||||
|     { |     { | ||||||
|         private readonly IMediator _mediator; |         _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); | ||||||
|         private readonly IOrderQueries _orderQueries; |         _orderQueries = orderQueries ?? throw new ArgumentNullException(nameof(orderQueries)); | ||||||
|         private readonly IIdentityService _identityService; |         _identityService = identityService ?? throw new ArgumentNullException(nameof(identityService)); | ||||||
|         private readonly ILogger<OrdersController> _logger; |         _logger = logger ?? throw new ArgumentNullException(nameof(logger)); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|         public OrdersController( |     [Route("cancel")] | ||||||
|             IMediator mediator, |     [HttpPut] | ||||||
|             IOrderQueries orderQueries, |     [ProducesResponseType((int)HttpStatusCode.OK)] | ||||||
|             IIdentityService identityService, |     [ProducesResponseType((int)HttpStatusCode.BadRequest)] | ||||||
|             ILogger<OrdersController> logger) |     public async Task<IActionResult> CancelOrderAsync([FromBody] CancelOrderCommand command, [FromHeader(Name = "x-requestid")] string requestId) | ||||||
|  |     { | ||||||
|  |         bool commandResult = false; | ||||||
|  | 
 | ||||||
|  |         if (Guid.TryParse(requestId, out Guid guid) && guid != Guid.Empty) | ||||||
|         { |         { | ||||||
|             _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); |             var requestCancelOrder = new IdentifiedCommand<CancelOrderCommand, bool>(command, guid); | ||||||
|             _orderQueries = orderQueries ?? throw new ArgumentNullException(nameof(orderQueries)); |  | ||||||
|             _identityService = identityService ?? throw new ArgumentNullException(nameof(identityService)); |  | ||||||
|             _logger = logger ?? throw new ArgumentNullException(nameof(logger)); |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         [Route("cancel")] |  | ||||||
|         [HttpPut] |  | ||||||
|         [ProducesResponseType((int)HttpStatusCode.OK)] |  | ||||||
|         [ProducesResponseType((int)HttpStatusCode.BadRequest)] |  | ||||||
|         public async Task<IActionResult> CancelOrderAsync([FromBody] CancelOrderCommand command, [FromHeader(Name = "x-requestid")] string requestId) |  | ||||||
|         { |  | ||||||
|             bool commandResult = false; |  | ||||||
| 
 |  | ||||||
|             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); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             if (!commandResult) |  | ||||||
|             { |  | ||||||
|                 return BadRequest(); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             return Ok(); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         [Route("ship")] |  | ||||||
|         [HttpPut] |  | ||||||
|         [ProducesResponseType((int)HttpStatusCode.OK)] |  | ||||||
|         [ProducesResponseType((int)HttpStatusCode.BadRequest)] |  | ||||||
|         public async Task<IActionResult> ShipOrderAsync([FromBody] ShipOrderCommand command, [FromHeader(Name = "x-requestid")] string requestId) |  | ||||||
|         { |  | ||||||
|             bool commandResult = false; |  | ||||||
| 
 |  | ||||||
|             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); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             if (!commandResult) |  | ||||||
|             { |  | ||||||
|                 return BadRequest(); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             return Ok(); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         [Route("{orderId:int}")] |  | ||||||
|         [HttpGet] |  | ||||||
|         [ProducesResponseType(typeof(Order), (int)HttpStatusCode.OK)] |  | ||||||
|         [ProducesResponseType((int)HttpStatusCode.NotFound)] |  | ||||||
|         public async Task<ActionResult> GetOrderAsync(int orderId) |  | ||||||
|         { |  | ||||||
|             try |  | ||||||
|             { |  | ||||||
|                 //Todo: It's good idea to take advantage of GetOrderByIdQuery and handle by GetCustomerByIdQueryHandler |  | ||||||
|                 //var order customer = await _mediator.Send(new GetOrderByIdQuery(orderId)); |  | ||||||
|                 var order = await _orderQueries.GetOrderAsync(orderId); |  | ||||||
| 
 |  | ||||||
|                 return Ok(order); |  | ||||||
|             } |  | ||||||
|             catch |  | ||||||
|             { |  | ||||||
|                 return NotFound(); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         [HttpGet] |  | ||||||
|         [ProducesResponseType(typeof(IEnumerable<OrderSummary>), (int)HttpStatusCode.OK)] |  | ||||||
|         public async Task<ActionResult<IEnumerable<OrderSummary>>> GetOrdersAsync() |  | ||||||
|         { |  | ||||||
|             var userid = _identityService.GetUserIdentity(); |  | ||||||
|             var orders = await _orderQueries.GetOrdersFromUserAsync(Guid.Parse(userid)); |  | ||||||
| 
 |  | ||||||
|             return Ok(orders); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         [Route("cardtypes")] |  | ||||||
|         [HttpGet] |  | ||||||
|         [ProducesResponseType(typeof(IEnumerable<CardType>), (int)HttpStatusCode.OK)] |  | ||||||
|         public async Task<ActionResult<IEnumerable<CardType>>> GetCardTypesAsync() |  | ||||||
|         { |  | ||||||
|             var cardTypes = await _orderQueries.GetCardTypesAsync(); |  | ||||||
| 
 |  | ||||||
|             return Ok(cardTypes); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         [Route("draft")] |  | ||||||
|         [HttpPost] |  | ||||||
|         public async Task<ActionResult<OrderDraftDTO>> CreateOrderDraftFromBasketDataAsync([FromBody] CreateOrderDraftCommand createOrderDraftCommand) |  | ||||||
|         { |  | ||||||
|             _logger.LogInformation( |             _logger.LogInformation( | ||||||
|                 "----- Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})", |                 "----- Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})", | ||||||
|                 createOrderDraftCommand.GetGenericTypeName(), |                 requestCancelOrder.GetGenericTypeName(), | ||||||
|                 nameof(createOrderDraftCommand.BuyerId), |                 nameof(requestCancelOrder.Command.OrderNumber), | ||||||
|                 createOrderDraftCommand.BuyerId, |                 requestCancelOrder.Command.OrderNumber, | ||||||
|                 createOrderDraftCommand); |                 requestCancelOrder); | ||||||
| 
 | 
 | ||||||
|             return await _mediator.Send(createOrderDraftCommand); |             commandResult = await _mediator.Send(requestCancelOrder); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (!commandResult) | ||||||
|  |         { | ||||||
|  |             return BadRequest(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return Ok(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     [Route("ship")] | ||||||
|  |     [HttpPut] | ||||||
|  |     [ProducesResponseType((int)HttpStatusCode.OK)] | ||||||
|  |     [ProducesResponseType((int)HttpStatusCode.BadRequest)] | ||||||
|  |     public async Task<IActionResult> ShipOrderAsync([FromBody] ShipOrderCommand command, [FromHeader(Name = "x-requestid")] string requestId) | ||||||
|  |     { | ||||||
|  |         bool commandResult = false; | ||||||
|  | 
 | ||||||
|  |         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); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (!commandResult) | ||||||
|  |         { | ||||||
|  |             return BadRequest(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return Ok(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     [Route("{orderId:int}")] | ||||||
|  |     [HttpGet] | ||||||
|  |     [ProducesResponseType(typeof(Order), (int)HttpStatusCode.OK)] | ||||||
|  |     [ProducesResponseType((int)HttpStatusCode.NotFound)] | ||||||
|  |     public async Task<ActionResult> GetOrderAsync(int orderId) | ||||||
|  |     { | ||||||
|  |         try | ||||||
|  |         { | ||||||
|  |             //Todo: It's good idea to take advantage of GetOrderByIdQuery and handle by GetCustomerByIdQueryHandler | ||||||
|  |             //var order customer = await _mediator.Send(new GetOrderByIdQuery(orderId)); | ||||||
|  |             var order = await _orderQueries.GetOrderAsync(orderId); | ||||||
|  | 
 | ||||||
|  |             return Ok(order); | ||||||
|  |         } | ||||||
|  |         catch | ||||||
|  |         { | ||||||
|  |             return NotFound(); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     [HttpGet] | ||||||
|  |     [ProducesResponseType(typeof(IEnumerable<OrderSummary>), (int)HttpStatusCode.OK)] | ||||||
|  |     public async Task<ActionResult<IEnumerable<OrderSummary>>> GetOrdersAsync() | ||||||
|  |     { | ||||||
|  |         var userid = _identityService.GetUserIdentity(); | ||||||
|  |         var orders = await _orderQueries.GetOrdersFromUserAsync(Guid.Parse(userid)); | ||||||
|  | 
 | ||||||
|  |         return Ok(orders); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     [Route("cardtypes")] | ||||||
|  |     [HttpGet] | ||||||
|  |     [ProducesResponseType(typeof(IEnumerable<CardType>), (int)HttpStatusCode.OK)] | ||||||
|  |     public async Task<ActionResult<IEnumerable<CardType>>> GetCardTypesAsync() | ||||||
|  |     { | ||||||
|  |         var cardTypes = await _orderQueries.GetCardTypesAsync(); | ||||||
|  | 
 | ||||||
|  |         return Ok(cardTypes); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     [Route("draft")] | ||||||
|  |     [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); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,28 +1,27 @@ | |||||||
| using System.Collections.Generic; | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Models; | ||||||
|  | 
 | ||||||
|  | using System.Collections.Generic; | ||||||
| using static Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands.CreateOrderCommand; | using static Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands.CreateOrderCommand; | ||||||
| 
 | 
 | ||||||
| namespace Ordering.API.Application.Models | public static class BasketItemExtensions | ||||||
| { | { | ||||||
|     public static class BasketItemExtensions |     public static IEnumerable<OrderItemDTO> ToOrderItemsDTO(this IEnumerable<BasketItem> basketItems) | ||||||
|     { |     { | ||||||
|         public static IEnumerable<OrderItemDTO> ToOrderItemsDTO(this IEnumerable<BasketItem> basketItems) |         foreach (var item in basketItems) | ||||||
|         { |         { | ||||||
|             foreach (var item in basketItems) |             yield return item.ToOrderItemDTO(); | ||||||
|             { |  | ||||||
|                 yield return item.ToOrderItemDTO(); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         public static OrderItemDTO ToOrderItemDTO(this BasketItem item) |  | ||||||
|         { |  | ||||||
|             return new OrderItemDTO() |  | ||||||
|             { |  | ||||||
|                 ProductId = item.ProductId, |  | ||||||
|                 ProductName = item.ProductName, |  | ||||||
|                 PictureUrl = item.PictureUrl, |  | ||||||
|                 UnitPrice = item.UnitPrice, |  | ||||||
|                 Units = item.Quantity |  | ||||||
|             }; |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     public static OrderItemDTO ToOrderItemDTO(this BasketItem item) | ||||||
|  |     { | ||||||
|  |         return new OrderItemDTO() | ||||||
|  |         { | ||||||
|  |             ProductId = item.ProductId, | ||||||
|  |             ProductName = item.ProductName, | ||||||
|  |             PictureUrl = item.PictureUrl, | ||||||
|  |             UnitPrice = item.UnitPrice, | ||||||
|  |             Units = item.Quantity | ||||||
|  |         }; | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,50 +1,45 @@ | |||||||
| using System; | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Extensions; | ||||||
| using System.Collections.Generic; |  | ||||||
| using System.Linq; |  | ||||||
| 
 | 
 | ||||||
| namespace Ordering.API.Extensions | public static class LinqSelectExtensions | ||||||
| { | { | ||||||
|     public static class LinqSelectExtensions |     public static IEnumerable<SelectTryResult<TSource, TResult>> SelectTry<TSource, TResult>(this IEnumerable<TSource> enumerable, Func<TSource, TResult> selector) | ||||||
|     { |     { | ||||||
|         public static IEnumerable<SelectTryResult<TSource, TResult>> SelectTry<TSource, TResult>(this IEnumerable<TSource> enumerable, Func<TSource, TResult> selector) |         foreach (TSource element in enumerable) | ||||||
|         { |         { | ||||||
|             foreach (TSource element in enumerable) |             SelectTryResult<TSource, TResult> returnedValue; | ||||||
|  |             try | ||||||
|             { |             { | ||||||
|                 SelectTryResult<TSource, TResult> returnedValue; |                 returnedValue = new SelectTryResult<TSource, TResult>(element, selector(element), null); | ||||||
|                 try |  | ||||||
|                 { |  | ||||||
|                     returnedValue = new SelectTryResult<TSource, TResult>(element, selector(element), null); |  | ||||||
|                 } |  | ||||||
|                 catch (Exception ex) |  | ||||||
|                 { |  | ||||||
|                     returnedValue = new SelectTryResult<TSource, TResult>(element, default(TResult), ex); |  | ||||||
|                 } |  | ||||||
|                 yield return returnedValue; |  | ||||||
|             } |             } | ||||||
|         } |             catch (Exception ex) | ||||||
| 
 |  | ||||||
|         public static IEnumerable<TResult> OnCaughtException<TSource, TResult>(this IEnumerable<SelectTryResult<TSource, TResult>> enumerable, Func<Exception, TResult> exceptionHandler) |  | ||||||
|         { |  | ||||||
|             return enumerable.Select(x => x.CaughtException == null ? x.Result : exceptionHandler(x.CaughtException)); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         public static IEnumerable<TResult> OnCaughtException<TSource, TResult>(this IEnumerable<SelectTryResult<TSource, TResult>> enumerable, Func<TSource, Exception, TResult> exceptionHandler) |  | ||||||
|         { |  | ||||||
|             return enumerable.Select(x => x.CaughtException == null ? x.Result : exceptionHandler(x.Source, x.CaughtException)); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         public class SelectTryResult<TSource, TResult> |  | ||||||
|         { |  | ||||||
|             internal SelectTryResult(TSource source, TResult result, Exception exception) |  | ||||||
|             { |             { | ||||||
|                 Source = source; |                 returnedValue = new SelectTryResult<TSource, TResult>(element, default(TResult), ex); | ||||||
|                 Result = result; |  | ||||||
|                 CaughtException = exception; |  | ||||||
|             } |             } | ||||||
| 
 |             yield return returnedValue; | ||||||
|             public TSource Source { get; private set; } |  | ||||||
|             public TResult Result { get; private set; } |  | ||||||
|             public Exception CaughtException { get; private set; } |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     public static IEnumerable<TResult> OnCaughtException<TSource, TResult>(this IEnumerable<SelectTryResult<TSource, TResult>> enumerable, Func<Exception, TResult> exceptionHandler) | ||||||
|  |     { | ||||||
|  |         return enumerable.Select(x => x.CaughtException == null ? x.Result : exceptionHandler(x.CaughtException)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static IEnumerable<TResult> OnCaughtException<TSource, TResult>(this IEnumerable<SelectTryResult<TSource, TResult>> enumerable, Func<TSource, Exception, TResult> exceptionHandler) | ||||||
|  |     { | ||||||
|  |         return enumerable.Select(x => x.CaughtException == null ? x.Result : exceptionHandler(x.Source, x.CaughtException)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public class SelectTryResult<TSource, TResult> | ||||||
|  |     { | ||||||
|  |         internal SelectTryResult(TSource source, TResult result, Exception exception) | ||||||
|  |         { | ||||||
|  |             Source = source; | ||||||
|  |             Result = result; | ||||||
|  |             CaughtException = exception; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public TSource Source { get; private set; } | ||||||
|  |         public TResult Result { get; private set; } | ||||||
|  |         public Exception CaughtException { get; private set; } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,90 +1,78 @@ | |||||||
| using Google.Protobuf.Collections; | namespace GrpcOrdering; | ||||||
| using Grpc.Core; |  | ||||||
| using MediatR; |  | ||||||
| using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Extensions; |  | ||||||
| using Microsoft.Extensions.Logging; |  | ||||||
| using System.Collections.Generic; |  | ||||||
| using System.Linq; |  | ||||||
| using System.Threading.Tasks; |  | ||||||
| using ApiModels = Ordering.API.Application.Models; |  | ||||||
| using AppCommand = Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; |  | ||||||
| 
 | 
 | ||||||
| namespace GrpcOrdering | public class OrderingService : OrderingGrpc.OrderingGrpcBase | ||||||
| { | { | ||||||
|     public class OrderingService : OrderingGrpc.OrderingGrpcBase |     private readonly IMediator _mediator; | ||||||
|  |     private readonly ILogger<OrderingService> _logger; | ||||||
|  | 
 | ||||||
|  |     public OrderingService(IMediator mediator, ILogger<OrderingService> logger) | ||||||
|     { |     { | ||||||
|         private readonly IMediator _mediator; |         _mediator = mediator; | ||||||
|         private readonly ILogger<OrderingService> _logger; |         _logger = logger; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|         public OrderingService(IMediator mediator, ILogger<OrderingService> logger) |     public override async Task<OrderDraftDTO> CreateOrderDraftFromBasketData(CreateOrderDraftCommand createOrderDraftCommand, ServerCallContext context) | ||||||
|  |     { | ||||||
|  |         _logger.LogInformation("Begin grpc call from method {Method} for ordering get order draft {CreateOrderDraftCommand}", context.Method, createOrderDraftCommand); | ||||||
|  |         _logger.LogTrace( | ||||||
|  |             "----- Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})", | ||||||
|  |             createOrderDraftCommand.GetGenericTypeName(), | ||||||
|  |             nameof(createOrderDraftCommand.BuyerId), | ||||||
|  |             createOrderDraftCommand.BuyerId, | ||||||
|  |             createOrderDraftCommand); | ||||||
|  | 
 | ||||||
|  |         var command = new AppCommand.CreateOrderDraftCommand( | ||||||
|  |                         createOrderDraftCommand.BuyerId, | ||||||
|  |                         this.MapBasketItems(createOrderDraftCommand.Items)); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         var data = await _mediator.Send(command); | ||||||
|  | 
 | ||||||
|  |         if (data != null) | ||||||
|         { |         { | ||||||
|             _mediator = mediator; |             context.Status = new Status(StatusCode.OK, $" ordering get order draft {createOrderDraftCommand} do exist"); | ||||||
|             _logger = logger; | 
 | ||||||
|  |             return this.MapResponse(data); | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             context.Status = new Status(StatusCode.NotFound, $" ordering get order draft {createOrderDraftCommand} do not exist"); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         public override async Task<OrderDraftDTO> CreateOrderDraftFromBasketData(CreateOrderDraftCommand createOrderDraftCommand, ServerCallContext context) |         return new OrderDraftDTO(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public OrderDraftDTO MapResponse(AppCommand.OrderDraftDTO order) | ||||||
|  |     { | ||||||
|  |         var result = new OrderDraftDTO() | ||||||
|         { |         { | ||||||
|             _logger.LogInformation("Begin grpc call from method {Method} for ordering get order draft {CreateOrderDraftCommand}", context.Method, createOrderDraftCommand); |             Total = (double)order.Total, | ||||||
|             _logger.LogTrace( |         }; | ||||||
|                 "----- Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})", |  | ||||||
|                 createOrderDraftCommand.GetGenericTypeName(), |  | ||||||
|                 nameof(createOrderDraftCommand.BuyerId), |  | ||||||
|                 createOrderDraftCommand.BuyerId, |  | ||||||
|                 createOrderDraftCommand); |  | ||||||
| 
 | 
 | ||||||
|             var command = new AppCommand.CreateOrderDraftCommand( |         order.OrderItems.ToList().ForEach(i => result.OrderItems.Add(new OrderItemDTO() | ||||||
|                             createOrderDraftCommand.BuyerId, |  | ||||||
|                             this.MapBasketItems(createOrderDraftCommand.Items)); |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|             var data = await _mediator.Send(command); |  | ||||||
| 
 |  | ||||||
|             if (data != null) |  | ||||||
|             { |  | ||||||
|                 context.Status = new Status(StatusCode.OK, $" ordering get order draft {createOrderDraftCommand} do exist"); |  | ||||||
| 
 |  | ||||||
|                 return this.MapResponse(data); |  | ||||||
|             } |  | ||||||
|             else |  | ||||||
|             { |  | ||||||
|                 context.Status = new Status(StatusCode.NotFound, $" ordering get order draft {createOrderDraftCommand} do not exist"); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             return new OrderDraftDTO(); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         public OrderDraftDTO MapResponse(AppCommand.OrderDraftDTO order) |  | ||||||
|         { |         { | ||||||
|             var result = new OrderDraftDTO() |             Discount = (double)i.Discount, | ||||||
|             { |             PictureUrl = i.PictureUrl, | ||||||
|                 Total = (double)order.Total, |             ProductId = i.ProductId, | ||||||
|             }; |             ProductName = i.ProductName, | ||||||
|  |             UnitPrice = (double)i.UnitPrice, | ||||||
|  |             Units = i.Units, | ||||||
|  |         })); | ||||||
| 
 | 
 | ||||||
|             order.OrderItems.ToList().ForEach(i => result.OrderItems.Add(new OrderItemDTO() |         return result; | ||||||
|             { |     } | ||||||
|                 Discount = (double)i.Discount, |  | ||||||
|                 PictureUrl = i.PictureUrl, |  | ||||||
|                 ProductId = i.ProductId, |  | ||||||
|                 ProductName = i.ProductName, |  | ||||||
|                 UnitPrice = (double)i.UnitPrice, |  | ||||||
|                 Units = i.Units, |  | ||||||
|             })); |  | ||||||
| 
 | 
 | ||||||
|             return result; |     public IEnumerable<ApiModels.BasketItem> MapBasketItems(RepeatedField<BasketItem> items) | ||||||
|         } |     { | ||||||
| 
 |         return items.Select(x => new ApiModels.BasketItem() | ||||||
|         public IEnumerable<ApiModels.BasketItem> MapBasketItems(RepeatedField<BasketItem> items) |  | ||||||
|         { |         { | ||||||
|             return items.Select(x => new ApiModels.BasketItem() |             Id = x.Id, | ||||||
|             { |             ProductId = x.ProductId, | ||||||
|                 Id = x.Id, |             ProductName = x.ProductName, | ||||||
|                 ProductId = x.ProductId, |             UnitPrice = (decimal)x.UnitPrice, | ||||||
|                 ProductName = x.ProductName, |             OldUnitPrice = (decimal)x.OldUnitPrice, | ||||||
|                 UnitPrice = (decimal)x.UnitPrice, |             Quantity = x.Quantity, | ||||||
|                 OldUnitPrice = (decimal)x.OldUnitPrice, |             PictureUrl = x.PictureUrl, | ||||||
|                 Quantity = x.Quantity, |         }); | ||||||
|                 PictureUrl = x.PictureUrl, |  | ||||||
|             }); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,14 +1,10 @@ | |||||||
| namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.ActionResults | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.ActionResults; | ||||||
| { |  | ||||||
|     using AspNetCore.Http; |  | ||||||
|     using Microsoft.AspNetCore.Mvc; |  | ||||||
| 
 | 
 | ||||||
|     public class InternalServerErrorObjectResult : ObjectResult | public class InternalServerErrorObjectResult : ObjectResult | ||||||
|  | { | ||||||
|  |     public InternalServerErrorObjectResult(object error) | ||||||
|  |         : base(error) | ||||||
|     { |     { | ||||||
|         public InternalServerErrorObjectResult(object error) |         StatusCode = StatusCodes.Status500InternalServerError; | ||||||
|             : base(error) |  | ||||||
|         { |  | ||||||
|             StatusCode = StatusCodes.Status500InternalServerError; |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,34 +1,27 @@ | |||||||
| using Microsoft.AspNetCore.Mvc.Authorization; | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Auth; | ||||||
| using Microsoft.OpenApi.Models; |  | ||||||
| using Swashbuckle.AspNetCore.SwaggerGen; |  | ||||||
| using System.Collections.Generic; |  | ||||||
| using System.Linq; |  | ||||||
| 
 | 
 | ||||||
| namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Auth | public class AuthorizationHeaderParameterOperationFilter : IOperationFilter | ||||||
| { | { | ||||||
|     public class AuthorizationHeaderParameterOperationFilter : IOperationFilter |     public void Apply(OpenApiOperation operation, OperationFilterContext context) | ||||||
|     { |     { | ||||||
|         public void Apply(OpenApiOperation operation, OperationFilterContext context) |         var filterPipeline = context.ApiDescription.ActionDescriptor.FilterDescriptors; | ||||||
|  |         var isAuthorized = filterPipeline.Select(filterInfo => filterInfo.Filter).Any(filter => filter is AuthorizeFilter); | ||||||
|  |         var allowAnonymous = filterPipeline.Select(filterInfo => filterInfo.Filter).Any(filter => filter is IAllowAnonymousFilter); | ||||||
|  | 
 | ||||||
|  |         if (isAuthorized && !allowAnonymous) | ||||||
|         { |         { | ||||||
|             var filterPipeline = context.ApiDescription.ActionDescriptor.FilterDescriptors; |             if (operation.Parameters == null) | ||||||
|             var isAuthorized = filterPipeline.Select(filterInfo => filterInfo.Filter).Any(filter => filter is AuthorizeFilter); |                 operation.Parameters = new List<OpenApiParameter>(); | ||||||
|             var allowAnonymous = filterPipeline.Select(filterInfo => filterInfo.Filter).Any(filter => filter is IAllowAnonymousFilter); |  | ||||||
| 
 | 
 | ||||||
|             if (isAuthorized && !allowAnonymous) | 
 | ||||||
|  |             operation.Parameters.Add(new OpenApiParameter | ||||||
|             { |             { | ||||||
|                 if (operation.Parameters == null) |                 Name = "Authorization", | ||||||
|                     operation.Parameters = new List<OpenApiParameter>(); |                 In = ParameterLocation.Header, | ||||||
| 
 |                 Description = "access token", | ||||||
| 
 |                 Required = true | ||||||
|                 operation.Parameters.Add(new OpenApiParameter |             }); | ||||||
|                 { |  | ||||||
|                     Name = "Authorization", |  | ||||||
|                     In = ParameterLocation.Header, |  | ||||||
|                     Description = "access token", |  | ||||||
|                     Required = true |  | ||||||
|                 }); |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
| 
 |  | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,50 +1,38 @@ | |||||||
| using Autofac; | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.AutofacModules; | ||||||
| using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; |  | ||||||
| using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; |  | ||||||
| using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Queries; |  | ||||||
| using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate; |  | ||||||
| using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; |  | ||||||
| using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency; |  | ||||||
| using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Repositories; |  | ||||||
| using System.Reflection; |  | ||||||
| 
 | 
 | ||||||
| namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.AutofacModules | public class ApplicationModule | ||||||
|  |     : Autofac.Module | ||||||
| { | { | ||||||
| 
 | 
 | ||||||
|     public class ApplicationModule |     public string QueriesConnectionString { get; } | ||||||
|         : Autofac.Module | 
 | ||||||
|  |     public ApplicationModule(string qconstr) | ||||||
|  |     { | ||||||
|  |         QueriesConnectionString = qconstr; | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     protected override void Load(ContainerBuilder builder) | ||||||
|     { |     { | ||||||
| 
 | 
 | ||||||
|         public string QueriesConnectionString { get; } |         builder.Register(c => new OrderQueries(QueriesConnectionString)) | ||||||
|  |             .As<IOrderQueries>() | ||||||
|  |             .InstancePerLifetimeScope(); | ||||||
| 
 | 
 | ||||||
|         public ApplicationModule(string qconstr) |         builder.RegisterType<BuyerRepository>() | ||||||
|         { |             .As<IBuyerRepository>() | ||||||
|             QueriesConnectionString = qconstr; |             .InstancePerLifetimeScope(); | ||||||
| 
 | 
 | ||||||
|         } |         builder.RegisterType<OrderRepository>() | ||||||
|  |             .As<IOrderRepository>() | ||||||
|  |             .InstancePerLifetimeScope(); | ||||||
| 
 | 
 | ||||||
|         protected override void Load(ContainerBuilder builder) |         builder.RegisterType<RequestManager>() | ||||||
|         { |             .As<IRequestManager>() | ||||||
|  |             .InstancePerLifetimeScope(); | ||||||
| 
 | 
 | ||||||
|             builder.Register(c => new OrderQueries(QueriesConnectionString)) |         builder.RegisterAssemblyTypes(typeof(CreateOrderCommandHandler).GetTypeInfo().Assembly) | ||||||
|                 .As<IOrderQueries>() |             .AsClosedTypesOf(typeof(IIntegrationEventHandler<>)); | ||||||
|                 .InstancePerLifetimeScope(); |  | ||||||
| 
 | 
 | ||||||
|             builder.RegisterType<BuyerRepository>() |  | ||||||
|                 .As<IBuyerRepository>() |  | ||||||
|                 .InstancePerLifetimeScope(); |  | ||||||
| 
 |  | ||||||
|             builder.RegisterType<OrderRepository>() |  | ||||||
|                 .As<IOrderRepository>() |  | ||||||
|                 .InstancePerLifetimeScope(); |  | ||||||
| 
 |  | ||||||
|             builder.RegisterType<RequestManager>() |  | ||||||
|                .As<IRequestManager>() |  | ||||||
|                .InstancePerLifetimeScope(); |  | ||||||
| 
 |  | ||||||
|             builder.RegisterAssemblyTypes(typeof(CreateOrderCommandHandler).GetTypeInfo().Assembly) |  | ||||||
|                 .AsClosedTypesOf(typeof(IIntegrationEventHandler<>)); |  | ||||||
| 
 |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,47 +1,36 @@ | |||||||
| using Autofac; | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.AutofacModules; | ||||||
| using FluentValidation; |  | ||||||
| using MediatR; |  | ||||||
| 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 System.Linq; |  | ||||||
| using System.Reflection; |  | ||||||
| 
 | 
 | ||||||
| namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.AutofacModules | public class MediatorModule : Autofac.Module | ||||||
| { | { | ||||||
|     public class MediatorModule : Autofac.Module |     protected override void Load(ContainerBuilder builder) | ||||||
|     { |     { | ||||||
|         protected override void Load(ContainerBuilder builder) |         builder.RegisterAssemblyTypes(typeof(IMediator).GetTypeInfo().Assembly) | ||||||
|  |             .AsImplementedInterfaces(); | ||||||
|  | 
 | ||||||
|  |         // Register all the Command classes (they implement IRequestHandler) in assembly holding the Commands | ||||||
|  |         builder.RegisterAssemblyTypes(typeof(CreateOrderCommand).GetTypeInfo().Assembly) | ||||||
|  |             .AsClosedTypesOf(typeof(IRequestHandler<,>)); | ||||||
|  | 
 | ||||||
|  |         // Register the DomainEventHandler classes (they implement INotificationHandler<>) in assembly holding the Domain Events | ||||||
|  |         builder.RegisterAssemblyTypes(typeof(ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler).GetTypeInfo().Assembly) | ||||||
|  |             .AsClosedTypesOf(typeof(INotificationHandler<>)); | ||||||
|  | 
 | ||||||
|  |         // Register the Command's Validators (Validators based on FluentValidation library) | ||||||
|  |         builder | ||||||
|  |             .RegisterAssemblyTypes(typeof(CreateOrderCommandValidator).GetTypeInfo().Assembly) | ||||||
|  |             .Where(t => t.IsClosedTypeOf(typeof(IValidator<>))) | ||||||
|  |             .AsImplementedInterfaces(); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         builder.Register<ServiceFactory>(context => | ||||||
|         { |         { | ||||||
|             builder.RegisterAssemblyTypes(typeof(IMediator).GetTypeInfo().Assembly) |             var componentContext = context.Resolve<IComponentContext>(); | ||||||
|                 .AsImplementedInterfaces(); |             return t => { object o; return componentContext.TryResolve(t, out o) ? o : null; }; | ||||||
|  |         }); | ||||||
| 
 | 
 | ||||||
|             // Register all the Command classes (they implement IRequestHandler) in assembly holding the Commands |         builder.RegisterGeneric(typeof(LoggingBehavior<,>)).As(typeof(IPipelineBehavior<,>)); | ||||||
|             builder.RegisterAssemblyTypes(typeof(CreateOrderCommand).GetTypeInfo().Assembly) |         builder.RegisterGeneric(typeof(ValidatorBehavior<,>)).As(typeof(IPipelineBehavior<,>)); | ||||||
|                 .AsClosedTypesOf(typeof(IRequestHandler<,>)); |         builder.RegisterGeneric(typeof(TransactionBehaviour<,>)).As(typeof(IPipelineBehavior<,>)); | ||||||
| 
 | 
 | ||||||
|             // Register the DomainEventHandler classes (they implement INotificationHandler<>) in assembly holding the Domain Events |  | ||||||
|             builder.RegisterAssemblyTypes(typeof(ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler).GetTypeInfo().Assembly) |  | ||||||
|                 .AsClosedTypesOf(typeof(INotificationHandler<>)); |  | ||||||
| 
 |  | ||||||
|             // Register the Command's Validators (Validators based on FluentValidation library) |  | ||||||
|             builder |  | ||||||
|                 .RegisterAssemblyTypes(typeof(CreateOrderCommandValidator).GetTypeInfo().Assembly) |  | ||||||
|                 .Where(t => t.IsClosedTypeOf(typeof(IValidator<>))) |  | ||||||
|                 .AsImplementedInterfaces(); |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|             builder.Register<ServiceFactory>(context => |  | ||||||
|             { |  | ||||||
|                 var componentContext = context.Resolve<IComponentContext>(); |  | ||||||
|                 return t => { object o; return componentContext.TryResolve(t, out o) ? o : null; }; |  | ||||||
|             }); |  | ||||||
| 
 |  | ||||||
|             builder.RegisterGeneric(typeof(LoggingBehavior<,>)).As(typeof(IPipelineBehavior<,>)); |  | ||||||
|             builder.RegisterGeneric(typeof(ValidatorBehavior<,>)).As(typeof(IPipelineBehavior<,>)); |  | ||||||
|             builder.RegisterGeneric(typeof(TransactionBehaviour<,>)).As(typeof(IPipelineBehavior<,>)); |  | ||||||
| 
 |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,10 +1,4 @@ | |||||||
| using Microsoft.EntityFrameworkCore; | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Factories | ||||||
| using Microsoft.EntityFrameworkCore.Design; |  | ||||||
| using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure; |  | ||||||
| using Microsoft.Extensions.Configuration; |  | ||||||
| using System.IO; |  | ||||||
| 
 |  | ||||||
| namespace Ordering.API.Infrastructure.Factories |  | ||||||
| { | { | ||||||
|     public class OrderingDbContextFactory : IDesignTimeDbContextFactory<OrderingContext> |     public class OrderingDbContextFactory : IDesignTimeDbContextFactory<OrderingContext> | ||||||
|     { |     { | ||||||
|  | |||||||
| @ -1,36 +1,29 @@ | |||||||
| using Microsoft.AspNetCore.Authorization; | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Filters; | ||||||
| using Microsoft.OpenApi.Models; |  | ||||||
| using Swashbuckle.AspNetCore.SwaggerGen; |  | ||||||
| using System.Collections.Generic; |  | ||||||
| using System.Linq; |  | ||||||
| 
 | 
 | ||||||
| namespace Ordering.API.Infrastructure.Filters | public class AuthorizeCheckOperationFilter : IOperationFilter | ||||||
| { | { | ||||||
|     public class AuthorizeCheckOperationFilter : IOperationFilter |     public void Apply(OpenApiOperation operation, OperationFilterContext context) | ||||||
|     { |     { | ||||||
|         public void Apply(OpenApiOperation operation, OperationFilterContext context) |         // Check for authorize attribute | ||||||
|  |         var hasAuthorize = context.MethodInfo.DeclaringType.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Any() || | ||||||
|  |                             context.MethodInfo.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Any(); | ||||||
|  | 
 | ||||||
|  |         if (!hasAuthorize) return; | ||||||
|  | 
 | ||||||
|  |         operation.Responses.TryAdd("401", new OpenApiResponse { Description = "Unauthorized" }); | ||||||
|  |         operation.Responses.TryAdd("403", new OpenApiResponse { Description = "Forbidden" }); | ||||||
|  | 
 | ||||||
|  |         var oAuthScheme = new OpenApiSecurityScheme | ||||||
|         { |         { | ||||||
|             // Check for authorize attribute |             Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" } | ||||||
|             var hasAuthorize = context.MethodInfo.DeclaringType.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Any() || |         }; | ||||||
|                                context.MethodInfo.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Any(); |  | ||||||
| 
 | 
 | ||||||
|             if (!hasAuthorize) return; |         operation.Security = new List<OpenApiSecurityRequirement> | ||||||
| 
 |  | ||||||
|             operation.Responses.TryAdd("401", new OpenApiResponse { Description = "Unauthorized" }); |  | ||||||
|             operation.Responses.TryAdd("403", new OpenApiResponse { Description = "Forbidden" }); |  | ||||||
| 
 |  | ||||||
|             var oAuthScheme = new OpenApiSecurityScheme |  | ||||||
|             { |             { | ||||||
|                 Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" } |                 new OpenApiSecurityRequirement | ||||||
|             }; |  | ||||||
| 
 |  | ||||||
|             operation.Security = new List<OpenApiSecurityRequirement> |  | ||||||
|                 { |                 { | ||||||
|                     new OpenApiSecurityRequirement |                     [ oAuthScheme ] = new [] { "orderingapi" } | ||||||
|                     { |                 } | ||||||
|                         [ oAuthScheme ] = new [] { "orderingapi" } |             }; | ||||||
|                     } |  | ||||||
|                 }; |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,71 +1,60 @@ | |||||||
| namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Filters | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Filters; | ||||||
|  | 
 | ||||||
|  | public class HttpGlobalExceptionFilter : IExceptionFilter | ||||||
| { | { | ||||||
|     using AspNetCore.Mvc; |     private readonly IWebHostEnvironment env; | ||||||
|     using global::Ordering.Domain.Exceptions; |     private readonly ILogger<HttpGlobalExceptionFilter> logger; | ||||||
|     using Microsoft.AspNetCore.Hosting; |  | ||||||
|     using Microsoft.AspNetCore.Http; |  | ||||||
|     using Microsoft.AspNetCore.Mvc.Filters; |  | ||||||
|     using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.ActionResults; |  | ||||||
|     using Microsoft.Extensions.Hosting; |  | ||||||
|     using Microsoft.Extensions.Logging; |  | ||||||
|     using System.Net; |  | ||||||
| 
 | 
 | ||||||
|     public class HttpGlobalExceptionFilter : IExceptionFilter |     public HttpGlobalExceptionFilter(IWebHostEnvironment env, ILogger<HttpGlobalExceptionFilter> logger) | ||||||
|     { |     { | ||||||
|         private readonly IWebHostEnvironment env; |         this.env = env; | ||||||
|         private readonly ILogger<HttpGlobalExceptionFilter> logger; |         this.logger = logger; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|         public HttpGlobalExceptionFilter(IWebHostEnvironment env, ILogger<HttpGlobalExceptionFilter> logger) |     public void OnException(ExceptionContext context) | ||||||
|  |     { | ||||||
|  |         logger.LogError(new EventId(context.Exception.HResult), | ||||||
|  |             context.Exception, | ||||||
|  |             context.Exception.Message); | ||||||
|  | 
 | ||||||
|  |         if (context.Exception.GetType() == typeof(OrderingDomainException)) | ||||||
|         { |         { | ||||||
|             this.env = env; |             var problemDetails = new ValidationProblemDetails() | ||||||
|             this.logger = logger; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         public void OnException(ExceptionContext context) |  | ||||||
|         { |  | ||||||
|             logger.LogError(new EventId(context.Exception.HResult), |  | ||||||
|                 context.Exception, |  | ||||||
|                 context.Exception.Message); |  | ||||||
| 
 |  | ||||||
|             if (context.Exception.GetType() == typeof(OrderingDomainException)) |  | ||||||
|             { |             { | ||||||
|                 var problemDetails = new ValidationProblemDetails() |                 Instance = context.HttpContext.Request.Path, | ||||||
|                 { |                 Status = StatusCodes.Status400BadRequest, | ||||||
|                     Instance = context.HttpContext.Request.Path, |                 Detail = "Please refer to the errors property for additional details." | ||||||
|                     Status = StatusCodes.Status400BadRequest, |             }; | ||||||
|                     Detail = "Please refer to the errors property for additional details." |  | ||||||
|                 }; |  | ||||||
| 
 | 
 | ||||||
|                 problemDetails.Errors.Add("DomainValidations", new string[] { context.Exception.Message.ToString() }); |             problemDetails.Errors.Add("DomainValidations", new string[] { context.Exception.Message.ToString() }); | ||||||
| 
 | 
 | ||||||
|                 context.Result = new BadRequestObjectResult(problemDetails); |             context.Result = new BadRequestObjectResult(problemDetails); | ||||||
|                 context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest; |             context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest; | ||||||
|             } |  | ||||||
|             else |  | ||||||
|             { |  | ||||||
|                 var json = new JsonErrorResponse |  | ||||||
|                 { |  | ||||||
|                     Messages = new[] { "An error occur.Try it again." } |  | ||||||
|                 }; |  | ||||||
| 
 |  | ||||||
|                 if (env.IsDevelopment()) |  | ||||||
|                 { |  | ||||||
|                     json.DeveloperMessage = context.Exception; |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 // Result asigned to a result object but in destiny the response is empty. This is a known bug of .net core 1.1 |  | ||||||
|                 // It will be fixed in .net core 1.1.2. See https://github.com/aspnet/Mvc/issues/5594 for more information |  | ||||||
|                 context.Result = new InternalServerErrorObjectResult(json); |  | ||||||
|                 context.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError; |  | ||||||
|             } |  | ||||||
|             context.ExceptionHandled = true; |  | ||||||
|         } |         } | ||||||
| 
 |         else | ||||||
|         private class JsonErrorResponse |  | ||||||
|         { |         { | ||||||
|             public string[] Messages { get; set; } |             var json = new JsonErrorResponse | ||||||
|  |             { | ||||||
|  |                 Messages = new[] { "An error occur.Try it again." } | ||||||
|  |             }; | ||||||
| 
 | 
 | ||||||
|             public object DeveloperMessage { get; set; } |             if (env.IsDevelopment()) | ||||||
|  |             { | ||||||
|  |                 json.DeveloperMessage = context.Exception; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // Result asigned to a result object but in destiny the response is empty. This is a known bug of .net core 1.1 | ||||||
|  |             // It will be fixed in .net core 1.1.2. See https://github.com/aspnet/Mvc/issues/5594 for more information | ||||||
|  |             context.Result = new InternalServerErrorObjectResult(json); | ||||||
|  |             context.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError; | ||||||
|         } |         } | ||||||
|  |         context.ExceptionHandled = true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private class JsonErrorResponse | ||||||
|  |     { | ||||||
|  |         public string[] Messages { get; set; } | ||||||
|  | 
 | ||||||
|  |         public object DeveloperMessage { get; set; } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,191 +1,174 @@ | |||||||
| namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure; | ||||||
|  | 
 | ||||||
|  | using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate; | ||||||
|  | 
 | ||||||
|  | public class OrderingContextSeed | ||||||
| { | { | ||||||
|     using global::Ordering.API.Extensions; |     public async Task SeedAsync(OrderingContext context, IWebHostEnvironment env, IOptions<OrderingSettings> settings, ILogger<OrderingContextSeed> logger) | ||||||
|     using Microsoft.AspNetCore.Hosting; |  | ||||||
|     using Microsoft.EntityFrameworkCore; |  | ||||||
|     using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate; |  | ||||||
|     using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; |  | ||||||
|     using Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork; |  | ||||||
|     using Microsoft.Extensions.Logging; |  | ||||||
|     using Microsoft.Extensions.Options; |  | ||||||
|     using Ordering.Infrastructure; |  | ||||||
|     using Polly; |  | ||||||
|     using Polly.Retry; |  | ||||||
|     using System; |  | ||||||
|     using System.Collections.Generic; |  | ||||||
|     using System.Data.SqlClient; |  | ||||||
|     using System.IO; |  | ||||||
|     using System.Linq; |  | ||||||
|     using System.Threading.Tasks; |  | ||||||
| 
 |  | ||||||
|     public class OrderingContextSeed |  | ||||||
|     { |     { | ||||||
|         public async Task SeedAsync(OrderingContext context, IWebHostEnvironment env, IOptions<OrderingSettings> settings, ILogger<OrderingContextSeed> logger) |         var policy = CreatePolicy(logger, nameof(OrderingContextSeed)); | ||||||
|  | 
 | ||||||
|  |         await policy.ExecuteAsync(async () => | ||||||
|         { |         { | ||||||
|             var policy = CreatePolicy(logger, nameof(OrderingContextSeed)); |  | ||||||
| 
 | 
 | ||||||
|             await policy.ExecuteAsync(async () => |             var useCustomizationData = settings.Value | ||||||
|  |             .UseCustomizationData; | ||||||
|  | 
 | ||||||
|  |             var contentRootPath = env.ContentRootPath; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |             using (context) | ||||||
|             { |             { | ||||||
|  |                 context.Database.Migrate(); | ||||||
| 
 | 
 | ||||||
|                 var useCustomizationData = settings.Value |                 if (!context.CardTypes.Any()) | ||||||
|                 .UseCustomizationData; |  | ||||||
| 
 |  | ||||||
|                 var contentRootPath = env.ContentRootPath; |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|                 using (context) |  | ||||||
|                 { |                 { | ||||||
|                     context.Database.Migrate(); |                     context.CardTypes.AddRange(useCustomizationData | ||||||
| 
 |                                             ? GetCardTypesFromFile(contentRootPath, logger) | ||||||
|                     if (!context.CardTypes.Any()) |                                             : GetPredefinedCardTypes()); | ||||||
|                     { |  | ||||||
|                         context.CardTypes.AddRange(useCustomizationData |  | ||||||
|                                                 ? GetCardTypesFromFile(contentRootPath, logger) |  | ||||||
|                                                 : GetPredefinedCardTypes()); |  | ||||||
| 
 |  | ||||||
|                         await context.SaveChangesAsync(); |  | ||||||
|                     } |  | ||||||
| 
 |  | ||||||
|                     if (!context.OrderStatus.Any()) |  | ||||||
|                     { |  | ||||||
|                         context.OrderStatus.AddRange(useCustomizationData |  | ||||||
|                                                 ? GetOrderStatusFromFile(contentRootPath, logger) |  | ||||||
|                                                 : GetPredefinedOrderStatus()); |  | ||||||
|                     } |  | ||||||
| 
 | 
 | ||||||
|                     await context.SaveChangesAsync(); |                     await context.SaveChangesAsync(); | ||||||
|                 } |                 } | ||||||
|             }); |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         private IEnumerable<CardType> GetCardTypesFromFile(string contentRootPath, ILogger<OrderingContextSeed> log) |                 if (!context.OrderStatus.Any()) | ||||||
|         { |  | ||||||
|             string csvFileCardTypes = Path.Combine(contentRootPath, "Setup", "CardTypes.csv"); |  | ||||||
| 
 |  | ||||||
|             if (!File.Exists(csvFileCardTypes)) |  | ||||||
|             { |  | ||||||
|                 return GetPredefinedCardTypes(); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             string[] csvheaders; |  | ||||||
|             try |  | ||||||
|             { |  | ||||||
|                 string[] requiredHeaders = { "CardType" }; |  | ||||||
|                 csvheaders = GetHeaders(requiredHeaders, csvFileCardTypes); |  | ||||||
|             } |  | ||||||
|             catch (Exception ex) |  | ||||||
|             { |  | ||||||
|                 log.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); |  | ||||||
|                 return GetPredefinedCardTypes(); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             int id = 1; |  | ||||||
|             return File.ReadAllLines(csvFileCardTypes) |  | ||||||
|                                         .Skip(1) // skip header column |  | ||||||
|                                         .SelectTry(x => CreateCardType(x, ref id)) |  | ||||||
|                                         .OnCaughtException(ex => { log.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); return null; }) |  | ||||||
|                                         .Where(x => x != null); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         private CardType CreateCardType(string value, ref int id) |  | ||||||
|         { |  | ||||||
|             if (String.IsNullOrEmpty(value)) |  | ||||||
|             { |  | ||||||
|                 throw new Exception("Orderstatus is null or empty"); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             return new CardType(id++, value.Trim('"').Trim()); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         private IEnumerable<CardType> GetPredefinedCardTypes() |  | ||||||
|         { |  | ||||||
|             return Enumeration.GetAll<CardType>(); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         private IEnumerable<OrderStatus> GetOrderStatusFromFile(string contentRootPath, ILogger<OrderingContextSeed> log) |  | ||||||
|         { |  | ||||||
|             string csvFileOrderStatus = Path.Combine(contentRootPath, "Setup", "OrderStatus.csv"); |  | ||||||
| 
 |  | ||||||
|             if (!File.Exists(csvFileOrderStatus)) |  | ||||||
|             { |  | ||||||
|                 return GetPredefinedOrderStatus(); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             string[] csvheaders; |  | ||||||
|             try |  | ||||||
|             { |  | ||||||
|                 string[] requiredHeaders = { "OrderStatus" }; |  | ||||||
|                 csvheaders = GetHeaders(requiredHeaders, csvFileOrderStatus); |  | ||||||
|             } |  | ||||||
|             catch (Exception ex) |  | ||||||
|             { |  | ||||||
|                 log.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); |  | ||||||
|                 return GetPredefinedOrderStatus(); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             int id = 1; |  | ||||||
|             return File.ReadAllLines(csvFileOrderStatus) |  | ||||||
|                                         .Skip(1) // skip header row |  | ||||||
|                                         .SelectTry(x => CreateOrderStatus(x, ref id)) |  | ||||||
|                                         .OnCaughtException(ex => { log.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); return null; }) |  | ||||||
|                                         .Where(x => x != null); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         private OrderStatus CreateOrderStatus(string value, ref int id) |  | ||||||
|         { |  | ||||||
|             if (String.IsNullOrEmpty(value)) |  | ||||||
|             { |  | ||||||
|                 throw new Exception("Orderstatus is null or empty"); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             return new OrderStatus(id++, value.Trim('"').Trim().ToLowerInvariant()); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         private IEnumerable<OrderStatus> GetPredefinedOrderStatus() |  | ||||||
|         { |  | ||||||
|             return new List<OrderStatus>() |  | ||||||
|             { |  | ||||||
|                 OrderStatus.Submitted, |  | ||||||
|                 OrderStatus.AwaitingValidation, |  | ||||||
|                 OrderStatus.StockConfirmed, |  | ||||||
|                 OrderStatus.Paid, |  | ||||||
|                 OrderStatus.Shipped, |  | ||||||
|                 OrderStatus.Cancelled |  | ||||||
|             }; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         private string[] GetHeaders(string[] requiredHeaders, string csvfile) |  | ||||||
|         { |  | ||||||
|             string[] csvheaders = File.ReadLines(csvfile).First().ToLowerInvariant().Split(','); |  | ||||||
| 
 |  | ||||||
|             if (csvheaders.Count() != requiredHeaders.Count()) |  | ||||||
|             { |  | ||||||
|                 throw new Exception($"requiredHeader count '{ requiredHeaders.Count()}' is different then read header '{csvheaders.Count()}'"); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             foreach (var requiredHeader in requiredHeaders) |  | ||||||
|             { |  | ||||||
|                 if (!csvheaders.Contains(requiredHeader)) |  | ||||||
|                 { |                 { | ||||||
|                     throw new Exception($"does not contain required header '{requiredHeader}'"); |                     context.OrderStatus.AddRange(useCustomizationData | ||||||
|  |                                             ? GetOrderStatusFromFile(contentRootPath, logger) | ||||||
|  |                                             : GetPredefinedOrderStatus()); | ||||||
|                 } |                 } | ||||||
|  | 
 | ||||||
|  |                 await context.SaveChangesAsync(); | ||||||
|             } |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|             return csvheaders; |     private IEnumerable<CardType> GetCardTypesFromFile(string contentRootPath, ILogger<OrderingContextSeed> log) | ||||||
|         } |     { | ||||||
|  |         string csvFileCardTypes = Path.Combine(contentRootPath, "Setup", "CardTypes.csv"); | ||||||
| 
 | 
 | ||||||
| 
 |         if (!File.Exists(csvFileCardTypes)) | ||||||
|         private AsyncRetryPolicy CreatePolicy(ILogger<OrderingContextSeed> logger, string prefix, int retries = 3) |  | ||||||
|         { |         { | ||||||
|             return Policy.Handle<SqlException>(). |             return GetPredefinedCardTypes(); | ||||||
|                 WaitAndRetryAsync( |  | ||||||
|                     retryCount: retries, |  | ||||||
|                     sleepDurationProvider: retry => TimeSpan.FromSeconds(5), |  | ||||||
|                     onRetry: (exception, timeSpan, retry, ctx) => |  | ||||||
|                     { |  | ||||||
|                         logger.LogWarning(exception, "[{prefix}] Exception {ExceptionType} with message {Message} detected on attempt {retry} of {retries}", prefix, exception.GetType().Name, exception.Message, retry, retries); |  | ||||||
|                     } |  | ||||||
|                 ); |  | ||||||
|         } |         } | ||||||
|  | 
 | ||||||
|  |         string[] csvheaders; | ||||||
|  |         try | ||||||
|  |         { | ||||||
|  |             string[] requiredHeaders = { "CardType" }; | ||||||
|  |             csvheaders = GetHeaders(requiredHeaders, csvFileCardTypes); | ||||||
|  |         } | ||||||
|  |         catch (Exception ex) | ||||||
|  |         { | ||||||
|  |             log.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); | ||||||
|  |             return GetPredefinedCardTypes(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         int id = 1; | ||||||
|  |         return File.ReadAllLines(csvFileCardTypes) | ||||||
|  |                                     .Skip(1) // skip header column | ||||||
|  |                                     .SelectTry(x => CreateCardType(x, ref id)) | ||||||
|  |                                     .OnCaughtException(ex => { log.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); return null; }) | ||||||
|  |                                     .Where(x => x != null); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private CardType CreateCardType(string value, ref int id) | ||||||
|  |     { | ||||||
|  |         if (String.IsNullOrEmpty(value)) | ||||||
|  |         { | ||||||
|  |             throw new Exception("Orderstatus is null or empty"); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return new CardType(id++, value.Trim('"').Trim()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private IEnumerable<CardType> GetPredefinedCardTypes() | ||||||
|  |     { | ||||||
|  |         return Enumeration.GetAll<CardType>(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private IEnumerable<OrderStatus> GetOrderStatusFromFile(string contentRootPath, ILogger<OrderingContextSeed> log) | ||||||
|  |     { | ||||||
|  |         string csvFileOrderStatus = Path.Combine(contentRootPath, "Setup", "OrderStatus.csv"); | ||||||
|  | 
 | ||||||
|  |         if (!File.Exists(csvFileOrderStatus)) | ||||||
|  |         { | ||||||
|  |             return GetPredefinedOrderStatus(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         string[] csvheaders; | ||||||
|  |         try | ||||||
|  |         { | ||||||
|  |             string[] requiredHeaders = { "OrderStatus" }; | ||||||
|  |             csvheaders = GetHeaders(requiredHeaders, csvFileOrderStatus); | ||||||
|  |         } | ||||||
|  |         catch (Exception ex) | ||||||
|  |         { | ||||||
|  |             log.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); | ||||||
|  |             return GetPredefinedOrderStatus(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         int id = 1; | ||||||
|  |         return File.ReadAllLines(csvFileOrderStatus) | ||||||
|  |                                     .Skip(1) // skip header row | ||||||
|  |                                     .SelectTry(x => CreateOrderStatus(x, ref id)) | ||||||
|  |                                     .OnCaughtException(ex => { log.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); return null; }) | ||||||
|  |                                     .Where(x => x != null); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private OrderStatus CreateOrderStatus(string value, ref int id) | ||||||
|  |     { | ||||||
|  |         if (String.IsNullOrEmpty(value)) | ||||||
|  |         { | ||||||
|  |             throw new Exception("Orderstatus is null or empty"); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return new OrderStatus(id++, value.Trim('"').Trim().ToLowerInvariant()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private IEnumerable<OrderStatus> GetPredefinedOrderStatus() | ||||||
|  |     { | ||||||
|  |         return new List<OrderStatus>() | ||||||
|  |         { | ||||||
|  |             OrderStatus.Submitted, | ||||||
|  |             OrderStatus.AwaitingValidation, | ||||||
|  |             OrderStatus.StockConfirmed, | ||||||
|  |             OrderStatus.Paid, | ||||||
|  |             OrderStatus.Shipped, | ||||||
|  |             OrderStatus.Cancelled | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private string[] GetHeaders(string[] requiredHeaders, string csvfile) | ||||||
|  |     { | ||||||
|  |         string[] csvheaders = File.ReadLines(csvfile).First().ToLowerInvariant().Split(','); | ||||||
|  | 
 | ||||||
|  |         if (csvheaders.Count() != requiredHeaders.Count()) | ||||||
|  |         { | ||||||
|  |             throw new Exception($"requiredHeader count '{ requiredHeaders.Count()}' is different then read header '{csvheaders.Count()}'"); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         foreach (var requiredHeader in requiredHeaders) | ||||||
|  |         { | ||||||
|  |             if (!csvheaders.Contains(requiredHeader)) | ||||||
|  |             { | ||||||
|  |                 throw new Exception($"does not contain required header '{requiredHeader}'"); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return csvheaders; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     private AsyncRetryPolicy CreatePolicy(ILogger<OrderingContextSeed> logger, string prefix, int retries = 3) | ||||||
|  |     { | ||||||
|  |         return Policy.Handle<SqlException>(). | ||||||
|  |             WaitAndRetryAsync( | ||||||
|  |                 retryCount: retries, | ||||||
|  |                 sleepDurationProvider: retry => TimeSpan.FromSeconds(5), | ||||||
|  |                 onRetry: (exception, timeSpan, retry, ctx) => | ||||||
|  |                 { | ||||||
|  |                     logger.LogWarning(exception, "[{prefix}] Exception {ExceptionType} with message {Message} detected on attempt {retry} of {retries}", prefix, exception.GetType().Name, exception.Message, retry, retries); | ||||||
|  |                 } | ||||||
|  |             ); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,9 +1,9 @@ | |||||||
| namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services; | ||||||
| { |  | ||||||
|     public interface IIdentityService |  | ||||||
|     { |  | ||||||
|         string GetUserIdentity(); |  | ||||||
| 
 | 
 | ||||||
|         string GetUserName(); | public interface IIdentityService | ||||||
|     } | { | ||||||
|  |     string GetUserIdentity(); | ||||||
|  | 
 | ||||||
|  |     string GetUserName(); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | |||||||
| @ -1,26 +1,21 @@ | |||||||
|  | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services; | ||||||
| using Microsoft.AspNetCore.Http; |  | ||||||
| using System; |  | ||||||
| 
 | 
 | ||||||
| namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services | public class IdentityService : IIdentityService | ||||||
| { | { | ||||||
|     public class IdentityService : IIdentityService |     private IHttpContextAccessor _context; | ||||||
|  | 
 | ||||||
|  |     public IdentityService(IHttpContextAccessor context) | ||||||
|     { |     { | ||||||
|         private IHttpContextAccessor _context; |         _context = context ?? throw new ArgumentNullException(nameof(context)); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|         public IdentityService(IHttpContextAccessor context) |     public string GetUserIdentity() | ||||||
|         { |     { | ||||||
|             _context = context ?? throw new ArgumentNullException(nameof(context)); |         return _context.HttpContext.User.FindFirst("sub").Value; | ||||||
|         } |     } | ||||||
| 
 | 
 | ||||||
|         public string GetUserIdentity() |     public string GetUserName() | ||||||
|         { |     { | ||||||
|             return _context.HttpContext.User.FindFirst("sub").Value; |         return _context.HttpContext.User.Identity.Name; | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         public string GetUserName() |  | ||||||
|         { |  | ||||||
|             return _context.HttpContext.User.Identity.Name; |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,15 +1,14 @@ | |||||||
| namespace Microsoft.eShopOnContainers.Services.Ordering.API | namespace Microsoft.eShopOnContainers.Services.Ordering.API; | ||||||
|  | 
 | ||||||
|  | public class OrderingSettings | ||||||
| { | { | ||||||
|     public class OrderingSettings |     public bool UseCustomizationData { get; set; } | ||||||
|     { |  | ||||||
|         public bool UseCustomizationData { get; set; } |  | ||||||
| 
 | 
 | ||||||
|         public string ConnectionString { get; set; } |     public string ConnectionString { get; set; } | ||||||
| 
 | 
 | ||||||
|         public string EventBusConnection { get; set; } |     public string EventBusConnection { get; set; } | ||||||
| 
 | 
 | ||||||
|         public int GracePeriodTime { get; set; } |     public int GracePeriodTime { get; set; } | ||||||
| 
 | 
 | ||||||
|         public int CheckUpdateTime { get; set; } |     public int CheckUpdateTime { get; set; } | ||||||
|     } |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,22 +1,4 @@ | |||||||
| using Microsoft.AspNetCore; | var configuration = GetConfiguration(); | ||||||
| using Microsoft.AspNetCore.Hosting; |  | ||||||
| using Microsoft.AspNetCore.Server.Kestrel.Core; |  | ||||||
| using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF; |  | ||||||
| using Microsoft.eShopOnContainers.Services.Ordering.API; |  | ||||||
| using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure; |  | ||||||
| using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure; |  | ||||||
| using Microsoft.Extensions.Configuration; |  | ||||||
| using Microsoft.Extensions.DependencyInjection; |  | ||||||
| using Microsoft.Extensions.Logging; |  | ||||||
| using Microsoft.Extensions.Options; |  | ||||||
| using Serilog; |  | ||||||
| using System; |  | ||||||
| using System.IO; |  | ||||||
| using System.Net; |  | ||||||
| using Azure.Identity; |  | ||||||
| using Azure.Core; |  | ||||||
| 
 |  | ||||||
| var configuration = GetConfiguration(); |  | ||||||
| 
 | 
 | ||||||
| Log.Logger = CreateSerilogLogger(configuration); | Log.Logger = CreateSerilogLogger(configuration); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,429 +1,391 @@ | |||||||
| namespace Microsoft.eShopOnContainers.Services.Ordering.API |  | ||||||
|  | namespace Microsoft.eShopOnContainers.Services.Ordering.API; | ||||||
|  | 
 | ||||||
|  | public class Startup | ||||||
| { | { | ||||||
|     using AspNetCore.Http; |     public Startup(IConfiguration configuration) | ||||||
|     using Autofac; |  | ||||||
|     using Autofac.Extensions.DependencyInjection; |  | ||||||
|     using global::Ordering.API.Application.IntegrationEvents; |  | ||||||
|     using global::Ordering.API.Application.IntegrationEvents.Events; |  | ||||||
|     using global::Ordering.API.Infrastructure.Filters; |  | ||||||
|     using GrpcOrdering; |  | ||||||
|     using HealthChecks.UI.Client; |  | ||||||
|     using Infrastructure.AutofacModules; |  | ||||||
|     using Infrastructure.Filters; |  | ||||||
|     using Infrastructure.Services; |  | ||||||
|     using Microsoft.AspNetCore.Builder; |  | ||||||
|     using Microsoft.AspNetCore.Diagnostics.HealthChecks; |  | ||||||
|     using Microsoft.AspNetCore.Hosting; |  | ||||||
|     using Microsoft.AspNetCore.Mvc; |  | ||||||
|     using Microsoft.Azure.ServiceBus; |  | ||||||
|     using Microsoft.EntityFrameworkCore; |  | ||||||
|     using Microsoft.eShopOnContainers.BuildingBlocks.EventBus; |  | ||||||
|     using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; |  | ||||||
|     using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ; |  | ||||||
|     using Microsoft.eShopOnContainers.BuildingBlocks.EventBusServiceBus; |  | ||||||
|     using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF; |  | ||||||
|     using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services; |  | ||||||
|     using Microsoft.eShopOnContainers.Services.Ordering.API.Controllers; |  | ||||||
|     using Microsoft.Extensions.Configuration; |  | ||||||
|     using Microsoft.Extensions.DependencyInjection; |  | ||||||
|     using Microsoft.Extensions.Diagnostics.HealthChecks; |  | ||||||
|     using Microsoft.Extensions.Logging; |  | ||||||
|     using Microsoft.OpenApi.Models; |  | ||||||
|     using Ordering.Infrastructure; |  | ||||||
|     using RabbitMQ.Client; |  | ||||||
|     using System; |  | ||||||
|     using System.Collections.Generic; |  | ||||||
|     using System.Data.Common; |  | ||||||
|     using System.IdentityModel.Tokens.Jwt; |  | ||||||
|     using System.IO; |  | ||||||
|     using System.Reflection; |  | ||||||
| 
 |  | ||||||
|     public class Startup |  | ||||||
|     { |     { | ||||||
|         public Startup(IConfiguration configuration) |         Configuration = configuration; | ||||||
|         { |  | ||||||
|             Configuration = configuration; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         public IConfiguration Configuration { get; } |  | ||||||
| 
 |  | ||||||
|         public virtual IServiceProvider ConfigureServices(IServiceCollection services) |  | ||||||
|         { |  | ||||||
|             services |  | ||||||
|                 .AddGrpc(options => |  | ||||||
|                 { |  | ||||||
|                     options.EnableDetailedErrors = true; |  | ||||||
|                 }) |  | ||||||
|                 .Services |  | ||||||
|                 .AddApplicationInsights(Configuration) |  | ||||||
|                 .AddCustomMvc() |  | ||||||
|                 .AddHealthChecks(Configuration) |  | ||||||
|                 .AddCustomDbContext(Configuration) |  | ||||||
|                 .AddCustomSwagger(Configuration) |  | ||||||
|                 .AddCustomIntegrations(Configuration) |  | ||||||
|                 .AddCustomConfiguration(Configuration) |  | ||||||
|                 .AddEventBus(Configuration) |  | ||||||
|                 .AddCustomAuthentication(Configuration); |  | ||||||
|             //configure autofac |  | ||||||
| 
 |  | ||||||
|             var container = new ContainerBuilder(); |  | ||||||
|             container.Populate(services); |  | ||||||
| 
 |  | ||||||
|             container.RegisterModule(new MediatorModule()); |  | ||||||
|             container.RegisterModule(new ApplicationModule(Configuration["ConnectionString"])); |  | ||||||
| 
 |  | ||||||
|             return new AutofacServiceProvider(container.Build()); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|         public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) |  | ||||||
|         { |  | ||||||
|             //loggerFactory.AddAzureWebAppDiagnostics(); |  | ||||||
|             //loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace); |  | ||||||
| 
 |  | ||||||
|             var pathBase = Configuration["PATH_BASE"]; |  | ||||||
|             if (!string.IsNullOrEmpty(pathBase)) |  | ||||||
|             { |  | ||||||
|                 loggerFactory.CreateLogger<Startup>().LogDebug("Using PATH BASE '{pathBase}'", pathBase); |  | ||||||
|                 app.UsePathBase(pathBase); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             app.UseSwagger() |  | ||||||
|                .UseSwaggerUI(c => |  | ||||||
|                { |  | ||||||
|                    c.SwaggerEndpoint($"{ (!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty) }/swagger/v1/swagger.json", "Ordering.API V1"); |  | ||||||
|                    c.OAuthClientId("orderingswaggerui"); |  | ||||||
|                    c.OAuthAppName("Ordering Swagger UI"); |  | ||||||
|                }); |  | ||||||
| 
 |  | ||||||
|             app.UseRouting(); |  | ||||||
|             app.UseCors("CorsPolicy"); |  | ||||||
|             ConfigureAuth(app); |  | ||||||
| 
 |  | ||||||
|             app.UseEndpoints(endpoints => |  | ||||||
|             { |  | ||||||
|                 endpoints.MapGrpcService<OrderingService>(); |  | ||||||
|                 endpoints.MapDefaultControllerRoute(); |  | ||||||
|                 endpoints.MapControllers(); |  | ||||||
|                 endpoints.MapGet("/_proto/", async ctx => |  | ||||||
|                 { |  | ||||||
|                     ctx.Response.ContentType = "text/plain"; |  | ||||||
|                     using var fs = new FileStream(Path.Combine(env.ContentRootPath, "Proto", "basket.proto"), FileMode.Open, FileAccess.Read); |  | ||||||
|                     using var sr = new StreamReader(fs); |  | ||||||
|                     while (!sr.EndOfStream) |  | ||||||
|                     { |  | ||||||
|                         var line = await sr.ReadLineAsync(); |  | ||||||
|                         if (line != "/* >>" || line != "<< */") |  | ||||||
|                         { |  | ||||||
|                             await ctx.Response.WriteAsync(line); |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 }); |  | ||||||
|                 endpoints.MapHealthChecks("/hc", new HealthCheckOptions() |  | ||||||
|                 { |  | ||||||
|                     Predicate = _ => true, |  | ||||||
|                     ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse |  | ||||||
|                 }); |  | ||||||
|                 endpoints.MapHealthChecks("/liveness", new HealthCheckOptions |  | ||||||
|                 { |  | ||||||
|                     Predicate = r => r.Name.Contains("self") |  | ||||||
|                 }); |  | ||||||
|             }); |  | ||||||
| 
 |  | ||||||
|             ConfigureEventBus(app); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|         private void ConfigureEventBus(IApplicationBuilder app) |  | ||||||
|         { |  | ||||||
|             var eventBus = app.ApplicationServices.GetRequiredService<BuildingBlocks.EventBus.Abstractions.IEventBus>(); |  | ||||||
| 
 |  | ||||||
|             eventBus.Subscribe<UserCheckoutAcceptedIntegrationEvent, IIntegrationEventHandler<UserCheckoutAcceptedIntegrationEvent>>(); |  | ||||||
|             eventBus.Subscribe<GracePeriodConfirmedIntegrationEvent, IIntegrationEventHandler<GracePeriodConfirmedIntegrationEvent>>(); |  | ||||||
|             eventBus.Subscribe<OrderStockConfirmedIntegrationEvent, IIntegrationEventHandler<OrderStockConfirmedIntegrationEvent>>(); |  | ||||||
|             eventBus.Subscribe<OrderStockRejectedIntegrationEvent, IIntegrationEventHandler<OrderStockRejectedIntegrationEvent>>(); |  | ||||||
|             eventBus.Subscribe<OrderPaymentFailedIntegrationEvent, IIntegrationEventHandler<OrderPaymentFailedIntegrationEvent>>(); |  | ||||||
|             eventBus.Subscribe<OrderPaymentSucceededIntegrationEvent, IIntegrationEventHandler<OrderPaymentSucceededIntegrationEvent>>(); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         protected virtual void ConfigureAuth(IApplicationBuilder app) |  | ||||||
|         { |  | ||||||
|             app.UseAuthentication(); |  | ||||||
|             app.UseAuthorization(); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     static class CustomExtensionsMethods |     public IConfiguration Configuration { get; } | ||||||
|  | 
 | ||||||
|  |     public virtual IServiceProvider ConfigureServices(IServiceCollection services) | ||||||
|     { |     { | ||||||
|         public static IServiceCollection AddApplicationInsights(this IServiceCollection services, IConfiguration configuration) |         services | ||||||
|         { |             .AddGrpc(options => | ||||||
|             services.AddApplicationInsightsTelemetry(configuration); |             { | ||||||
|             services.AddApplicationInsightsKubernetesEnricher(); |                 options.EnableDetailedErrors = true; | ||||||
|  |             }) | ||||||
|  |             .Services | ||||||
|  |             .AddApplicationInsights(Configuration) | ||||||
|  |             .AddCustomMvc() | ||||||
|  |             .AddHealthChecks(Configuration) | ||||||
|  |             .AddCustomDbContext(Configuration) | ||||||
|  |             .AddCustomSwagger(Configuration) | ||||||
|  |             .AddCustomIntegrations(Configuration) | ||||||
|  |             .AddCustomConfiguration(Configuration) | ||||||
|  |             .AddEventBus(Configuration) | ||||||
|  |             .AddCustomAuthentication(Configuration); | ||||||
|  |         //configure autofac | ||||||
| 
 | 
 | ||||||
|             return services; |         var container = new ContainerBuilder(); | ||||||
|  |         container.Populate(services); | ||||||
|  | 
 | ||||||
|  |         container.RegisterModule(new MediatorModule()); | ||||||
|  |         container.RegisterModule(new ApplicationModule(Configuration["ConnectionString"])); | ||||||
|  | 
 | ||||||
|  |         return new AutofacServiceProvider(container.Build()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) | ||||||
|  |     { | ||||||
|  |         //loggerFactory.AddAzureWebAppDiagnostics(); | ||||||
|  |         //loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace); | ||||||
|  | 
 | ||||||
|  |         var pathBase = Configuration["PATH_BASE"]; | ||||||
|  |         if (!string.IsNullOrEmpty(pathBase)) | ||||||
|  |         { | ||||||
|  |             loggerFactory.CreateLogger<Startup>().LogDebug("Using PATH BASE '{pathBase}'", pathBase); | ||||||
|  |             app.UsePathBase(pathBase); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         public static IServiceCollection AddCustomMvc(this IServiceCollection services) |         app.UseSwagger() | ||||||
|         { |             .UseSwaggerUI(c => | ||||||
|             // Add framework services. |  | ||||||
|             services.AddControllers(options => |  | ||||||
|                 { |  | ||||||
|                     options.Filters.Add(typeof(HttpGlobalExceptionFilter)); |  | ||||||
|                 }) |  | ||||||
|                 // Added for functional tests |  | ||||||
|                 .AddApplicationPart(typeof(OrdersController).Assembly) |  | ||||||
|                 .AddJsonOptions(options => options.JsonSerializerOptions.WriteIndented = true) |  | ||||||
|                 .SetCompatibilityVersion(CompatibilityVersion.Version_3_0); |  | ||||||
| 
 |  | ||||||
|             services.AddCors(options => |  | ||||||
|             { |             { | ||||||
|                 options.AddPolicy("CorsPolicy", |                 c.SwaggerEndpoint($"{ (!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty) }/swagger/v1/swagger.json", "Ordering.API V1"); | ||||||
|                     builder => builder |                 c.OAuthClientId("orderingswaggerui"); | ||||||
|                     .SetIsOriginAllowed((host) => true) |                 c.OAuthAppName("Ordering Swagger UI"); | ||||||
|                     .AllowAnyMethod() |  | ||||||
|                     .AllowAnyHeader() |  | ||||||
|                     .AllowCredentials()); |  | ||||||
|             }); |             }); | ||||||
| 
 | 
 | ||||||
|             return services; |         app.UseRouting(); | ||||||
|         } |         app.UseCors("CorsPolicy"); | ||||||
|  |         ConfigureAuth(app); | ||||||
| 
 | 
 | ||||||
|         public static IServiceCollection AddHealthChecks(this IServiceCollection services, IConfiguration configuration) |         app.UseEndpoints(endpoints => | ||||||
|         { |         { | ||||||
|             var hcBuilder = services.AddHealthChecks(); |             endpoints.MapGrpcService<OrderingService>(); | ||||||
| 
 |             endpoints.MapDefaultControllerRoute(); | ||||||
|             hcBuilder.AddCheck("self", () => HealthCheckResult.Healthy()); |             endpoints.MapControllers(); | ||||||
| 
 |             endpoints.MapGet("/_proto/", async ctx => | ||||||
|             hcBuilder |  | ||||||
|                 .AddSqlServer( |  | ||||||
|                     configuration["ConnectionString"], |  | ||||||
|                     name: "OrderingDB-check", |  | ||||||
|                     tags: new string[] { "orderingdb" }); |  | ||||||
| 
 |  | ||||||
|             if (configuration.GetValue<bool>("AzureServiceBusEnabled")) |  | ||||||
|             { |             { | ||||||
|                 hcBuilder |                 ctx.Response.ContentType = "text/plain"; | ||||||
|                     .AddAzureServiceBusTopic( |                 using var fs = new FileStream(Path.Combine(env.ContentRootPath, "Proto", "basket.proto"), FileMode.Open, FileAccess.Read); | ||||||
|                         configuration["EventBusConnection"], |                 using var sr = new StreamReader(fs); | ||||||
|                         topicName: "eshop_event_bus", |                 while (!sr.EndOfStream) | ||||||
|                         name: "ordering-servicebus-check", |                 { | ||||||
|                         tags: new string[] { "servicebus" }); |                     var line = await sr.ReadLineAsync(); | ||||||
|             } |                     if (line != "/* >>" || line != "<< */") | ||||||
|             else |                     { | ||||||
|             { |                         await ctx.Response.WriteAsync(line); | ||||||
|                 hcBuilder |                     } | ||||||
|                     .AddRabbitMQ( |                 } | ||||||
|                         $"amqp://{configuration["EventBusConnection"]}", |  | ||||||
|                         name: "ordering-rabbitmqbus-check", |  | ||||||
|                         tags: new string[] { "rabbitmqbus" }); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             return services; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         public static IServiceCollection AddCustomDbContext(this IServiceCollection services, IConfiguration configuration) |  | ||||||
|         { |  | ||||||
|             services.AddDbContext<OrderingContext>(options => |  | ||||||
|                    { |  | ||||||
|                        options.UseSqlServer(configuration["ConnectionString"], |  | ||||||
|                            sqlServerOptionsAction: sqlOptions => |  | ||||||
|                            { |  | ||||||
|                                sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name); |  | ||||||
|                                sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null); |  | ||||||
|                            }); |  | ||||||
|                    }, |  | ||||||
|                        ServiceLifetime.Scoped  //Showing explicitly that the DbContext is shared across the HTTP request scope (graph of objects started in the HTTP request) |  | ||||||
|                    ); |  | ||||||
| 
 |  | ||||||
|             services.AddDbContext<IntegrationEventLogContext>(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); |  | ||||||
|                                      }); |  | ||||||
|             }); |             }); | ||||||
| 
 |             endpoints.MapHealthChecks("/hc", new HealthCheckOptions() | ||||||
|             return services; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         public static IServiceCollection AddCustomSwagger(this IServiceCollection services, IConfiguration configuration) |  | ||||||
|         { |  | ||||||
|             services.AddSwaggerGen(options => |  | ||||||
|             { |             { | ||||||
|                 options.DescribeAllEnumsAsStrings(); |                 Predicate = _ => true, | ||||||
|                 options.SwaggerDoc("v1", new OpenApiInfo |                 ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse | ||||||
|                 { |  | ||||||
|                     Title = "eShopOnContainers - Ordering HTTP API", |  | ||||||
|                     Version = "v1", |  | ||||||
|                     Description = "The Ordering Service HTTP API" |  | ||||||
|                 }); |  | ||||||
|                 options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme |  | ||||||
|                 { |  | ||||||
|                     Type = SecuritySchemeType.OAuth2, |  | ||||||
|                     Flows = new OpenApiOAuthFlows() |  | ||||||
|                     { |  | ||||||
|                         Implicit = new OpenApiOAuthFlow() |  | ||||||
|                         { |  | ||||||
|                             AuthorizationUrl = new Uri($"{configuration.GetValue<string>("IdentityUrlExternal")}/connect/authorize"), |  | ||||||
|                             TokenUrl = new Uri($"{configuration.GetValue<string>("IdentityUrlExternal")}/connect/token"), |  | ||||||
|                             Scopes = new Dictionary<string, string>() |  | ||||||
|                             { |  | ||||||
|                                 { "orders", "Ordering API" } |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 }); |  | ||||||
| 
 |  | ||||||
|                 options.OperationFilter<AuthorizeCheckOperationFilter>(); |  | ||||||
|             }); |             }); | ||||||
| 
 |             endpoints.MapHealthChecks("/liveness", new HealthCheckOptions | ||||||
|             return services; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         public static IServiceCollection AddCustomIntegrations(this IServiceCollection services, IConfiguration configuration) |  | ||||||
|         { |  | ||||||
|             services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); |  | ||||||
|             services.AddTransient<IIdentityService, IdentityService>(); |  | ||||||
|             services.AddTransient<Func<DbConnection, IIntegrationEventLogService>>( |  | ||||||
|                 sp => (DbConnection c) => new IntegrationEventLogService(c)); |  | ||||||
| 
 |  | ||||||
|             services.AddTransient<IOrderingIntegrationEventService, OrderingIntegrationEventService>(); |  | ||||||
| 
 |  | ||||||
|             if (configuration.GetValue<bool>("AzureServiceBusEnabled")) |  | ||||||
|             { |             { | ||||||
|                 services.AddSingleton<IServiceBusPersisterConnection>(sp => |                 Predicate = r => r.Name.Contains("self") | ||||||
|                 { |  | ||||||
|                     var serviceBusConnectionString = configuration["EventBusConnection"]; |  | ||||||
|                     var serviceBusConnection = new ServiceBusConnectionStringBuilder(serviceBusConnectionString); |  | ||||||
|                     var subscriptionClientName = configuration["SubscriptionClientName"]; |  | ||||||
| 
 |  | ||||||
|                     return new DefaultServiceBusPersisterConnection(serviceBusConnection, subscriptionClientName); |  | ||||||
|                 }); |  | ||||||
|             } |  | ||||||
|             else |  | ||||||
|             { |  | ||||||
|                 services.AddSingleton<IRabbitMQPersistentConnection>(sp => |  | ||||||
|                 { |  | ||||||
|                     var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersistentConnection>>(); |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|                     var factory = new ConnectionFactory() |  | ||||||
|                     { |  | ||||||
|                         HostName = configuration["EventBusConnection"], |  | ||||||
|                         DispatchConsumersAsync = true |  | ||||||
|                     }; |  | ||||||
| 
 |  | ||||||
|                     if (!string.IsNullOrEmpty(configuration["EventBusUserName"])) |  | ||||||
|                     { |  | ||||||
|                         factory.UserName = configuration["EventBusUserName"]; |  | ||||||
|                     } |  | ||||||
| 
 |  | ||||||
|                     if (!string.IsNullOrEmpty(configuration["EventBusPassword"])) |  | ||||||
|                     { |  | ||||||
|                         factory.Password = configuration["EventBusPassword"]; |  | ||||||
|                     } |  | ||||||
| 
 |  | ||||||
|                     var retryCount = 5; |  | ||||||
|                     if (!string.IsNullOrEmpty(configuration["EventBusRetryCount"])) |  | ||||||
|                     { |  | ||||||
|                         retryCount = int.Parse(configuration["EventBusRetryCount"]); |  | ||||||
|                     } |  | ||||||
| 
 |  | ||||||
|                     return new DefaultRabbitMQPersistentConnection(factory, logger, retryCount); |  | ||||||
|                 }); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             return services; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         public static IServiceCollection AddCustomConfiguration(this IServiceCollection services, IConfiguration configuration) |  | ||||||
|         { |  | ||||||
|             services.AddOptions(); |  | ||||||
|             services.Configure<OrderingSettings>(configuration); |  | ||||||
|             services.Configure<ApiBehaviorOptions>(options => |  | ||||||
|             { |  | ||||||
|                 options.InvalidModelStateResponseFactory = context => |  | ||||||
|                 { |  | ||||||
|                     var problemDetails = new ValidationProblemDetails(context.ModelState) |  | ||||||
|                     { |  | ||||||
|                         Instance = context.HttpContext.Request.Path, |  | ||||||
|                         Status = StatusCodes.Status400BadRequest, |  | ||||||
|                         Detail = "Please refer to the errors property for additional details." |  | ||||||
|                     }; |  | ||||||
| 
 |  | ||||||
|                     return new BadRequestObjectResult(problemDetails) |  | ||||||
|                     { |  | ||||||
|                         ContentTypes = { "application/problem+json", "application/problem+xml" } |  | ||||||
|                     }; |  | ||||||
|                 }; |  | ||||||
|             }); |             }); | ||||||
|  |         }); | ||||||
| 
 | 
 | ||||||
|             return services; |         ConfigureEventBus(app); | ||||||
|         } |     } | ||||||
| 
 | 
 | ||||||
|         public static IServiceCollection AddEventBus(this IServiceCollection services, IConfiguration configuration) |  | ||||||
|         { |  | ||||||
|             if (configuration.GetValue<bool>("AzureServiceBusEnabled")) |  | ||||||
|             { |  | ||||||
|                 services.AddSingleton<IEventBus, EventBusServiceBus>(sp => |  | ||||||
|                 { |  | ||||||
|                     var serviceBusPersisterConnection = sp.GetRequiredService<IServiceBusPersisterConnection>(); |  | ||||||
|                     var iLifetimeScope = sp.GetRequiredService<ILifetimeScope>(); |  | ||||||
|                     var logger = sp.GetRequiredService<ILogger<EventBusServiceBus>>(); |  | ||||||
|                     var eventBusSubcriptionsManager = sp.GetRequiredService<IEventBusSubscriptionsManager>(); |  | ||||||
| 
 | 
 | ||||||
|                     return new EventBusServiceBus(serviceBusPersisterConnection, logger, |     private void ConfigureEventBus(IApplicationBuilder app) | ||||||
|                         eventBusSubcriptionsManager, iLifetimeScope); |     { | ||||||
|                 }); |         var eventBus = app.ApplicationServices.GetRequiredService<BuildingBlocks.EventBus.Abstractions.IEventBus>(); | ||||||
|             } |  | ||||||
|             else |  | ||||||
|             { |  | ||||||
|                 services.AddSingleton<IEventBus, EventBusRabbitMQ>(sp => |  | ||||||
|                 { |  | ||||||
|                     var subscriptionClientName = configuration["SubscriptionClientName"]; |  | ||||||
|                     var rabbitMQPersistentConnection = sp.GetRequiredService<IRabbitMQPersistentConnection>(); |  | ||||||
|                     var iLifetimeScope = sp.GetRequiredService<ILifetimeScope>(); |  | ||||||
|                     var logger = sp.GetRequiredService<ILogger<EventBusRabbitMQ>>(); |  | ||||||
|                     var eventBusSubcriptionsManager = sp.GetRequiredService<IEventBusSubscriptionsManager>(); |  | ||||||
| 
 | 
 | ||||||
|                     var retryCount = 5; |         eventBus.Subscribe<UserCheckoutAcceptedIntegrationEvent, IIntegrationEventHandler<UserCheckoutAcceptedIntegrationEvent>>(); | ||||||
|                     if (!string.IsNullOrEmpty(configuration["EventBusRetryCount"])) |         eventBus.Subscribe<GracePeriodConfirmedIntegrationEvent, IIntegrationEventHandler<GracePeriodConfirmedIntegrationEvent>>(); | ||||||
|                     { |         eventBus.Subscribe<OrderStockConfirmedIntegrationEvent, IIntegrationEventHandler<OrderStockConfirmedIntegrationEvent>>(); | ||||||
|                         retryCount = int.Parse(configuration["EventBusRetryCount"]); |         eventBus.Subscribe<OrderStockRejectedIntegrationEvent, IIntegrationEventHandler<OrderStockRejectedIntegrationEvent>>(); | ||||||
|                     } |         eventBus.Subscribe<OrderPaymentFailedIntegrationEvent, IIntegrationEventHandler<OrderPaymentFailedIntegrationEvent>>(); | ||||||
|  |         eventBus.Subscribe<OrderPaymentSucceededIntegrationEvent, IIntegrationEventHandler<OrderPaymentSucceededIntegrationEvent>>(); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|                     return new EventBusRabbitMQ(rabbitMQPersistentConnection, logger, iLifetimeScope, eventBusSubcriptionsManager, subscriptionClientName, retryCount); |     protected virtual void ConfigureAuth(IApplicationBuilder app) | ||||||
|                 }); |     { | ||||||
|             } |         app.UseAuthentication(); | ||||||
| 
 |         app.UseAuthorization(); | ||||||
|             services.AddSingleton<IEventBusSubscriptionsManager, InMemoryEventBusSubscriptionsManager>(); |     } | ||||||
| 
 | } | ||||||
|             return services; | 
 | ||||||
|         } | static class CustomExtensionsMethods | ||||||
| 
 | { | ||||||
|         public static IServiceCollection AddCustomAuthentication(this IServiceCollection services, IConfiguration configuration) |     public static IServiceCollection AddApplicationInsights(this IServiceCollection services, IConfiguration configuration) | ||||||
|         { |     { | ||||||
|             // prevent from mapping "sub" claim to nameidentifier. |         services.AddApplicationInsightsTelemetry(configuration); | ||||||
|             JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub"); |         services.AddApplicationInsightsKubernetesEnricher(); | ||||||
| 
 | 
 | ||||||
|             var identityUrl = configuration.GetValue<string>("IdentityUrl"); |         return services; | ||||||
| 
 |     } | ||||||
|             services.AddAuthentication(options => | 
 | ||||||
|             { |     public static IServiceCollection AddCustomMvc(this IServiceCollection services) | ||||||
|                 options.DefaultAuthenticateScheme = AspNetCore.Authentication.JwtBearer.JwtBearerDefaults.AuthenticationScheme; |     { | ||||||
|                 options.DefaultChallengeScheme = AspNetCore.Authentication.JwtBearer.JwtBearerDefaults.AuthenticationScheme; |         // Add framework services. | ||||||
| 
 |         services.AddControllers(options => | ||||||
|             }).AddJwtBearer(options => |             { | ||||||
|             { |                 options.Filters.Add(typeof(HttpGlobalExceptionFilter)); | ||||||
|                 options.Authority = identityUrl; |             }) | ||||||
|                 options.RequireHttpsMetadata = false; |             // Added for functional tests | ||||||
|                 options.Audience = "orders"; |             .AddApplicationPart(typeof(OrdersController).Assembly) | ||||||
|             }); |             .AddJsonOptions(options => options.JsonSerializerOptions.WriteIndented = true) | ||||||
| 
 |             .SetCompatibilityVersion(CompatibilityVersion.Version_3_0); | ||||||
|             return services; | 
 | ||||||
|         } |         services.AddCors(options => | ||||||
|  |         { | ||||||
|  |             options.AddPolicy("CorsPolicy", | ||||||
|  |                 builder => builder | ||||||
|  |                 .SetIsOriginAllowed((host) => true) | ||||||
|  |                 .AllowAnyMethod() | ||||||
|  |                 .AllowAnyHeader() | ||||||
|  |                 .AllowCredentials()); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         return services; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static IServiceCollection AddHealthChecks(this IServiceCollection services, IConfiguration configuration) | ||||||
|  |     { | ||||||
|  |         var hcBuilder = services.AddHealthChecks(); | ||||||
|  | 
 | ||||||
|  |         hcBuilder.AddCheck("self", () => HealthCheckResult.Healthy()); | ||||||
|  | 
 | ||||||
|  |         hcBuilder | ||||||
|  |             .AddSqlServer( | ||||||
|  |                 configuration["ConnectionString"], | ||||||
|  |                 name: "OrderingDB-check", | ||||||
|  |                 tags: new string[] { "orderingdb" }); | ||||||
|  | 
 | ||||||
|  |         if (configuration.GetValue<bool>("AzureServiceBusEnabled")) | ||||||
|  |         { | ||||||
|  |             hcBuilder | ||||||
|  |                 .AddAzureServiceBusTopic( | ||||||
|  |                     configuration["EventBusConnection"], | ||||||
|  |                     topicName: "eshop_event_bus", | ||||||
|  |                     name: "ordering-servicebus-check", | ||||||
|  |                     tags: new string[] { "servicebus" }); | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             hcBuilder | ||||||
|  |                 .AddRabbitMQ( | ||||||
|  |                     $"amqp://{configuration["EventBusConnection"]}", | ||||||
|  |                     name: "ordering-rabbitmqbus-check", | ||||||
|  |                     tags: new string[] { "rabbitmqbus" }); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return services; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static IServiceCollection AddCustomDbContext(this IServiceCollection services, IConfiguration configuration) | ||||||
|  |     { | ||||||
|  |         services.AddDbContext<OrderingContext>(options => | ||||||
|  |                 { | ||||||
|  |                     options.UseSqlServer(configuration["ConnectionString"], | ||||||
|  |                         sqlServerOptionsAction: sqlOptions => | ||||||
|  |                         { | ||||||
|  |                             sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name); | ||||||
|  |                             sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null); | ||||||
|  |                         }); | ||||||
|  |                 }, | ||||||
|  |                     ServiceLifetime.Scoped  //Showing explicitly that the DbContext is shared across the HTTP request scope (graph of objects started in the HTTP request) | ||||||
|  |                 ); | ||||||
|  | 
 | ||||||
|  |         services.AddDbContext<IntegrationEventLogContext>(options => | ||||||
|  |         { | ||||||
|  |             options.UseSqlServer(configuration["ConnectionString"], | ||||||
|  |                                     sqlServerOptionsAction: sqlOptions => | ||||||
|  |                                     { | ||||||
|  |                                         sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name); | ||||||
|  |                                         //Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency  | ||||||
|  |                                         sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null); | ||||||
|  |                                     }); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         return services; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static IServiceCollection AddCustomSwagger(this IServiceCollection services, IConfiguration configuration) | ||||||
|  |     { | ||||||
|  |         services.AddSwaggerGen(options => | ||||||
|  |         { | ||||||
|  |             options.DescribeAllEnumsAsStrings(); | ||||||
|  |             options.SwaggerDoc("v1", new OpenApiInfo | ||||||
|  |             { | ||||||
|  |                 Title = "eShopOnContainers - Ordering HTTP API", | ||||||
|  |                 Version = "v1", | ||||||
|  |                 Description = "The Ordering Service HTTP API" | ||||||
|  |             }); | ||||||
|  |             options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme | ||||||
|  |             { | ||||||
|  |                 Type = SecuritySchemeType.OAuth2, | ||||||
|  |                 Flows = new OpenApiOAuthFlows() | ||||||
|  |                 { | ||||||
|  |                     Implicit = new OpenApiOAuthFlow() | ||||||
|  |                     { | ||||||
|  |                         AuthorizationUrl = new Uri($"{configuration.GetValue<string>("IdentityUrlExternal")}/connect/authorize"), | ||||||
|  |                         TokenUrl = new Uri($"{configuration.GetValue<string>("IdentityUrlExternal")}/connect/token"), | ||||||
|  |                         Scopes = new Dictionary<string, string>() | ||||||
|  |                         { | ||||||
|  |                             { "orders", "Ordering API" } | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |             options.OperationFilter<AuthorizeCheckOperationFilter>(); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         return services; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static IServiceCollection AddCustomIntegrations(this IServiceCollection services, IConfiguration configuration) | ||||||
|  |     { | ||||||
|  |         services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); | ||||||
|  |         services.AddTransient<IIdentityService, IdentityService>(); | ||||||
|  |         services.AddTransient<Func<DbConnection, IIntegrationEventLogService>>( | ||||||
|  |             sp => (DbConnection c) => new IntegrationEventLogService(c)); | ||||||
|  | 
 | ||||||
|  |         services.AddTransient<IOrderingIntegrationEventService, OrderingIntegrationEventService>(); | ||||||
|  | 
 | ||||||
|  |         if (configuration.GetValue<bool>("AzureServiceBusEnabled")) | ||||||
|  |         { | ||||||
|  |             services.AddSingleton<IServiceBusPersisterConnection>(sp => | ||||||
|  |             { | ||||||
|  |                 var serviceBusConnectionString = configuration["EventBusConnection"]; | ||||||
|  |                 var serviceBusConnection = new ServiceBusConnectionStringBuilder(serviceBusConnectionString); | ||||||
|  |                 var subscriptionClientName = configuration["SubscriptionClientName"]; | ||||||
|  | 
 | ||||||
|  |                 return new DefaultServiceBusPersisterConnection(serviceBusConnection, subscriptionClientName); | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             services.AddSingleton<IRabbitMQPersistentConnection>(sp => | ||||||
|  |             { | ||||||
|  |                 var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersistentConnection>>(); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |                 var factory = new ConnectionFactory() | ||||||
|  |                 { | ||||||
|  |                     HostName = configuration["EventBusConnection"], | ||||||
|  |                     DispatchConsumersAsync = true | ||||||
|  |                 }; | ||||||
|  | 
 | ||||||
|  |                 if (!string.IsNullOrEmpty(configuration["EventBusUserName"])) | ||||||
|  |                 { | ||||||
|  |                     factory.UserName = configuration["EventBusUserName"]; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 if (!string.IsNullOrEmpty(configuration["EventBusPassword"])) | ||||||
|  |                 { | ||||||
|  |                     factory.Password = configuration["EventBusPassword"]; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 var retryCount = 5; | ||||||
|  |                 if (!string.IsNullOrEmpty(configuration["EventBusRetryCount"])) | ||||||
|  |                 { | ||||||
|  |                     retryCount = int.Parse(configuration["EventBusRetryCount"]); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 return new DefaultRabbitMQPersistentConnection(factory, logger, retryCount); | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return services; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static IServiceCollection AddCustomConfiguration(this IServiceCollection services, IConfiguration configuration) | ||||||
|  |     { | ||||||
|  |         services.AddOptions(); | ||||||
|  |         services.Configure<OrderingSettings>(configuration); | ||||||
|  |         services.Configure<ApiBehaviorOptions>(options => | ||||||
|  |         { | ||||||
|  |             options.InvalidModelStateResponseFactory = context => | ||||||
|  |             { | ||||||
|  |                 var problemDetails = new ValidationProblemDetails(context.ModelState) | ||||||
|  |                 { | ||||||
|  |                     Instance = context.HttpContext.Request.Path, | ||||||
|  |                     Status = StatusCodes.Status400BadRequest, | ||||||
|  |                     Detail = "Please refer to the errors property for additional details." | ||||||
|  |                 }; | ||||||
|  | 
 | ||||||
|  |                 return new BadRequestObjectResult(problemDetails) | ||||||
|  |                 { | ||||||
|  |                     ContentTypes = { "application/problem+json", "application/problem+xml" } | ||||||
|  |                 }; | ||||||
|  |             }; | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         return services; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static IServiceCollection AddEventBus(this IServiceCollection services, IConfiguration configuration) | ||||||
|  |     { | ||||||
|  |         if (configuration.GetValue<bool>("AzureServiceBusEnabled")) | ||||||
|  |         { | ||||||
|  |             services.AddSingleton<IEventBus, EventBusServiceBus>(sp => | ||||||
|  |             { | ||||||
|  |                 var serviceBusPersisterConnection = sp.GetRequiredService<IServiceBusPersisterConnection>(); | ||||||
|  |                 var iLifetimeScope = sp.GetRequiredService<ILifetimeScope>(); | ||||||
|  |                 var logger = sp.GetRequiredService<ILogger<EventBusServiceBus>>(); | ||||||
|  |                 var eventBusSubcriptionsManager = sp.GetRequiredService<IEventBusSubscriptionsManager>(); | ||||||
|  | 
 | ||||||
|  |                 return new EventBusServiceBus(serviceBusPersisterConnection, logger, | ||||||
|  |                     eventBusSubcriptionsManager, iLifetimeScope); | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             services.AddSingleton<IEventBus, EventBusRabbitMQ>(sp => | ||||||
|  |             { | ||||||
|  |                 var subscriptionClientName = configuration["SubscriptionClientName"]; | ||||||
|  |                 var rabbitMQPersistentConnection = sp.GetRequiredService<IRabbitMQPersistentConnection>(); | ||||||
|  |                 var iLifetimeScope = sp.GetRequiredService<ILifetimeScope>(); | ||||||
|  |                 var logger = sp.GetRequiredService<ILogger<EventBusRabbitMQ>>(); | ||||||
|  |                 var eventBusSubcriptionsManager = sp.GetRequiredService<IEventBusSubscriptionsManager>(); | ||||||
|  | 
 | ||||||
|  |                 var retryCount = 5; | ||||||
|  |                 if (!string.IsNullOrEmpty(configuration["EventBusRetryCount"])) | ||||||
|  |                 { | ||||||
|  |                     retryCount = int.Parse(configuration["EventBusRetryCount"]); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 return new EventBusRabbitMQ(rabbitMQPersistentConnection, logger, iLifetimeScope, eventBusSubcriptionsManager, subscriptionClientName, retryCount); | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         services.AddSingleton<IEventBusSubscriptionsManager, InMemoryEventBusSubscriptionsManager>(); | ||||||
|  | 
 | ||||||
|  |         return services; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static IServiceCollection AddCustomAuthentication(this IServiceCollection services, IConfiguration configuration) | ||||||
|  |     { | ||||||
|  |         // prevent from mapping "sub" claim to nameidentifier. | ||||||
|  |         JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub"); | ||||||
|  | 
 | ||||||
|  |         var identityUrl = configuration.GetValue<string>("IdentityUrl"); | ||||||
|  | 
 | ||||||
|  |         services.AddAuthentication(options => | ||||||
|  |         { | ||||||
|  |             options.DefaultAuthenticateScheme = AspNetCore.Authentication.JwtBearer.JwtBearerDefaults.AuthenticationScheme; | ||||||
|  |             options.DefaultChallengeScheme = AspNetCore.Authentication.JwtBearer.JwtBearerDefaults.AuthenticationScheme; | ||||||
|  | 
 | ||||||
|  |         }).AddJwtBearer(options => | ||||||
|  |         { | ||||||
|  |             options.Authority = identityUrl; | ||||||
|  |             options.RequireHttpsMetadata = false; | ||||||
|  |             options.Audience = "orders"; | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         return services; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user