Browse Source

Merge from origin/dev to dev

pull/126/head
dsanz 8 years ago
parent
commit
a0da160a77
35 changed files with 299 additions and 222 deletions
  1. +0
    -1
      src/BuildingBlocks/EventBus/EventBus/EventBus.csproj
  2. +1
    -1
      src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEventLog/EventStateEnum.cs
  3. +1
    -1
      src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEventLog/IntegrationEventLogEntry.cs
  4. +3
    -3
      src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedIntegrationEventHandler.cs
  5. +2
    -2
      src/Services/Basket/Basket.API/IntegrationEvents/Events/ProductPriceChangedIntegrationEvent.cs
  6. +3
    -3
      src/Services/Basket/Basket.API/Startup.cs
  7. +0
    -1
      src/Services/Catalog/Catalog.API/Catalog.API.csproj
  8. +2
    -2
      src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs
  9. +2
    -2
      src/Services/Catalog/Catalog.API/Infrastructure/CatalogContext.cs
  10. +0
    -3
      src/Services/Catalog/Catalog.API/IntegrationEvents/Events/ProductPriceChangedEvent.cs.txt
  11. +2
    -2
      src/Services/Catalog/Catalog.API/IntegrationEvents/Events/ProductPriceChangedIntegrationEvent.cs
  12. +5
    -5
      src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderCreatedDomainEventHandler.cs
  13. +5
    -5
      src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/PaymentMethodCheckedDomainEventHandler.cs
  14. +2
    -2
      src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs
  15. +2
    -2
      src/Services/Ordering/Ordering.Domain/AggregatesModel/BuyerAggregate/Buyer.cs
  16. +7
    -4
      src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs
  17. +2
    -2
      src/Services/Ordering/Ordering.Domain/Events/OrderCreatedDomainEvent.cs
  18. +2
    -2
      src/Services/Ordering/Ordering.Domain/Events/PaymentMethodCheckedDomainEvent.cs
  19. +8
    -8
      src/Services/Ordering/Ordering.Domain/SeedWork/Entity.cs
  20. +3
    -3
      src/Services/Ordering/Ordering.Infrastructure/MediatorExtension.cs
  21. +6
    -4
      src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs
  22. +15
    -8
      src/Web/WebMVC/Controllers/OrderController.cs
  23. +16
    -19
      src/Web/WebMVC/Services/BasketService.cs
  24. +10
    -22
      src/Web/WebMVC/Services/CatalogService.cs
  25. +11
    -14
      src/Web/WebMVC/Services/OrderingService.cs
  26. +45
    -0
      src/Web/WebMVC/Services/Utilities/HttpApiClient.cs
  27. +99
    -0
      src/Web/WebMVC/Services/Utilities/HttpApiClientWrapper.cs
  28. +16
    -0
      src/Web/WebMVC/Services/Utilities/IHttpClient.cs
  29. +0
    -90
      src/Web/WebMVC/Services/Utilities/RetryWithExponentialBackoff.cs
  30. +12
    -3
      src/Web/WebMVC/Startup.cs
  31. +7
    -0
      src/Web/WebMVC/Views/Order/Create.cshtml
  32. +1
    -0
      src/Web/WebMVC/WebMVC.csproj
  33. +1
    -0
      src/Web/WebMVC/appsettings.json
  34. +1
    -1
      test/Services/UnitTest/Ordering/Domain/BuyerAggregateTest.cs
  35. +7
    -7
      test/Services/UnitTest/Ordering/Domain/OrderAggregateTest.cs

+ 0
- 1
src/BuildingBlocks/EventBus/EventBus/EventBus.csproj View File

@ -8,7 +8,6 @@
<ItemGroup>
<Folder Include="Abstractions\" />
<Folder Include="Events\" />
</ItemGroup>
<ItemGroup>


src/BuildingBlocks/EventBus/EventBus/Events/EventStateEnum.cs → src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEventLog/EventStateEnum.cs View File

@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Text;
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events.IntegrationEventLog
{
public enum EventStateEnum
{

src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEventLogEntry.cs → src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEventLog/IntegrationEventLogEntry.cs View File

@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Text;
using Newtonsoft.Json;
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events.IntegrationEventLog
{
public class IntegrationEventLogEntry
{

src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedEventHandler.cs → src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedIntegrationEventHandler.cs View File

@ -6,15 +6,15 @@ using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.EventHandling
{
public class ProductPriceChangedEventHandler : IIntegrationEventHandler<ProductPriceChangedEvent>
public class ProductPriceChangedIntegrationEventHandler : IIntegrationEventHandler<ProductPriceChangedIntegrationEvent>
{
private readonly IBasketRepository _repository;
public ProductPriceChangedEventHandler(IBasketRepository repository)
public ProductPriceChangedIntegrationEventHandler(IBasketRepository repository)
{
_repository = repository;
}
public async Task Handle(ProductPriceChangedEvent @event)
public async Task Handle(ProductPriceChangedIntegrationEvent @event)
{
var userIds = await _repository.GetUsers();
foreach (var id in userIds)

src/Services/Basket/Basket.API/IntegrationEvents/Events/ProductPriceChangedEvent.cs → src/Services/Basket/Basket.API/IntegrationEvents/Events/ProductPriceChangedIntegrationEvent.cs View File

@ -8,7 +8,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.Even
// 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 class ProductPriceChangedEvent : IntegrationEvent
public class ProductPriceChangedIntegrationEvent : IntegrationEvent
{
public int ProductId { get; private set; }
@ -16,7 +16,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.Even
public decimal OldPrice { get; private set; }
public ProductPriceChangedEvent(int productId, decimal newPrice, decimal oldPrice)
public ProductPriceChangedIntegrationEvent(int productId, decimal newPrice, decimal oldPrice)
{
ProductId = productId;
NewPrice = newPrice;

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

@ -77,7 +77,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
});
services.AddTransient<IBasketRepository, RedisBasketRepository>();
services.AddTransient<IIntegrationEventHandler<ProductPriceChangedEvent>, ProductPriceChangedEventHandler>();
services.AddTransient<IIntegrationEventHandler<ProductPriceChangedIntegrationEvent>, ProductPriceChangedIntegrationEventHandler>();
var serviceProvider = services.BuildServiceProvider();
var configuration = serviceProvider.GetRequiredService<IOptionsSnapshot<BasketSettings>>().Value;
@ -103,9 +103,9 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
app.UseSwagger()
.UseSwaggerUi();
var catalogPriceHandler = app.ApplicationServices.GetService<IIntegrationEventHandler<ProductPriceChangedEvent>>();
var catalogPriceHandler = app.ApplicationServices.GetService<IIntegrationEventHandler<ProductPriceChangedIntegrationEvent>>();
var eventBus = app.ApplicationServices.GetRequiredService<IEventBus>();
eventBus.Subscribe<ProductPriceChangedEvent>(catalogPriceHandler);
eventBus.Subscribe<ProductPriceChangedIntegrationEvent>(catalogPriceHandler);
}


+ 0
- 1
src/Services/Catalog/Catalog.API/Catalog.API.csproj View File

@ -22,7 +22,6 @@
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Compile Include="IntegrationEvents\EventHandling\AnyFutureIntegrationEventHandler.cs.txt" />
<Compile Include="IntegrationEvents\Events\ProductPriceChangedEvent.cs.txt" />
<Content Update="web.config;">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>


+ 2
- 2
src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs View File

@ -1,7 +1,7 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events.IntegrationEventLog;
using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure;
using Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents.Events;
using Microsoft.eShopOnContainers.Services.Catalog.API.Model;
@ -146,7 +146,7 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
item.Price = value.Price;
_context.CatalogItems.Update(item);
var @event = new ProductPriceChangedEvent(item.Id, item.Price, oldPrice);
var @event = new ProductPriceChangedIntegrationEvent(item.Id, item.Price, oldPrice);
var eventLogEntry = new IntegrationEventLogEntry(@event);
_context.IntegrationEventLog.Add(eventLogEntry);


+ 2
- 2
src/Services/Catalog/Catalog.API/Infrastructure/CatalogContext.cs View File

@ -2,8 +2,8 @@
{
using EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore;
using Model;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using Model;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events.IntegrationEventLog;
public class CatalogContext : DbContext
{


+ 0
- 3
src/Services/Catalog/Catalog.API/IntegrationEvents/Events/ProductPriceChangedEvent.cs.txt View File

@ -1,3 +0,0 @@

// To implement any future integration event handler here

src/Services/Catalog/Catalog.API/IntegrationEvents/Events/ProductPriceChangedEvent.cs → src/Services/Catalog/Catalog.API/IntegrationEvents/Events/ProductPriceChangedIntegrationEvent.cs View File

@ -8,7 +8,7 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents.Eve
// 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 class ProductPriceChangedEvent : IntegrationEvent
public class ProductPriceChangedIntegrationEvent : IntegrationEvent
{
public int ProductId { get; private set; }
@ -16,7 +16,7 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents.Eve
public decimal OldPrice { get; private set; }
public ProductPriceChangedEvent(int productId, decimal newPrice, decimal oldPrice)
public ProductPriceChangedIntegrationEvent(int productId, decimal newPrice, decimal oldPrice)
{
ProductId = productId;
NewPrice = newPrice;

src/Services/Ordering/Ordering.API/Application/EventHandlers/OrderCreatedEventHandler.cs → src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderCreatedDomainEventHandler.cs View File

@ -6,22 +6,22 @@ using Ordering.Domain.Events;
using System;
using System.Threading.Tasks;
namespace Ordering.API.Application.EventHandlers
namespace Ordering.API.Application.DomainEventHandlers
{
public class OrderCreatedEventHandler : IAsyncNotificationHandler<OrderCreated>
public class OrderCreatedDomainEventHandler : IAsyncNotificationHandler<OrderCreatedDomainEvent>
{
private readonly ILoggerFactory _logger;
private readonly IBuyerRepository<Buyer> _buyerRepository;
private readonly IIdentityService _identityService;
public OrderCreatedEventHandler(ILoggerFactory logger, IBuyerRepository<Buyer> buyerRepository, IIdentityService identityService)
public OrderCreatedDomainEventHandler(ILoggerFactory logger, IBuyerRepository<Buyer> buyerRepository, IIdentityService identityService)
{
_buyerRepository = buyerRepository ?? throw new ArgumentNullException(nameof(buyerRepository));
_identityService = identityService ?? throw new ArgumentNullException(nameof(identityService));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task Handle(OrderCreated orderNotification)
public async Task Handle(OrderCreatedDomainEvent orderNotification)
{
var cardTypeId = orderNotification.CardTypeId != 0 ? orderNotification.CardTypeId : 1;
@ -46,7 +46,7 @@ namespace Ordering.API.Application.EventHandlers
await _buyerRepository.UnitOfWork
.SaveEntitiesAsync();
_logger.CreateLogger(nameof(OrderCreatedEventHandler)).LogTrace($"A new payment method has been successfully added for orderId: {orderNotification.Order.Id}.");
_logger.CreateLogger(nameof(OrderCreatedDomainEventHandler)).LogTrace($"A new payment method has been successfully added for orderId: {orderNotification.Order.Id}.");
}
}

src/Services/Ordering/Ordering.API/Application/EventHandlers/PaymentMethodCheckedEventHandler.cs → src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/PaymentMethodCheckedDomainEventHandler.cs View File

@ -5,19 +5,19 @@ using Ordering.Domain.Events;
using System;
using System.Threading.Tasks;
namespace Ordering.API.Application.EventHandlers
namespace Ordering.API.Application.DomainEventHandlers
{
public class PaymentMethodCheckedEventHandler : IAsyncNotificationHandler<PaymentMethodChecked>
public class PaymentMethodCheckedDomainEventHandler : IAsyncNotificationHandler<PaymentMethodCheckedDomainEvent>
{
private readonly IOrderRepository<Order> _orderRepository;
private readonly ILoggerFactory _logger;
public PaymentMethodCheckedEventHandler(IOrderRepository<Order> orderRepository, ILoggerFactory logger)
public PaymentMethodCheckedDomainEventHandler(IOrderRepository<Order> orderRepository, ILoggerFactory logger)
{
_orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task Handle(PaymentMethodChecked paymentMethodNotification)
public async Task Handle(PaymentMethodCheckedDomainEvent paymentMethodNotification)
{
var orderToUpdate = await _orderRepository.GetAsync(paymentMethodNotification.OrderId);
orderToUpdate.SetBuyerId(paymentMethodNotification.Buyer.Id);
@ -26,7 +26,7 @@ namespace Ordering.API.Application.EventHandlers
await _orderRepository.UnitOfWork
.SaveEntitiesAsync();
_logger.CreateLogger(nameof(PaymentMethodCheckedEventHandler))
_logger.CreateLogger(nameof(PaymentMethodCheckedDomainEventHandler))
.LogTrace($"Order with Id: {paymentMethodNotification.OrderId} has been successfully updated with a new payment method id: { paymentMethodNotification.Payment.Id }");
}
}

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

@ -3,7 +3,7 @@ using Autofac.Core;
using MediatR;
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands;
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Decorators;
using Ordering.API.Application.EventHandlers;
using Ordering.API.Application.DomainEventHandlers;
using Ordering.Domain.Events;
using System.Collections.Generic;
using System.Linq;
@ -24,7 +24,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Autof
.Select(i => new KeyedService("IAsyncRequestHandler", i)));
builder
.RegisterAssemblyTypes(typeof(OrderCreatedEventHandler).GetTypeInfo().Assembly)
.RegisterAssemblyTypes(typeof(OrderCreatedDomainEventHandler).GetTypeInfo().Assembly)
.Where(t => t.IsClosedTypeOf(typeof(IAsyncNotificationHandler<>)))
.AsImplementedInterfaces();


+ 2
- 2
src/Services/Ordering/Ordering.Domain/AggregatesModel/BuyerAggregate/Buyer.cs View File

@ -33,7 +33,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.B
if (existingPayment != null)
{
AddEvent(new PaymentMethodChecked(this, existingPayment, orderId));
AddDomainEvent(new PaymentMethodCheckedDomainEvent(this, existingPayment, orderId));
return existingPayment;
}
else
@ -41,7 +41,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.B
var payment = new PaymentMethod(cardTypeId, alias, cardNumber, securityNumber, cardHolderName, expiration);
_paymentMethods.Add(payment);
AddEvent(new PaymentMethodChecked(this, payment, orderId));
AddDomainEvent(new PaymentMethodCheckedDomainEvent(this, payment, orderId));
return payment;
}
}


+ 7
- 4
src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs View File

@ -51,7 +51,10 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O
_orderStatusId = OrderStatus.InProcess.Id;
_orderDate = DateTime.UtcNow;
Address = address;
AddCreatedOrderEvent(cardTypeId, cardNumber,
// Add the OrderCreatedEvent to the domain events collection
// to be raised/dispatched when comitting changes into the Database [ After DbContext.SaveChanges() ]
AddOrderCreatedDomainEvent(cardTypeId, cardNumber,
cardSecurityNumber, cardHolderName, cardExpiration);
}
@ -93,14 +96,14 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O
_buyerId = id;
}
private void AddCreatedOrderEvent(int cardTypeId, string cardNumber,
private void AddOrderCreatedDomainEvent(int cardTypeId, string cardNumber,
string cardSecurityNumber, string cardHolderName, DateTime cardExpiration)
{
var @orderCreatedEvent = new OrderCreated(
var orderCreatedDomainEvent = new OrderCreatedDomainEvent(
this, cardTypeId, cardNumber, cardSecurityNumber,
cardHolderName, cardExpiration);
AddEvent(@orderCreatedEvent);
AddDomainEvent(orderCreatedDomainEvent);
}
}
}

src/Services/Ordering/Ordering.Domain/Events/OrderCreated.cs → src/Services/Ordering/Ordering.Domain/Events/OrderCreatedDomainEvent.cs View File

@ -9,7 +9,7 @@ namespace Ordering.Domain.Events
/// <summary>
/// Event used when an order is created
/// </summary>
public class OrderCreated
public class OrderCreatedDomainEvent
: IAsyncNotification
{
public int CardTypeId { get; private set; }
@ -19,7 +19,7 @@ namespace Ordering.Domain.Events
public DateTime CardExpiration { get; private set; }
public Order Order { get; private set; }
public OrderCreated(Order order,
public OrderCreatedDomainEvent(Order order,
int cardTypeId, string cardNumber,
string cardSecurityNumber, string cardHolderName,
DateTime cardExpiration)

src/Services/Ordering/Ordering.Domain/Events/PaymentMethodChecked.cs → src/Services/Ordering/Ordering.Domain/Events/PaymentMethodCheckedDomainEvent.cs View File

@ -6,14 +6,14 @@ using System.Text;
namespace Ordering.Domain.Events
{
public class PaymentMethodChecked
public class PaymentMethodCheckedDomainEvent
: IAsyncNotification
{
public Buyer Buyer { get; private set; }
public PaymentMethod Payment { get; private set; }
public int OrderId { get; private set; }
public PaymentMethodChecked(Buyer buyer, PaymentMethod payment, int orderId)
public PaymentMethodCheckedDomainEvent(Buyer buyer, PaymentMethod payment, int orderId)
{
Buyer = buyer;
Payment = payment;

+ 8
- 8
src/Services/Ordering/Ordering.Domain/SeedWork/Entity.cs View File

@ -10,7 +10,7 @@
int? _requestedHashCode;
int _Id;
private List<IAsyncNotification> _events;
private List<IAsyncNotification> _domainEvents;
public virtual int Id
{
@ -24,17 +24,17 @@
}
}
public List<IAsyncNotification> Events => _events;
public void AddEvent(IAsyncNotification eventItem)
public List<IAsyncNotification> DomainEvents => _domainEvents;
public void AddDomainEvent(IAsyncNotification eventItem)
{
_events = _events ?? new List<IAsyncNotification>();
_events.Add(eventItem);
_domainEvents = _domainEvents ?? new List<IAsyncNotification>();
_domainEvents.Add(eventItem);
}
public void RemoveEvent(IAsyncNotification eventItem)
public void RemoveDomainEvent(IAsyncNotification eventItem)
{
if (_events is null) return;
_events.Remove(eventItem);
if (_domainEvents is null) return;
_domainEvents.Remove(eventItem);
}
public bool IsTransient()


+ 3
- 3
src/Services/Ordering/Ordering.Infrastructure/MediatorExtension.cs View File

@ -10,9 +10,9 @@ namespace Ordering.Infrastructure
{
public static async Task RaiseDomainEventsAsync(this IMediator mediator, OrderingContext ctx)
{
var domainEntities = ctx.ChangeTracker.Entries<Entity>().Where(x => x.Entity.Events != null && x.Entity.Events.Any());
var domainEvents = domainEntities.SelectMany(x => x.Entity.Events).ToList();
domainEntities.ToList().ForEach(entity => entity.Entity.Events.Clear());
var domainEntities = ctx.ChangeTracker.Entries<Entity>().Where(x => x.Entity.DomainEvents != null && x.Entity.DomainEvents.Any());
var domainEvents = domainEntities.SelectMany(x => x.Entity.DomainEvents).ToList();
domainEntities.ToList().ForEach(entity => entity.Entity.DomainEvents.Clear());
var tasks = domainEvents
.Select(async (domainEvent) => {


+ 6
- 4
src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs View File

@ -74,7 +74,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
buyerConfiguration.HasKey(b => b.Id);
buyerConfiguration.Ignore(b => b.Events);
buyerConfiguration.Ignore(b => b.DomainEvents);
buyerConfiguration.Property(b => b.Id)
.ForSqlServerUseSequenceHiLo("buyerseq", DEFAULT_SCHEMA);
@ -102,7 +102,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
paymentConfiguration.HasKey(b => b.Id);
paymentConfiguration.Ignore(b => b.Events);
paymentConfiguration.Ignore(b => b.DomainEvents);
paymentConfiguration.Property(b => b.Id)
.ForSqlServerUseSequenceHiLo("paymentseq", DEFAULT_SCHEMA);
@ -139,7 +139,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
orderConfiguration.HasKey(o => o.Id);
orderConfiguration.Ignore(b => b.Events);
orderConfiguration.Ignore(b => b.DomainEvents);
orderConfiguration.Property(o => o.Id)
.ForSqlServerUseSequenceHiLo("orderseq", DEFAULT_SCHEMA);
@ -176,7 +176,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
orderItemConfiguration.HasKey(o => o.Id);
orderItemConfiguration.Ignore(b => b.Events);
orderItemConfiguration.Ignore(b => b.DomainEvents);
orderItemConfiguration.Property(o => o.Id)
.ForSqlServerUseSequenceHiLo("orderitemseq");
@ -238,6 +238,8 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
public async Task<int> SaveEntitiesAsync(CancellationToken cancellationToken = default(CancellationToken))
{
var result = await base.SaveChangesAsync();
// Dispatch the Domain Events collection right after saving/commiting data into the database
await _mediator.RaiseDomainEventsAsync(this);
return result;
}


+ 15
- 8
src/Web/WebMVC/Controllers/OrderController.cs View File

@ -7,6 +7,7 @@ using Microsoft.eShopOnContainers.WebMVC.Services;
using Microsoft.eShopOnContainers.WebMVC.ViewModels;
using Microsoft.AspNetCore.Authorization;
using System.Net.Http;
using Polly.CircuitBreaker;
namespace Microsoft.eShopOnContainers.WebMVC.Controllers
{
@ -37,18 +38,24 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers
[HttpPost]
public async Task<IActionResult> Create(Order model, string action)
{
if (ModelState.IsValid)
try
{
var user = _appUserParser.Parse(HttpContext.User);
await _orderSvc.CreateOrder(model);
if (ModelState.IsValid)
{
var user = _appUserParser.Parse(HttpContext.User);
await _orderSvc.CreateOrder(model);
//Empty basket for current user.
await _basketSvc.CleanBasket(user);
//Empty basket for current user.
await _basketSvc.CleanBasket(user);
//Redirect to historic list.
return RedirectToAction("Index");
//Redirect to historic list.
return RedirectToAction("Index");
}
}
catch(BrokenCircuitException ex)
{
ModelState.AddModelError("Error", "Not possible to create a new order, please try later on");
}
return View(model);
}


+ 16
- 19
src/Web/WebMVC/Services/BasketService.cs View File

@ -1,29 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.eShopOnContainers.WebMVC.ViewModels;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using System.Net.Http;
using Microsoft.eShopOnContainers.WebMVC.ViewModels;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Microsoft.AspNetCore.Authentication;
using Microsoft.eShopOnContainers.WebMVC.Extensions;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using WebMVC.Services.Utilities;
namespace Microsoft.eShopOnContainers.WebMVC.Services
{
public class BasketService : IBasketService
{
private readonly IOptionsSnapshot<AppSettings> _settings;
private HttpClient _apiClient;
private IHttpClient _apiClient;
private readonly string _remoteServiceBaseUrl;
private IHttpContextAccessor _httpContextAccesor;
public BasketService(IOptionsSnapshot<AppSettings> settings, IHttpContextAccessor httpContextAccesor)
public BasketService(IOptionsSnapshot<AppSettings> settings, IHttpContextAccessor httpContextAccesor, IHttpClient httpClient)
{
_settings = settings;
_remoteServiceBaseUrl = _settings.Value.BasketUrl;
_httpContextAccesor = httpContextAccesor;
_apiClient = httpClient;
}
public async Task<Basket> GetBasket(ApplicationUser user)
@ -31,8 +31,7 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
var context = _httpContextAccesor.HttpContext;
var token = await context.Authentication.GetTokenAsync("access_token");
_apiClient = new HttpClient();
_apiClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
_apiClient.Inst.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
var basketUrl = $"{_remoteServiceBaseUrl}/{user.Id.ToString()}";
var dataString = await _apiClient.GetStringAsync(basketUrl);
@ -52,13 +51,12 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
{
var context = _httpContextAccesor.HttpContext;
var token = await context.Authentication.GetTokenAsync("access_token");
_apiClient = new HttpClient();
_apiClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
_apiClient.Inst.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
var basketUrl = _remoteServiceBaseUrl;
StringContent content = new StringContent(JsonConvert.SerializeObject(basket), System.Text.Encoding.UTF8, "application/json");
var response = await _apiClient.PostAsync(basketUrl, content);
var response = await _apiClient.PostAsync(basketUrl, basket);
return basket;
}
@ -120,8 +118,7 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
var context = _httpContextAccesor.HttpContext;
var token = await context.Authentication.GetTokenAsync("access_token");
_apiClient = new HttpClient();
_apiClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
_apiClient.Inst.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
var basketUrl = $"{_remoteServiceBaseUrl}/{user.Id.ToString()}";
var response = await _apiClient.DeleteAsync(basketUrl);


+ 10
- 22
src/Web/WebMVC/Services/CatalogService.cs View File

@ -1,35 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.eShopOnContainers.WebMVC.ViewModels;
using Microsoft.CodeAnalysis.Options;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Net.Http;
using Microsoft.AspNetCore.Mvc.Rendering;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Threading.Tasks;
using WebMVC.Services.Utilities;
namespace Microsoft.eShopOnContainers.WebMVC.Services
{
public class CatalogService : ICatalogService
{
private readonly IOptionsSnapshot<AppSettings> _settings;
private HttpClient _apiClient;
private IHttpClient _apiClient;
private readonly string _remoteServiceBaseUrl;
public CatalogService(IOptionsSnapshot<AppSettings> settings, ILoggerFactory loggerFactory) {
public CatalogService(IOptionsSnapshot<AppSettings> settings, ILoggerFactory loggerFactory, IHttpClient httpClient) {
_settings = settings;
_remoteServiceBaseUrl = $"{_settings.Value.CatalogUrl}/api/v1/catalog/";
_apiClient = httpClient;
var log = loggerFactory.CreateLogger("catalog service");
log.LogDebug(settings.Value.CatalogUrl);
}
public async Task<Catalog> GetCatalogItems(int page,int take, int? brand, int? type)
{
_apiClient = new HttpClient();
var itemsQs = $"items?pageIndex={page}&pageSize={take}";
var filterQs = "";
@ -45,16 +41,10 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
var dataString = "";
//
// Using HttpClient with Retry and Exponential Backoff
// Using a HttpClient wrapper with Retry and Exponential Backoff
//
var retry = new RetryWithExponentialBackoff();
await retry.RunAsync(async () =>
{
// work with HttpClient call
dataString = await _apiClient.GetStringAsync(catalogUrl);
});
dataString = await _apiClient.GetStringAsync(catalogUrl);
//var dataString = await _apiClient.GetStringAsync(catalogUrl);
var response = JsonConvert.DeserializeObject<Catalog>(dataString);
return response;
@ -62,7 +52,6 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
public async Task<IEnumerable<SelectListItem>> GetBrands()
{
_apiClient = new HttpClient();
var url = $"{_remoteServiceBaseUrl}catalogBrands";
var dataString = await _apiClient.GetStringAsync(url);
@ -81,7 +70,6 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
public async Task<IEnumerable<SelectListItem>> GetTypes()
{
_apiClient = new HttpClient();
var url = $"{_remoteServiceBaseUrl}catalogTypes";
var dataString = await _apiClient.GetStringAsync(url);


+ 11
- 14
src/Web/WebMVC/Services/OrderingService.cs View File

@ -8,30 +8,30 @@ using Microsoft.Extensions.Options;
using System.Net.Http;
using Newtonsoft.Json;
using Microsoft.AspNetCore.Authentication;
using WebMVC.Services.Utilities;
namespace Microsoft.eShopOnContainers.WebMVC.Services
{
public class OrderingService : IOrderingService
{
private HttpClient _apiClient;
private IHttpClient _apiClient;
private readonly string _remoteServiceBaseUrl;
private readonly IOptionsSnapshot<AppSettings> _settings;
private readonly IHttpContextAccessor _httpContextAccesor;
public OrderingService(IOptionsSnapshot<AppSettings> settings, IHttpContextAccessor httpContextAccesor)
public OrderingService(IOptionsSnapshot<AppSettings> settings, IHttpContextAccessor httpContextAccesor, IHttpClient httpClient)
{
_remoteServiceBaseUrl = $"{settings.Value.OrderingUrl}/api/v1/orders";
_settings = settings;
_httpContextAccesor = httpContextAccesor;
_apiClient = httpClient;
}
async public Task<Order> GetOrder(ApplicationUser user, string Id)
{
var context = _httpContextAccesor.HttpContext;
var token = await context.Authentication.GetTokenAsync("access_token");
_apiClient = new HttpClient();
_apiClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
_apiClient.Inst.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
var ordersUrl = $"{_remoteServiceBaseUrl}/{Id}";
var dataString = await _apiClient.GetStringAsync(ordersUrl);
@ -46,8 +46,7 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
var context = _httpContextAccesor.HttpContext;
var token = await context.Authentication.GetTokenAsync("access_token");
_apiClient = new HttpClient();
_apiClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
_apiClient.Inst.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
var ordersUrl = _remoteServiceBaseUrl;
var dataString = await _apiClient.GetStringAsync(ordersUrl);
@ -77,17 +76,15 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
var context = _httpContextAccesor.HttpContext;
var token = await context.Authentication.GetTokenAsync("access_token");
_apiClient = new HttpClient();
_apiClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
_apiClient.DefaultRequestHeaders.Add("x-requestid", order.RequestId.ToString());
_apiClient.Inst.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
_apiClient.Inst.DefaultRequestHeaders.Add("x-requestid", order.RequestId.ToString());
var ordersUrl = $"{_remoteServiceBaseUrl}/new";
order.CardTypeId = 1;
order.CardExpirationApiFormat();
SetFakeIdToProducts(order);
StringContent content = new StringContent(JsonConvert.SerializeObject(order), System.Text.Encoding.UTF8, "application/json");
var response = await _apiClient.PostAsync(ordersUrl, content);
var response = await _apiClient.PostAsync(ordersUrl, order);
if (response.StatusCode == System.Net.HttpStatusCode.InternalServerError)
throw new Exception("Error creating order, try later");


+ 45
- 0
src/Web/WebMVC/Services/Utilities/HttpApiClient.cs View File

@ -0,0 +1,45 @@
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace WebMVC.Services.Utilities
{
public class HttpApiClient : IHttpClient
{
private HttpClient _client;
private ILogger _logger;
public HttpClient Inst => _client;
public HttpApiClient()
{
_client = new HttpClient();
_logger = new LoggerFactory().CreateLogger(nameof(HttpApiClientWrapper));
}
public async Task<string> GetStringAsync(string uri)
{
return await HttpInvoker(async () =>
await _client.GetStringAsync(uri));
}
public async Task<HttpResponseMessage> PostAsync<T>(string uri, T item)
{
var contentString = new StringContent(JsonConvert.SerializeObject(item), System.Text.Encoding.UTF8, "application/json");
return await HttpInvoker(async () =>
await _client.PostAsync(uri, contentString));
}
public async Task<HttpResponseMessage> DeleteAsync(string uri)
{
return await HttpInvoker(async () =>
await _client.DeleteAsync(uri));
}
private async Task<T> HttpInvoker<T>(Func<Task<T>> action)
{
return await action();
}
}
}

+ 99
- 0
src/Web/WebMVC/Services/Utilities/HttpApiClientWrapper.cs View File

@ -0,0 +1,99 @@
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Polly;
using Polly.Wrap;
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace WebMVC.Services.Utilities
{
public class HttpApiClientWrapper : IHttpClient
{
private HttpClient _client;
private PolicyWrap _policyWrapper;
private ILogger _logger;
public HttpClient Inst => _client;
public HttpApiClientWrapper()
{
_client = new HttpClient();
_logger = new LoggerFactory().CreateLogger(nameof(HttpApiClientWrapper));
// Add Policies to be applied
_policyWrapper = Policy.WrapAsync(
CreateRetryPolicy(),
CreateCircuitBreakerPolicy()
);
}
private Policy CreateCircuitBreakerPolicy()
{
return Policy
.Handle<HttpRequestException>()
.CircuitBreakerAsync(
// number of exceptions before breaking circuit
3,
// time circuit opened before retry
TimeSpan.FromMinutes(1),
(exception, duration) => {
// on circuit opened
_logger.LogTrace("Circuit breaker opened");
},
() => {
// on circuit closed
_logger.LogTrace("Circuit breaker reset");
}
);
}
private Policy CreateRetryPolicy()
{
return Policy
.Handle<HttpRequestException>()
.WaitAndRetryAsync(
// number of retries
3,
// exponential backofff
retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
// on retry
(exception, timeSpan, retryCount, context) =>
{
_logger.LogTrace($"Retry {retryCount} " +
$"of {context.PolicyKey} " +
$"at {context.ExecutionKey}, " +
$"due to: {exception}.");
});
}
public async Task<string> GetStringAsync(string uri)
{
return await HttpInvoker(async () =>
await _client.GetStringAsync(uri));
}
public async Task<HttpResponseMessage> PostAsync<T>(string uri, T item)
{
// a new StringContent must be created for each retry
// as it is disposed after each call
return await HttpInvoker(async () =>
await _client.PostAsync(uri,
new StringContent(JsonConvert.SerializeObject(item),
System.Text.Encoding.UTF8, "application/json")));
}
public async Task<HttpResponseMessage> DeleteAsync(string uri)
{
return await HttpInvoker(async () =>
await _client.DeleteAsync(uri));
}
private async Task<T> HttpInvoker<T>(Func<Task<T>> action)
{
// Executes the action applying all
// the policies defined in the wrapper
return await _policyWrapper
.ExecuteAsync(async () => await action());
}
}
}

+ 16
- 0
src/Web/WebMVC/Services/Utilities/IHttpClient.cs View File

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
namespace WebMVC.Services.Utilities
{
public interface IHttpClient
{
HttpClient Inst { get; }
Task<string> GetStringAsync(string uri);
Task<HttpResponseMessage> PostAsync<T>(string uri, T item);
Task<HttpResponseMessage> DeleteAsync(string uri);
}
}

+ 0
- 90
src/Web/WebMVC/Services/Utilities/RetryWithExponentialBackoff.cs View File

@ -1,90 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.WebMVC.Services
{
/// <summary>
/// When working with cloud services and Docker containers, it's very important to always catch
/// TimeoutException, and retry the operation.
/// RetryWithExponentialBackoff makes it easy to implement such pattern.
/// Usage:
/// var retry = new RetryWithExponentialBackoff();
/// await retry.RunAsync(async ()=>
/// {
/// // work with HttpClient
/// });
/// </summary>
public sealed class RetryWithExponentialBackoff
{
private readonly int maxRetries, delayMilliseconds, maxDelayMilliseconds;
public RetryWithExponentialBackoff(int maxRetries = 50, int delayMilliseconds = 200, int maxDelayMilliseconds = 2000)
{
this.maxRetries = maxRetries;
this.delayMilliseconds = delayMilliseconds;
this.maxDelayMilliseconds = maxDelayMilliseconds;
}
public async Task RunAsync(Func<Task> func)
{
ExponentialBackoff backoff = new ExponentialBackoff(this.maxRetries, this.delayMilliseconds, this.maxDelayMilliseconds);
retry:
try
{
await func();
}
catch (Exception ex) when (ex is TimeoutException || ex is System.Net.Http.HttpRequestException)
{
Debug.WriteLine("Exception raised is: " + ex.GetType().ToString() + " -- Message: " + ex.Message + " -- Inner Message: " + ex.InnerException.Message);
await backoff.Delay();
goto retry;
}
}
}
/// <summary>
/// Usage:
/// ExponentialBackoff backoff = new ExponentialBackoff(3, 10, 100);
/// retry:
/// try {
/// // ...
/// }
/// catch (Exception ex) {
/// await backoff.Delay(cancellationToken);
/// goto retry;
/// }
/// </summary>
public struct ExponentialBackoff
{
private readonly int m_maxRetries, m_delayMilliseconds, m_maxDelayMilliseconds;
private int m_retries, m_pow;
public ExponentialBackoff(int maxRetries, int delayMilliseconds, int maxDelayMilliseconds)
{
m_maxRetries = maxRetries;
m_delayMilliseconds = delayMilliseconds;
m_maxDelayMilliseconds = maxDelayMilliseconds;
m_retries = 0;
m_pow = 1;
}
public Task Delay()
{
if (m_retries == m_maxRetries)
{
throw new TimeoutException("Max retry attempts exceeded.");
}
++m_retries;
if (m_retries < 31)
{
m_pow = m_pow << 1; // m_pow = Pow(2, m_retries - 1)
}
int delay = Math.Min(m_delayMilliseconds * (m_pow - 1) / 2, m_maxDelayMilliseconds);
return Task.Delay(delay);
}
}
}

+ 12
- 3
src/Web/WebMVC/Startup.cs View File

@ -14,6 +14,7 @@ using Microsoft.IdentityModel.Tokens;
using Microsoft.AspNetCore.Http;
using System.Threading;
using Microsoft.Extensions.Options;
using WebMVC.Services.Utilities;
namespace Microsoft.eShopOnContainers.WebMVC
{
@ -43,14 +44,22 @@ namespace Microsoft.eShopOnContainers.WebMVC
{
services.AddMvc();
services.Configure<AppSettings>(Configuration);
// Add application services.
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddTransient<ICatalogService, CatalogService>();
services.AddTransient<IOrderingService, OrderingService>();
services.AddTransient<IBasketService, BasketService>();
services.AddTransient<IIdentityParser<ApplicationUser>, IdentityParser>();
if(Configuration.GetValue<string>("ActivateCircuitBreaker") == bool.TrueString)
{
services.AddSingleton<IHttpClient, HttpApiClientWrapper>();
}
else
{
services.AddSingleton<IHttpClient, HttpApiClient>();
}
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.


+ 7
- 0
src/Web/WebMVC/Views/Order/Create.cshtml View File

@ -11,6 +11,13 @@
<div class="container">
<form method="post" asp-controller="Order" asp-action="Create">
<section class="esh-orders_new-section">
<div class="row">
@foreach (var error in ViewData.ModelState.Values.SelectMany(err => err.Errors)) {
<div class="alert alert-warning" role="alert">
&nbsp;@error.ErrorMessage
</div>
}
</div>
<h4 class="esh-orders_new-title">Shipping address</h4>
<div class="row">
<div class="col-md-6">


+ 1
- 0
src/Web/WebMVC/WebMVC.csproj View File

@ -32,6 +32,7 @@
<PrivateAssets>All</PrivateAssets>
</PackageReference>
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
<PackageReference Include="Polly" Version="5.0.6" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="5.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="1.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="1.1.0" />


+ 1
- 0
src/Web/WebMVC/appsettings.json View File

@ -4,6 +4,7 @@
"BasketUrl": "http://localhost:5103",
"IdentityUrl": "http://localhost:5105",
"CallBackUrl": "http://localhost:5100/",
"ActivateCircuitBreaker": "True",
"Logging": {
"IncludeScopes": false,
"LogLevel": {


+ 1
- 1
test/Services/UnitTest/Ordering/Domain/BuyerAggregateTest.cs View File

@ -122,6 +122,6 @@ public class BuyerAggregateTest
fakeBuyer.AddPaymentMethod(cardTypeId, alias, cardNumber, cardSecurityNumber, cardHolderName, cardExpiration, orderId);
//Assert
Assert.Equal(fakeBuyer.Events.Count, expectedResult);
Assert.Equal(fakeBuyer.DomainEvents.Count, expectedResult);
}
}

+ 7
- 7
test/Services/UnitTest/Ordering/Domain/OrderAggregateTest.cs View File

@ -112,7 +112,7 @@ public class OrderAggregateTest
var fakeOrder = new Order(new Address(street, city, state, country, zipcode), cardTypeId, cardNumber, cardSecurityNumber, cardHolderName, cardExpiration);
//Assert
Assert.Equal(fakeOrder.Events.Count, expectedResult);
Assert.Equal(fakeOrder.DomainEvents.Count, expectedResult);
}
[Fact]
@ -133,9 +133,9 @@ public class OrderAggregateTest
//Act
var fakeOrder = new Order(new Address(street, city, state, country, zipcode), cardTypeId, cardNumber, cardSecurityNumber, cardHolderName, cardExpiration);
fakeOrder.AddEvent(new OrderCreated(fakeOrder,cardTypeId,cardNumber,cardSecurityNumber,cardHolderName,cardExpiration));
fakeOrder.AddDomainEvent(new OrderCreatedDomainEvent(fakeOrder,cardTypeId,cardNumber,cardSecurityNumber,cardHolderName,cardExpiration));
//Assert
Assert.Equal(fakeOrder.Events.Count, expectedResult);
Assert.Equal(fakeOrder.DomainEvents.Count, expectedResult);
}
[Fact]
@ -153,13 +153,13 @@ public class OrderAggregateTest
var cardHolderName = "FakeName";
var cardExpiration = DateTime.Now.AddYears(1);
var fakeOrder = new Order(new Address(street, city, state, country, zipcode), cardTypeId, cardNumber, cardSecurityNumber, cardHolderName, cardExpiration);
var @fakeEvent = new OrderCreated(fakeOrder, cardTypeId, cardNumber, cardSecurityNumber, cardHolderName, cardExpiration);
var @fakeEvent = new OrderCreatedDomainEvent(fakeOrder, cardTypeId, cardNumber, cardSecurityNumber, cardHolderName, cardExpiration);
var expectedResult = 1;
//Act
fakeOrder.AddEvent(@fakeEvent);
fakeOrder.RemoveEvent(@fakeEvent);
fakeOrder.AddDomainEvent(@fakeEvent);
fakeOrder.RemoveDomainEvent(@fakeEvent);
//Assert
Assert.Equal(fakeOrder.Events.Count, expectedResult);
Assert.Equal(fakeOrder.DomainEvents.Count, expectedResult);
}
}

Loading…
Cancel
Save