Moved all usings to globalusing file

This commit is contained in:
Sumit Ghosh 2021-10-12 17:34:41 +05:30
parent 818995d8b8
commit 666fba815f
77 changed files with 2458 additions and 3026 deletions

View File

@ -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;
}
} }
} }

View File

@ -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;
}
} }
} }
} }

View File

@ -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();
}
} }
} }

View File

@ -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;
}
} }
} }

View File

@ -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.
} }
} }

View File

@ -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; }
}
} }
} }

View File

@ -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.
} }
} }

View File

@ -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;
}
} }
} }

View File

@ -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()
};
}
}

View File

@ -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;
}
} }
} }

View File

@ -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);
}
} }
} }
} }
} }

View File

@ -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;
}
} }
} }

View File

@ -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.
} }
} }

View File

@ -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;
}
} }
} }

View File

@ -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.
} }
} }

View File

@ -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;
}
} }
} }

View File

@ -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.
} }
} }

View File

@ -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;
}
} }
} }

View File

@ -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.
} }
} }

View File

@ -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;
}
} }
} }

View File

@ -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.
} }
} }

View File

@ -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);
}
} }
} }

View File

@ -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);
}
} }
} }

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
} }
} }

View File

@ -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
//}
} }

View File

@ -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);
}
} }
} }

View File

@ -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);
}
}

View File

@ -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);
}
} }
} }
} }

View File

@ -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);
}
} }
} }
} }

View File

@ -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);
}
} }
} }
} }

View File

@ -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);
}
} }
} }
} }

View File

@ -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);
}
} }
} }
} }

View File

@ -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);
} }
} }
} }
} }

View File

@ -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;
}
} }

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
} }
}

View File

@ -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;
}
} }
} }

View File

@ -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;
}
} }
} }

View File

@ -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;
}
} }
} }

View File

@ -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;
}
} }
} }

View File

@ -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;
}
} }
} }

View File

@ -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;
}
} }
} }

View File

@ -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;
}
}

View File

@ -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;
}
} }
} }

View File

@ -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;
} }
} }

View File

@ -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);
}
} }

View File

@ -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());
}
} }
} }

View File

@ -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; }
}
} }

View File

@ -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;
}
} }
} }

View File

@ -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();
}
} }

View File

@ -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;
}
} }

View File

@ -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; }
}
} }

View File

@ -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);
}
} }
} }

View File

@ -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();
}
}

View File

@ -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);
}
} }
} }

View File

@ -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);
}
} }
} }

View File

@ -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");
}
} }
} }

View File

@ -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);
}
} }

View File

@ -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
};
}
} }

View File

@ -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; }
}
} }

View File

@ -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,
});
}
} }
} }

View File

@ -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;
}
} }
} }

View File

@ -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
});
}
} }
} }
} }

View File

@ -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<>));
}
} }
} }

View File

@ -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<,>));
}
} }
} }

View File

@ -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>
{ {

View File

@ -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" } };
}
};
}
} }
} }

View File

@ -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; }
} }
} }

View File

@ -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);
}
);
} }
} }

View File

@ -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();
} }

View File

@ -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;
}
} }
} }

View File

@ -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; }
}
} }

View File

@ -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);

View File

@ -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;
} }
} }