Browse Source

Merge new creation ordering workflow

pull/223/head
Ramón Tomás 7 years ago
parent
commit
152bc9fd01
175 changed files with 4222 additions and 569 deletions
  1. +14
    -2
      docker-compose.override.yml
  2. +28
    -0
      docker-compose.vs.debug.yml
  3. +20
    -0
      docker-compose.vs.release.yml
  4. +21
    -3
      docker-compose.yml
  5. +159
    -55
      eShopOnContainers-ServicesAndWebApps.sln
  6. +8
    -0
      src/BuildingBlocks/CommandBus/CommandBus/CommandBus.csproj
  7. +13
    -0
      src/BuildingBlocks/CommandBus/CommandBus/ICommandBus.cs
  8. +18
    -0
      src/BuildingBlocks/CommandBus/CommandBus/IntegrationCommand.cs
  9. +7
    -0
      src/BuildingBlocks/CommandBus/CommandBusRabbitMQ/CommandBusRabbitMQ.csproj
  10. +8
    -0
      src/BuildingBlocks/EventBus/CommandBus/CommandBus.csproj
  11. +16
    -0
      src/BuildingBlocks/EventBus/CommandBus/ICommandBus.cs
  12. +16
    -0
      src/BuildingBlocks/EventBus/CommandBus/IIntegrationCommandHandler.cs
  13. +35
    -0
      src/BuildingBlocks/EventBus/CommandBus/IntegrationCommand.cs
  14. +5
    -5
      src/BuildingBlocks/EventBus/EventBus.Tests/InMemory_SubscriptionManager_Tests.cs
  15. +13
    -0
      src/BuildingBlocks/EventBus/EventBus/Abstractions/IDynamicIntegrationEventHandler.cs
  16. +7
    -1
      src/BuildingBlocks/EventBus/EventBus/Abstractions/IEventBus.cs
  17. +14
    -6
      src/BuildingBlocks/EventBus/EventBus/IEventBusSubscriptionsManager.cs
  18. +84
    -37
      src/BuildingBlocks/EventBus/EventBus/InMemoryEventBusSubscriptionsManager.cs
  19. +28
    -0
      src/BuildingBlocks/EventBus/EventBus/SubscriptionInfo.cs
  20. +145
    -0
      src/BuildingBlocks/EventBus/EventBusRabbitMQ/CommandBusRabbitMQ.cs
  21. +49
    -28
      src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs
  22. +1
    -0
      src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj
  23. +36
    -0
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Basket/BasketCheckout.cs
  24. +9
    -0
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Basket/BasketService.cs
  25. +1
    -0
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Basket/IBasketService.cs
  26. +4
    -2
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Order/IOrderService.cs
  27. +17
    -10
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Order/OrderMockService.cs
  28. +18
    -12
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Order/OrderService.cs
  29. +4
    -2
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/CheckoutViewModel.cs
  30. +6
    -0
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/eShopOnContainers.Core.csproj
  31. +1
    -0
      src/Services/Basket/Basket.API/Basket.API.csproj
  32. +41
    -3
      src/Services/Basket/Basket.API/Controllers/BasketController.cs
  33. +1
    -0
      src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/OrderStartedIntegrationEventHandler.cs
  34. +3
    -3
      src/Services/Basket/Basket.API/IntegrationEvents/Events/OrderStartedIntegrationEvent.cs
  35. +64
    -0
      src/Services/Basket/Basket.API/IntegrationEvents/Events/UserCheckoutAcceptedIntegrationEvent.cs
  36. +1
    -1
      src/Services/Basket/Basket.API/Model/Basket.cs
  37. +35
    -0
      src/Services/Basket/Basket.API/Model/BasketCheckout.cs
  38. +12
    -0
      src/Services/Basket/Basket.API/Services/IIdentityService.cs
  39. +24
    -0
      src/Services/Basket/Basket.API/Services/IdentityService.cs
  40. +16
    -17
      src/Services/Basket/Basket.API/Startup.cs
  41. +6
    -3
      src/Services/Catalog/Catalog.API/Catalog.API.csproj
  42. +12
    -12
      src/Services/Catalog/Catalog.API/Infrastructure/CatalogContextSeed.cs
  43. +107
    -0
      src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20170509130025_AddStockProductItem.Designer.cs
  44. +55
    -0
      src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20170509130025_AddStockProductItem.cs
  45. +8
    -0
      src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/CatalogContextModelSnapshot.cs
  46. +46
    -0
      src/Services/Catalog/Catalog.API/IntegrationEvents/EventHandling/OrderStatusChangedToAwaitingValidationIntegrationEventHandler.cs
  47. +31
    -0
      src/Services/Catalog/Catalog.API/IntegrationEvents/EventHandling/OrderStatusChangedToPaidIntegrationEventHandler.cs
  48. +30
    -0
      src/Services/Catalog/Catalog.API/IntegrationEvents/Events/OrderStatusChangedToAwaitingValidationIntegrationEvent.cs
  49. +18
    -0
      src/Services/Catalog/Catalog.API/IntegrationEvents/Events/OrderStatusChangedToPaidIntegrationEvent.cs
  50. +11
    -0
      src/Services/Catalog/Catalog.API/IntegrationEvents/Events/OrderStockConfirmedIntegrationEvent.cs
  51. +31
    -0
      src/Services/Catalog/Catalog.API/IntegrationEvents/Events/OrderStockRejectedIntegrationEvent.cs
  52. +4
    -7
      src/Services/Catalog/Catalog.API/IntegrationEvents/Events/ProductPriceChangedIntegrationEvent.cs
  53. +77
    -3
      src/Services/Catalog/Catalog.API/Model/CatalogItem.cs
  54. +33
    -3
      src/Services/Catalog/Catalog.API/Startup.cs
  55. +3
    -0
      src/Services/GracePeriod/GracePeriodManager/.dockerignore
  56. +5
    -0
      src/Services/GracePeriod/GracePeriodManager/Dockerfile
  57. +36
    -0
      src/Services/GracePeriod/GracePeriodManager/GracePeriodManager.csproj
  58. +11
    -0
      src/Services/GracePeriod/GracePeriodManager/IntegrationEvents/Events/GracePeriodConfirmedIntegrationEvent.cs
  59. +13
    -0
      src/Services/GracePeriod/GracePeriodManager/ManagerSettings.cs
  60. +94
    -0
      src/Services/GracePeriod/GracePeriodManager/Program.cs
  61. +7
    -0
      src/Services/GracePeriod/GracePeriodManager/Services/IManagerService.cs
  62. +62
    -0
      src/Services/GracePeriod/GracePeriodManager/Services/ManagerService.cs
  63. +13
    -0
      src/Services/GracePeriod/GracePeriodManager/appsettings.json
  64. +2
    -0
      src/Services/Identity/Identity.API/Identity.API.csproj
  65. +10
    -2
      src/Services/Identity/Identity.API/Startup.cs
  66. +21
    -0
      src/Services/Ordering/Ordering.API/Application/Commands/CancelOrderCommand.cs
  67. +46
    -0
      src/Services/Ordering/Ordering.API/Application/Commands/CancelOrderCommandHandler.cs
  68. +23
    -11
      src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommand.cs
  69. +2
    -2
      src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommandHandler.cs
  70. +21
    -0
      src/Services/Ordering/Ordering.API/Application/Commands/ShipOrderCommand.cs
  71. +43
    -0
      src/Services/Ordering/Ordering.API/Application/Commands/ShipOrderCommandHandler.cs
  72. +6
    -20
      src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/BuyerAndPaymentMethodVerified/UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler.cs
  73. +43
    -0
      src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderGracePeriodConfirmed/OrderStatusChangedToAwaitingValidationDomainEventHandler.cs
  74. +43
    -0
      src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderPaid/OrderStatusChangedToPaidDomainEventHandler.cs
  75. +2
    -4
      src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler.cs
  76. +38
    -0
      src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStockConfirmed/OrderStatusChangedToStockConfirmedDomainEventHandler.cs
  77. +32
    -0
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/GracePeriodConfirmedIntegrationEventHandler.cs
  78. +27
    -0
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentFailedIntegrationEventHandler.cs
  79. +27
    -0
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentSuccededIntegrationEventHandler.cs
  80. +27
    -0
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockConfirmedIntegrationEventHandler.cs
  81. +31
    -0
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockRejectedIntegrationEventHandler.cs
  82. +58
    -0
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/UserCheckoutAcceptedIntegrationEventHandler.cs
  83. +12
    -0
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/Events/GracePeriodConfirmedIntegrationEvent.cs
  84. +11
    -0
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/Events/OrderPaymentFailedIntegrationEvent .cs
  85. +11
    -0
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/Events/OrderPaymentSuccededIntegrationEvent.cs
  86. +5
    -5
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/Events/OrderStartedIntegrationEvent.cs
  87. +30
    -0
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/Events/OrderStatusChangedToAwaitingValidationIntegrationEvent.cs
  88. +18
    -0
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/Events/OrderStatusChangedToPaidIntegrationEvent.cs
  89. +12
    -0
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/Events/OrderStatusChangedToStockConfirmedIntegrationEvent.cs
  90. +11
    -0
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/Events/OrderStockConfirmedIntegrationEvent.cs
  91. +31
    -0
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/Events/OrderStockRejectedIntegrationEvent.cs
  92. +59
    -0
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/Events/UserCheckoutAcceptedIntegrationEvent.cs
  93. +1
    -5
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/IOrderingIntegrationEventService.cs
  94. +5
    -4
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/OrderingIntegrationEventService.cs
  95. +18
    -0
      src/Services/Ordering/Ordering.API/Application/Models/BasketItem.cs
  96. +19
    -0
      src/Services/Ordering/Ordering.API/Application/Models/CustomerBasket.cs
  97. +2
    -1
      src/Services/Ordering/Ordering.API/Application/Queries/OrderQueries.cs
  98. +17
    -0
      src/Services/Ordering/Ordering.API/Application/Validations/CancelOrderCommandValidator.cs
  99. +17
    -0
      src/Services/Ordering/Ordering.API/Application/Validations/ShipOrderCommandValidator.cs
  100. +19
    -9
      src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs

+ 14
- 2
docker-compose.override.yml View File

@ -7,7 +7,11 @@ version: '2'
# An external IP or DNS name has to be used (instead localhost and the 10.0.75.1 IP) when testing the Web apps and the Xamarin apps from remote machines/devices using the same WiFi, for instance. # An external IP or DNS name has to be used (instead localhost and the 10.0.75.1 IP) when testing the Web apps and the Xamarin apps from remote machines/devices using the same WiFi, for instance.
services: services:
graceperiodmanager:
environment:
- ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word
- EventBusConnection=rabbitmq
basket.api: basket.api:
environment: environment:
- ASPNETCORE_ENVIRONMENT=Development - ASPNETCORE_ENVIRONMENT=Development
@ -109,6 +113,14 @@ services:
ports: ports:
- "5107:80" - "5107:80"
payment.api:
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=http://0.0.0.0:5108
- EventBusConnection=rabbitmq
ports:
- "5108:80"
locations.api: locations.api:
environment: environment:
- ASPNETCORE_ENVIRONMENT=Development - ASPNETCORE_ENVIRONMENT=Development
@ -118,4 +130,4 @@ services:
- identityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105. - identityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
- EventBusConnection=rabbitmq - EventBusConnection=rabbitmq
ports: ports:
- "5109:80"
- "5109:80"

+ 28
- 0
docker-compose.vs.debug.yml View File

@ -122,6 +122,34 @@ services:
labels: labels:
- "com.microsoft.visualstudio.targetoperatingsystem=linux" - "com.microsoft.visualstudio.targetoperatingsystem=linux"
payment.api:
image: eshop/payment.api:dev
build:
args:
source: ${DOCKER_BUILD_SOURCE}
environment:
- DOTNET_USE_POLLING_FILE_WATCHER=1
volumes:
- ./src/Services/Payment/Payment.API:/app
- ~/.nuget/packages:/root/.nuget/packages:ro
- ~/clrdbg:/clrdbg:ro
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=linux"
graceperiodmanager:
image: eshop/graceperiodmanager:dev
build:
args:
source: ${DOCKER_BUILD_SOURCE}
volumes:
- ./src/Services/GracePeriod/GracePeriodManager:/app
- ~/.nuget/packages:/root/.nuget/packages:ro
- ~/clrdbg:/clrdbg:ro
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=linux"
locations.api: locations.api:
image: eshop/locations.api:dev image: eshop/locations.api:dev
build: build:


+ 20
- 0
docker-compose.vs.release.yml View File

@ -81,6 +81,26 @@ services:
labels: labels:
- "com.microsoft.visualstudio.targetoperatingsystem=linux" - "com.microsoft.visualstudio.targetoperatingsystem=linux"
payment.api:
build:
args:
source: ${DOCKER_BUILD_SOURCE}
volumes:
- ~/clrdbg:/clrdbg:ro
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=linux"
graceperiodmanager:
build:
args:
source: ${DOCKER_BUILD_SOURCE}
volumes:
- ~/clrdbg:/clrdbg:ro
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=linux"
locations.api: locations.api:
build: build:
args: args:


+ 21
- 3
docker-compose.yml View File

@ -1,6 +1,16 @@
version: '2' version: '2'
services: services:
graceperiodmanager:
image: eshop/graceperiodmanager
build:
context: ./src/Services/GracePeriod/GracePeriodManager
dockerfile: Dockerfile
depends_on:
- sql.data
- rabbitmq
basket.api: basket.api:
image: eshop/basket.api image: eshop/basket.api
build: build:
@ -9,7 +19,6 @@ services:
depends_on: depends_on:
- basket.data - basket.data
- identity.api - identity.api
- rabbitmq
catalog.api: catalog.api:
image: eshop/catalog.api image: eshop/catalog.api
@ -35,6 +44,7 @@ services:
dockerfile: Dockerfile dockerfile: Dockerfile
depends_on: depends_on:
- sql.data - sql.data
- rabbitmq
marketing.api: marketing.api:
image: eshop/marketing.api image: eshop/marketing.api
@ -85,7 +95,15 @@ services:
image: eshop/webstatus image: eshop/webstatus
build: build:
context: ./src/Web/WebStatus context: ./src/Web/WebStatus
dockerfile: Dockerfile
dockerfile: Dockerfile
payment.api:
image: eshop/payment.api
build:
context: ./src/Services/Payment/Payment.API
dockerfile: Dockerfile
depends_on:
- rabbitmq
locations.api: locations.api:
image: locations.api image: locations.api
@ -93,4 +111,4 @@ services:
context: ./src/Services/Location/Locations.API context: ./src/Services/Location/Locations.API
dockerfile: Dockerfile dockerfile: Dockerfile
depends_on: depends_on:
- nosql.data
- nosql.data

+ 159
- 55
eShopOnContainers-ServicesAndWebApps.sln View File

@ -1,7 +1,7 @@
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15 # Visual Studio 15
VisualStudioVersion = 15.0.26430.12
VisualStudioVersion = 15.0.26430.6
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{932D8224-11F6-4D07-B109-DA28AD288A63}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{932D8224-11F6-4D07-B109-DA28AD288A63}"
EndProject EndProject
@ -70,11 +70,19 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Resilience", "Resilience",
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Resilience.Http", "src\BuildingBlocks\Resilience\Resilience.Http\Resilience.Http.csproj", "{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Resilience.Http", "src\BuildingBlocks\Resilience\Resilience.Http\Resilience.Http.csproj", "{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Payment", "Payment", "{022E145D-1593-47EE-9608-8E323D3C63F5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Payment.API", "src\Services\Payment\Payment.API\Payment.API.csproj", "{1A01AF82-6FCB-464C-B39C-F127AEBD315D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.HealthChecks", "src\BuildingBlocks\HealthChecks\src\Microsoft.AspNetCore.HealthChecks\Microsoft.AspNetCore.HealthChecks.csproj", "{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.HealthChecks", "src\BuildingBlocks\HealthChecks\src\Microsoft.AspNetCore.HealthChecks\Microsoft.AspNetCore.HealthChecks.csproj", "{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.HealthChecks.SqlServer", "src\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.SqlServer\Microsoft.Extensions.HealthChecks.SqlServer.csproj", "{4BD76717-3102-4969-8C2C-BAAA3F0263B6}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.HealthChecks.SqlServer", "src\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.SqlServer\Microsoft.Extensions.HealthChecks.SqlServer.csproj", "{4BD76717-3102-4969-8C2C-BAAA3F0263B6}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventBus.Tests", "src\BuildingBlocks\EventBus\EventBus.Tests\EventBus.Tests.csproj", "{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EventBus.Tests", "src\BuildingBlocks\EventBus\EventBus.Tests\EventBus.Tests.csproj", "{4A980AC4-7205-46BF-8CCB-09E44D700FD4}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GracePeriod", "GracePeriod", "{F38B4FF0-0B49-405A-B1B4-F7A5E3BC4C4E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GracePeriodManager", "src\Services\GracePeriod\GracePeriodManager\GracePeriodManager.csproj", "{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Location", "Location", "{41139F64-4046-4F16-96B7-D941D96FA9C6}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Location", "Location", "{41139F64-4046-4F16-96B7-D941D96FA9C6}"
EndProject EndProject
@ -870,6 +878,54 @@ Global
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Release|x64.Build.0 = Release|Any CPU {D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Release|x64.Build.0 = Release|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Release|x86.ActiveCfg = Release|Any CPU {D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Release|x86.ActiveCfg = Release|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Release|x86.Build.0 = Release|Any CPU {D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Release|x86.Build.0 = Release|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.AppStore|ARM.ActiveCfg = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.AppStore|ARM.Build.0 = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.AppStore|iPhone.Build.0 = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.AppStore|x64.ActiveCfg = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.AppStore|x64.Build.0 = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.AppStore|x86.ActiveCfg = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.AppStore|x86.Build.0 = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Debug|ARM.ActiveCfg = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Debug|ARM.Build.0 = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Debug|iPhone.Build.0 = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Debug|x64.ActiveCfg = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Debug|x64.Build.0 = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Debug|x86.ActiveCfg = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Debug|x86.Build.0 = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Release|Any CPU.Build.0 = Release|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Release|ARM.ActiveCfg = Release|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Release|ARM.Build.0 = Release|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Release|iPhone.ActiveCfg = Release|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Release|iPhone.Build.0 = Release|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Release|x64.ActiveCfg = Release|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Release|x64.Build.0 = Release|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Release|x86.ActiveCfg = Release|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Release|x86.Build.0 = Release|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
@ -966,54 +1022,102 @@ Global
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|x64.Build.0 = Release|Any CPU {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|x64.Build.0 = Release|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|x86.ActiveCfg = Release|Any CPU {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|x86.ActiveCfg = Release|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|x86.Build.0 = Release|Any CPU {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|x86.Build.0 = Release|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|ARM.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|ARM.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|iPhone.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|x64.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|x64.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|x86.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|x86.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|ARM.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|ARM.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|iPhone.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|x64.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|x64.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|x86.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|x86.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|Any CPU.Build.0 = Release|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|ARM.ActiveCfg = Release|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|ARM.Build.0 = Release|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|iPhone.ActiveCfg = Release|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|iPhone.Build.0 = Release|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|x64.ActiveCfg = Release|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|x64.Build.0 = Release|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|x86.ActiveCfg = Release|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|x86.Build.0 = Release|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.AppStore|ARM.ActiveCfg = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.AppStore|ARM.Build.0 = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.AppStore|iPhone.Build.0 = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.AppStore|x64.ActiveCfg = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.AppStore|x64.Build.0 = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.AppStore|x86.ActiveCfg = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.AppStore|x86.Build.0 = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Debug|ARM.ActiveCfg = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Debug|ARM.Build.0 = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Debug|iPhone.Build.0 = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Debug|x64.ActiveCfg = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Debug|x64.Build.0 = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Debug|x86.ActiveCfg = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Debug|x86.Build.0 = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Release|Any CPU.Build.0 = Release|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Release|ARM.ActiveCfg = Release|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Release|ARM.Build.0 = Release|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Release|iPhone.ActiveCfg = Release|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Release|iPhone.Build.0 = Release|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Release|x64.ActiveCfg = Release|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Release|x64.Build.0 = Release|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Release|x86.ActiveCfg = Release|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Release|x86.Build.0 = Release|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|ARM.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|ARM.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|iPhone.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|x64.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|x64.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|x86.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|x86.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|ARM.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|ARM.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|iPhone.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|x64.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|x64.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|x86.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|x86.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|Any CPU.Build.0 = Release|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|ARM.ActiveCfg = Release|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|ARM.Build.0 = Release|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|iPhone.ActiveCfg = Release|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|iPhone.Build.0 = Release|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|x64.ActiveCfg = Release|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|x64.Build.0 = Release|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|x86.ActiveCfg = Release|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|x86.Build.0 = Release|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU {E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU {E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU {E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
@ -1191,18 +1295,18 @@ Global
{C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F} = {E279BF0F-7F66-4F3A-A3AB-2CDA66C1CD04} {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F} = {E279BF0F-7F66-4F3A-A3AB-2CDA66C1CD04}
{FBF43D93-F2E7-4FF8-B4AB-186895949B88} = {DB0EFB20-B024-4E5E-A75C-52143C131D25} {FBF43D93-F2E7-4FF8-B4AB-186895949B88} = {DB0EFB20-B024-4E5E-A75C-52143C131D25}
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502} = {FBF43D93-F2E7-4FF8-B4AB-186895949B88} {D1C47FF1-91F1-4CAF-9ABB-AD642B821502} = {FBF43D93-F2E7-4FF8-B4AB-186895949B88}
{022E145D-1593-47EE-9608-8E323D3C63F5} = {91CF7717-08AB-4E65-B10E-0B426F01E2E8}
{1A01AF82-6FCB-464C-B39C-F127AEBD315D} = {022E145D-1593-47EE-9608-8E323D3C63F5}
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5} = {A81ECBC2-6B00-4DCD-8388-469174033379} {22A0F9C1-2D4A-4107-95B7-8459E6688BC5} = {A81ECBC2-6B00-4DCD-8388-469174033379}
{4BD76717-3102-4969-8C2C-BAAA3F0263B6} = {A81ECBC2-6B00-4DCD-8388-469174033379} {4BD76717-3102-4969-8C2C-BAAA3F0263B6} = {A81ECBC2-6B00-4DCD-8388-469174033379}
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D} = {807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F}
{4A980AC4-7205-46BF-8CCB-09E44D700FD4} = {807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F}
{F38B4FF0-0B49-405A-B1B4-F7A5E3BC4C4E} = {91CF7717-08AB-4E65-B10E-0B426F01E2E8}
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47} = {F38B4FF0-0B49-405A-B1B4-F7A5E3BC4C4E}
{41139F64-4046-4F16-96B7-D941D96FA9C6} = {91CF7717-08AB-4E65-B10E-0B426F01E2E8} {41139F64-4046-4F16-96B7-D941D96FA9C6} = {91CF7717-08AB-4E65-B10E-0B426F01E2E8}
{E7581357-FC34-474C-B8F5-307EE3CE05EF} = {41139F64-4046-4F16-96B7-D941D96FA9C6} {E7581357-FC34-474C-B8F5-307EE3CE05EF} = {41139F64-4046-4F16-96B7-D941D96FA9C6}
{88B22DBB-AA8F-4290-A454-2C109352C345} = {DB0EFB20-B024-4E5E-A75C-52143C131D25} {88B22DBB-AA8F-4290-A454-2C109352C345} = {DB0EFB20-B024-4E5E-A75C-52143C131D25}
{23A33F9B-7672-426D-ACF9-FF8436ADC81A} = {88B22DBB-AA8F-4290-A454-2C109352C345} {23A33F9B-7672-426D-ACF9-FF8436ADC81A} = {88B22DBB-AA8F-4290-A454-2C109352C345}
{A5260DE0-1FDD-467E-9CC1-A028AB081CEE} = {91CF7717-08AB-4E65-B10E-0B426F01E2E8} {A5260DE0-1FDD-467E-9CC1-A028AB081CEE} = {91CF7717-08AB-4E65-B10E-0B426F01E2E8}
{DF395F85-B010-465D-857A-7EBCC512C0C2} = {A5260DE0-1FDD-467E-9CC1-A028AB081CEE} {DF395F85-B010-465D-857A-7EBCC512C0C2} = {A5260DE0-1FDD-467E-9CC1-A028AB081CEE}
{88B22DBB-AA8F-4290-A454-2C109352C345} = {DB0EFB20-B024-4E5E-A75C-52143C131D25}
{23A33F9B-7672-426D-ACF9-FF8436ADC81A} = {88B22DBB-AA8F-4290-A454-2C109352C345}
{41139F64-4046-4F16-96B7-D941D96FA9C6} = {91CF7717-08AB-4E65-B10E-0B426F01E2E8}
{E7581357-FC34-474C-B8F5-307EE3CE05EF} = {41139F64-4046-4F16-96B7-D941D96FA9C6}
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal

+ 8
- 0
src/BuildingBlocks/CommandBus/CommandBus/CommandBus.csproj View File

@ -0,0 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.4</TargetFramework>
<RootNamespace>Microsoft.eShopOnContainers.BuildingBlocks.CommandBus</RootNamespace>
</PropertyGroup>
</Project>

+ 13
- 0
src/BuildingBlocks/CommandBus/CommandBus/ICommandBus.cs View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.BuildingBlocks.CommandBus
{
public interface ICommandBus
{
Task SendAsync<T>(T command) where T : IntegrationCommand;
}
}

+ 18
- 0
src/BuildingBlocks/CommandBus/CommandBus/IntegrationCommand.cs View File

@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Microsoft.eShopOnContainers.BuildingBlocks.CommandBus
{
public abstract class IntegrationCommand
{
public Guid Id { get; private set; }
public DateTime Sent { get; private set; }
protected IntegrationCommand()
{
Id = Guid.NewGuid();
Sent = DateTime.UtcNow;
}
}
}

+ 7
- 0
src/BuildingBlocks/CommandBus/CommandBusRabbitMQ/CommandBusRabbitMQ.csproj View File

@ -0,0 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.4</TargetFramework>
</PropertyGroup>
</Project>

+ 8
- 0
src/BuildingBlocks/EventBus/CommandBus/CommandBus.csproj View File

@ -0,0 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.0</TargetFramework>
<RootNamespace>Microsoft.eShopOnContainers.BuildingBlocks.CommandBus</RootNamespace>
</PropertyGroup>
</Project>

+ 16
- 0
src/BuildingBlocks/EventBus/CommandBus/ICommandBus.cs View File

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.BuildingBlocks.CommandBus
{
public interface ICommandBus
{
void Send<T>(string name, T data);
void Handle<TC>(string name, IIntegrationCommandHandler<TC> handler);
void Handle(string name, IIntegrationCommandHandler handler);
void Handle<TI, TC>(TI handler)
where TI : IIntegrationCommandHandler<TC>;
}
}

+ 16
- 0
src/BuildingBlocks/EventBus/CommandBus/IIntegrationCommandHandler.cs View File

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Microsoft.eShopOnContainers.BuildingBlocks.CommandBus
{
public interface IIntegrationCommandHandler
{
void Handle(IntegrationCommand command);
}
public interface IIntegrationCommandHandler<T> : IIntegrationCommandHandler
{
void Handle(IntegrationCommand<T> command);
}
}

+ 35
- 0
src/BuildingBlocks/EventBus/CommandBus/IntegrationCommand.cs View File

@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Microsoft.eShopOnContainers.BuildingBlocks.CommandBus
{
public abstract class IntegrationCommand
{
public Guid Id { get; }
public DateTime Sent { get; }
public abstract object GetDataAsObject();
protected IntegrationCommand()
{
Id = Guid.NewGuid();
Sent = DateTime.UtcNow;
}
}
public class IntegrationCommand<T> : IntegrationCommand
{
public T Data { get; }
public string Name { get; }
public override object GetDataAsObject() => Data;
public IntegrationCommand(string name, T data) : base()
{
Data = data;
Name = name;
}
}
}

+ 5
- 5
src/BuildingBlocks/EventBus/EventBus.Tests/InMemory_SubscriptionManager_Tests.cs View File

@ -18,7 +18,7 @@ namespace EventBus.Tests
public void After_One_Event_Subscription_Should_Contain_The_Event() public void After_One_Event_Subscription_Should_Contain_The_Event()
{ {
var manager = new InMemoryEventBusSubscriptionsManager(); var manager = new InMemoryEventBusSubscriptionsManager();
manager.AddSubscription<TestIntegrationEvent,TestIntegrationEventHandler>(() => new TestIntegrationEventHandler());
manager.AddSubscription<TestIntegrationEvent,TestIntegrationEventHandler>();
Assert.True(manager.HasSubscriptionsForEvent<TestIntegrationEvent>()); Assert.True(manager.HasSubscriptionsForEvent<TestIntegrationEvent>());
} }
@ -26,7 +26,7 @@ namespace EventBus.Tests
public void After_All_Subscriptions_Are_Deleted_Event_Should_No_Longer_Exists() public void After_All_Subscriptions_Are_Deleted_Event_Should_No_Longer_Exists()
{ {
var manager = new InMemoryEventBusSubscriptionsManager(); var manager = new InMemoryEventBusSubscriptionsManager();
manager.AddSubscription<TestIntegrationEvent, TestIntegrationEventHandler>(() => new TestIntegrationEventHandler());
manager.AddSubscription<TestIntegrationEvent, TestIntegrationEventHandler>();
manager.RemoveSubscription<TestIntegrationEvent, TestIntegrationEventHandler>(); manager.RemoveSubscription<TestIntegrationEvent, TestIntegrationEventHandler>();
Assert.False(manager.HasSubscriptionsForEvent<TestIntegrationEvent>()); Assert.False(manager.HasSubscriptionsForEvent<TestIntegrationEvent>());
} }
@ -37,7 +37,7 @@ namespace EventBus.Tests
bool raised = false; bool raised = false;
var manager = new InMemoryEventBusSubscriptionsManager(); var manager = new InMemoryEventBusSubscriptionsManager();
manager.OnEventRemoved += (o, e) => raised = true; manager.OnEventRemoved += (o, e) => raised = true;
manager.AddSubscription<TestIntegrationEvent, TestIntegrationEventHandler>(() => new TestIntegrationEventHandler());
manager.AddSubscription<TestIntegrationEvent, TestIntegrationEventHandler>();
manager.RemoveSubscription<TestIntegrationEvent, TestIntegrationEventHandler>(); manager.RemoveSubscription<TestIntegrationEvent, TestIntegrationEventHandler>();
Assert.True(raised); Assert.True(raised);
} }
@ -46,8 +46,8 @@ namespace EventBus.Tests
public void Get_Handlers_For_Event_Should_Return_All_Handlers() public void Get_Handlers_For_Event_Should_Return_All_Handlers()
{ {
var manager = new InMemoryEventBusSubscriptionsManager(); var manager = new InMemoryEventBusSubscriptionsManager();
manager.AddSubscription<TestIntegrationEvent, TestIntegrationEventHandler>(() => new TestIntegrationEventHandler());
manager.AddSubscription<TestIntegrationEvent, TestIntegrationOtherEventHandler>(() => new TestIntegrationOtherEventHandler());
manager.AddSubscription<TestIntegrationEvent, TestIntegrationEventHandler>();
manager.AddSubscription<TestIntegrationEvent, TestIntegrationOtherEventHandler>();
var handlers = manager.GetHandlersForEvent<TestIntegrationEvent>(); var handlers = manager.GetHandlersForEvent<TestIntegrationEvent>();
Assert.Equal(2, handlers.Count()); Assert.Equal(2, handlers.Count());
} }


+ 13
- 0
src/BuildingBlocks/EventBus/EventBus/Abstractions/IDynamicIntegrationEventHandler.cs View File

@ -0,0 +1,13 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions
{
public interface IDynamicIntegrationEventHandler
{
Task Handle(dynamic eventData);
}
}

+ 7
- 1
src/BuildingBlocks/EventBus/EventBus/Abstractions/IEventBus.cs View File

@ -5,9 +5,15 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions
{ {
public interface IEventBus public interface IEventBus
{ {
void Subscribe<T, TH>(Func<TH> handler)
void Subscribe<T, TH>()
where T : IntegrationEvent where T : IntegrationEvent
where TH : IIntegrationEventHandler<T>; where TH : IIntegrationEventHandler<T>;
void SubscribeDynamic<TH>(string eventName)
where TH : IDynamicIntegrationEventHandler;
void UnsubscribeDynamic<TH>(string eventName)
where TH : IDynamicIntegrationEventHandler;
void Unsubscribe<T, TH>() void Unsubscribe<T, TH>()
where TH : IIntegrationEventHandler<T> where TH : IIntegrationEventHandler<T>
where T : IntegrationEvent; where T : IntegrationEvent;


+ 14
- 6
src/BuildingBlocks/EventBus/EventBus/IEventBusSubscriptionsManager.cs View File

@ -2,6 +2,7 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using static Microsoft.eShopOnContainers.BuildingBlocks.EventBus.InMemoryEventBusSubscriptionsManager;
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus
{ {
@ -9,18 +10,25 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus
{ {
bool IsEmpty { get; } bool IsEmpty { get; }
event EventHandler<string> OnEventRemoved; event EventHandler<string> OnEventRemoved;
void AddSubscription<T, TH>(Func<TH> handler)
void AddDynamicSubscription<TH>(string eventName)
where TH : IDynamicIntegrationEventHandler;
void AddSubscription<T, TH>()
where T : IntegrationEvent where T : IntegrationEvent
where TH : IIntegrationEventHandler<T>; where TH : IIntegrationEventHandler<T>;
void RemoveSubscription<T, TH>()
where TH : IIntegrationEventHandler<T>
where T : IntegrationEvent;
void RemoveSubscription<T, TH>()
where TH : IIntegrationEventHandler<T>
where T : IntegrationEvent;
void RemoveDynamicSubscription<TH>(string eventName)
where TH : IDynamicIntegrationEventHandler;
bool HasSubscriptionsForEvent<T>() where T : IntegrationEvent; bool HasSubscriptionsForEvent<T>() where T : IntegrationEvent;
bool HasSubscriptionsForEvent(string eventName); bool HasSubscriptionsForEvent(string eventName);
Type GetEventTypeByName(string eventName); Type GetEventTypeByName(string eventName);
void Clear(); void Clear();
IEnumerable<Delegate> GetHandlersForEvent<T>() where T : IntegrationEvent;
IEnumerable<Delegate> GetHandlersForEvent(string eventName);
IEnumerable<SubscriptionInfo> GetHandlersForEvent<T>() where T : IntegrationEvent;
IEnumerable<SubscriptionInfo> GetHandlersForEvent(string eventName);
string GetEventKey<T>();
} }
} }

+ 84
- 37
src/BuildingBlocks/EventBus/EventBus/InMemoryEventBusSubscriptionsManager.cs View File

@ -8,64 +8,106 @@ using System.Text;
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus
{ {
public class InMemoryEventBusSubscriptionsManager : IEventBusSubscriptionsManager
public partial class InMemoryEventBusSubscriptionsManager : IEventBusSubscriptionsManager
{ {
private readonly Dictionary<string, List<Delegate>> _handlers;
private readonly Dictionary<string, List<SubscriptionInfo>> _handlers;
private readonly List<Type> _eventTypes; private readonly List<Type> _eventTypes;
public event EventHandler<string> OnEventRemoved; public event EventHandler<string> OnEventRemoved;
public InMemoryEventBusSubscriptionsManager() public InMemoryEventBusSubscriptionsManager()
{ {
_handlers = new Dictionary<string, List<Delegate>>();
_handlers = new Dictionary<string, List<SubscriptionInfo>>();
_eventTypes = new List<Type>(); _eventTypes = new List<Type>();
} }
public bool IsEmpty => !_handlers.Keys.Any(); public bool IsEmpty => !_handlers.Keys.Any();
public void Clear() => _handlers.Clear(); public void Clear() => _handlers.Clear();
public void AddSubscription<T, TH>(Func<TH> handler)
public void AddDynamicSubscription<TH>(string eventName)
where TH : IDynamicIntegrationEventHandler
{
DoAddSubscription(typeof(TH), eventName, isDynamic: true);
}
public void AddSubscription<T, TH>()
where T : IntegrationEvent where T : IntegrationEvent
where TH : IIntegrationEventHandler<T> where TH : IIntegrationEventHandler<T>
{ {
var key = GetEventKey<T>();
if (!HasSubscriptionsForEvent<T>())
var eventName = GetEventKey<T>();
DoAddSubscription(typeof(TH), eventName, isDynamic: false);
_eventTypes.Add(typeof(T));
}
private void DoAddSubscription(Type handlerType, string eventName, bool isDynamic)
{
if (!HasSubscriptionsForEvent(eventName))
{ {
_handlers.Add(key, new List<Delegate>());
_handlers.Add(eventName, new List<SubscriptionInfo>());
} }
_handlers[key].Add(handler);
_eventTypes.Add(typeof(T));
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(handlerType));
}
else
{
_handlers[eventName].Add(SubscriptionInfo.Typed(handlerType));
}
}
public void RemoveDynamicSubscription<TH>(string eventName)
where TH : IDynamicIntegrationEventHandler
{
var handlerToRemove = FindDynamicSubscriptionToRemove<TH>(eventName);
DoRemoveHandler(eventName, handlerToRemove);
} }
public void RemoveSubscription<T, TH>() public void RemoveSubscription<T, TH>()
where TH : IIntegrationEventHandler<T> where TH : IIntegrationEventHandler<T>
where T : IntegrationEvent where T : IntegrationEvent
{ {
var handlerToRemove = FindHandlerToRemove<T, TH>();
if (handlerToRemove != null)
var handlerToRemove = FindSubscriptionToRemove<T, TH>();
var eventName = GetEventKey<T>();
DoRemoveHandler(eventName, handlerToRemove);
}
private void DoRemoveHandler(string eventName, SubscriptionInfo subsToRemove)
{
if (subsToRemove != null)
{ {
var key = GetEventKey<T>();
_handlers[key].Remove(handlerToRemove);
if (!_handlers[key].Any())
_handlers[eventName].Remove(subsToRemove);
if (!_handlers[eventName].Any())
{ {
_handlers.Remove(key);
var eventType = _eventTypes.SingleOrDefault(e => e.Name == key);
_handlers.Remove(eventName);
var eventType = _eventTypes.SingleOrDefault(e => e.Name == eventName);
if (eventType != null) if (eventType != null)
{ {
_eventTypes.Remove(eventType); _eventTypes.Remove(eventType);
RaiseOnEventRemoved(eventType.Name);
} }
RaiseOnEventRemoved(eventName);
} }
} }
} }
public IEnumerable<Delegate> GetHandlersForEvent<T>() where T : IntegrationEvent
public IEnumerable<SubscriptionInfo> GetHandlersForEvent<T>() where T : IntegrationEvent
{ {
var key = GetEventKey<T>(); var key = GetEventKey<T>();
return GetHandlersForEvent(key); return GetHandlersForEvent(key);
} }
public IEnumerable<Delegate> GetHandlersForEvent(string eventName) => _handlers[eventName];
public IEnumerable<SubscriptionInfo> GetHandlersForEvent(string eventName) => _handlers[eventName];
private void RaiseOnEventRemoved(string eventName) private void RaiseOnEventRemoved(string eventName)
{ {
@ -76,26 +118,31 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus
} }
} }
private Delegate FindHandlerToRemove<T, TH>()
where T : IntegrationEvent
where TH : IIntegrationEventHandler<T>
private SubscriptionInfo FindDynamicSubscriptionToRemove<TH>(string eventName)
where TH : IDynamicIntegrationEventHandler
{
return DoFindSubscriptionToRemove(eventName, typeof(TH));
}
private SubscriptionInfo FindSubscriptionToRemove<T, TH>()
where T : IntegrationEvent
where TH : IIntegrationEventHandler<T>
{
var eventName = GetEventKey<T>();
return DoFindSubscriptionToRemove(eventName, typeof(TH));
}
private SubscriptionInfo DoFindSubscriptionToRemove(string eventName, Type handlerType)
{ {
if (!HasSubscriptionsForEvent<T>())
if (!HasSubscriptionsForEvent(eventName))
{ {
return null; return null;
} }
var key = GetEventKey<T>();
foreach (var func in _handlers[key])
{
var genericArgs = func.GetType().GetGenericArguments();
if (genericArgs.SingleOrDefault() == typeof(TH))
{
return func;
}
}
return _handlers[eventName].SingleOrDefault(s => s.HandlerType == handlerType);
return null;
} }
public bool HasSubscriptionsForEvent<T>() where T : IntegrationEvent public bool HasSubscriptionsForEvent<T>() where T : IntegrationEvent
@ -104,10 +151,10 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus
return HasSubscriptionsForEvent(key); return HasSubscriptionsForEvent(key);
} }
public bool HasSubscriptionsForEvent(string eventName) => _handlers.ContainsKey(eventName); public bool HasSubscriptionsForEvent(string eventName) => _handlers.ContainsKey(eventName);
public Type GetEventTypeByName(string eventName) => _eventTypes.Single(t => t.Name == eventName);
private string GetEventKey<T>()
public Type GetEventTypeByName(string eventName) => _eventTypes.SingleOrDefault(t => t.Name == eventName);
public string GetEventKey<T>()
{ {
return typeof(T).Name; return typeof(T).Name;
} }


+ 28
- 0
src/BuildingBlocks/EventBus/EventBus/SubscriptionInfo.cs View File

@ -0,0 +1,28 @@
using System;
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus
{
public partial class InMemoryEventBusSubscriptionsManager : IEventBusSubscriptionsManager
{
public class SubscriptionInfo
{
public bool IsDynamic { get; }
public Type HandlerType{ get; }
private SubscriptionInfo(bool isDynamic, Type handlerType)
{
IsDynamic = isDynamic;
HandlerType = handlerType;
}
public static SubscriptionInfo Dynamic(Type handlerType)
{
return new SubscriptionInfo(true, handlerType);
}
public static SubscriptionInfo Typed(Type handlerType)
{
return new SubscriptionInfo(false, handlerType);
}
}
}
}

+ 145
- 0
src/BuildingBlocks/EventBus/EventBusRabbitMQ/CommandBusRabbitMQ.cs View File

@ -0,0 +1,145 @@
//using Microsoft.eShopOnContainers.BuildingBlocks.CommandBus;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Polly;
using Polly.Retry;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using RabbitMQ.Client.Exceptions;
using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
/*
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
{
public class CommandBusRabbitMQ : ICommandBus, IDisposable
{
const string BROKER_NAME = "eshop_command_bus";
private readonly IRabbitMQPersistentConnection _persistentConnection;
private readonly ILogger<CommandBusRabbitMQ> _logger;
private IModel _consumerChannel;
private string _queueName;
private readonly Dictionary<string, IIntegrationCommandHandler> _handlers;
private readonly Dictionary<string, Type> _typeMappings;
public CommandBusRabbitMQ(IRabbitMQPersistentConnection persistentConnection,
ILogger<CommandBusRabbitMQ> logger)
{
_logger = logger;
_persistentConnection = persistentConnection;
_handlers = new Dictionary<string, IIntegrationCommandHandler>();
_typeMappings = new Dictionary<string, Type>();
}
public void Send<T>(string name, T data)
{
Send(new IntegrationCommand<T>(name, data));
}
public void Handle<TC>(string name, IIntegrationCommandHandler<TC> handler)
{
_handlers.Add(name, handler);
_typeMappings.Add(name, typeof(TC));
}
public void Handle(string name, IIntegrationCommandHandler handler)
{
_handlers.Add(name, handler);
}
public void Handle<TI, TC>(TI handler) where TI : IIntegrationCommandHandler<TC>
{
var name = typeof(TI).Name;
_handlers.Add(name, handler);
_typeMappings.Add(name, typeof(TC));
}
private void Send<T>(IntegrationCommand<T> command)
{
if (!_persistentConnection.IsConnected)
{
_persistentConnection.TryConnect();
}
var policy = RetryPolicy.Handle<BrokerUnreachableException>()
.Or<SocketException>()
.WaitAndRetry(5, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (ex, time) =>
{
_logger.LogWarning(ex.ToString());
});
using (var channel = _persistentConnection.CreateModel())
{
var commandName = command.Name;
channel.ExchangeDeclare(exchange: BROKER_NAME, type: "direct");
var message = JsonConvert.SerializeObject(command);
var body = Encoding.UTF8.GetBytes(message);
policy.Execute(() =>
{
channel.BasicPublish(exchange: BROKER_NAME,
routingKey: commandName,
basicProperties: null,
body: body);
});
}
}
private IModel CreateConsumerChannel()
{
if (!_persistentConnection.IsConnected)
{
_persistentConnection.TryConnect();
}
var channel = _persistentConnection.CreateModel();
channel.ExchangeDeclare(exchange: BROKER_NAME, type: "direct");
_queueName = channel.QueueDeclare().QueueName;
var consumer = new EventingBasicConsumer(channel);
consumer.Received += async (model, ea) =>
{
var commandName = ea.RoutingKey;
var message = Encoding.UTF8.GetString(ea.Body);
await InvokeHandler(commandName, message);
};
channel.BasicConsume(queue: _queueName,
noAck: true,
consumer: consumer);
channel.CallbackException += (sender, ea) =>
{
_consumerChannel.Dispose();
_consumerChannel = CreateConsumerChannel();
};
return channel;
}
private Task InvokeHandler(string commandName, string message)
{
if (_handlers.ContainsKey(commandName))
{
}
}
public void Dispose()
{
if (_consumerChannel != null)
{
_consumerChannel.Dispose();
}
}
}
}
*/

+ 49
- 28
src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs View File

@ -1,8 +1,10 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus;
using Autofac;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Polly; using Polly;
using Polly.Retry; using Polly.Retry;
using RabbitMQ.Client; using RabbitMQ.Client;
@ -25,17 +27,20 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
private readonly IRabbitMQPersistentConnection _persistentConnection; private readonly IRabbitMQPersistentConnection _persistentConnection;
private readonly ILogger<EventBusRabbitMQ> _logger; private readonly ILogger<EventBusRabbitMQ> _logger;
private readonly IEventBusSubscriptionsManager _subsManager; private readonly IEventBusSubscriptionsManager _subsManager;
private readonly ILifetimeScope _autofac;
private readonly string AUTOFAC_SCOPE_NAME = "eshop_event_bus";
private IModel _consumerChannel; private IModel _consumerChannel;
private string _queueName; private string _queueName;
public EventBusRabbitMQ(IRabbitMQPersistentConnection persistentConnection, ILogger<EventBusRabbitMQ> logger, IEventBusSubscriptionsManager subsManager)
public EventBusRabbitMQ(IRabbitMQPersistentConnection persistentConnection, ILogger<EventBusRabbitMQ> logger,
ILifetimeScope autofac, IEventBusSubscriptionsManager subsManager)
{ {
_persistentConnection = persistentConnection ?? throw new ArgumentNullException(nameof(persistentConnection)); _persistentConnection = persistentConnection ?? throw new ArgumentNullException(nameof(persistentConnection));
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); _logger = logger ?? throw new ArgumentNullException(nameof(logger));
_subsManager = subsManager ?? new InMemoryEventBusSubscriptionsManager(); _subsManager = subsManager ?? new InMemoryEventBusSubscriptionsManager();
_consumerChannel = CreateConsumerChannel(); _consumerChannel = CreateConsumerChannel();
_autofac = autofac;
_subsManager.OnEventRemoved += SubsManager_OnEventRemoved; _subsManager.OnEventRemoved += SubsManager_OnEventRemoved;
} }
@ -96,12 +101,25 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
} }
} }
public void Subscribe<T, TH>(Func<TH> handler)
public void SubscribeDynamic<TH>(string eventName)
where TH : IDynamicIntegrationEventHandler
{
DoInternalSubscription(eventName);
_subsManager.AddDynamicSubscription<TH>(eventName);
}
public void Subscribe<T, TH>()
where T : IntegrationEvent where T : IntegrationEvent
where TH : IIntegrationEventHandler<T> where TH : IIntegrationEventHandler<T>
{ {
var eventName = typeof(T).Name;
var containsKey = _subsManager.HasSubscriptionsForEvent<T>();
var eventName = _subsManager.GetEventKey<T>();
DoInternalSubscription(eventName);
_subsManager.AddSubscription<T, TH>();
}
private void DoInternalSubscription(string eventName)
{
var containsKey = _subsManager.HasSubscriptionsForEvent(eventName);
if (!containsKey) if (!containsKey)
{ {
if (!_persistentConnection.IsConnected) if (!_persistentConnection.IsConnected)
@ -116,9 +134,6 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
routingKey: eventName); routingKey: eventName);
} }
} }
_subsManager.AddSubscription<T, TH>(handler);
} }
public void Unsubscribe<T, TH>() public void Unsubscribe<T, TH>()
@ -128,19 +143,13 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
_subsManager.RemoveSubscription<T, TH>(); _subsManager.RemoveSubscription<T, TH>();
} }
private static Func<IIntegrationEventHandler> FindHandlerByType(Type handlerType, IEnumerable<Func<IIntegrationEventHandler>> handlers)
public void UnsubscribeDynamic<TH>(string eventName)
where TH : IDynamicIntegrationEventHandler
{ {
foreach (var func in handlers)
{
if (func.GetMethodInfo().ReturnType == handlerType)
{
return func;
}
}
return null;
_subsManager.RemoveDynamicSubscription<TH>(eventName);
} }
public void Dispose() public void Dispose()
{ {
if (_consumerChannel != null) if (_consumerChannel != null)
@ -190,17 +199,29 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
private async Task ProcessEvent(string eventName, string message) private async Task ProcessEvent(string eventName, string message)
{ {
if (_subsManager.HasSubscriptionsForEvent(eventName))
{
var eventType = _subsManager.GetEventTypeByName(eventName);
var integrationEvent = JsonConvert.DeserializeObject(message, eventType);
var handlers = _subsManager.GetHandlersForEvent(eventName);
foreach (var handlerfactory in handlers)
if (_subsManager.HasSubscriptionsForEvent(eventName))
{
using (var scope = _autofac.BeginLifetimeScope(AUTOFAC_SCOPE_NAME))
{ {
var handler = handlerfactory.DynamicInvoke();
var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType);
await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent });
var subscriptions = _subsManager.GetHandlersForEvent(eventName);
foreach (var subscription in subscriptions)
{
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 });
}
}
} }
} }
} }


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

@ -6,6 +6,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Autofac" Version="4.5.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.2" /> <PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.2" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.2" /> <PackageReference Include="Newtonsoft.Json" Version="10.0.2" />
<PackageReference Include="Polly" Version="5.1.0" /> <PackageReference Include="Polly" Version="5.1.0" />


+ 36
- 0
src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Basket/BasketCheckout.cs View File

@ -0,0 +1,36 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace eShopOnContainers.Core.Models.Basket
{
public class BasketCheckout
{
[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; }
}
}

+ 9
- 0
src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Basket/BasketService.cs View File

@ -42,6 +42,15 @@ namespace eShopOnContainers.Core.Services.Basket
return result; return result;
} }
public async Task CheckoutAsync(BasketCheckout basketCheckout, string token)
{
UriBuilder builder = new UriBuilder(GlobalSetting.Instance.BasketEndpoint + "/checkout");
string uri = builder.ToString();
await _requestProvider.PostAsync(uri, basketCheckout, token);
}
public async Task ClearBasketAsync(string guidUser, string token) public async Task ClearBasketAsync(string guidUser, string token)
{ {
UriBuilder builder = new UriBuilder(GlobalSetting.Instance.BasketEndpoint); UriBuilder builder = new UriBuilder(GlobalSetting.Instance.BasketEndpoint);


+ 1
- 0
src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Basket/IBasketService.cs View File

@ -7,6 +7,7 @@ namespace eShopOnContainers.Core.Services.Basket
{ {
Task<CustomerBasket> GetBasketAsync(string guidUser, string token); Task<CustomerBasket> GetBasketAsync(string guidUser, string token);
Task<CustomerBasket> UpdateBasketAsync(CustomerBasket customerBasket, string token); Task<CustomerBasket> UpdateBasketAsync(CustomerBasket customerBasket, string token);
Task CheckoutAsync(BasketCheckout basketCheckout, string token);
Task ClearBasketAsync(string guidUser, string token); Task ClearBasketAsync(string guidUser, string token);
} }
} }

+ 4
- 2
src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Order/IOrderService.cs View File

@ -1,13 +1,15 @@
using System.Collections.ObjectModel;
using eShopOnContainers.Core.Models.Basket;
using System.Collections.ObjectModel;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace eShopOnContainers.Core.Services.Order namespace eShopOnContainers.Core.Services.Order
{ {
public interface IOrderService public interface IOrderService
{ {
Task CreateOrderAsync(Models.Orders.Order newOrder, string token);
//Task CreateOrderAsync(Models.Orders.Order newOrder, string token);
Task<ObservableCollection<Models.Orders.Order>> GetOrdersAsync(string token); Task<ObservableCollection<Models.Orders.Order>> GetOrdersAsync(string token);
Task<Models.Orders.Order> GetOrderAsync(int orderId, string token); Task<Models.Orders.Order> GetOrderAsync(int orderId, string token);
Task<ObservableCollection<Models.Orders.CardType>> GetCardTypesAsync(string token); Task<ObservableCollection<Models.Orders.CardType>> GetCardTypesAsync(string token);
BasketCheckout MapOrderToBasket(Models.Orders.Order order);
} }
} }

+ 17
- 10
src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Order/OrderMockService.cs View File

@ -1,4 +1,5 @@
using eShopOnContainers.Core.Extensions; using eShopOnContainers.Core.Extensions;
using eShopOnContainers.Core.Models.Basket;
using eShopOnContainers.Core.Models.Orders; using eShopOnContainers.Core.Models.Orders;
using eShopOnContainers.Core.Models.User; using eShopOnContainers.Core.Models.User;
using System; using System;
@ -64,17 +65,18 @@ namespace eShopOnContainers.Core.Services.Order
new CardType { Id = 3, Name = "MasterCard" }, new CardType { Id = 3, Name = "MasterCard" },
}; };
public async Task CreateOrderAsync(Models.Orders.Order newOrder, string token)
private static BasketCheckout MockBasketCheckout = new BasketCheckout()
{ {
await Task.Delay(500);
if (!string.IsNullOrEmpty(token))
{
newOrder.OrderNumber = string.Format("{0}", MockOrders.Count + 1);
MockOrders.Insert(0, newOrder);
}
}
CardExpiration = DateTime.UtcNow,
CardHolderName = "FakeCardHolderName",
CardNumber = "122333423224",
CardSecurityNumber = "1234",
CardTypeId = 1,
City = "FakeCity",
Country = "FakeCountry",
ZipCode = "FakeZipCode",
Street = "FakeStreet"
};
public async Task<ObservableCollection<Models.Orders.Order>> GetOrdersAsync(string token) public async Task<ObservableCollection<Models.Orders.Order>> GetOrdersAsync(string token)
{ {
@ -111,5 +113,10 @@ namespace eShopOnContainers.Core.Services.Order
else else
return new ObservableCollection<CardType>(); return new ObservableCollection<CardType>();
} }
public BasketCheckout MapOrderToBasket(Models.Orders.Order order)
{
return MockBasketCheckout;
}
} }
} }

+ 18
- 12
src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Order/OrderService.cs View File

@ -1,4 +1,5 @@
using eShopOnContainers.Core.Services.RequestProvider;
using eShopOnContainers.Core.Models.Basket;
using eShopOnContainers.Core.Services.RequestProvider;
using System; using System;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -14,17 +15,6 @@ namespace eShopOnContainers.Core.Services.Order
_requestProvider = requestProvider; _requestProvider = requestProvider;
} }
public async Task CreateOrderAsync(Models.Orders.Order newOrder, string token)
{
UriBuilder builder = new UriBuilder(GlobalSetting.Instance.OrdersEndpoint);
builder.Path = "api/v1/orders/new";
string uri = builder.ToString();
await _requestProvider.PostAsync(uri, newOrder, token, "x-requestid");
}
public async Task<ObservableCollection<Models.Orders.Order>> GetOrdersAsync(string token) public async Task<ObservableCollection<Models.Orders.Order>> GetOrdersAsync(string token)
{ {
@ -82,5 +72,21 @@ namespace eShopOnContainers.Core.Services.Order
return new ObservableCollection<Models.Orders.CardType>(); return new ObservableCollection<Models.Orders.CardType>();
} }
} }
public BasketCheckout MapOrderToBasket(Models.Orders.Order order)
{
return new BasketCheckout()
{
CardExpiration = order.CardExpiration,
CardHolderName = order.CardHolderName,
CardNumber = order.CardNumber,
CardSecurityNumber = order.CardSecurityNumber,
CardTypeId = order.CardTypeId,
City = order.ShippingCity,
Country = order.ShippingCountry,
ZipCode = order.ShippingZipCode,
Street = order.ShippingStreet
};
}
} }
} }

+ 4
- 2
src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/CheckoutViewModel.cs View File

@ -132,8 +132,10 @@ namespace eShopOnContainers.Core.ViewModels
{ {
var authToken = Settings.AuthAccessToken; var authToken = Settings.AuthAccessToken;
// Create new order
await _orderService.CreateOrderAsync(Order, authToken);
var basket = _orderService.MapOrderToBasket(Order);
// Create basket checkout
await _basketService.CheckoutAsync(basket, authToken);
// Clean Basket // Clean Basket
await _basketService.ClearBasketAsync(_shippingAddress.Id.ToString(), authToken); await _basketService.ClearBasketAsync(_shippingAddress.Id.ToString(), authToken);


+ 6
- 0
src/Mobile/eShopOnContainers/eShopOnContainers.Core/eShopOnContainers.Core.csproj View File

@ -64,6 +64,7 @@
<Compile Include="Helpers\EasingHelper.cs" /> <Compile Include="Helpers\EasingHelper.cs" />
<Compile Include="Helpers\ServicesHelper.cs" /> <Compile Include="Helpers\ServicesHelper.cs" />
<Compile Include="Helpers\Settings.cs" /> <Compile Include="Helpers\Settings.cs" />
<Compile Include="Models\Basket\BasketCheckout.cs" />
<Compile Include="Models\Basket\BasketItem.cs" /> <Compile Include="Models\Basket\BasketItem.cs" />
<Compile Include="Models\Basket\CustomerBasket.cs" /> <Compile Include="Models\Basket\CustomerBasket.cs" />
<Compile Include="Models\Catalog\CatalogBrand.cs" /> <Compile Include="Models\Catalog\CatalogBrand.cs" />
@ -262,6 +263,11 @@
<Generator>MSBuild:UpdateDesignTimeXaml</Generator> <Generator>MSBuild:UpdateDesignTimeXaml</Generator>
</EmbeddedResource> </EmbeddedResource>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Reference Include="System.ComponentModel.Annotations">
<HintPath>..\..\..\..\..\..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETPortable\v4.6\Profile\Profile44\System.ComponentModel.Annotations.dll</HintPath>
</Reference>
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" /> <Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup> <PropertyGroup>


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

@ -18,6 +18,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="4.1.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="1.0.0" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="1.0.0" />
<PackageReference Include="System.Threading" Version="4.3.0" /> <PackageReference Include="System.Threading" Version="4.3.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.3" /> <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.3" />


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

@ -5,6 +5,11 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.eShopOnContainers.Services.Basket.API.Model; using Microsoft.eShopOnContainers.Services.Basket.API.Model;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
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 namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers
{ {
@ -12,11 +17,17 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers
[Authorize] [Authorize]
public class BasketController : Controller public class BasketController : Controller
{ {
private IBasketRepository _repository;
private readonly IBasketRepository _repository;
private readonly IIdentityService _identitySvc;
private readonly IEventBus _eventBus;
public BasketController(IBasketRepository repository)
public BasketController(IBasketRepository repository,
IIdentityService identityService,
IEventBus eventBus)
{ {
_repository = repository; _repository = repository;
_identitySvc = identityService;
_eventBus = eventBus;
} }
// GET /id // GET /id
[HttpGet("{id}")] [HttpGet("{id}")]
@ -36,11 +47,38 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers
return Ok(basket); return Ok(basket);
} }
// DELETE /id
[Route("checkout")]
[HttpPost]
public async Task<IActionResult> Checkout([FromBody]BasketCheckout basketCheckout, [FromHeader(Name = "x-requestid")] string requestId)
{
var userId = _identitySvc.GetUserIdentity();
basketCheckout.RequestId = (Guid.TryParse(requestId, out Guid guid) && guid != Guid.Empty) ?
guid : basketCheckout.RequestId;
var basket = await _repository.GetBasketAsync(userId);
var eventMessage = new UserCheckoutAcceptedIntegrationEvent(userId, basketCheckout.City, basketCheckout.Street,
basketCheckout.State, basketCheckout.Country, basketCheckout.ZipCode, basketCheckout.CardNumber, basketCheckout.CardHolderName,
basketCheckout.CardExpiration, basketCheckout.CardSecurityNumber, basketCheckout.CardTypeId, basketCheckout.Buyer, basketCheckout.RequestId, basket);
// Once basket is checkout, sends an integration event to
// ordering.api to convert basket to order and proceeds with
// order creation process
_eventBus.Publish(eventMessage);
if (basket == null)
{
return BadRequest();
}
return Accepted();
}
// DELETE api/values/5
[HttpDelete("{id}")] [HttpDelete("{id}")]
public void Delete(string id) public void Delete(string id)
{ {
_repository.DeleteBasketAsync(id); _repository.DeleteBasketAsync(id);
} }
} }
} }

+ 1
- 0
src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/OrderStartedIntegrationEventHandler.cs View File

@ -1,5 +1,6 @@
using Basket.API.IntegrationEvents.Events; using Basket.API.IntegrationEvents.Events;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using Microsoft.eShopOnContainers.Services.Basket.API.Model; using Microsoft.eShopOnContainers.Services.Basket.API.Model;
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;


+ 3
- 3
src/Services/Basket/Basket.API/IntegrationEvents/Events/OrderStartedIntegrationEvent.cs View File

@ -7,9 +7,9 @@ namespace Basket.API.IntegrationEvents.Events
// An Integration Event is an event that can cause side effects to other microsrvices, Bounded-Contexts or external systems. // An Integration Event is an event that can cause side effects to other microsrvices, Bounded-Contexts or external systems.
public class OrderStartedIntegrationEvent : IntegrationEvent public class OrderStartedIntegrationEvent : IntegrationEvent
{ {
public string UserId { get; }
public string UserId { get; set; }
public OrderStartedIntegrationEvent(string userId) =>
UserId = userId;
public OrderStartedIntegrationEvent(string userId)
=> UserId = userId;
} }
} }

+ 64
- 0
src/Services/Basket/Basket.API/IntegrationEvents/Events/UserCheckoutAcceptedIntegrationEvent.cs View File

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

+ 1
- 1
src/Services/Basket/Basket.API/Model/Basket.cs View File

@ -10,7 +10,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Model
public CustomerBasket(string customerId) public CustomerBasket(string customerId)
{ {
BuyerId = customerId; BuyerId = customerId;
Items = new List<Model.BasketItem>();
Items = new List<BasketItem>();
} }
} }
} }

+ 35
- 0
src/Services/Basket/Basket.API/Model/BasketCheckout.cs View File

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

+ 12
- 0
src/Services/Basket/Basket.API/Services/IIdentityService.cs View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Services.Basket.API.Services
{
public interface IIdentityService
{
string GetUserIdentity();
}
}

+ 24
- 0
src/Services/Basket/Basket.API/Services/IdentityService.cs View File

@ -0,0 +1,24 @@

using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Services.Basket.API.Services
{
public class IdentityService : IIdentityService
{
private IHttpContextAccessor _context;
public IdentityService(IHttpContextAccessor context)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
}
public string GetUserIdentity()
{
return _context.HttpContext.User.FindFirst("sub").Value;
}
}
}

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

@ -1,8 +1,11 @@
using Basket.API.Infrastructure.Filters;
using Autofac;
using Autofac.Extensions.DependencyInjection;
using Basket.API.Infrastructure.Filters;
using Basket.API.IntegrationEvents.EventHandling; using Basket.API.IntegrationEvents.EventHandling;
using Basket.API.IntegrationEvents.Events; using Basket.API.IntegrationEvents.Events;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ; using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ;
@ -10,6 +13,7 @@ using Microsoft.eShopOnContainers.Services.Basket.API.Auth.Server;
using Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.EventHandling; using Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.EventHandling;
using Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.Events; using Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.Events;
using Microsoft.eShopOnContainers.Services.Basket.API.Model; using Microsoft.eShopOnContainers.Services.Basket.API.Model;
using Microsoft.eShopOnContainers.Services.Basket.API.Services;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.HealthChecks; using Microsoft.Extensions.HealthChecks;
@ -17,10 +21,10 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using RabbitMQ.Client; using RabbitMQ.Client;
using StackExchange.Redis; using StackExchange.Redis;
using System;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
using System;
namespace Microsoft.eShopOnContainers.Services.Basket.API namespace Microsoft.eShopOnContainers.Services.Basket.API
{ {
@ -39,7 +43,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
public IConfigurationRoot Configuration { get; } public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container. // 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 => services.AddHealthChecks(checks =>
@ -102,9 +106,15 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
.AllowAnyHeader() .AllowAnyHeader()
.AllowCredentials()); .AllowCredentials());
}); });
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddTransient<IBasketRepository, RedisBasketRepository>(); services.AddTransient<IBasketRepository, RedisBasketRepository>();
services.AddTransient<IIdentityService, IdentityService>();
RegisterServiceBus(services); RegisterServiceBus(services);
services.AddOptions();
var container = new ContainerBuilder();
container.Populate(services);
return new AutofacServiceProvider(container.Build());
} }
private void RegisterServiceBus(IServiceCollection services) private void RegisterServiceBus(IServiceCollection services)
@ -114,7 +124,6 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
services.AddTransient<ProductPriceChangedIntegrationEventHandler>(); services.AddTransient<ProductPriceChangedIntegrationEventHandler>();
services.AddTransient<OrderStartedIntegrationEventHandler>(); services.AddTransient<OrderStartedIntegrationEventHandler>();
} }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
@ -155,19 +164,9 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
protected virtual void ConfigureEventBus(IApplicationBuilder app) protected virtual void ConfigureEventBus(IApplicationBuilder app)
{ {
var catalogPriceHandler = app.ApplicationServices
.GetService<IIntegrationEventHandler<ProductPriceChangedIntegrationEvent>>();
var orderStartedHandler = app.ApplicationServices
.GetService<IIntegrationEventHandler<OrderStartedIntegrationEvent>>();
var eventBus = app.ApplicationServices.GetRequiredService<IEventBus>(); var eventBus = app.ApplicationServices.GetRequiredService<IEventBus>();
eventBus.Subscribe<ProductPriceChangedIntegrationEvent, ProductPriceChangedIntegrationEventHandler>
(() => app.ApplicationServices.GetRequiredService<ProductPriceChangedIntegrationEventHandler>());
eventBus.Subscribe<OrderStartedIntegrationEvent, OrderStartedIntegrationEventHandler>
(() => app.ApplicationServices.GetRequiredService<OrderStartedIntegrationEventHandler>());
eventBus.Subscribe<ProductPriceChangedIntegrationEvent, ProductPriceChangedIntegrationEventHandler>();
eventBus.Subscribe<OrderStartedIntegrationEvent, OrderStartedIntegrationEventHandler>();
} }
} }
} }

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

@ -2,9 +2,13 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp1.1</TargetFramework> <TargetFramework>netcoreapp1.1</TargetFramework>
<RuntimeFrameworkVersion>1.1.2</RuntimeFrameworkVersion>
<DebugType>portable</DebugType>
<PreserveCompilationContext>true</PreserveCompilationContext>
<AssemblyName>Catalog.API</AssemblyName>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<PackageId>Catalog.API</PackageId>
<UserSecretsId>aspnet-Catalog.API-20161122013618</UserSecretsId> <UserSecretsId>aspnet-Catalog.API-20161122013618</UserSecretsId>
<RuntimeFrameworkVersion>1.1.2</RuntimeFrameworkVersion>
<PackageTargetFallback>$(PackageTargetFallback);dotnet5.6;portable-net45+win8</PackageTargetFallback> <PackageTargetFallback>$(PackageTargetFallback);dotnet5.6;portable-net45+win8</PackageTargetFallback>
<DockerComposeProjectPath>..\..\..\..\docker-compose.dcproj</DockerComposeProjectPath> <DockerComposeProjectPath>..\..\..\..\docker-compose.dcproj</DockerComposeProjectPath>
</PropertyGroup> </PropertyGroup>
@ -25,12 +29,12 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="4.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.3" /> <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.3" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="1.1.2" /> <PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="1.1.2" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.Abstractions" Version="1.1.2" /> <PackageReference Include="Microsoft.AspNetCore.Diagnostics.Abstractions" Version="1.1.2" />
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="1.1.2" /> <PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="1.1.2" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="1.1.2" /> <PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="1.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.Design" Version="1.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="1.1.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="1.1.2" /> <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="1.1.2" /> <PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="1.1.2" />
@ -46,7 +50,6 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.2" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.2" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.2" /> <PackageReference Include="Newtonsoft.Json" Version="10.0.2" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="1.0.0" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="1.0.0" />
<PackageReference Include="Polly" Version="5.1.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>


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

@ -70,18 +70,18 @@
{ {
return new List<CatalogItem>() return new List<CatalogItem>()
{ {
new CatalogItem() { CatalogTypeId=2,CatalogBrandId=2, Description = ".NET Bot Black Hoodie", Name = ".NET Bot Black Hoodie", Price = 19.5M, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/1" },
new CatalogItem() { CatalogTypeId=1,CatalogBrandId=2, Description = ".NET Black & White Mug", Name = ".NET Black & White Mug", Price= 8.50M, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/2" },
new CatalogItem() { CatalogTypeId=2,CatalogBrandId=5, Description = "Prism White T-Shirt", Name = "Prism White T-Shirt", Price = 12, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/3" },
new CatalogItem() { CatalogTypeId=2,CatalogBrandId=2, Description = ".NET Foundation T-shirt", Name = ".NET Foundation T-shirt", Price = 12, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/4" },
new CatalogItem() { CatalogTypeId=3,CatalogBrandId=5, Description = "Roslyn Red Sheet", Name = "Roslyn Red Sheet", Price = 8.5M, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/5" },
new CatalogItem() { CatalogTypeId=2,CatalogBrandId=2, Description = ".NET Blue Hoodie", Name = ".NET Blue Hoodie", Price = 12, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/6" },
new CatalogItem() { CatalogTypeId=2,CatalogBrandId=5, Description = "Roslyn Red T-Shirt", Name = "Roslyn Red T-Shirt", Price = 12, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/7" },
new CatalogItem() { CatalogTypeId=2,CatalogBrandId=5, Description = "Kudu Purple Hoodie", Name = "Kudu Purple Hoodie", Price = 8.5M, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/8" },
new CatalogItem() { CatalogTypeId=1,CatalogBrandId=5, Description = "Cup<T> White Mug", Name = "Cup<T> White Mug", Price = 12, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/9" },
new CatalogItem() { CatalogTypeId=3,CatalogBrandId=2, Description = ".NET Foundation Sheet", Name = ".NET Foundation Sheet", Price = 12, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/10" },
new CatalogItem() { CatalogTypeId=3,CatalogBrandId=2, Description = "Cup<T> Sheet", Name = "Cup<T> Sheet", Price = 8.5M, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/11" },
new CatalogItem() { CatalogTypeId=2,CatalogBrandId=5, Description = "Prism White TShirt", Name = "Prism White TShirt", Price = 12, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/12" }
new CatalogItem() { CatalogTypeId=2,CatalogBrandId=2, Description = ".NET Bot Black Hoodie", Name = ".NET Bot Black Hoodie", Price = 19.5M, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/1", AvailableStock = 1},
new CatalogItem() { CatalogTypeId=1,CatalogBrandId=2, Description = ".NET Black & White Mug", Name = ".NET Black & White Mug", Price= 8.50M, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/2", AvailableStock = 1 },
new CatalogItem() { CatalogTypeId=2,CatalogBrandId=5, Description = "Prism White T-Shirt", Name = "Prism White T-Shirt", Price = 12, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/3", AvailableStock = 1 },
new CatalogItem() { CatalogTypeId=2,CatalogBrandId=2, Description = ".NET Foundation T-shirt", Name = ".NET Foundation T-shirt", Price = 12, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/4", AvailableStock = 1 },
new CatalogItem() { CatalogTypeId=3,CatalogBrandId=5, Description = "Roslyn Red Sheet", Name = "Roslyn Red Sheet", Price = 8.5M, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/5", AvailableStock = 1 },
new CatalogItem() { CatalogTypeId=2,CatalogBrandId=2, Description = ".NET Blue Hoodie", Name = ".NET Blue Hoodie", Price = 12, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/6", AvailableStock = 1 },
new CatalogItem() { CatalogTypeId=2,CatalogBrandId=5, Description = "Roslyn Red T-Shirt", Name = "Roslyn Red T-Shirt", Price = 12, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/7", AvailableStock = 1 },
new CatalogItem() { CatalogTypeId=2,CatalogBrandId=5, Description = "Kudu Purple Hoodie", Name = "Kudu Purple Hoodie", Price = 8.5M, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/8", AvailableStock = 1 },
new CatalogItem() { CatalogTypeId=1,CatalogBrandId=5, Description = "Cup<T> White Mug", Name = "Cup<T> White Mug", Price = 12, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/9", AvailableStock = 1 },
new CatalogItem() { CatalogTypeId=3,CatalogBrandId=2, Description = ".NET Foundation Sheet", Name = ".NET Foundation Sheet", Price = 12, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/10", AvailableStock = 1 },
new CatalogItem() { CatalogTypeId=3,CatalogBrandId=2, Description = "Cup<T> Sheet", Name = "Cup<T> Sheet", Price = 8.5M, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/11", AvailableStock = 1 },
new CatalogItem() { CatalogTypeId=2,CatalogBrandId=5, Description = "Prism White TShirt", Name = "Prism White TShirt", Price = 12, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/12", AvailableStock = 1 }
}; };
} }
} }


+ 107
- 0
src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20170509130025_AddStockProductItem.Designer.cs View File

@ -0,0 +1,107 @@
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure;
namespace Catalog.API.Infrastructure.Migrations
{
[DbContext(typeof(CatalogContext))]
[Migration("20170509130025_AddStockProductItem")]
partial class AddStockProductItem
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
modelBuilder
.HasAnnotation("ProductVersion", "1.1.1")
.HasAnnotation("SqlServer:Sequence:.catalog_brand_hilo", "'catalog_brand_hilo', '', '1', '10', '', '', 'Int64', 'False'")
.HasAnnotation("SqlServer:Sequence:.catalog_hilo", "'catalog_hilo', '', '1', '10', '', '', 'Int64', 'False'")
.HasAnnotation("SqlServer:Sequence:.catalog_type_hilo", "'catalog_type_hilo', '', '1', '10', '', '', 'Int64', 'False'")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogBrand", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:HiLoSequenceName", "catalog_brand_hilo")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
b.Property<string>("Brand")
.IsRequired()
.HasMaxLength(100);
b.HasKey("Id");
b.ToTable("CatalogBrand");
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogItem", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:HiLoSequenceName", "catalog_hilo")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
b.Property<int>("AvailableStock");
b.Property<int>("CatalogBrandId");
b.Property<int>("CatalogTypeId");
b.Property<string>("Description");
b.Property<int>("MaxStockThreshold");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(50);
b.Property<bool>("OnReorder");
b.Property<string>("PictureUri");
b.Property<decimal>("Price");
b.Property<int>("RestockThreshold");
b.HasKey("Id");
b.HasIndex("CatalogBrandId");
b.HasIndex("CatalogTypeId");
b.ToTable("Catalog");
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogType", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:HiLoSequenceName", "catalog_type_hilo")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
b.Property<string>("Type")
.IsRequired()
.HasMaxLength(100);
b.HasKey("Id");
b.ToTable("CatalogType");
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogItem", b =>
{
b.HasOne("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogBrand", "CatalogBrand")
.WithMany()
.HasForeignKey("CatalogBrandId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogType", "CatalogType")
.WithMany()
.HasForeignKey("CatalogTypeId")
.OnDelete(DeleteBehavior.Cascade);
});
}
}
}

+ 55
- 0
src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20170509130025_AddStockProductItem.cs View File

@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Catalog.API.Infrastructure.Migrations
{
public partial class AddStockProductItem : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "AvailableStock",
table: "Catalog",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "MaxStockThreshold",
table: "Catalog",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<bool>(
name: "OnReorder",
table: "Catalog",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<int>(
name: "RestockThreshold",
table: "Catalog",
nullable: false,
defaultValue: 0);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "AvailableStock",
table: "Catalog");
migrationBuilder.DropColumn(
name: "MaxStockThreshold",
table: "Catalog");
migrationBuilder.DropColumn(
name: "OnReorder",
table: "Catalog");
migrationBuilder.DropColumn(
name: "RestockThreshold",
table: "Catalog");
}
}
}

+ 8
- 0
src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/CatalogContextModelSnapshot.cs View File

@ -42,20 +42,28 @@ namespace Catalog.API.Infrastructure.Migrations
.HasAnnotation("SqlServer:HiLoSequenceName", "catalog_hilo") .HasAnnotation("SqlServer:HiLoSequenceName", "catalog_hilo")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
b.Property<int>("AvailableStock");
b.Property<int>("CatalogBrandId"); b.Property<int>("CatalogBrandId");
b.Property<int>("CatalogTypeId"); b.Property<int>("CatalogTypeId");
b.Property<string>("Description"); b.Property<string>("Description");
b.Property<int>("MaxStockThreshold");
b.Property<string>("Name") b.Property<string>("Name")
.IsRequired() .IsRequired()
.HasMaxLength(50); .HasMaxLength(50);
b.Property<bool>("OnReorder");
b.Property<string>("PictureUri"); b.Property<string>("PictureUri");
b.Property<decimal>("Price"); b.Property<decimal>("Price");
b.Property<int>("RestockThreshold");
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("CatalogBrandId"); b.HasIndex("CatalogBrandId");


+ 46
- 0
src/Services/Catalog/Catalog.API/IntegrationEvents/EventHandling/OrderStatusChangedToAwaitingValidationIntegrationEventHandler.cs View File

@ -0,0 +1,46 @@
namespace Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents.EventHandling
{
using BuildingBlocks.EventBus.Abstractions;
using System.Threading.Tasks;
using BuildingBlocks.EventBus.Events;
using Infrastructure;
using System.Collections.Generic;
using System.Linq;
using global::Catalog.API.IntegrationEvents;
using IntegrationEvents.Events;
public class OrderStatusChangedToAwaitingValidationIntegrationEventHandler :
IIntegrationEventHandler<OrderStatusChangedToAwaitingValidationIntegrationEvent>
{
private readonly CatalogContext _catalogContext;
private readonly ICatalogIntegrationEventService _catalogIntegrationEventService;
public OrderStatusChangedToAwaitingValidationIntegrationEventHandler(CatalogContext catalogContext,
ICatalogIntegrationEventService catalogIntegrationEventService)
{
_catalogContext = catalogContext;
_catalogIntegrationEventService = catalogIntegrationEventService;
}
public async Task Handle(OrderStatusChangedToAwaitingValidationIntegrationEvent command)
{
var confirmedOrderStockItems = new List<ConfirmedOrderStockItem>();
foreach (var orderStockItem in command.OrderStockItems)
{
var catalogItem = _catalogContext.CatalogItems.Find(orderStockItem.ProductId);
var hasStock = catalogItem.AvailableStock >= orderStockItem.Units;
var confirmedOrderStockItem = new ConfirmedOrderStockItem(catalogItem.Id, hasStock);
confirmedOrderStockItems.Add(confirmedOrderStockItem);
}
var confirmedIntegrationEvent = confirmedOrderStockItems.Any(c => !c.HasStock)
? (IntegrationEvent) new OrderStockRejectedIntegrationEvent(command.OrderId, confirmedOrderStockItems)
: new OrderStockConfirmedIntegrationEvent(command.OrderId);
await _catalogIntegrationEventService.SaveEventAndCatalogContextChangesAsync(confirmedIntegrationEvent);
await _catalogIntegrationEventService.PublishThroughEventBusAsync(confirmedIntegrationEvent);
}
}
}

+ 31
- 0
src/Services/Catalog/Catalog.API/IntegrationEvents/EventHandling/OrderStatusChangedToPaidIntegrationEventHandler.cs View File

@ -0,0 +1,31 @@
namespace Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents.EventHandling
{
using BuildingBlocks.EventBus.Abstractions;
using System.Threading.Tasks;
using Infrastructure;
using Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents.Events;
public class OrderStatusChangedToPaidIntegrationEventHandler :
IIntegrationEventHandler<OrderStatusChangedToPaidIntegrationEvent>
{
private readonly CatalogContext _catalogContext;
public OrderStatusChangedToPaidIntegrationEventHandler(CatalogContext catalogContext)
{
_catalogContext = catalogContext;
}
public async Task Handle(OrderStatusChangedToPaidIntegrationEvent command)
{
//we're not blocking stock/inventory
foreach (var orderStockItem in command.OrderStockItems)
{
var catalogItem = _catalogContext.CatalogItems.Find(orderStockItem.ProductId);
catalogItem.RemoveStock(orderStockItem.Units);
}
await _catalogContext.SaveChangesAsync();
}
}
}

+ 30
- 0
src/Services/Catalog/Catalog.API/IntegrationEvents/Events/OrderStatusChangedToAwaitingValidationIntegrationEvent.cs View File

@ -0,0 +1,30 @@
namespace Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents.Events
{
using BuildingBlocks.EventBus.Events;
using System.Collections.Generic;
public class OrderStatusChangedToAwaitingValidationIntegrationEvent : IntegrationEvent
{
public int OrderId { get; }
public IEnumerable<OrderStockItem> OrderStockItems { get; }
public OrderStatusChangedToAwaitingValidationIntegrationEvent(int orderId,
IEnumerable<OrderStockItem> orderStockItems)
{
OrderId = orderId;
OrderStockItems = orderStockItems;
}
}
public class OrderStockItem
{
public int ProductId { get; }
public int Units { get; }
public OrderStockItem(int productId, int units)
{
ProductId = productId;
Units = units;
}
}
}

+ 18
- 0
src/Services/Catalog/Catalog.API/IntegrationEvents/Events/OrderStatusChangedToPaidIntegrationEvent.cs View File

@ -0,0 +1,18 @@
namespace Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents.Events
{
using System.Collections.Generic;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
public class OrderStatusChangedToPaidIntegrationEvent : IntegrationEvent
{
public int OrderId { get; }
public IEnumerable<OrderStockItem> OrderStockItems { get; }
public OrderStatusChangedToPaidIntegrationEvent(int orderId,
IEnumerable<OrderStockItem> orderStockItems)
{
OrderId = orderId;
OrderStockItems = orderStockItems;
}
}
}

+ 11
- 0
src/Services/Catalog/Catalog.API/IntegrationEvents/Events/OrderStockConfirmedIntegrationEvent.cs View File

@ -0,0 +1,11 @@
namespace Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents.Events
{
using BuildingBlocks.EventBus.Events;
public class OrderStockConfirmedIntegrationEvent : IntegrationEvent
{
public int OrderId { get; }
public OrderStockConfirmedIntegrationEvent(int orderId) => OrderId = orderId;
}
}

+ 31
- 0
src/Services/Catalog/Catalog.API/IntegrationEvents/Events/OrderStockRejectedIntegrationEvent.cs View File

@ -0,0 +1,31 @@
namespace Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents.Events
{
using BuildingBlocks.EventBus.Events;
using System.Collections.Generic;
public class OrderStockRejectedIntegrationEvent : IntegrationEvent
{
public int OrderId { get; }
public List<ConfirmedOrderStockItem> OrderStockItems { get; }
public OrderStockRejectedIntegrationEvent(int orderId,
List<ConfirmedOrderStockItem> orderStockItems)
{
OrderId = orderId;
OrderStockItems = orderStockItems;
}
}
public class ConfirmedOrderStockItem
{
public int ProductId { get; }
public bool HasStock { get; }
public ConfirmedOrderStockItem(int productId, bool hasStock)
{
ProductId = productId;
HasStock = hasStock;
}
}
}

+ 4
- 7
src/Services/Catalog/Catalog.API/IntegrationEvents/Events/ProductPriceChangedIntegrationEvent.cs View File

@ -1,10 +1,7 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using System;
using System.Collections.Generic;
using System.Text;
namespace Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents.Events
namespace Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents.Events
{ {
using BuildingBlocks.EventBus.Events;
// Integration Events notes: // Integration Events notes:
// An Event is “something that has happened in the past”, therefore its name has to be // 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. // An Integration Event is an event that can cause side effects to other microsrvices, Bounded-Contexts or external systems.
@ -23,4 +20,4 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents.Eve
OldPrice = oldPrice; OldPrice = oldPrice;
} }
} }
}
}

+ 77
- 3
src/Services/Catalog/Catalog.API/Model/CatalogItem.cs View File

@ -1,4 +1,5 @@
using System;
using Catalog.API.Infrastructure.Exceptions;
using System;
namespace Microsoft.eShopOnContainers.Services.Catalog.API.Model namespace Microsoft.eShopOnContainers.Services.Catalog.API.Model
{ {
@ -22,6 +23,79 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Model
public CatalogBrand CatalogBrand { get; set; } public CatalogBrand CatalogBrand { get; set; }
public CatalogItem() { }
// Quantity in stock
public int AvailableStock { get; set; }
// Available stock at which we should reorder
public int RestockThreshold { get; set; }
// Maximum number of units that can be in-stock at any time (due to physicial/logistical constraints in warehouses)
public int MaxStockThreshold { get; set; }
/// <summary>
/// True if item is on reorder
/// </summary>
public bool OnReorder { get; set; }
public CatalogItem() { }
/// <summary>
/// Decrements the quantity of a particular item in inventory and ensures the restockThreshold hasn't
/// been breached. If so, a RestockRequest is generated in CheckThreshold.
///
/// If there is sufficient stock of an item, then the integer returned at the end of this call should be the same as quantityDesired.
/// In the event that there is not sufficient stock available, the method will remove whatever stock is available and return that quantity to the client.
/// In this case, it is the responsibility of the client to determine if the amount that is returned is the same as quantityDesired.
/// It is invalid to pass in a negative number.
/// </summary>
/// <param name="quantityDesired"></param>
/// <returns>int: Returns the number actually removed from stock. </returns>
///
public int RemoveStock(int quantityDesired)
{
if (AvailableStock == 0)
{
throw new CatalogDomainException($"Empty stock, product item {Name} is sold out");
}
if (quantityDesired <= 0)
{
throw new CatalogDomainException($"Item units desired should be greater than cero");
}
int removed = Math.Min(quantityDesired, this.AvailableStock);
this.AvailableStock -= removed;
return removed;
}
/// <summary>
/// Increments the quantity of a particular item in inventory.
/// <param name="quantity"></param>
/// <returns>int: Returns the quantity that has been added to stock</returns>
/// </summary>
public int AddStock(int quantity)
{
int original = this.AvailableStock;
// The quantity that the client is trying to add to stock is greater than what can be physically accommodated in the Warehouse
if ((this.AvailableStock + quantity) > this.MaxStockThreshold)
{
// For now, this method only adds new units up maximum stock threshold. In an expanded version of this application, we
//could include tracking for the remaining units and store information about overstock elsewhere.
this.AvailableStock += (this.MaxStockThreshold - this.AvailableStock);
}
else
{
this.AvailableStock += quantity;
}
this.OnReorder = false;
return this.AvailableStock - original;
}
} }
}
}

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

@ -1,5 +1,7 @@
namespace Microsoft.eShopOnContainers.Services.Catalog.API namespace Microsoft.eShopOnContainers.Services.Catalog.API
{ {
using Autofac;
using Autofac.Extensions.DependencyInjection;
using global::Catalog.API.Infrastructure.Filters; using global::Catalog.API.Infrastructure.Filters;
using global::Catalog.API.IntegrationEvents; using global::Catalog.API.IntegrationEvents;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
@ -12,6 +14,8 @@
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF; using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF;
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services; using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services;
using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure; using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure;
using Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents.EventHandling;
using Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents.Events;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.HealthChecks; using Microsoft.Extensions.HealthChecks;
@ -46,7 +50,7 @@
Configuration = builder.Build(); Configuration = builder.Build();
} }
public void ConfigureServices(IServiceCollection services)
public IServiceProvider ConfigureServices(IServiceCollection services)
{ {
// Add framework services. // Add framework services.
@ -122,8 +126,11 @@
return new DefaultRabbitMQPersistentConnection(factory, logger); return new DefaultRabbitMQPersistentConnection(factory, logger);
}); });
services.AddSingleton<IEventBusSubscriptionsManager, InMemoryEventBusSubscriptionsManager>();
services.AddSingleton<IEventBus, EventBusRabbitMQ>();
RegisterServiceBus(services);
var container = new ContainerBuilder();
container.Populate(services);
return new AutofacServiceProvider(container.Build());
} }
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
@ -148,6 +155,8 @@
WaitForSqlAvailabilityAsync(context, loggerFactory, app).Wait(); WaitForSqlAvailabilityAsync(context, loggerFactory, app).Wait();
ConfigureEventBus(app);
var integrationEventLogContext = new IntegrationEventLogContext( var integrationEventLogContext = new IntegrationEventLogContext(
new DbContextOptionsBuilder<IntegrationEventLogContext>() new DbContextOptionsBuilder<IntegrationEventLogContext>()
.UseSqlServer(Configuration["ConnectionString"], b => b.MigrationsAssembly("Catalog.API")) .UseSqlServer(Configuration["ConnectionString"], b => b.MigrationsAssembly("Catalog.API"))
@ -179,5 +188,26 @@
} }
); );
} }
private void RegisterServiceBus(IServiceCollection services)
{
services.AddSingleton<IEventBusSubscriptionsManager, InMemoryEventBusSubscriptionsManager>();
services.AddSingleton<IEventBus, EventBusRabbitMQ>();
services.AddTransient<IIntegrationEventHandler<OrderStatusChangedToAwaitingValidationIntegrationEvent>,
OrderStatusChangedToAwaitingValidationIntegrationEventHandler>();
services.AddTransient<IIntegrationEventHandler<OrderStatusChangedToPaidIntegrationEvent>,
OrderStatusChangedToPaidIntegrationEventHandler>();
}
private void ConfigureEventBus(IApplicationBuilder app)
{
var eventBus = app.ApplicationServices.GetRequiredService<IEventBus>();
eventBus.Subscribe<OrderStatusChangedToAwaitingValidationIntegrationEvent,
IIntegrationEventHandler<OrderStatusChangedToAwaitingValidationIntegrationEvent>>();
eventBus.Subscribe<OrderStatusChangedToPaidIntegrationEvent,
IIntegrationEventHandler<OrderStatusChangedToPaidIntegrationEvent>>();
}
} }
} }

+ 3
- 0
src/Services/GracePeriod/GracePeriodManager/.dockerignore View File

@ -0,0 +1,3 @@
*
!obj/Docker/publish/*
!obj/Docker/empty/

+ 5
- 0
src/Services/GracePeriod/GracePeriodManager/Dockerfile View File

@ -0,0 +1,5 @@
FROM microsoft/aspnetcore:1.1.2
ARG source
WORKDIR /app
COPY ${source:-obj/Docker/publish} .
ENTRYPOINT ["dotnet", "GracePeriodManager.dll"]

+ 36
- 0
src/Services/GracePeriod/GracePeriodManager/GracePeriodManager.csproj View File

@ -0,0 +1,36 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp1.1</TargetFramework>
<RuntimeFrameworkVersion>1.1.2</RuntimeFrameworkVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="4.1.0" />
<PackageReference Include="Dapper" Version="1.50.2" />
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="1.1.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.Options" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="1.1.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBusRabbitMQ\EventBusRabbitMQ.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBus\EventBus.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\IntegrationEventLogEF\IntegrationEventLogEF.csproj" />
<ProjectReference Include="..\..\Ordering\Ordering.Infrastructure\Ordering.Infrastructure.csproj" />
</ItemGroup>
<ItemGroup>
<None Update=".dockerignore">
<DependentUpon>Dockerfile</DependentUpon>
</None>
</ItemGroup>
</Project>

+ 11
- 0
src/Services/GracePeriod/GracePeriodManager/IntegrationEvents/Events/GracePeriodConfirmedIntegrationEvent.cs View File

@ -0,0 +1,11 @@
namespace GracePeriodManager.IntegrationEvents.Events
{
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
public class GracePeriodConfirmedIntegrationEvent : IntegrationEvent
{
public int OrderId { get;}
public GracePeriodConfirmedIntegrationEvent(int orderId) => OrderId = orderId;
}
}

+ 13
- 0
src/Services/GracePeriod/GracePeriodManager/ManagerSettings.cs View File

@ -0,0 +1,13 @@
namespace GracePeriodManager
{
public class ManagerSettings
{
public string ConnectionString { get; set; }
public string EventBusConnection { get; set; }
public int GracePeriodTime { get; set; }
public int CheckUpdateTime { get; set; }
}
}

+ 94
- 0
src/Services/GracePeriod/GracePeriodManager/Program.cs View File

@ -0,0 +1,94 @@
namespace GracePeriodManager
{
using System.IO;
using System;
using System.Threading.Tasks;
using Autofac.Extensions.DependencyInjection;
using Autofac;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using RabbitMQ.Client;
using Services;
public class Program
{
public static IConfigurationRoot Configuration { get; set; }
public static void Main(string[] args) => MainAsync().Wait();
static async Task MainAsync()
{
StartUp();
IServiceCollection services = new ServiceCollection();
var serviceProvider = ConfigureServices(services);
var logger = serviceProvider.GetService<ILoggerFactory>();
Configure(logger);
var gracePeriodManagerService = serviceProvider
.GetRequiredService<IManagerService>();
var checkUpdateTime = serviceProvider
.GetRequiredService<IOptions<ManagerSettings>>().Value.CheckUpdateTime;
while (true)
{
gracePeriodManagerService.CheckConfirmedGracePeriodOrders();
await Task.Delay(checkUpdateTime);
}
}
public static void StartUp()
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public static IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddLogging()
.AddOptions()
.Configure<ManagerSettings>(Configuration)
.AddSingleton<IManagerService, ManagerService>()
.AddSingleton<IRabbitMQPersistentConnection>(sp =>
{
var settings = sp.GetRequiredService<IOptions<ManagerSettings>>().Value;
var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersistentConnection>>();
var factory = new ConnectionFactory()
{
HostName = settings.EventBusConnection
};
return new DefaultRabbitMQPersistentConnection(factory, logger);
});
RegisterEventBus(services);
var container = new ContainerBuilder();
container.Populate(services);
return new AutofacServiceProvider(container.Build());
}
public static void Configure(ILoggerFactory loggerFactory)
{
loggerFactory
.AddConsole(Configuration.GetSection("Logging"))
.AddConsole(LogLevel.Debug);
}
private static void RegisterEventBus(IServiceCollection services)
{
services.AddSingleton<IEventBus, EventBusRabbitMQ>();
services.AddSingleton<IEventBusSubscriptionsManager, InMemoryEventBusSubscriptionsManager>();
}
}
}

+ 7
- 0
src/Services/GracePeriod/GracePeriodManager/Services/IManagerService.cs View File

@ -0,0 +1,7 @@
namespace GracePeriodManager.Services
{
public interface IManagerService
{
void CheckConfirmedGracePeriodOrders();
}
}

+ 62
- 0
src/Services/GracePeriod/GracePeriodManager/Services/ManagerService.cs View File

@ -0,0 +1,62 @@
namespace GracePeriodManager.Services
{
using Dapper;
using GracePeriodManager.IntegrationEvents.Events;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Collections.Generic;
using System.Data.SqlClient;
public class ManagerService : IManagerService
{
private readonly ManagerSettings _settings;
private readonly IEventBus _eventBus;
private readonly ILogger<ManagerService> _logger;
public ManagerService(IOptions<ManagerSettings> settings,
IEventBus eventBus,
ILogger<ManagerService> logger)
{
_settings = settings.Value;
_eventBus = eventBus;
_logger = logger;
}
public void CheckConfirmedGracePeriodOrders()
{
var orderIds = GetConfirmedGracePeriodOrders();
foreach (var orderId in orderIds)
{
var confirmGracePeriodEvent = new GracePeriodConfirmedIntegrationEvent(orderId);
_eventBus.Publish(confirmGracePeriodEvent);
}
}
private IEnumerable<int> GetConfirmedGracePeriodOrders()
{
IEnumerable<int> orderIds = new List<int>();
using (var conn = new SqlConnection(_settings.ConnectionString))
{
try
{
_logger.LogInformation("Grace Period Manager Client is trying to connect to database server");
conn.Open();
orderIds = conn.Query<int>(
@"SELECT Id FROM [Microsoft.eShopOnContainers.Services.OrderingDb].[ordering].[orders]
WHERE DATEDIFF(minute, [OrderDate], GETDATE()) >= @GracePeriodTime
AND [OrderStatusId] = 1",
new { GracePeriodTime = _settings.GracePeriodTime });
}
catch (SqlException exception)
{
_logger.LogCritical($"FATAL ERROR: Database connections could not be opened: {exception.Message}");
}
}
return orderIds;
}
}
}

+ 13
- 0
src/Services/GracePeriod/GracePeriodManager/appsettings.json View File

@ -0,0 +1,13 @@
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
},
"ConnectionString": "Server=tcp:127.0.0.1,5433;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word;",
"GracePeriodTime": "1",
"CheckUpdateTime": "30000"
}

+ 2
- 0
src/Services/Identity/Identity.API/Identity.API.csproj View File

@ -10,6 +10,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="4.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.Cookies" Version="1.1.2" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.Cookies" Version="1.1.2" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="1.1.2" /> <PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="1.1.2" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="1.1.2" /> <PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="1.1.2" />
@ -39,6 +40,7 @@
</PackageReference> </PackageReference>
<PackageReference Include="IdentityServer4.AspNetIdentity" Version="1.0.1" /> <PackageReference Include="IdentityServer4.AspNetIdentity" Version="1.0.1" />
<PackageReference Include="IdentityServer4.EntityFramework" Version="1.0.1" /> <PackageReference Include="IdentityServer4.EntityFramework" Version="1.0.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="1.0.0" />
</ItemGroup> </ItemGroup>
<Target Name="PrepublishScript" BeforeTargets="PrepareForPublish"> <Target Name="PrepublishScript" BeforeTargets="PrepareForPublish">


+ 10
- 2
src/Services/Identity/Identity.API/Startup.cs View File

@ -16,6 +16,9 @@ using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.HealthChecks; using Microsoft.Extensions.HealthChecks;
using Identity.API.Certificate;
using Autofac.Extensions.DependencyInjection;
using Autofac;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -47,8 +50,9 @@ namespace eShopOnContainers.Identity
public IConfigurationRoot Configuration { get; } public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container. // 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. // Add framework services.
services.AddDbContext<ApplicationDbContext>(options => services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
@ -99,6 +103,10 @@ namespace eShopOnContainers.Identity
builder.UseSqlServer(connectionString, options => builder.UseSqlServer(connectionString, options =>
options.MigrationsAssembly(migrationsAssembly))) options.MigrationsAssembly(migrationsAssembly)))
.Services.AddTransient<IProfileService, ProfileService>(); .Services.AddTransient<IProfileService, ProfileService>();
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. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.


+ 21
- 0
src/Services/Ordering/Ordering.API/Application/Commands/CancelOrderCommand.cs View File

@ -0,0 +1,21 @@
using MediatR;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Threading.Tasks;
namespace Ordering.API.Application.Commands
{
public class CancelOrderCommand : IRequest<bool>
{
[DataMember]
public int OrderNumber { get; private set; }
public CancelOrderCommand(int orderNumber)
{
OrderNumber = orderNumber;
}
}
}

+ 46
- 0
src/Services/Ordering/Ordering.API/Application/Commands/CancelOrderCommandHandler.cs View File

@ -0,0 +1,46 @@
using MediatR;
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Ordering.API.Application.Commands
{
public class CancelOrderCommandIdentifiedHandler : IdentifierCommandHandler<CancelOrderCommand, bool>
{
public CancelOrderCommandIdentifiedHandler(IMediator mediator, IRequestManager requestManager) : base(mediator, requestManager)
{
}
protected override bool CreateResultForDuplicateRequest()
{
return true; // Ignore duplicate requests for processing order.
}
}
public class CancelOrderCommandHandler : IAsyncRequestHandler<CancelOrderCommand, bool>
{
private readonly IOrderRepository _orderRepository;
public CancelOrderCommandHandler(IOrderRepository orderRepository)
{
_orderRepository = orderRepository;
}
/// <summary>
/// Handler which processes the command when
/// customer executes cancel order from app
/// </summary>
/// <param name="command"></param>
/// <returns></returns>
public async Task<bool> Handle(CancelOrderCommand command)
{
var orderToUpdate = await _orderRepository.GetAsync(command.OrderNumber);
orderToUpdate.SetCancelledStatus();
return await _orderRepository.UnitOfWork.SaveEntitiesAsync();
}
}
}

+ 23
- 11
src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommand.cs View File

@ -3,6 +3,7 @@ using MediatR;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.Serialization; using System.Runtime.Serialization;
using System.Collections; using System.Collections;
using Ordering.API.Application.Models;
namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands
{ {
@ -22,6 +23,9 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands
[DataMember] [DataMember]
private readonly List<OrderItemDTO> _orderItems; private readonly List<OrderItemDTO> _orderItems;
[DataMember]
public string UserId { get; private set; }
[DataMember] [DataMember]
public string City { get; private set; } public string City { get; private set; }
@ -52,12 +56,6 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands
[DataMember] [DataMember]
public int CardTypeId { get; private set; } public int CardTypeId { get; private set; }
[DataMember]
public int PaymentId { get; private set; }
[DataMember]
public int BuyerId { get; private set; }
[DataMember] [DataMember]
public IEnumerable<OrderItemDTO> OrderItems => _orderItems; public IEnumerable<OrderItemDTO> OrderItems => _orderItems;
@ -66,11 +64,12 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands
_orderItems = new List<OrderItemDTO>(); _orderItems = new List<OrderItemDTO>();
} }
public CreateOrderCommand(List<OrderItemDTO> orderItems, string city, string street, string state, string country, string zipcode,
public CreateOrderCommand(List<BasketItem> basketItems, string userId, string city, string street, string state, string country, string zipcode,
string cardNumber, string cardHolderName, DateTime cardExpiration, 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);
UserId = userId;
City = city; City = city;
Street = street; Street = street;
State = state; State = state;
@ -82,8 +81,21 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands
CardSecurityNumber = cardSecurityNumber; CardSecurityNumber = cardSecurityNumber;
CardTypeId = cardTypeId; CardTypeId = cardTypeId;
CardExpiration = cardExpiration; CardExpiration = cardExpiration;
PaymentId = paymentId;
BuyerId = buyerId;
}
private List<OrderItemDTO> MapToOrderItems(List<BasketItem> basketItems)
{
var result = new List<OrderItemDTO>();
basketItems.ForEach((item) => {
result.Add(new OrderItemDTO() {
ProductId = int.TryParse(item.ProductId, out int id) ? id : -1,
ProductName = item.ProductName,
PictureUrl = item.PictureUrl,
UnitPrice = item.UnitPrice,
Units = item.Quantity
});
});
return result;
} }
public class OrderItemDTO public class OrderItemDTO


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

@ -42,8 +42,8 @@
// methods and constructor so validations, invariants and business logic // methods and constructor so validations, invariants and business logic
// make sure that consistency is preserved across the whole aggregate // 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 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);
var order = new Order(message.UserId, address, message.CardTypeId, message.CardNumber, message.CardSecurityNumber, message.CardHolderName, message.CardExpiration);
foreach (var item in message.OrderItems) foreach (var item in message.OrderItems)
{ {
order.AddOrderItem(item.ProductId, item.ProductName, item.UnitPrice, item.Discount, item.PictureUrl, item.Units); order.AddOrderItem(item.ProductId, item.ProductName, item.UnitPrice, item.Discount, item.PictureUrl, item.Units);


+ 21
- 0
src/Services/Ordering/Ordering.API/Application/Commands/ShipOrderCommand.cs View File

@ -0,0 +1,21 @@
using MediatR;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Threading.Tasks;
namespace Ordering.API.Application.Commands
{
public class ShipOrderCommand : IRequest<bool>
{
[DataMember]
public int OrderNumber { get; private set; }
public ShipOrderCommand(int orderNumber)
{
OrderNumber = orderNumber;
}
}
}

+ 43
- 0
src/Services/Ordering/Ordering.API/Application/Commands/ShipOrderCommandHandler.cs View File

@ -0,0 +1,43 @@
using MediatR;
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency;
using System.Threading.Tasks;
namespace Ordering.API.Application.Commands
{
public class ShipOrderCommandIdentifiedHandler : IdentifierCommandHandler<ShipOrderCommand, bool>
{
public ShipOrderCommandIdentifiedHandler(IMediator mediator, IRequestManager requestManager) : base(mediator, requestManager)
{
}
protected override bool CreateResultForDuplicateRequest()
{
return true; // Ignore duplicate requests for processing order.
}
}
public class ShipOrderCommandHandler : IAsyncRequestHandler<ShipOrderCommand, bool>
{
private readonly IOrderRepository _orderRepository;
public ShipOrderCommandHandler(IOrderRepository orderRepository)
{
_orderRepository = orderRepository;
}
/// <summary>
/// Handler which processes the command when
/// administrator executes ship order from app
/// </summary>
/// <param name="command"></param>
/// <returns></returns>
public async Task<bool> Handle(ShipOrderCommand command)
{
var orderToUpdate = await _orderRepository.GetAsync(command.OrderNumber);
orderToUpdate.SetShippedStatus();
return await _orderRepository.UnitOfWork.SaveEntitiesAsync();
}
}
}

+ 6
- 20
src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/BuyerAndPaymentMethodVerified/UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler.cs View File

@ -1,8 +1,8 @@
using MediatR; using MediatR;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Ordering.API.IntegrationEvents;
using Ordering.API.IntegrationEvents.Events;
using Ordering.API.Application.IntegrationEvents;
using Ordering.API.Application.IntegrationEvents.Events;
using Ordering.Domain.Events; using Ordering.Domain.Events;
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -12,16 +12,13 @@ namespace Ordering.API.Application.DomainEventHandlers.BuyerAndPaymentMethodVeri
public class UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler public class UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler
: IAsyncNotificationHandler<BuyerAndPaymentMethodVerifiedDomainEvent> : IAsyncNotificationHandler<BuyerAndPaymentMethodVerifiedDomainEvent>
{ {
private readonly IOrderRepository _orderRepository;
private readonly IOrderingIntegrationEventService _orderingIntegrationEventService;
private readonly IOrderRepository _orderRepository;
private readonly ILoggerFactory _logger; private readonly ILoggerFactory _logger;
public UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler( public UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler(
IOrderRepository orderRepository, ILoggerFactory logger,
IOrderingIntegrationEventService orderingIntegrationEventService)
IOrderRepository orderRepository, ILoggerFactory logger)
{ {
_orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository));
_orderingIntegrationEventService = orderingIntegrationEventService ?? throw new ArgumentNullException(nameof(orderingIntegrationEventService));
_orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository));
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); _logger = logger ?? throw new ArgumentNullException(nameof(logger));
} }
@ -32,18 +29,7 @@ namespace Ordering.API.Application.DomainEventHandlers.BuyerAndPaymentMethodVeri
{ {
var orderToUpdate = await _orderRepository.GetAsync(buyerPaymentMethodVerifiedEvent.OrderId); var orderToUpdate = await _orderRepository.GetAsync(buyerPaymentMethodVerifiedEvent.OrderId);
orderToUpdate.SetBuyerId(buyerPaymentMethodVerifiedEvent.Buyer.Id); orderToUpdate.SetBuyerId(buyerPaymentMethodVerifiedEvent.Buyer.Id);
orderToUpdate.SetPaymentId(buyerPaymentMethodVerifiedEvent.Payment.Id);
var orderStartedIntegrationEvent = new OrderStartedIntegrationEvent(buyerPaymentMethodVerifiedEvent.Buyer.IdentityGuid);
// Using a local transaction to achieve atomicity between original Ordering database operation and
// the IntegrationEventLog. Only saving event if order has been successfully persisted to db
await _orderingIntegrationEventService
.SaveEventAndOrderingContextChangesAsync(orderStartedIntegrationEvent);
// Publish ordering integration event and mark it as published
await _orderingIntegrationEventService
.PublishThroughEventBusAsync(orderStartedIntegrationEvent);
orderToUpdate.SetPaymentId(buyerPaymentMethodVerifiedEvent.Payment.Id);
_logger.CreateLogger(nameof(UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler)) _logger.CreateLogger(nameof(UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler))
.LogTrace($"Order with Id: {buyerPaymentMethodVerifiedEvent.OrderId} has been successfully updated with a payment method id: { buyerPaymentMethodVerifiedEvent.Payment.Id }"); .LogTrace($"Order with Id: {buyerPaymentMethodVerifiedEvent.OrderId} has been successfully updated with a payment method id: { buyerPaymentMethodVerifiedEvent.Payment.Id }");


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

@ -0,0 +1,43 @@
namespace Ordering.API.Application.DomainEventHandlers.OrderGracePeriodConfirmed
{
using MediatR;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Microsoft.Extensions.Logging;
using Domain.Events;
using System;
using System.Threading.Tasks;
using Ordering.API.Application.IntegrationEvents;
using System.Linq;
using Ordering.API.Application.IntegrationEvents.Events;
public class OrderStatusChangedToAwaitingValidationDomainEventHandler
: IAsyncNotificationHandler<OrderStatusChangedToAwaitingValidationDomainEvent>
{
private readonly IOrderRepository _orderRepository;
private readonly ILoggerFactory _logger;
private readonly IOrderingIntegrationEventService _orderingIntegrationEventService;
public OrderStatusChangedToAwaitingValidationDomainEventHandler(
IOrderRepository orderRepository, ILoggerFactory logger,
IOrderingIntegrationEventService orderingIntegrationEventService)
{
_orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_orderingIntegrationEventService = orderingIntegrationEventService;
}
public async Task Handle(OrderStatusChangedToAwaitingValidationDomainEvent orderStatusChangedToAwaitingValidationDomainEvent)
{
_logger.CreateLogger(nameof(OrderStatusChangedToAwaitingValidationDomainEvent))
.LogTrace($"Order with Id: {orderStatusChangedToAwaitingValidationDomainEvent.OrderId} has been successfully updated with " +
$"a status order id: {OrderStatus.AwaitingValidation.Id}");
var orderStockList = orderStatusChangedToAwaitingValidationDomainEvent.OrderItems
.Select(orderItem => new OrderStockItem(orderItem.ProductId, orderItem.GetUnits()));
var orderStatusChangedToAwaitingValidationIntegrationEvent = new OrderStatusChangedToAwaitingValidationIntegrationEvent(
orderStatusChangedToAwaitingValidationDomainEvent.OrderId, orderStockList);
await _orderingIntegrationEventService.PublishThroughEventBusAsync(orderStatusChangedToAwaitingValidationIntegrationEvent);
}
}
}

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

@ -0,0 +1,43 @@
namespace Ordering.API.Application.DomainEventHandlers.OrderPaid
{
using MediatR;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Microsoft.Extensions.Logging;
using Domain.Events;
using System;
using System.Threading.Tasks;
using Ordering.API.Application.IntegrationEvents;
using System.Linq;
using Ordering.API.Application.IntegrationEvents.Events;
public class OrderStatusChangedToPaidDomainEventHandler
: IAsyncNotificationHandler<OrderStatusChangedToPaidDomainEvent>
{
private readonly IOrderRepository _orderRepository;
private readonly ILoggerFactory _logger;
private readonly IOrderingIntegrationEventService _orderingIntegrationEventService;
public OrderStatusChangedToPaidDomainEventHandler(
IOrderRepository orderRepository, ILoggerFactory logger,
IOrderingIntegrationEventService orderingIntegrationEventService)
{
_orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_orderingIntegrationEventService = orderingIntegrationEventService;
}
public async Task Handle(OrderStatusChangedToPaidDomainEvent orderStatusChangedToPaidDomainEvent)
{
_logger.CreateLogger(nameof(OrderStatusChangedToPaidDomainEventHandler))
.LogTrace($"Order with Id: {orderStatusChangedToPaidDomainEvent.OrderId} has been successfully updated with " +
$"a status order id: {OrderStatus.Paid.Id}");
var orderStockList = orderStatusChangedToPaidDomainEvent.OrderItems
.Select(orderItem => new OrderStockItem(orderItem.ProductId, orderItem.GetUnits()));
var orderStatusChangedToPaidIntegrationEvent = new OrderStatusChangedToPaidIntegrationEvent(orderStatusChangedToPaidDomainEvent.OrderId,
orderStockList);
await _orderingIntegrationEventService.PublishThroughEventBusAsync(orderStatusChangedToPaidIntegrationEvent);
}
}
}

+ 2
- 4
src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler.cs View File

@ -26,14 +26,12 @@ namespace Ordering.API.Application.DomainEventHandlers.OrderStartedEvent
{ {
var cardTypeId = (orderStartedEvent.CardTypeId != 0) ? orderStartedEvent.CardTypeId : 1; var cardTypeId = (orderStartedEvent.CardTypeId != 0) ? orderStartedEvent.CardTypeId : 1;
var userGuid = _identityService.GetUserIdentity();
var buyer = await _buyerRepository.FindAsync(userGuid);
var buyer = await _buyerRepository.FindAsync(orderStartedEvent.UserId);
bool buyerOriginallyExisted = (buyer == null) ? false : true; bool buyerOriginallyExisted = (buyer == null) ? false : true;
if (!buyerOriginallyExisted) if (!buyerOriginallyExisted)
{ {
buyer = new Buyer(userGuid);
buyer = new Buyer(orderStartedEvent.UserId);
} }
buyer.VerifyOrAddPaymentMethod(cardTypeId, buyer.VerifyOrAddPaymentMethod(cardTypeId,


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

@ -0,0 +1,38 @@
namespace Ordering.API.Application.DomainEventHandlers.OrderStockConfirmed
{
using MediatR;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Microsoft.Extensions.Logging;
using Domain.Events;
using System;
using System.Threading.Tasks;
using Ordering.API.Application.IntegrationEvents;
using Ordering.API.Application.IntegrationEvents.Events;
public class OrderStatusChangedToStockConfirmedDomainEventHandler
: IAsyncNotificationHandler<OrderStatusChangedToStockConfirmedDomainEvent>
{
private readonly IOrderRepository _orderRepository;
private readonly ILoggerFactory _logger;
private readonly IOrderingIntegrationEventService _orderingIntegrationEventService;
public OrderStatusChangedToStockConfirmedDomainEventHandler(
IOrderRepository orderRepository, ILoggerFactory logger,
IOrderingIntegrationEventService orderingIntegrationEventService)
{
_orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_orderingIntegrationEventService = orderingIntegrationEventService;
}
public async Task Handle(OrderStatusChangedToStockConfirmedDomainEvent orderStatusChangedToStockConfirmedDomainEvent)
{
_logger.CreateLogger(nameof(OrderStatusChangedToStockConfirmedDomainEventHandler))
.LogTrace($"Order with Id: {orderStatusChangedToStockConfirmedDomainEvent.OrderId} has been successfully updated with " +
$"a status order id: {OrderStatus.StockConfirmed.Id}");
var orderStatusChangedToStockConfirmedIntegrationEvent = new OrderStatusChangedToStockConfirmedIntegrationEvent(orderStatusChangedToStockConfirmedDomainEvent.OrderId);
await _orderingIntegrationEventService.PublishThroughEventBusAsync(orderStatusChangedToStockConfirmedIntegrationEvent);
}
}
}

+ 32
- 0
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/GracePeriodConfirmedIntegrationEventHandler.cs View File

@ -0,0 +1,32 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Ordering.API.Application.IntegrationEvents.Events;
using System.Threading.Tasks;
namespace Ordering.API.Application.IntegrationEvents.EventHandling
{
public class GracePeriodConfirmedIntegrationEventHandler : IIntegrationEventHandler<GracePeriodConfirmedIntegrationEvent>
{
private readonly IOrderRepository _orderRepository;
public GracePeriodConfirmedIntegrationEventHandler(IOrderRepository orderRepository)
{
_orderRepository = orderRepository;
}
/// <summary>
/// Event handler which confirms that the grace period
/// has been completed and order will not initially be cancelled.
/// Therefore, the order process continues for validation.
/// </summary>
/// <param name="event">
/// </param>
/// <returns></returns>
public async Task Handle(GracePeriodConfirmedIntegrationEvent @event)
{
var orderToUpdate = await _orderRepository.GetAsync(@event.OrderId);
orderToUpdate.SetAwaitingValidationStatus();
await _orderRepository.UnitOfWork.SaveEntitiesAsync();
}
}
}

+ 27
- 0
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentFailedIntegrationEventHandler.cs View File

@ -0,0 +1,27 @@
namespace Ordering.API.Application.IntegrationEvents.EventHandling
{
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Ordering.API.Application.IntegrationEvents.Events;
using System.Threading.Tasks;
public class OrderPaymentFailedIntegrationEventHandler :
IIntegrationEventHandler<OrderPaymentFailedIntegrationEvent>
{
private readonly IOrderRepository _orderRepository;
public OrderPaymentFailedIntegrationEventHandler(IOrderRepository orderRepository)
{
_orderRepository = orderRepository;
}
public async Task Handle(OrderPaymentFailedIntegrationEvent @event)
{
var orderToUpdate = await _orderRepository.GetAsync(@event.OrderId);
orderToUpdate.SetCancelledStatus();
await _orderRepository.UnitOfWork.SaveEntitiesAsync();
}
}
}

+ 27
- 0
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentSuccededIntegrationEventHandler.cs View File

@ -0,0 +1,27 @@
namespace Ordering.API.Application.IntegrationEvents.EventHandling
{
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Ordering.API.Application.IntegrationEvents.Events;
using System.Threading.Tasks;
public class OrderPaymentSuccededIntegrationEventHandler :
IIntegrationEventHandler<OrderPaymentSuccededIntegrationEvent>
{
private readonly IOrderRepository _orderRepository;
public OrderPaymentSuccededIntegrationEventHandler(IOrderRepository orderRepository)
{
_orderRepository = orderRepository;
}
public async Task Handle(OrderPaymentSuccededIntegrationEvent @event)
{
var orderToUpdate = await _orderRepository.GetAsync(@event.OrderId);
orderToUpdate.SetPaidStatus();
await _orderRepository.UnitOfWork.SaveEntitiesAsync();
}
}
}

+ 27
- 0
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockConfirmedIntegrationEventHandler.cs View File

@ -0,0 +1,27 @@
namespace Ordering.API.Application.IntegrationEvents.EventHandling
{
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using System.Threading.Tasks;
using Events;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
public class OrderStockConfirmedIntegrationEventHandler :
IIntegrationEventHandler<OrderStockConfirmedIntegrationEvent>
{
private readonly IOrderRepository _orderRepository;
public OrderStockConfirmedIntegrationEventHandler(IOrderRepository orderRepository)
{
_orderRepository = orderRepository;
}
public async Task Handle(OrderStockConfirmedIntegrationEvent @event)
{
var orderToUpdate = await _orderRepository.GetAsync(@event.OrderId);
orderToUpdate.SetStockConfirmedStatus();
await _orderRepository.UnitOfWork.SaveEntitiesAsync();
}
}
}

+ 31
- 0
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockRejectedIntegrationEventHandler.cs View File

@ -0,0 +1,31 @@
namespace Ordering.API.Application.IntegrationEvents.EventHandling
{
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using System.Threading.Tasks;
using Events;
using System.Linq;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
public class OrderStockRejectedIntegrationEventHandler : IIntegrationEventHandler<OrderStockRejectedIntegrationEvent>
{
private readonly IOrderRepository _orderRepository;
public OrderStockRejectedIntegrationEventHandler(IOrderRepository orderRepository)
{
_orderRepository = orderRepository;
}
public async Task Handle(OrderStockRejectedIntegrationEvent @event)
{
var orderToUpdate = await _orderRepository.GetAsync(@event.OrderId);
var orderStockRejectedItems = @event.OrderStockItems
.FindAll(c => !c.HasStock)
.Select(c => c.ProductId);
orderToUpdate.SetCancelledStatusWhenStockIsRejected(orderStockRejectedItems);
await _orderRepository.UnitOfWork.SaveEntitiesAsync();
}
}
}

+ 58
- 0
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/UserCheckoutAcceptedIntegrationEventHandler.cs View File

@ -0,0 +1,58 @@
using System;
using MediatR;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using System.Threading.Tasks;
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands;
using Microsoft.Extensions.Logging;
using Ordering.API.Application.IntegrationEvents.Events;
namespace Ordering.API.Application.IntegrationEvents.EventHandling
{
public class UserCheckoutAcceptedIntegrationEventHandler : IIntegrationEventHandler<UserCheckoutAcceptedIntegrationEvent>
{
private readonly IMediator _mediator;
private readonly ILoggerFactory _logger;
private readonly IOrderingIntegrationEventService _orderingIntegrationEventService;
public UserCheckoutAcceptedIntegrationEventHandler(IMediator mediator,
ILoggerFactory logger, IOrderingIntegrationEventService orderingIntegrationEventService)
{
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_orderingIntegrationEventService = orderingIntegrationEventService ?? throw new ArgumentNullException(nameof(orderingIntegrationEventService));
}
/// <summary>
/// Integration event handler which starts the create order process
/// </summary>
/// <param name="eventMsg">
/// Integration event message which is sent by the
/// basket.api once it has successfully process the
/// order items.
/// </param>
/// <returns></returns>
public async Task Handle(UserCheckoutAcceptedIntegrationEvent eventMsg)
{
var result = false;
// Send Integration event to clean basket once basket is converted to Order and before starting with the order creation process
var orderStartedIntegrationEvent = new OrderStartedIntegrationEvent(eventMsg.UserId);
await _orderingIntegrationEventService.PublishThroughEventBusAsync(orderStartedIntegrationEvent);
if (eventMsg.RequestId != Guid.Empty)
{
var createOrderCommand = new CreateOrderCommand(eventMsg.Basket.Items, eventMsg.UserId, eventMsg.City, eventMsg.Street,
eventMsg.State, eventMsg.Country, eventMsg.ZipCode,
eventMsg.CardNumber, eventMsg.CardHolderName, eventMsg.CardExpiration,
eventMsg.CardSecurityNumber, eventMsg.CardTypeId);
var requestCreateOrder = new IdentifiedCommand<CreateOrderCommand, bool>(createOrderCommand, eventMsg.RequestId);
result = await _mediator.Send(requestCreateOrder);
}
_logger.CreateLogger(nameof(UserCheckoutAcceptedIntegrationEventHandler))
.LogTrace(result ? $"UserCheckoutAccepted integration event has been received and a create new order process is started with requestId: {eventMsg.RequestId}" :
$"UserCheckoutAccepted integration event has been received but a new order process has failed with requestId: {eventMsg.RequestId}");
}
}
}

+ 12
- 0
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/Events/GracePeriodConfirmedIntegrationEvent.cs View File

@ -0,0 +1,12 @@
namespace Ordering.API.Application.IntegrationEvents.Events
{
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
public class GracePeriodConfirmedIntegrationEvent : IntegrationEvent
{
public int OrderId { get; }
public GracePeriodConfirmedIntegrationEvent(int orderId) =>
OrderId = orderId;
}
}

+ 11
- 0
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/Events/OrderPaymentFailedIntegrationEvent .cs View File

@ -0,0 +1,11 @@
namespace Ordering.API.Application.IntegrationEvents.Events
{
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
public class OrderPaymentFailedIntegrationEvent : IntegrationEvent
{
public int OrderId { get; }
public OrderPaymentFailedIntegrationEvent(int orderId) => OrderId = orderId;
}
}

+ 11
- 0
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/Events/OrderPaymentSuccededIntegrationEvent.cs View File

@ -0,0 +1,11 @@
namespace Ordering.API.Application.IntegrationEvents.Events
{
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
public class OrderPaymentSuccededIntegrationEvent : IntegrationEvent
{
public int OrderId { get; }
public OrderPaymentSuccededIntegrationEvent(int orderId) => OrderId = orderId;
}
}

src/Services/Ordering/Ordering.API/IntegrationEvents/Events/OrderStartedIntegrationEvent.cs → src/Services/Ordering/Ordering.API/Application/IntegrationEvents/Events/OrderStartedIntegrationEvent.cs View File

@ -4,16 +4,16 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Ordering.API.IntegrationEvents.Events
namespace Ordering.API.Application.IntegrationEvents.Events
{ {
// Integration Events notes: // Integration Events notes:
// An Event is “something that has happened in the past”, therefore its name has to be // 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. // An Integration Event is an event that can cause side effects to other microsrvices, Bounded-Contexts or external systems.
public class OrderStartedIntegrationEvent : IntegrationEvent public class OrderStartedIntegrationEvent : IntegrationEvent
{ {
public string UserId { get; }
public string UserId { get; set; }
public OrderStartedIntegrationEvent(string userId) =>
UserId = userId;
public OrderStartedIntegrationEvent(string userId)
=> UserId = userId;
} }
}
}

+ 30
- 0
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/Events/OrderStatusChangedToAwaitingValidationIntegrationEvent.cs View File

@ -0,0 +1,30 @@
namespace Ordering.API.Application.IntegrationEvents.Events
{
using System.Collections.Generic;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
public class OrderStatusChangedToAwaitingValidationIntegrationEvent : IntegrationEvent
{
public int OrderId { get; }
public IEnumerable<OrderStockItem> OrderStockItems { get; }
public OrderStatusChangedToAwaitingValidationIntegrationEvent(int orderId,
IEnumerable<OrderStockItem> orderStockItems)
{
OrderId = orderId;
OrderStockItems = orderStockItems;
}
}
public class OrderStockItem
{
public int ProductId { get; }
public int Units { get; }
public OrderStockItem(int productId, int units)
{
ProductId = productId;
Units = units;
}
}
}

+ 18
- 0
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/Events/OrderStatusChangedToPaidIntegrationEvent.cs View File

@ -0,0 +1,18 @@
namespace Ordering.API.Application.IntegrationEvents.Events
{
using System.Collections.Generic;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
public class OrderStatusChangedToPaidIntegrationEvent : IntegrationEvent
{
public int OrderId { get; }
public IEnumerable<OrderStockItem> OrderStockItems { get; }
public OrderStatusChangedToPaidIntegrationEvent(int orderId,
IEnumerable<OrderStockItem> orderStockItems)
{
OrderId = orderId;
OrderStockItems = orderStockItems;
}
}
}

+ 12
- 0
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/Events/OrderStatusChangedToStockConfirmedIntegrationEvent.cs View File

@ -0,0 +1,12 @@
namespace Ordering.API.Application.IntegrationEvents.Events
{
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
public class OrderStatusChangedToStockConfirmedIntegrationEvent : IntegrationEvent
{
public int OrderId { get; }
public OrderStatusChangedToStockConfirmedIntegrationEvent(int orderId)
=> OrderId = orderId;
}
}

+ 11
- 0
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/Events/OrderStockConfirmedIntegrationEvent.cs View File

@ -0,0 +1,11 @@
namespace Ordering.API.Application.IntegrationEvents.Events
{
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
public class OrderStockConfirmedIntegrationEvent : IntegrationEvent
{
public int OrderId { get; }
public OrderStockConfirmedIntegrationEvent(int orderId) => OrderId = orderId;
}
}

+ 31
- 0
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/Events/OrderStockRejectedIntegrationEvent.cs View File

@ -0,0 +1,31 @@
namespace Ordering.API.Application.IntegrationEvents.Events
{
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using System.Collections.Generic;
public class OrderStockRejectedIntegrationEvent : IntegrationEvent
{
public int OrderId { get; }
public List<ConfirmedOrderStockItem> OrderStockItems { get; }
public OrderStockRejectedIntegrationEvent(int orderId,
List<ConfirmedOrderStockItem> orderStockItems)
{
OrderId = orderId;
OrderStockItems = orderStockItems;
}
}
public class ConfirmedOrderStockItem
{
public int ProductId { get; }
public bool HasStock { get; }
public ConfirmedOrderStockItem(int productId, bool hasStock)
{
ProductId = productId;
HasStock = hasStock;
}
}
}

+ 59
- 0
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/Events/UserCheckoutAcceptedIntegrationEvent.cs View File

@ -0,0 +1,59 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using Ordering.API.Application.Models;
using System;
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;
}
}
}

src/Services/Ordering/Ordering.API/IntegrationEvents/IOrderingIntegrationEventService.cs → src/Services/Ordering/Ordering.API/Application/IntegrationEvents/IOrderingIntegrationEventService.cs View File

@ -1,14 +1,10 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Ordering.API.IntegrationEvents
namespace Ordering.API.Application.IntegrationEvents
{ {
public interface IOrderingIntegrationEventService public interface IOrderingIntegrationEventService
{ {
Task SaveEventAndOrderingContextChangesAsync(IntegrationEvent evt);
Task PublishThroughEventBusAsync(IntegrationEvent evt); Task PublishThroughEventBusAsync(IntegrationEvent evt);
} }
} }

src/Services/Ordering/Ordering.API/IntegrationEvents/OrderingIntegrationEventService.cs → src/Services/Ordering/Ordering.API/Application/IntegrationEvents/OrderingIntegrationEventService.cs View File

@ -4,13 +4,13 @@ using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services; using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services;
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Utilities; using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Utilities;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure; using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure;
using System; using System;
using System.Data.Common; using System.Data.Common;
using System.Diagnostics;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Ordering.API.IntegrationEvents
namespace Ordering.API.Application.IntegrationEvents
{ {
public class OrderingIntegrationEventService : IOrderingIntegrationEventService public class OrderingIntegrationEventService : IOrderingIntegrationEventService
{ {
@ -19,7 +19,7 @@ namespace Ordering.API.IntegrationEvents
private readonly OrderingContext _orderingContext; private readonly OrderingContext _orderingContext;
private readonly IIntegrationEventLogService _eventLogService; private readonly IIntegrationEventLogService _eventLogService;
public OrderingIntegrationEventService (IEventBus eventBus, OrderingContext orderingContext,
public OrderingIntegrationEventService(IEventBus eventBus, OrderingContext orderingContext,
Func<DbConnection, IIntegrationEventLogService> integrationEventLogServiceFactory) Func<DbConnection, IIntegrationEventLogService> integrationEventLogServiceFactory)
{ {
_orderingContext = orderingContext ?? throw new ArgumentNullException(nameof(orderingContext)); _orderingContext = orderingContext ?? throw new ArgumentNullException(nameof(orderingContext));
@ -30,11 +30,12 @@ namespace Ordering.API.IntegrationEvents
public async Task PublishThroughEventBusAsync(IntegrationEvent evt) public async Task PublishThroughEventBusAsync(IntegrationEvent evt)
{ {
await SaveEventAndOrderingContextChangesAsync(evt);
_eventBus.Publish(evt); _eventBus.Publish(evt);
await _eventLogService.MarkEventAsPublishedAsync(evt); await _eventLogService.MarkEventAsPublishedAsync(evt);
} }
public async Task SaveEventAndOrderingContextChangesAsync(IntegrationEvent evt)
private async Task SaveEventAndOrderingContextChangesAsync(IntegrationEvent evt)
{ {
//Use of an EF Core resiliency strategy when using multiple DbContexts within an explicit BeginTransaction(): //Use of an EF Core resiliency strategy when using multiple DbContexts within an explicit BeginTransaction():
//See: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency //See: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency

+ 18
- 0
src/Services/Ordering/Ordering.API/Application/Models/BasketItem.cs View File

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

+ 19
- 0
src/Services/Ordering/Ordering.API/Application/Models/CustomerBasket.cs View File

@ -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<BasketItem> Items { get; set; }
public CustomerBasket(string customerId)
{
BuyerId = customerId;
Items = new List<BasketItem>();
}
}
}

+ 2
- 1
src/Services/Ordering/Ordering.API/Application/Queries/OrderQueries.cs View File

@ -26,7 +26,7 @@
connection.Open(); connection.Open();
var result = await connection.QueryAsync<dynamic>( var result = await connection.QueryAsync<dynamic>(
@"select o.[Id] as ordernumber,o.OrderDate as date, os.Name as status,
@"select o.[Id] as ordernumber,o.OrderDate as date, o.Description as description, os.Name as status,
oi.ProductName as productname, oi.Units as units, oi.UnitPrice as unitprice, oi.PictureUrl as pictureurl, oi.ProductName as productname, oi.Units as units, oi.UnitPrice as unitprice, oi.PictureUrl as pictureurl,
a.Street as street, a.City as city, a.Country as country, a.State as state, a.ZipCode as zipcode a.Street as street, a.City as city, a.Country as country, a.State as state, a.ZipCode as zipcode
FROM ordering.Orders o FROM ordering.Orders o
@ -75,6 +75,7 @@
order.ordernumber = result[0].ordernumber; order.ordernumber = result[0].ordernumber;
order.date = result[0].date; order.date = result[0].date;
order.status = result[0].status; order.status = result[0].status;
order.description = result[0].description;
order.street = result[0].street; order.street = result[0].street;
order.city = result[0].city; order.city = result[0].city;
order.zipcode = result[0].zipcode; order.zipcode = result[0].zipcode;


+ 17
- 0
src/Services/Ordering/Ordering.API/Application/Validations/CancelOrderCommandValidator.cs View File

@ -0,0 +1,17 @@
using FluentValidation;
using Ordering.API.Application.Commands;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Ordering.API.Application.Validations
{
public class CancelOrderCommandValidator : AbstractValidator<CancelOrderCommand>
{
public CancelOrderCommandValidator()
{
RuleFor(order => order.OrderNumber).NotEmpty().WithMessage("No orderId found");
}
}
}

+ 17
- 0
src/Services/Ordering/Ordering.API/Application/Validations/ShipOrderCommandValidator.cs View File

@ -0,0 +1,17 @@
using FluentValidation;
using Ordering.API.Application.Commands;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Ordering.API.Application.Validations
{
public class ShipOrderCommandValidator : AbstractValidator<ShipOrderCommand>
{
public ShipOrderCommandValidator()
{
RuleFor(order => order.OrderNumber).NotEmpty().WithMessage("No orderId found");
}
}
}

+ 19
- 9
src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs View File

@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands;
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Queries; using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Queries;
using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services; using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services;
using Ordering.API.Application.Commands;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -26,21 +27,30 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers
_identityService = identityService ?? throw new ArgumentNullException(nameof(identityService)); _identityService = identityService ?? throw new ArgumentNullException(nameof(identityService));
} }
[Route("new")]
[HttpPost]
public async Task<IActionResult> CreateOrder([FromBody]CreateOrderCommand command, [FromHeader(Name = "x-requestid")] string requestId)
[Route("cancel")]
[HttpPut]
public async Task<IActionResult> CancelOrder([FromBody]CancelOrderCommand command, [FromHeader(Name = "x-requestid")] string requestId)
{ {
bool commandResult = false; bool commandResult = false;
if (Guid.TryParse(requestId, out Guid guid) && guid != Guid.Empty) if (Guid.TryParse(requestId, out Guid guid) && guid != Guid.Empty)
{ {
var requestCreateOrder = new IdentifiedCommand<CreateOrderCommand, bool>(command, guid);
commandResult = await _mediator.Send(requestCreateOrder);
var requestCancelOrder = new IdentifiedCommand<CancelOrderCommand, bool>(command, guid);
commandResult = await _mediator.Send(requestCancelOrder);
} }
else
return commandResult ? (IActionResult)Ok() : (IActionResult)BadRequest();
}
[Route("ship")]
[HttpPut]
public async Task<IActionResult> ShipOrder([FromBody]ShipOrderCommand command, [FromHeader(Name = "x-requestid")] string requestId)
{
bool commandResult = false;
if (Guid.TryParse(requestId, out Guid guid) && guid != Guid.Empty)
{ {
// 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.Send(command);
var requestShipOrder = new IdentifiedCommand<ShipOrderCommand, bool>(command, guid);
commandResult = await _mediator.Send(requestShipOrder);
} }
return commandResult ? (IActionResult)Ok() : (IActionResult)BadRequest(); return commandResult ? (IActionResult)Ok() : (IActionResult)BadRequest();


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

Loading…
Cancel
Save