diff --git a/src/BuildingBlocks/EventBus/EventBus.Tests/InMemory_SubscriptionManager_Tests.cs b/src/BuildingBlocks/EventBus/EventBus.Tests/InMemory_SubscriptionManager_Tests.cs index dd5f7f5b4..13ae99afa 100644 --- a/src/BuildingBlocks/EventBus/EventBus.Tests/InMemory_SubscriptionManager_Tests.cs +++ b/src/BuildingBlocks/EventBus/EventBus.Tests/InMemory_SubscriptionManager_Tests.cs @@ -18,7 +18,7 @@ namespace EventBus.Tests public void After_One_Event_Subscription_Should_Contain_The_Event() { var manager = new InMemoryEventBusSubscriptionsManager(); - manager.AddSubscription(() => new TestIntegrationEventHandler()); + manager.AddSubscription(); Assert.True(manager.HasSubscriptionsForEvent()); } @@ -26,7 +26,7 @@ namespace EventBus.Tests public void After_All_Subscriptions_Are_Deleted_Event_Should_No_Longer_Exists() { var manager = new InMemoryEventBusSubscriptionsManager(); - manager.AddSubscription(() => new TestIntegrationEventHandler()); + manager.AddSubscription(); manager.RemoveSubscription(); Assert.False(manager.HasSubscriptionsForEvent()); } @@ -37,7 +37,7 @@ namespace EventBus.Tests bool raised = false; var manager = new InMemoryEventBusSubscriptionsManager(); manager.OnEventRemoved += (o, e) => raised = true; - manager.AddSubscription(() => new TestIntegrationEventHandler()); + manager.AddSubscription(); manager.RemoveSubscription(); Assert.True(raised); } @@ -46,8 +46,8 @@ namespace EventBus.Tests public void Get_Handlers_For_Event_Should_Return_All_Handlers() { var manager = new InMemoryEventBusSubscriptionsManager(); - manager.AddSubscription(() => new TestIntegrationEventHandler()); - manager.AddSubscription(() => new TestIntegrationOtherEventHandler()); + manager.AddSubscription(); + manager.AddSubscription(); var handlers = manager.GetHandlersForEvent(); Assert.Equal(2, handlers.Count()); } diff --git a/src/BuildingBlocks/EventBus/EventBus/Abstractions/IEventBus.cs b/src/BuildingBlocks/EventBus/EventBus/Abstractions/IEventBus.cs index 7dd91541b..21436d3cd 100644 --- a/src/BuildingBlocks/EventBus/EventBus/Abstractions/IEventBus.cs +++ b/src/BuildingBlocks/EventBus/EventBus/Abstractions/IEventBus.cs @@ -5,10 +5,10 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions { public interface IEventBus { - void Subscribe(Func handler) + void Subscribe() where T : IntegrationEvent where TH : IIntegrationEventHandler; - void SubscribeDynamic(string eventName, Func handler) + void SubscribeDynamic(string eventName) where TH : IDynamicIntegrationEventHandler; void UnsubscribeDynamic(string eventName) diff --git a/src/BuildingBlocks/EventBus/EventBus/IEventBusSubscriptionsManager.cs b/src/BuildingBlocks/EventBus/EventBus/IEventBusSubscriptionsManager.cs index d46292356..c83c505b1 100644 --- a/src/BuildingBlocks/EventBus/EventBus/IEventBusSubscriptionsManager.cs +++ b/src/BuildingBlocks/EventBus/EventBus/IEventBusSubscriptionsManager.cs @@ -10,10 +10,10 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus { bool IsEmpty { get; } event EventHandler OnEventRemoved; - void AddDynamicSubscription(string eventName, Func handler) + void AddDynamicSubscription(string eventName) where TH : IDynamicIntegrationEventHandler; - void AddSubscription(Func handler) + void AddSubscription() where T : IntegrationEvent where TH : IIntegrationEventHandler; diff --git a/src/BuildingBlocks/EventBus/EventBus/InMemoryEventBusSubscriptionsManager.cs b/src/BuildingBlocks/EventBus/EventBus/InMemoryEventBusSubscriptionsManager.cs index e85ef7064..88be8cf96 100644 --- a/src/BuildingBlocks/EventBus/EventBus/InMemoryEventBusSubscriptionsManager.cs +++ b/src/BuildingBlocks/EventBus/EventBus/InMemoryEventBusSubscriptionsManager.cs @@ -26,34 +26,41 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus public bool IsEmpty => !_handlers.Keys.Any(); public void Clear() => _handlers.Clear(); - public void AddDynamicSubscription(string eventName, Func handler) + public void AddDynamicSubscription(string eventName) where TH : IDynamicIntegrationEventHandler { - DoAddSubscription(handler, eventName, isDynamic: true); + DoAddSubscription(typeof(TH), eventName, isDynamic: true); } - public void AddSubscription(Func handler) + public void AddSubscription() where T : IntegrationEvent where TH : IIntegrationEventHandler { var eventName = GetEventKey(); - DoAddSubscription(handler, eventName, isDynamic: false); + DoAddSubscription(typeof(TH), eventName, isDynamic: false); _eventTypes.Add(typeof(T)); } - private void DoAddSubscription(Delegate handler, string eventName, bool isDynamic) + private void DoAddSubscription(Type handlerType, string eventName, bool isDynamic) { if (!HasSubscriptionsForEvent(eventName)) { _handlers.Add(eventName, new List()); } + + if (_handlers[eventName].Any(s => s.HandlerType == handlerType)) + { + throw new ArgumentException( + $"Handler Type {handlerType.Name} already registered for '{eventName}'", nameof(handlerType)); + } + if (isDynamic) { - _handlers[eventName].Add(SubscriptionInfo.Dynamic(handler)); + _handlers[eventName].Add(SubscriptionInfo.Dynamic(handlerType)); } else { - _handlers[eventName].Add(SubscriptionInfo.Typed(handler)); + _handlers[eventName].Add(SubscriptionInfo.Typed(handlerType)); } } @@ -115,7 +122,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus private SubscriptionInfo FindDynamicSubscriptionToRemove(string eventName) where TH : IDynamicIntegrationEventHandler { - return DoFindHandlerToRemove(eventName, typeof(TH)); + return DoFindSubscriptionToRemove(eventName, typeof(TH)); } @@ -124,25 +131,18 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus where TH : IIntegrationEventHandler { var eventName = GetEventKey(); - return DoFindHandlerToRemove(eventName, typeof(TH)); + return DoFindSubscriptionToRemove(eventName, typeof(TH)); } - private SubscriptionInfo DoFindHandlerToRemove(string eventName, Type handlerType) + private SubscriptionInfo DoFindSubscriptionToRemove(string eventName, Type handlerType) { if (!HasSubscriptionsForEvent(eventName)) { return null; } - foreach (var subscription in _handlers[eventName]) - { - var genericArgs = subscription.Factory.GetType().GetGenericArguments(); - if (genericArgs.SingleOrDefault() == handlerType) - { - return subscription; - } - } - return null; + return _handlers[eventName].SingleOrDefault(s => s.HandlerType == handlerType); + } public bool HasSubscriptionsForEvent() where T : IntegrationEvent diff --git a/src/BuildingBlocks/EventBus/EventBus/SubscriptionInfo.cs b/src/BuildingBlocks/EventBus/EventBus/SubscriptionInfo.cs index 33c0aec26..a20b3031c 100644 --- a/src/BuildingBlocks/EventBus/EventBus/SubscriptionInfo.cs +++ b/src/BuildingBlocks/EventBus/EventBus/SubscriptionInfo.cs @@ -7,21 +7,21 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus public class SubscriptionInfo { public bool IsDynamic { get; } - public Delegate Factory { get; } + public Type HandlerType{ get; } - private SubscriptionInfo(bool isDynamic, Delegate factory) + private SubscriptionInfo(bool isDynamic, Type handlerType) { IsDynamic = isDynamic; - Factory = factory; + HandlerType = handlerType; } - public static SubscriptionInfo Dynamic(Delegate factory) + public static SubscriptionInfo Dynamic(Type handlerType) { - return new SubscriptionInfo(true, factory); + return new SubscriptionInfo(true, handlerType); } - public static SubscriptionInfo Typed(Delegate factory) + public static SubscriptionInfo Typed(Type handlerType) { - return new SubscriptionInfo(false, factory); + return new SubscriptionInfo(false, handlerType); } } } diff --git a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs index 3d32073a3..07a130f22 100644 --- a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs +++ b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs @@ -1,4 +1,5 @@ -using Microsoft.eShopOnContainers.BuildingBlocks.EventBus; +using Autofac; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; using Microsoft.Extensions.Logging; @@ -26,17 +27,20 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ private readonly IRabbitMQPersistentConnection _persistentConnection; private readonly ILogger _logger; private readonly IEventBusSubscriptionsManager _subsManager; - + private readonly ILifetimeScope _autofac; + private readonly string AUTOFAC_SCOPE_NAME = "eshop_event_bus"; private IModel _consumerChannel; private string _queueName; - public EventBusRabbitMQ(IRabbitMQPersistentConnection persistentConnection, ILogger logger, IEventBusSubscriptionsManager subsManager) + public EventBusRabbitMQ(IRabbitMQPersistentConnection persistentConnection, ILogger logger, + ILifetimeScope autofac, IEventBusSubscriptionsManager subsManager) { _persistentConnection = persistentConnection ?? throw new ArgumentNullException(nameof(persistentConnection)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _subsManager = subsManager ?? new InMemoryEventBusSubscriptionsManager(); _consumerChannel = CreateConsumerChannel(); + _autofac = autofac; _subsManager.OnEventRemoved += SubsManager_OnEventRemoved; } @@ -97,20 +101,20 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ } } - public void SubscribeDynamic(string eventName, Func handler) - where TH: IDynamicIntegrationEventHandler + public void SubscribeDynamic(string eventName) + where TH : IDynamicIntegrationEventHandler { DoInternalSubscription(eventName); - _subsManager.AddDynamicSubscription(eventName,handler); + _subsManager.AddDynamicSubscription(eventName); } - public void Subscribe(Func handler) + public void Subscribe() where T : IntegrationEvent where TH : IIntegrationEventHandler { var eventName = _subsManager.GetEventKey(); DoInternalSubscription(eventName); - _subsManager.AddSubscription(handler); + _subsManager.AddSubscription(); } private void DoInternalSubscription(string eventName) @@ -140,7 +144,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ } public void UnsubscribeDynamic(string eventName) - where TH: IDynamicIntegrationEventHandler + where TH : IDynamicIntegrationEventHandler { _subsManager.RemoveDynamicSubscription(eventName); } @@ -195,25 +199,28 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ private async Task ProcessEvent(string eventName, string message) { + if (_subsManager.HasSubscriptionsForEvent(eventName)) { - var subscriptions = _subsManager.GetHandlersForEvent(eventName); - - foreach (var subscription in subscriptions) + using (var scope = _autofac.BeginLifetimeScope(AUTOFAC_SCOPE_NAME)) { - if (subscription.IsDynamic) - { - var handler = subscription.Factory.DynamicInvoke() as IDynamicIntegrationEventHandler; - dynamic eventData = JObject.Parse(message); - await handler.Handle(eventData); - } - else + var subscriptions = _subsManager.GetHandlersForEvent(eventName); + foreach (var subscription in subscriptions) { - var eventType = _subsManager.GetEventTypeByName(eventName); - var integrationEvent = JsonConvert.DeserializeObject(message, eventType); - var handler = subscription.Factory.DynamicInvoke(); - var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType); - await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent }); + if (subscription.IsDynamic) + { + var handler = scope.ResolveOptional(subscription.HandlerType) as IDynamicIntegrationEventHandler; + dynamic eventData = JObject.Parse(message); + await handler.Handle(eventData); + } + else + { + var eventType = _subsManager.GetEventTypeByName(eventName); + var integrationEvent = JsonConvert.DeserializeObject(message, eventType); + var handler = scope.ResolveOptional(subscription.HandlerType); + var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType); + await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent }); + } } } } diff --git a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj index 023a5d5ec..fd2b44b30 100644 --- a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj +++ b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj @@ -7,6 +7,7 @@ + diff --git a/src/Services/Basket/Basket.API/Basket.API.csproj b/src/Services/Basket/Basket.API/Basket.API.csproj index b3ba97b10..de78d78b6 100644 --- a/src/Services/Basket/Basket.API/Basket.API.csproj +++ b/src/Services/Basket/Basket.API/Basket.API.csproj @@ -22,6 +22,7 @@ + diff --git a/src/Services/Basket/Basket.API/Controllers/BasketController.cs b/src/Services/Basket/Basket.API/Controllers/BasketController.cs index db5fcd318..9a120efc0 100644 --- a/src/Services/Basket/Basket.API/Controllers/BasketController.cs +++ b/src/Services/Basket/Basket.API/Controllers/BasketController.cs @@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Basket.API.IntegrationEvents.Events; using Microsoft.eShopOnContainers.Services.Basket.API.Services; +using Basket.API.Model; namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers { @@ -50,20 +51,23 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers return Ok(basket); } - [Route("checkouts")] - [HttpPost] - public async Task Checkout() + [Route("checkout")] + [HttpPut] + public async Task Checkout([FromBody]BasketCheckout value) { var userId = _identitySvc.GetUserIdentity(); var basket = await _repository.GetBasketAsync(userId); - _eventBus.Publish(new UserCheckoutAccepted(userId, basket)); + var eventMessage = new UserCheckoutAcceptedIntegrationEvent(userId, value.City, value.Street, + value.State, value.Country, value.ZipCode, value.CardNumber, value.CardHolderName, + value.CardExpiration, value.CardSecurityNumber, value.CardTypeId, value.Buyer, value.RequestId, basket); + + _eventBus.Publish(eventMessage); + if (basket == null) { return BadRequest(); } - - return Accepted(); } diff --git a/src/Services/Basket/Basket.API/IntegrationEvents/Events/UserCheckoutAccepted.cs b/src/Services/Basket/Basket.API/IntegrationEvents/Events/UserCheckoutAccepted.cs deleted file mode 100644 index 2d3e15ff3..000000000 --- a/src/Services/Basket/Basket.API/IntegrationEvents/Events/UserCheckoutAccepted.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; -using Microsoft.eShopOnContainers.Services.Basket.API.Model; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Basket.API.IntegrationEvents.Events -{ - public class UserCheckoutAccepted : IntegrationEvent - { - public string UserId {get; } - CustomerBasket Basket { get; } - public UserCheckoutAccepted(string userId, CustomerBasket basket) - { - UserId = userId; - Basket = basket; - } - - } -} diff --git a/src/Services/Basket/Basket.API/IntegrationEvents/Events/UserCheckoutAcceptedIntegrationEvent.cs b/src/Services/Basket/Basket.API/IntegrationEvents/Events/UserCheckoutAcceptedIntegrationEvent.cs new file mode 100644 index 000000000..4c02612c5 --- /dev/null +++ b/src/Services/Basket/Basket.API/IntegrationEvents/Events/UserCheckoutAcceptedIntegrationEvent.cs @@ -0,0 +1,64 @@ +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; +using Microsoft.eShopOnContainers.Services.Basket.API.Model; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Basket.API.IntegrationEvents.Events +{ + public class UserCheckoutAcceptedIntegrationEvent : IntegrationEvent + { + public string UserId { get; } + + public int OrderNumber { get; set; } + + public string City { get; set; } + + public string Street { get; set; } + + public string State { get; set; } + + public string Country { get; set; } + + public string ZipCode { get; set; } + + public string CardNumber { get; set; } + + public string CardHolderName { get; set; } + + public DateTime CardExpiration { get; set; } + + public string CardSecurityNumber { get; set; } + + public int CardTypeId { get; set; } + + public string Buyer { get; set; } + + public Guid RequestId { get; set; } + + public CustomerBasket Basket { get; } + + public UserCheckoutAcceptedIntegrationEvent(string userId, 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; + } + + } +} diff --git a/src/Services/Basket/Basket.API/Model/BasketCheckout.cs b/src/Services/Basket/Basket.API/Model/BasketCheckout.cs new file mode 100644 index 000000000..5241a3672 --- /dev/null +++ b/src/Services/Basket/Basket.API/Model/BasketCheckout.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Basket.API.Model +{ + public class BasketCheckout + { + public string City { get; set; } + + public string Street { get; set; } + + public string State { get; set; } + + public string Country { get; set; } + + public string ZipCode { get; set; } + + public string CardNumber { get; set; } + + public string CardHolderName { get; set; } + + public DateTime CardExpiration { get; set; } + + public string CardSecurityNumber { get; set; } + + public int CardTypeId { get; set; } + + public string Buyer { get; set; } + + public Guid RequestId { get; set; } + } +} + diff --git a/src/Services/Basket/Basket.API/Startup.cs b/src/Services/Basket/Basket.API/Startup.cs index bf63b2c29..2c1e70645 100644 --- a/src/Services/Basket/Basket.API/Startup.cs +++ b/src/Services/Basket/Basket.API/Startup.cs @@ -23,6 +23,8 @@ using System.Threading.Tasks; using System; using Microsoft.eShopOnContainers.Services.Basket.API.Services; using Microsoft.AspNetCore.Http; +using Autofac; +using Autofac.Extensions.DependencyInjection; namespace Microsoft.eShopOnContainers.Services.Basket.API { @@ -41,7 +43,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API public IConfigurationRoot Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) + public IServiceProvider ConfigureServices(IServiceCollection services) { services.AddHealthChecks(checks => @@ -112,6 +114,10 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API services.AddTransient(); services.AddTransient(); RegisterServiceBus(services); + + var container = new ContainerBuilder(); + container.Populate(services); + return new AutofacServiceProvider(container.Build()); } private void RegisterServiceBus(IServiceCollection services) @@ -167,11 +173,8 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API var eventBus = app.ApplicationServices.GetRequiredService(); - eventBus.Subscribe - (() => app.ApplicationServices.GetRequiredService()); - - eventBus.Subscribe - (() => app.ApplicationServices.GetRequiredService()); + eventBus.Subscribe(); + eventBus.Subscribe(); } } } diff --git a/src/Services/Catalog/Catalog.API/Catalog.API.csproj b/src/Services/Catalog/Catalog.API/Catalog.API.csproj index 4306d6922..3c2686278 100644 --- a/src/Services/Catalog/Catalog.API/Catalog.API.csproj +++ b/src/Services/Catalog/Catalog.API/Catalog.API.csproj @@ -29,6 +29,7 @@ + diff --git a/src/Services/Catalog/Catalog.API/Startup.cs b/src/Services/Catalog/Catalog.API/Startup.cs index 9eb195674..509048d4c 100644 --- a/src/Services/Catalog/Catalog.API/Startup.cs +++ b/src/Services/Catalog/Catalog.API/Startup.cs @@ -1,5 +1,7 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API { + using Autofac; + using Autofac.Extensions.DependencyInjection; using global::Catalog.API.Infrastructure.Filters; using global::Catalog.API.IntegrationEvents; using Microsoft.AspNetCore.Builder; @@ -44,7 +46,7 @@ Configuration = builder.Build(); } - public void ConfigureServices(IServiceCollection services) + public IServiceProvider ConfigureServices(IServiceCollection services) { // Add framework services. @@ -118,6 +120,10 @@ services.AddSingleton(); services.AddSingleton(); + + var container = new ContainerBuilder(); + container.Populate(services); + return new AutofacServiceProvider(container.Build()); } public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) diff --git a/src/Services/Identity/Identity.API/Identity.API.csproj b/src/Services/Identity/Identity.API/Identity.API.csproj index e99a6efa9..e88054a12 100644 --- a/src/Services/Identity/Identity.API/Identity.API.csproj +++ b/src/Services/Identity/Identity.API/Identity.API.csproj @@ -14,6 +14,7 @@ + diff --git a/src/Services/Identity/Identity.API/Startup.cs b/src/Services/Identity/Identity.API/Startup.cs index b47f0535d..b9458c078 100644 --- a/src/Services/Identity/Identity.API/Startup.cs +++ b/src/Services/Identity/Identity.API/Startup.cs @@ -19,6 +19,8 @@ using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.HealthChecks; using Identity.API.Certificate; +using Autofac.Extensions.DependencyInjection; +using Autofac; namespace eShopOnContainers.Identity { @@ -44,7 +46,7 @@ namespace eShopOnContainers.Identity public IConfigurationRoot Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) + public IServiceProvider ConfigureServices(IServiceCollection services) { // Add framework services. @@ -87,7 +89,11 @@ namespace eShopOnContainers.Identity .AddInMemoryIdentityResources(Config.GetResources()) .AddInMemoryClients(Config.GetClients(clientUrls)) .AddAspNetIdentity() - .Services.AddTransient(); + .Services.AddTransient(); + + var container = new ContainerBuilder(); + container.Populate(services); + return new AutofacServiceProvider(container.Build()); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommand.cs b/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommand.cs index 950c4bdc5..d83293308 100644 --- a/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommand.cs +++ b/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommand.cs @@ -3,6 +3,7 @@ using MediatR; using System.Collections.Generic; using System.Runtime.Serialization; using System.Collections; +using Ordering.API.Application.Models; namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands { @@ -52,12 +53,6 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands [DataMember] public int CardTypeId { get; private set; } - [DataMember] - public int PaymentId { get; private set; } - - [DataMember] - public int BuyerId { get; private set; } - [DataMember] public IEnumerable OrderItems => _orderItems; @@ -66,11 +61,11 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands _orderItems = new List(); } - public CreateOrderCommand(List orderItems, string city, string street, string state, string country, string zipcode, + public CreateOrderCommand(List basketItems, string city, string street, string state, string country, string zipcode, string cardNumber, string cardHolderName, DateTime cardExpiration, - string cardSecurityNumber, int cardTypeId, int paymentId, int buyerId) : this() + string cardSecurityNumber, int cardTypeId) : this() { - _orderItems = orderItems; + _orderItems = MapToOrderItems(basketItems); City = city; Street = street; State = state; @@ -82,8 +77,21 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands CardSecurityNumber = cardSecurityNumber; CardTypeId = cardTypeId; CardExpiration = cardExpiration; - PaymentId = paymentId; - BuyerId = buyerId; + } + + private List MapToOrderItems(List basketItems) + { + var result = new List(); + basketItems.ForEach((item) => { + result.Add(new OrderItemDTO() { + ProductId = int.TryParse(item.Id, out int id) ? id : -1, + ProductName = item.ProductName, + PictureUrl = item.PictureUrl, + UnitPrice = item.UnitPrice, + Units = item.Quantity + }); + }); + return result; } public class OrderItemDTO diff --git a/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommandHandler.cs b/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommandHandler.cs index 2a3e8b8c0..0cc31c813 100644 --- a/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommandHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommandHandler.cs @@ -43,7 +43,7 @@ // 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(address , message.CardTypeId, message.CardNumber, message.CardSecurityNumber, message.CardHolderName, message.CardExpiration); - + order.SetOrderStatusId(OrderStatus.Submited.Id); foreach (var item in message.OrderItems) { order.AddOrderItem(item.ProductId, item.ProductName, item.UnitPrice, item.Discount, item.PictureUrl, item.Units); diff --git a/src/Services/Ordering/Ordering.API/Application/IntegrationCommands/Commands/SubmitOrderCommandMsg.cs b/src/Services/Ordering/Ordering.API/Application/IntegrationCommands/Commands/SubmitOrderCommandMsg.cs deleted file mode 100644 index 5a64a352d..000000000 --- a/src/Services/Ordering/Ordering.API/Application/IntegrationCommands/Commands/SubmitOrderCommandMsg.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; - -namespace Ordering.API.Application.IntegrationCommands.Commands -{ - public class SubmitOrderCommandMsg : IntegrationEvent - { - public int OrderNumber { get; private set; } - //TODO: message should change to Integration command type once command bus is implemented - } -} diff --git a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/UserCheckoutAcceptedIntegrationEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/UserCheckoutAcceptedIntegrationEventHandler.cs index 71d07ae64..d500a67bb 100644 --- a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/UserCheckoutAcceptedIntegrationEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/UserCheckoutAcceptedIntegrationEventHandler.cs @@ -1,13 +1,48 @@ -using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; +using MediatR; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using System.Threading.Tasks; namespace Ordering.API.Application.IntegrationEvents.EventHandling { - public class UserCheckoutAcceptedIntegrationEventHandler : IDynamicIntegrationEventHandler + public class UserCheckoutAcceptedIntegrationEventHandler : IIntegrationEventHandler { - public async Task Handle(dynamic eventData) + private readonly IMediator _mediator; + private readonly ILoggerFactory _logger; + + public UserCheckoutAcceptedIntegrationEventHandler(IMediator mediator, + IOrderingIntegrationEventService orderingIntegrationEventService, + ILoggerFactory logger) + { + _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + /// + /// Integration event handler which starts the create order process + /// + /// + /// Integration event message which is sent by the + /// basket.api once it has successfully process the + /// order items. + /// + /// + public async Task Handle(UserCheckoutAcceptedIntegrationEvent eventMsg) { - int i = 0; + var result = false; + if (eventMsg.RequestId != Guid.Empty) + { + var createOrderCommand = new CreateOrderCommand(eventMsg.Basket.Items, eventMsg.City, eventMsg.Street, + eventMsg.State, eventMsg.Country, eventMsg.ZipCode, + eventMsg.CardNumber, eventMsg.CardHolderName, eventMsg.CardExpiration, + eventMsg.CardSecurityNumber, eventMsg.CardTypeId); + + var requestCreateOrder = new IdentifiedCommand(createOrderCommand, eventMsg.RequestId); + result = await _mediator.SendAsync(requestCreateOrder); + } + + _logger.CreateLogger(nameof(UserCheckoutAcceptedIntegrationEventHandler)) + .LogTrace(result ? $"UserCheckoutAccepted integration event has been received and a create new order process is started with requestId: {eventMsg.RequestId}" : + $"UserCheckoutAccepted integration event has been received but a new order process has failed with requestId: {eventMsg.RequestId}"); } } -} +} \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/Events/UserCheckoutAcceptedIntegrationEvent.cs b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/Events/UserCheckoutAcceptedIntegrationEvent.cs new file mode 100644 index 000000000..92c32069c --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/Events/UserCheckoutAcceptedIntegrationEvent.cs @@ -0,0 +1,62 @@ +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; +using Ordering.API.Application.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ordering.API.Application.IntegrationEvents.Events +{ + public class UserCheckoutAcceptedIntegrationEvent : IntegrationEvent + { + public string UserId { get; } + + public string City { get; set; } + + public string Street { get; set; } + + public string State { get; set; } + + public string Country { get; set; } + + public string ZipCode { get; set; } + + public string CardNumber { get; set; } + + public string CardHolderName { get; set; } + + public DateTime CardExpiration { get; set; } + + public string CardSecurityNumber { get; set; } + + public int CardTypeId { get; set; } + + public string Buyer { get; set; } + + public Guid RequestId { get; set; } + + public CustomerBasket Basket { get; } + + public UserCheckoutAcceptedIntegrationEvent(string userId, 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; + } + + } +} diff --git a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/OrderingIntegrationEventService.cs b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/OrderingIntegrationEventService.cs index 2b8902652..831a1ec1e 100644 --- a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/OrderingIntegrationEventService.cs +++ b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/OrderingIntegrationEventService.cs @@ -7,6 +7,7 @@ using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Utilities using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure; using System; using System.Data.Common; +using System.Diagnostics; using System.Threading.Tasks; namespace Ordering.API.Application.IntegrationEvents diff --git a/src/Services/Ordering/Ordering.API/Application/Models/BasketItem.cs b/src/Services/Ordering/Ordering.API/Application/Models/BasketItem.cs new file mode 100644 index 000000000..698bdea3e --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Application/Models/BasketItem.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ordering.API.Application.Models +{ + public class BasketItem + { + public string Id { get; set; } + public string ProductId { get; set; } + public string ProductName { get; set; } + public decimal UnitPrice { get; set; } + public decimal OldUnitPrice { get; set; } + public int Quantity { get; set; } + public string PictureUrl { get; set; } + } +} diff --git a/src/Services/Ordering/Ordering.API/Application/Models/CustomerBasket.cs b/src/Services/Ordering/Ordering.API/Application/Models/CustomerBasket.cs new file mode 100644 index 000000000..0275d1f80 --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Application/Models/CustomerBasket.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ordering.API.Application.Models +{ + public class CustomerBasket + { + public string BuyerId { get; set; } + public List Items { get; set; } + + public CustomerBasket(string customerId) + { + BuyerId = customerId; + Items = new List(); + } + } +} diff --git a/src/Services/Ordering/Ordering.API/Application/Sagas/OrderProcessSaga.cs b/src/Services/Ordering/Ordering.API/Application/Sagas/OrderProcessSaga.cs index 0f06a893c..8a4c51795 100644 --- a/src/Services/Ordering/Ordering.API/Application/Sagas/OrderProcessSaga.cs +++ b/src/Services/Ordering/Ordering.API/Application/Sagas/OrderProcessSaga.cs @@ -7,6 +7,7 @@ using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure; using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency; using Ordering.API.Application.Commands; using Ordering.API.Application.IntegrationCommands.Commands; +using Ordering.API.Application.IntegrationEvents.Events; using Ordering.Domain.Exceptions; using System; using System.Collections.Generic; @@ -27,7 +28,6 @@ namespace Ordering.API.Application.Sagas /// with the validations. /// public class OrderProcessSaga : Saga, - IIntegrationEventHandler, IIntegrationEventHandler, IAsyncRequestHandler { @@ -43,29 +43,7 @@ namespace Ordering.API.Application.Sagas _dbContextFactory = dbContextFactory; _mediator = mediator; _orderingIntegrationEventService = orderingIntegrationEventService; - } - - /// - /// Command handler which starts the create order process - /// and initializes the saga - /// - /// - /// Integration command message which is sent by the - /// basket.api once it has successfully process the - /// order items. - /// - /// - public async Task Handle(SubmitOrderCommandMsg command) - { - var orderSaga = FindSagaById(command.OrderNumber); - CheckValidSagaId(orderSaga); - - // TODO: This handler should change to Integration command handler type once command bus is implemented - - // TODO: Send createOrder Command - - // TODO: Set saga timeout - } + } /// /// Command handler which confirms that the grace period diff --git a/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs b/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs index 902eb007b..2d4ce4059 100644 --- a/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs +++ b/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Queries; using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services; +using Ordering.API.Application.Commands; using System; using System.Collections.Generic; using System.Threading.Tasks; @@ -26,23 +27,17 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers _identityService = identityService ?? throw new ArgumentNullException(nameof(identityService)); } - [Route("new")] + [Route("cancel")] [HttpPost] - public async Task CreateOrder([FromBody]CreateOrderCommand command, [FromHeader(Name = "x-requestid")] string requestId) + public async Task CancelOrder([FromBody]CancelOrderCommand command, [FromHeader(Name = "x-requestid")] string requestId) { bool commandResult = false; if (Guid.TryParse(requestId, out Guid guid) && guid != Guid.Empty) { - var requestCreateOrder = new IdentifiedCommand(command, guid); - commandResult = await _mediator.SendAsync(requestCreateOrder); + var requestCancelOrder = new IdentifiedCommand(command, guid); + commandResult = await _mediator.SendAsync(requestCancelOrder); } - else - { - // If no x-requestid header is found we process the order anyway. This is just temporary to not break existing clients - // that aren't still updated. When all clients were updated this could be removed. - commandResult = await _mediator.SendAsync(command); - } - + return commandResult ? (IActionResult)Ok() : (IActionResult)BadRequest(); } diff --git a/src/Services/Ordering/Ordering.API/Startup.cs b/src/Services/Ordering/Ordering.API/Startup.cs index 2319c72b9..af3894096 100644 --- a/src/Services/Ordering/Ordering.API/Startup.cs +++ b/src/Services/Ordering/Ordering.API/Startup.cs @@ -4,7 +4,7 @@ using Autofac; using Autofac.Extensions.DependencyInjection; using global::Ordering.API.Application.IntegrationEvents; - using global::Ordering.API.Application.IntegrationEvents.EventHandling; + using global::Ordering.API.Application.IntegrationEvents.Events; using global::Ordering.API.Infrastructure.Middlewares; using global::Ordering.API.Application.IntegrationCommands.Commands; using global::Ordering.API.Application.IntegrationEvents.Events; @@ -31,6 +31,7 @@ using System; using System.Data.Common; using System.Reflection; + using global::Ordering.API.Application.IntegrationEvents.EventHandling; public class Startup { @@ -127,8 +128,7 @@ services.AddSingleton(); services.AddSingleton(); - - services.AddTransient(); + services.AddTransient(); services.AddTransient, OrderProcessSaga>(); services.AddTransient(); services.AddTransient(); @@ -156,14 +156,13 @@ app.UseFailingMiddleware(); ConfigureAuth(app); - ConfigureEventBus(app); - app.UseMvcWithDefaultRoute(); app.UseSwagger() .UseSwaggerUi(); OrderingContextSeed.SeedAsync(app).Wait(); + ConfigureEventBus(app); var integrationEventLogContext = new IntegrationEventLogContext( new DbContextOptionsBuilder() @@ -176,9 +175,9 @@ private void ConfigureEventBus(IApplicationBuilder app) { var eventBus = app.ApplicationServices.GetRequiredService(); - eventBus.SubscribeDynamic( - "UserCheckoutAccepted", - () => app.ApplicationServices.GetRequiredService()); + + eventBus.Subscribe>( + () => app.ApplicationServices.GetRequiredService>()); eventBus.Subscribe> (() => app.ApplicationServices.GetRequiredService>()); diff --git a/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs b/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs index fc57fc553..1920eb030 100644 --- a/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs +++ b/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs @@ -37,7 +37,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O private int? _paymentMethodId; - protected Order() { } + protected Order() { _orderItems = new List(); } public Order(Address address, int cardTypeId, string cardNumber, string cardSecurityNumber, string cardHolderName, DateTime cardExpiration, int? buyerId = null, int? paymentMethodId = null) diff --git a/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs b/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs index 5a0efd97f..0c69fe566 100644 --- a/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs +++ b/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs @@ -36,6 +36,9 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure public OrderingContext(DbContextOptions options, IMediator mediator) : base(options) { _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); + + + System.Diagnostics.Debug.WriteLine("OrderingContext::ctor ->" + this.GetHashCode()); } protected override void OnModelCreating(ModelBuilder modelBuilder) diff --git a/src/Services/Payment/Payment.API/Payment.API.csproj b/src/Services/Payment/Payment.API/Payment.API.csproj index 6176c30fc..147cd2e3c 100644 --- a/src/Services/Payment/Payment.API/Payment.API.csproj +++ b/src/Services/Payment/Payment.API/Payment.API.csproj @@ -10,6 +10,7 @@ + diff --git a/src/Services/Payment/Payment.API/Startup.cs b/src/Services/Payment/Payment.API/Startup.cs index 1e65fef5f..c5160a81d 100644 --- a/src/Services/Payment/Payment.API/Startup.cs +++ b/src/Services/Payment/Payment.API/Startup.cs @@ -1,8 +1,11 @@ -using Microsoft.AspNetCore.Builder; +using Autofac; +using Autofac.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using System; namespace Payment.API { @@ -21,7 +24,7 @@ namespace Payment.API public IConfigurationRoot Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) + public IServiceProvider ConfigureServices(IServiceCollection services) { // Add framework services. services.AddMvc(); @@ -38,6 +41,10 @@ namespace Payment.API TermsOfService = "Terms Of Service" }); }); + + var container = new ContainerBuilder(); + container.Populate(services); + return new AutofacServiceProvider(container.Build()); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/src/Services/SagaManager/SagaManager/Program.cs b/src/Services/SagaManager/SagaManager/Program.cs index 06fdec6ba..7de0704ea 100644 --- a/src/Services/SagaManager/SagaManager/Program.cs +++ b/src/Services/SagaManager/SagaManager/Program.cs @@ -16,7 +16,8 @@ namespace SagaManager using Microsoft.Extensions.Options; using RabbitMQ.Client; using Services; - + using Autofac.Extensions.DependencyInjection; + using Autofac; public class Program { @@ -76,6 +77,10 @@ namespace SagaManager RegisterServiceBus(services); + var container = new ContainerBuilder(); + container.Populate(services); + return new AutofacServiceProvider(container.Build()); + return services.BuildServiceProvider(); } diff --git a/src/Services/SagaManager/SagaManager/SagaManager.csproj b/src/Services/SagaManager/SagaManager/SagaManager.csproj index ed76ff6d8..f5eafa793 100644 --- a/src/Services/SagaManager/SagaManager/SagaManager.csproj +++ b/src/Services/SagaManager/SagaManager/SagaManager.csproj @@ -6,6 +6,7 @@ + diff --git a/src/Web/WebMVC/Controllers/OrderController.cs b/src/Web/WebMVC/Controllers/OrderController.cs index 83152d697..b8efd930e 100644 --- a/src/Web/WebMVC/Controllers/OrderController.cs +++ b/src/Web/WebMVC/Controllers/OrderController.cs @@ -8,6 +8,7 @@ using Microsoft.eShopOnContainers.WebMVC.ViewModels; using Microsoft.AspNetCore.Authorization; using System.Net.Http; using Polly.CircuitBreaker; +using WebMVC.Models; namespace Microsoft.eShopOnContainers.WebMVC.Controllers { @@ -36,14 +37,16 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers } [HttpPost] - public async Task Create(Order model, string action) + public async Task Checkout(Order model) { try { if (ModelState.IsValid) { var user = _appUserParser.Parse(HttpContext.User); - await _orderSvc.CreateOrder(model); + var basket = _orderSvc.MapOrderToBasket(model); + + await _basketSvc.Checkout(basket); //Redirect to historic list. return RedirectToAction("Index"); @@ -56,6 +59,20 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers return View(model); } + [HttpPut] + public async Task Cancel(Order model) + { + if (ModelState.IsValid) + { + var user = _appUserParser.Parse(HttpContext.User); + await _orderSvc.CancelOrder(model); + + //Redirect to historic list. + return RedirectToAction("Index"); + } + return View(model); + } + public async Task Detail(string orderId) { var user = _appUserParser.Parse(HttpContext.User); diff --git a/src/Web/WebMVC/Infrastructure/API.cs b/src/Web/WebMVC/Infrastructure/API.cs index c837b8067..f23b94288 100644 --- a/src/Web/WebMVC/Infrastructure/API.cs +++ b/src/Web/WebMVC/Infrastructure/API.cs @@ -14,6 +14,11 @@ return baseUri; } + public static string CheckoutBasket(string baseUri) + { + return $"{baseUri}/checkout"; + } + public static string CleanBasket(string baseUri, string basketId) { return $"{baseUri}/{basketId}"; @@ -36,6 +41,11 @@ { return $"{baseUri}/new"; } + + public static string CancelOrder(string baseUri) + { + return $"{baseUri}/cancel"; + } } public static class Catalog diff --git a/src/Web/WebMVC/Models/BasketDTO.cs b/src/Web/WebMVC/Models/BasketDTO.cs new file mode 100644 index 000000000..4609c8533 --- /dev/null +++ b/src/Web/WebMVC/Models/BasketDTO.cs @@ -0,0 +1,37 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace WebMVC.Models +{ + public class BasketDTO + { + [Required] + public string City { get; set; } + [Required] + public string Street { get; set; } + [Required] + public string State { get; set; } + [Required] + public string Country { get; set; } + + public string ZipCode { get; set; } + [Required] + public string CardNumber { get; set; } + [Required] + public string CardHolderName { get; set; } + + [Required] + public DateTime CardExpiration { get; set; } + + [Required] + public string CardSecurityNumber { get; set; } + + public int CardTypeId { get; set; } + + public string Buyer { get; set; } + + [Required] + public Guid RequestId { get; set; } + } +} + diff --git a/src/Web/WebMVC/Services/BasketService.cs b/src/Web/WebMVC/Services/BasketService.cs index bd418ea26..538980052 100644 --- a/src/Web/WebMVC/Services/BasketService.cs +++ b/src/Web/WebMVC/Services/BasketService.cs @@ -7,6 +7,7 @@ using Newtonsoft.Json; using System.Collections.Generic; using System.Threading.Tasks; using WebMVC.Infrastructure; +using WebMVC.Models; namespace Microsoft.eShopOnContainers.WebMVC.Services { @@ -54,6 +55,16 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services return basket; } + public async Task Checkout(BasketDTO basket) + { + var token = await GetUserTokenAsync(); + var updateBasketUri = API.Basket.CheckoutBasket(_remoteServiceBaseUrl); + + var response = await _apiClient.PutAsync(updateBasketUri, basket, token); + + response.EnsureSuccessStatusCode(); + } + public async Task SetQuantities(ApplicationUser user, Dictionary quantities) { var basket = await GetBasket(user); diff --git a/src/Web/WebMVC/Services/IBasketService.cs b/src/Web/WebMVC/Services/IBasketService.cs index 114246d5e..215a958f1 100644 --- a/src/Web/WebMVC/Services/IBasketService.cs +++ b/src/Web/WebMVC/Services/IBasketService.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using WebMVC.Models; namespace Microsoft.eShopOnContainers.WebMVC.Services { @@ -11,6 +12,7 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services Task GetBasket(ApplicationUser user); Task AddItemToBasket(ApplicationUser user, BasketItem product); Task UpdateBasket(Basket basket); + Task Checkout(BasketDTO basket); Task SetQuantities(ApplicationUser user, Dictionary quantities); Order MapBasketToOrder(Basket basket); Task CleanBasket(ApplicationUser user); diff --git a/src/Web/WebMVC/Services/IOrderingService.cs b/src/Web/WebMVC/Services/IOrderingService.cs index e1a7e6f83..fca0151f1 100644 --- a/src/Web/WebMVC/Services/IOrderingService.cs +++ b/src/Web/WebMVC/Services/IOrderingService.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using WebMVC.Models; namespace Microsoft.eShopOnContainers.WebMVC.Services { @@ -10,8 +11,9 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services { Task> GetMyOrders(ApplicationUser user); Task GetOrder(ApplicationUser user, string orderId); - Task CreateOrder(Order order); + Task CancelOrder(Order order); Order MapUserInfoIntoOrder(ApplicationUser user, Order order); + BasketDTO MapOrderToBasket(Order order); void OverrideUserInfoIntoOrder(Order original, Order destination); } } diff --git a/src/Web/WebMVC/Services/OrderingService.cs b/src/Web/WebMVC/Services/OrderingService.cs index 8f198fbed..7329d1d14 100644 --- a/src/Web/WebMVC/Services/OrderingService.cs +++ b/src/Web/WebMVC/Services/OrderingService.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; using WebMVC.Infrastructure; +using WebMVC.Models; namespace Microsoft.eShopOnContainers.WebMVC.Services { @@ -65,22 +66,17 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services return order; } - async public Task CreateOrder(Order order) + async public Task CancelOrder(Order order) { var token = await GetUserTokenAsync(); var requestId = order.RequestId.ToString(); - var addNewOrderUri = API.Order.AddNewOrder(_remoteServiceBaseUrl); - - order.CardTypeId = 1; - order.CardExpirationApiFormat(); - - SetFakeIdToProducts(order); - - var response = await _apiClient.PostAsync(addNewOrderUri, order, token, requestId); + var cancelOrderUri = API.Order.CancelOrder(_remoteServiceBaseUrl); + + var response = await _apiClient.PutAsync(cancelOrderUri, order, token, requestId); if (response.StatusCode == System.Net.HttpStatusCode.InternalServerError) { - throw new Exception("Error creating order, try later."); + throw new Exception("Error cancelling order, try later."); } response.EnsureSuccessStatusCode(); @@ -100,6 +96,25 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services destination.CardSecurityNumber = original.CardSecurityNumber; } + public BasketDTO MapOrderToBasket(Order order) + { + return new BasketDTO() + { + City = order.City, + Street = order.Street, + State = order.State, + Country = order.Country, + ZipCode = order.ZipCode, + CardNumber = order.CardNumber, + CardHolderName = order.CardHolderName, + CardExpiration = order.CardExpiration, + CardSecurityNumber = order.CardSecurityNumber, + CardTypeId = order.CardTypeId, + Buyer = order.Buyer, + RequestId = order.RequestId + }; + } + void SetFakeIdToProducts(Order order) { var id = 1; @@ -111,6 +126,6 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services var context = _httpContextAccesor.HttpContext; return await context.Authentication.GetTokenAsync("access_token"); - } + } } } diff --git a/src/Web/WebMVC/Views/Order/Create.cshtml b/src/Web/WebMVC/Views/Order/Create.cshtml index e05d3a7b7..79ced5b3a 100644 --- a/src/Web/WebMVC/Views/Order/Create.cshtml +++ b/src/Web/WebMVC/Views/Order/Create.cshtml @@ -9,7 +9,7 @@ @Html.Partial("_Header", new Header() { Controller = "Cart", Text = "Back to cart" })
-
+
@foreach (var error in ViewData.ModelState.Values.SelectMany(err => err.Errors)) { diff --git a/test/Services/UnitTest/Basket/Application/BasketWebApiTest.cs b/test/Services/UnitTest/Basket/Application/BasketWebApiTest.cs index b6e45425a..be551115f 100644 --- a/test/Services/UnitTest/Basket/Application/BasketWebApiTest.cs +++ b/test/Services/UnitTest/Basket/Application/BasketWebApiTest.cs @@ -1,4 +1,5 @@ using Basket.API.IntegrationEvents.Events; +using Basket.API.Model; using Microsoft.AspNetCore.Mvc; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.Services.Basket.API.Controllers; @@ -35,7 +36,7 @@ namespace UnitTest.Basket.Application .Returns(Task.FromResult(fakeCustomerBasket)); _identityServiceMock.Setup(x => x.GetUserIdentity()).Returns(fakeCustomerId); - _serviceBusMock.Setup(x => x.Publish(It.IsAny())); + _serviceBusMock.Setup(x => x.Publish(It.IsAny())); //Act var basketController = new BasketController( _basketRepositoryMock.Object, _identityServiceMock.Object, _serviceBusMock.Object); @@ -56,7 +57,7 @@ namespace UnitTest.Basket.Application _basketRepositoryMock.Setup(x => x.UpdateBasketAsync(It.IsAny())) .Returns(Task.FromResult(fakeCustomerBasket)); _identityServiceMock.Setup(x => x.GetUserIdentity()).Returns(fakeCustomerId); - _serviceBusMock.Setup(x => x.Publish(It.IsAny())); + _serviceBusMock.Setup(x => x.Publish(It.IsAny())); //Act var basketController = new BasketController( _basketRepositoryMock.Object, _identityServiceMock.Object, _serviceBusMock.Object); @@ -79,7 +80,7 @@ namespace UnitTest.Basket.Application var basketController = new BasketController( _basketRepositoryMock.Object, _identityServiceMock.Object, _serviceBusMock.Object); - var result = await basketController.Checkout() as BadRequestResult; + var result = await basketController.Checkout(new BasketCheckout()) as BadRequestResult; Assert.NotNull(result); } @@ -95,8 +96,8 @@ namespace UnitTest.Basket.Application var basketController = new BasketController( _basketRepositoryMock.Object, _identityServiceMock.Object, _serviceBusMock.Object); - var result = await basketController.Checkout() as AcceptedResult; - _serviceBusMock.Verify(mock => mock.Publish(It.IsAny()), Times.Once); + var result = await basketController.Checkout(new BasketCheckout()) as AcceptedResult; + _serviceBusMock.Verify(mock => mock.Publish(It.IsAny()), Times.Once); Assert.NotNull(result); } diff --git a/test/Services/UnitTest/Ordering/Application/OrderControllerTest.cs b/test/Services/UnitTest/Ordering/Application/OrderControllerTest.cs index 27c524010..66c42906f 100644 --- a/test/Services/UnitTest/Ordering/Application/OrderControllerTest.cs +++ b/test/Services/UnitTest/Ordering/Application/OrderControllerTest.cs @@ -95,51 +95,51 @@ namespace UnitTest.Ordering.Application Assert.IsAssignableFrom(viewResult.ViewData.Model); } - [Fact] - public async Task Post_create_order_success() - { - //Arrange - var fakeOrder = GetFakeOrder(); - - _basketServiceMock.Setup(x => x.CleanBasket(It.IsAny())) - .Returns(Task.FromResult(1)); - - _orderServiceMock.Setup(x => x.CreateOrder(It.IsAny())) - .Returns(Task.FromResult(1)); - - //Act - var orderController = new OrderController(_orderServiceMock.Object, _basketServiceMock.Object, _identityParserMock.Object); - orderController.ControllerContext.HttpContext = _contextMock.Object; - var actionResult = await orderController.Create(fakeOrder, "fakeAction"); - - //Assert - var redirectToActionResult = Assert.IsType(actionResult); - Assert.Null(redirectToActionResult.ControllerName); - Assert.Equal("Index", redirectToActionResult.ActionName); - } - - [Fact] - public async Task Post_create_order_fail() - { - //Arrange - var fakeOrder = GetFakeOrder(); - - _basketServiceMock.Setup(x => x.CleanBasket(It.IsAny())) - .Returns(Task.FromResult(1)); - - _orderServiceMock.Setup(x => x.CreateOrder(It.IsAny())) - .Returns(Task.FromResult(1)); - - //Act - var orderController = new OrderController(_orderServiceMock.Object, _basketServiceMock.Object, _identityParserMock.Object); - orderController.ControllerContext.HttpContext = _contextMock.Object; - orderController.ModelState.AddModelError("fakeError", "fakeError"); - var actionResult = await orderController.Create(fakeOrder, "action"); - - //Assert - var viewResult = Assert.IsType(actionResult); - Assert.IsAssignableFrom(viewResult.ViewData.Model); - } + //[Fact] + //public async Task Post_create_order_success() + //{ + // //Arrange + // var fakeOrder = GetFakeOrder(); + + // _basketServiceMock.Setup(x => x.CleanBasket(It.IsAny())) + // .Returns(Task.FromResult(1)); + + // _orderServiceMock.Setup(x => x.CreateOrder(It.IsAny())) + // .Returns(Task.FromResult(1)); + + // //Act + // var orderController = new OrderController(_orderServiceMock.Object, _basketServiceMock.Object, _identityParserMock.Object); + // orderController.ControllerContext.HttpContext = _contextMock.Object; + // var actionResult = await orderController.Create(fakeOrder, "fakeAction"); + + // //Assert + // var redirectToActionResult = Assert.IsType(actionResult); + // Assert.Null(redirectToActionResult.ControllerName); + // Assert.Equal("Index", redirectToActionResult.ActionName); + //} + + //[Fact] + //public async Task Post_create_order_fail() + //{ + // //Arrange + // var fakeOrder = GetFakeOrder(); + + // _basketServiceMock.Setup(x => x.CleanBasket(It.IsAny())) + // .Returns(Task.FromResult(1)); + + // _orderServiceMock.Setup(x => x.CreateOrder(It.IsAny())) + // .Returns(Task.FromResult(1)); + + // //Act + // var orderController = new OrderController(_orderServiceMock.Object, _basketServiceMock.Object, _identityParserMock.Object); + // orderController.ControllerContext.HttpContext = _contextMock.Object; + // orderController.ModelState.AddModelError("fakeError", "fakeError"); + // var actionResult = await orderController.Create(fakeOrder, "action"); + + // //Assert + // var viewResult = Assert.IsType(actionResult); + // Assert.IsAssignableFrom(viewResult.ViewData.Model); + //} private BasketModel GetFakeBasket(string buyerId) { diff --git a/test/Services/UnitTest/Ordering/Application/OrdersWebApiTest.cs b/test/Services/UnitTest/Ordering/Application/OrdersWebApiTest.cs index c0656f050..22142dfbf 100644 --- a/test/Services/UnitTest/Ordering/Application/OrdersWebApiTest.cs +++ b/test/Services/UnitTest/Ordering/Application/OrdersWebApiTest.cs @@ -25,36 +25,36 @@ namespace UnitTest.Ordering.Application _identityServiceMock = new Mock(); } - [Fact] - public async Task Create_order_with_requestId_success() - { - //Arrange - _mediatorMock.Setup(x => x.SendAsync(It.IsAny>())) - .Returns(Task.FromResult(true)); - - //Act - var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object); - var actionResult = await orderController.CreateOrder(new CreateOrderCommand(), Guid.NewGuid().ToString()) as OkResult; - - //Assert - Assert.Equal(actionResult.StatusCode, (int)System.Net.HttpStatusCode.OK); - - } - - [Fact] - public async Task Create_order_bad_request() - { - //Arrange - _mediatorMock.Setup(x => x.SendAsync(It.IsAny>())) - .Returns(Task.FromResult(true)); - - //Act - var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object); - var actionResult = await orderController.CreateOrder(new CreateOrderCommand(), String.Empty) as BadRequestResult; - - //Assert - Assert.Equal(actionResult.StatusCode, (int)System.Net.HttpStatusCode.BadRequest); - } + //[Fact] + //public async Task Create_order_with_requestId_success() + //{ + // //Arrange + // _mediatorMock.Setup(x => x.SendAsync(It.IsAny>())) + // .Returns(Task.FromResult(true)); + + // //Act + // var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object); + // var actionResult = await orderController.CreateOrder(new CreateOrderCommand(), Guid.NewGuid().ToString()) as OkResult; + + // //Assert + // Assert.Equal(actionResult.StatusCode, (int)System.Net.HttpStatusCode.OK); + + //} + + //[Fact] + //public async Task Create_order_bad_request() + //{ + // //Arrange + // _mediatorMock.Setup(x => x.SendAsync(It.IsAny>())) + // .Returns(Task.FromResult(true)); + + // //Act + // var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object); + // var actionResult = await orderController.CreateOrder(new CreateOrderCommand(), String.Empty) as BadRequestResult; + + // //Assert + // Assert.Equal(actionResult.StatusCode, (int)System.Net.HttpStatusCode.BadRequest); + //} [Fact] public async Task Get_orders_success()