Browse Source

Meged with mainline

pull/151/head
Ollie Ferns 7 years ago
parent
commit
d3a203ab5a
93 changed files with 1402 additions and 419 deletions
  1. +2
    -1
      .gitignore
  2. +6
    -2
      README.md
  3. +2
    -1
      cli-linux/build-bits-linux.sh
  4. +1
    -0
      cli-mac/build-bits.sh
  5. +0
    -160
      cli-windows/build-bits-expanded.ps1
  6. +2
    -1
      cli-windows/build-bits.ps1
  7. +2
    -1
      docker-compose.override.yml
  8. +1
    -0
      docker-compose.prod.yml
  9. +55
    -1
      eShopOnContainers-ServicesAndWebApps.sln
  10. BIN
      img/eShopOnContainers_Architecture_Diagram.png
  11. BIN
      img/eShopOnContainers_Architecture_Diagram_old.png
  12. BIN
      img/eShopOnContainers_Types_Of_Microservices.png
  13. +37
    -0
      src/BuildingBlocks/EventBus/IntegrationEventLogEF/Utilities/ResilientTransaction.cs
  14. +1
    -1
      src/BuildingBlocks/Resilience/Resilience.Http/IHttpClient.cs
  15. +14
    -0
      src/BuildingBlocks/Resilience/Resilience.Http/Resilience.Http.csproj
  16. +10
    -0
      src/BuildingBlocks/Resilience/Resilience.Http/ResiliencePolicy.cs
  17. +7
    -47
      src/BuildingBlocks/Resilience/Resilience.Http/ResilientHttpClient.cs
  18. +1
    -1
      src/BuildingBlocks/Resilience/Resilience.Http/StandardHttpClient.cs
  19. +3
    -2
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/App.xaml
  20. +23
    -0
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/Converters/WebNavigatedEventArgsConverter.cs
  21. +1
    -1
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/GlobalSettings.cs
  22. +10
    -0
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Basket/BasketItem.cs
  23. +3
    -0
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Orders/Order.cs
  24. +2
    -0
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/BasketViewModel.cs
  25. +3
    -2
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/CheckoutViewModel.cs
  26. +4
    -2
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/LoginViewModel.cs
  27. +20
    -4
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/LoginView.xaml
  28. +11
    -0
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/MainView.xaml.cs
  29. +16
    -1
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/Templates/BasketItemTemplate.xaml
  30. +1
    -0
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/eShopOnContainers.Core.csproj
  31. +1
    -0
      src/Mobile/eShopOnContainers/eShopOnContainers.Windows/Package.appxmanifest
  32. +27
    -0
      src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/OrderStartedIntegrationEventHandler.cs
  33. +19
    -0
      src/Services/Basket/Basket.API/IntegrationEvents/Events/OrderStartedIntegrationEvent.cs
  34. +6
    -1
      src/Services/Basket/Basket.API/Startup.cs
  35. +22
    -45
      src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs
  36. +0
    -28
      src/Services/Catalog/Catalog.API/Infrastructure/CatalogContext.cs
  37. +48
    -0
      src/Services/Catalog/Catalog.API/IntegrationEvents/CatalogIntegrationEventService.cs
  38. +14
    -0
      src/Services/Catalog/Catalog.API/IntegrationEvents/ICatalogIntegrationEventService.cs
  39. +4
    -3
      src/Services/Catalog/Catalog.API/Startup.cs
  40. +25
    -25
      src/Services/Identity/Identity.API/Configuration/Config.cs
  41. +0
    -1
      src/Services/Identity/Identity.API/Controllers/AccountController.cs
  42. +6
    -6
      src/Services/Identity/Identity.API/Controllers/ConsentController.cs
  43. +2
    -2
      src/Services/Identity/Identity.API/Identity.API.csproj
  44. +13
    -3
      src/Services/Identity/Identity.API/Models/AccountViewModels/ConsentViewModel.cs
  45. +3
    -1
      src/Services/Identity/Identity.API/Startup.cs
  46. +1
    -0
      src/Services/Identity/Identity.API/appsettings.json
  47. +23
    -8
      src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/BuyerAndPaymentMethodVerified/UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler.cs
  48. +43
    -0
      src/Services/Ordering/Ordering.API/Infrastructure/IntegrationEventMigrations/20170330131634_IntegrationEventInitial.Designer.cs
  49. +34
    -0
      src/Services/Ordering/Ordering.API/Infrastructure/IntegrationEventMigrations/20170330131634_IntegrationEventInitial.cs
  50. +42
    -0
      src/Services/Ordering/Ordering.API/Infrastructure/IntegrationEventMigrations/IntegrationEventLogContextModelSnapshot.cs
  51. +244
    -0
      src/Services/Ordering/Ordering.API/Infrastructure/Migrations/20170403082405_NoBuyerPropertyInOrder.Designer.cs
  52. +19
    -0
      src/Services/Ordering/Ordering.API/Infrastructure/Migrations/20170403082405_NoBuyerPropertyInOrder.cs
  53. +244
    -0
      src/Services/Ordering/Ordering.API/Infrastructure/Migrations/20170405110939_NoPaymentMethodPropertyInOrder.Designer.cs
  54. +19
    -0
      src/Services/Ordering/Ordering.API/Infrastructure/Migrations/20170405110939_NoPaymentMethodPropertyInOrder.cs
  55. +3
    -3
      src/Services/Ordering/Ordering.API/Infrastructure/Migrations/OrderingContextModelSnapshot.cs
  56. +19
    -0
      src/Services/Ordering/Ordering.API/IntegrationEvents/Events/OrderStartedIntegrationEvent.cs
  57. +14
    -0
      src/Services/Ordering/Ordering.API/IntegrationEvents/IOrderingIntegrationEventService.cs
  58. +49
    -0
      src/Services/Ordering/Ordering.API/IntegrationEvents/OrderingIntegrationEventService.cs
  59. +13
    -3
      src/Services/Ordering/Ordering.API/Ordering.API.csproj
  60. +19
    -3
      src/Services/Ordering/Ordering.API/Startup.cs
  61. +0
    -1
      src/Services/Ordering/Ordering.Domain/AggregatesModel/BuyerAggregate/PaymentMethod.cs
  62. +0
    -2
      src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs
  63. +0
    -1
      src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/OrderItem.cs
  64. +3
    -2
      src/Services/Ordering/Ordering.Infrastructure/Ordering.Infrastructure.csproj
  65. +6
    -2
      src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs
  66. +1
    -4
      src/Web/WebMVC/Controllers/OrderController.cs
  67. +10
    -0
      src/Web/WebMVC/Infrastructure/IResilientHttpClientFactory.cs
  68. +59
    -0
      src/Web/WebMVC/Infrastructure/ResilientHttpClientFactory.cs
  69. +1
    -1
      src/Web/WebMVC/Services/BasketService.cs
  70. +1
    -1
      src/Web/WebMVC/Services/CatalogService.cs
  71. +1
    -1
      src/Web/WebMVC/Services/OrderingService.cs
  72. +8
    -11
      src/Web/WebMVC/Startup.cs
  73. +1
    -1
      src/Web/WebMVC/Views/Shared/Components/CartList/Default.cshtml
  74. +8
    -1
      src/Web/WebMVC/WebMVC.csproj
  75. +1
    -1
      src/Web/WebMVC/appsettings.json
  76. +1
    -1
      src/Web/WebSPA/Client/modules/basket/basket.component.html
  77. +2
    -5
      src/Web/WebSPA/Client/modules/orders/orders-new/orders-new.component.ts
  78. +1
    -0
      src/Web/WebSPA/Program.cs
  79. +5
    -1
      src/Web/WebSPA/Startup.cs
  80. +1
    -1
      src/Web/WebStatus/Controllers/HomeController.cs
  81. +4
    -4
      src/Web/WebStatus/Viewmodels/HealthStatusViewModel.cs
  82. +20
    -0
      src/Web/WebStatus/Viewmodels/NamedCheckResult.cs
  83. +14
    -10
      src/Web/WebStatus/Views/Home/Index.cshtml
  84. +11
    -1
      test/Services/FunctionalTests/FunctionalTests.csproj
  85. +1
    -1
      test/Services/FunctionalTests/Services/Catalog/CatalogScenariosBase.cs
  86. +7
    -0
      test/Services/FunctionalTests/Services/Catalog/settings.json
  87. +1
    -1
      test/Services/FunctionalTests/Services/Ordering/OrderingScenariosBase.cs
  88. +0
    -0
      test/Services/FunctionalTests/Services/Ordering/settings.json
  89. +9
    -2
      test/Services/IntegrationTests/IntegrationTests.csproj
  90. +1
    -1
      test/Services/IntegrationTests/Services/Catalog/CatalogScenarioBase.cs
  91. +7
    -0
      test/Services/IntegrationTests/Services/Catalog/settings.json
  92. +1
    -1
      test/Services/IntegrationTests/Services/Ordering/OrderingScenarioBase.cs
  93. +2
    -1
      test/Services/IntegrationTests/Services/Ordering/settings.json

+ 2
- 1
.gitignore View File

@ -25,7 +25,7 @@ bld/
# Visual Studio 2015 cache/options directory # Visual Studio 2015 cache/options directory
.vs/ .vs/
# Uncomment if you have tasks that create the project's static files in wwwroot # Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
**/wwwroot/lib/
**/wwwroot/lib/ **/wwwroot/lib/
@ -260,3 +260,4 @@ pub/
#Ignore marker-file used to know which docker files we have. #Ignore marker-file used to know which docker files we have.
.eshopdocker_* .eshopdocker_*

+ 6
- 2
README.md View File

@ -14,11 +14,15 @@ Sample .NET Core reference application, powered by Microsoft, based on a simplif
**Architecture overview**: This reference application is cross-platform either at the server and client side, thanks to .NET Core services capable of running on Linux or Windows containers depending on your Docker host, and to Xamarin for mobile apps running on Android, iOS or Windows/UWP plus any browser for the client web apps. **Architecture overview**: This reference application is cross-platform either at the server and client side, thanks to .NET Core services capable of running on Linux or Windows containers depending on your Docker host, and to Xamarin for mobile apps running on Android, iOS or Windows/UWP plus any browser for the client web apps.
The architecture proposes a simplified microservice oriented architecture implementation with multiple autonomous microservices (each one owning its own data/db) and implementing different approaches within each microservice (simple CRUD vs. DDD/CQRS patterns) using Http as the current communication protocol. The architecture proposes a simplified microservice oriented architecture implementation with multiple autonomous microservices (each one owning its own data/db) and implementing different approaches within each microservice (simple CRUD vs. DDD/CQRS patterns) using Http as the current communication protocol.
<p> <p>
The plan is to add asynchronous communication for data updates propagation across multiple services based on integration events and an event bus plus other features defined at the <a href='https://github.com/dotnet/eShopOnContainers/wiki/01.-Roadmap-and-Milestones-for-future-releases'>roadmap</a>.
It also supports asynchronous communication for data updates propagation across multiple services based on Integration Events and an Event Bus plus other features defined at the <a href='https://github.com/dotnet/eShopOnContainers/wiki/01.-Roadmap-and-Milestones-for-future-releases'>roadmap</a>.
<p> <p>
<img src="img/eshop_logo.png"> <img src="img/eshop_logo.png">
<img src="img/eShopOnContainers_Architecture_Diagram.png"> <img src="img/eShopOnContainers_Architecture_Diagram.png">
<p> <p>
The microservices are different in type, meaning different internal architecture patterns approaches depending on it purpose, as shown in the image below.
<p>
<img src="img/eShopOnContainers_Types_Of_Microservices.png">
<p>
<p> <p>
Additional miroservice styles with other frameworks and No-SQL databases will be added, eventually. This is a great opportunity for pull requests from the community, like a new microservice using Nancy, or even other languages like Node, Go, Python or data containers with MongoDB with Azure DocDB compatibility, PostgreSQL, RavenDB, Event Store, MySql, etc. You name it! :) Additional miroservice styles with other frameworks and No-SQL databases will be added, eventually. This is a great opportunity for pull requests from the community, like a new microservice using Nancy, or even other languages like Node, Go, Python or data containers with MongoDB with Azure DocDB compatibility, PostgreSQL, RavenDB, Event Store, MySql, etc. You name it! :)
@ -88,7 +92,7 @@ The app was also partially tested on "Docker for Mac" using a development MacOS
## Sending feedback and pull requests ## Sending feedback and pull requests
As mentioned, we'd appreciate to your feedback, improvements and ideas. As mentioned, we'd appreciate to your feedback, improvements and ideas.
You can create new issues at the issues section, do pull requests and/or send emails to eshop_feedback@service.microsoft.com
You can create new issues at the issues section, do pull requests and/or send emails to **eshop_feedback@service.microsoft.com**
## Questions ## Questions
[QUESTION] Answer +1 if the solution is working for you (Through VS2017 or CLI environment): [QUESTION] Answer +1 if the solution is working for you (Through VS2017 or CLI environment):


+ 2
- 1
cli-linux/build-bits-linux.sh View File

@ -6,6 +6,7 @@ projectList=(
"/src/Services/Identity/Identity.API" "/src/Services/Identity/Identity.API"
"/src/Web/WebMVC" "/src/Web/WebMVC"
"/src/Web/WebSPA" "/src/Web/WebSPA"
"/src/Web/WebStatus
) )
# Build SPA app # Build SPA app
@ -36,4 +37,4 @@ done
#fi #fi
# No need to build the images, docker build or docker compose will # No need to build the images, docker build or docker compose will
# do that using the images and containers defined in the docker-compose.yml file.
# do that using the images and containers defined in the docker-compose.yml file.

+ 1
- 0
cli-mac/build-bits.sh View File

@ -7,6 +7,7 @@ projectList=(
"../src/Services/Identity/Identity.API" "../src/Services/Identity/Identity.API"
"../src/Web/WebMVC" "../src/Web/WebMVC"
"../src/Web/WebSPA" "../src/Web/WebSPA"
"../src/Web/WebStatus"
) )
for project in "${projectList[@]}" for project in "${projectList[@]}"


+ 0
- 160
cli-windows/build-bits-expanded.ps1 View File

@ -1,160 +0,0 @@
#########################################################################################################
# This "expanded Script" can be used when debugging issues when building the .NET Core bits
# as it is easier to follow and debug than when using a loop (like in the optimized build-bits.ps1)
#########################################################################################################
Param([string] $rootPath)
$scriptPath = Split-Path $script:MyInvocation.MyCommand.Path
Write-Host "Current script directory is $scriptPath" -ForegroundColor Yellow
if ([string]::IsNullOrEmpty($rootPath)) {
$rootPath = "$scriptPath\.."
}
Write-Host "Root path used is $rootPath" -ForegroundColor Yellow
# *** WebMVC paths ***
$webMVCPath = $rootPath + "\src\Web\WebMVC"
$webMVCPathToProject = $webMVCPath + "\WebMVC.csproj"
Write-Host "webMVCPathToProject is $webMVCPathToProject" -ForegroundColor Yellow
$webMVCPathToPub = $webMVCPath + "\obj\Docker\publish"
Write-Host "webMVCPathToPub is $webMVCPathToPub" -ForegroundColor Yellow
# *** WebSPA paths ***
$webSPAPath = $rootPath + "\src\Web\WebSPA"
$webSPAPathToProject = $webSPAPath + "\WebSPA.csproj"
Write-Host "webSPAPathToProject is $webSPAPathToProject" -ForegroundColor Yellow
$webSPAPathToPub = $webSPAPath + "\obj\Docker\publish"
Write-Host "webSPAPathToPub is $webSPAPathToPub" -ForegroundColor Yellow
# *** IdentitySvc paths ***
$identitySvcPath = $rootPath + "\src\Services\Identity\Identity.API"
$identitySvcToProject = $identitySvcPath + "\Identity.API.csproj"
Write-Host "identitySvcToProject is $identitySvcToProject" -ForegroundColor Yellow
$identitySvcPathToPub = $identitySvcPath + "\obj\Docker\publish"
Write-Host "identitySvcPathToPub is $identitySvcPathToPub" -ForegroundColor Yellow
# *** Catalog paths ***
$catalogPath = $rootPath + "\src\Services\Catalog\Catalog.API"
$catalogPathToProject = $catalogPath + "\Catalog.API.csproj"
Write-Host "catalogPathToProject is $catalogPathToProject" -ForegroundColor Yellow
$catalogPathToPub = $catalogPath + "\obj\Docker\publish"
Write-Host "catalogPathToPub is $catalogPathToPub" -ForegroundColor Yellow
# *** Ordering paths ***
$orderingPath = $rootPath + "\src\Services\Ordering\Ordering.API"
$orderingPathToProject = $orderingPath + "\Ordering.API.csproj"
Write-Host "orderingPathToProject is $orderingPathToProject" -ForegroundColor Yellow
$orderingPathToPub = $orderingPath + "\obj\Docker\publish"
Write-Host "orderingPathToPub is $orderingPathToPub" -ForegroundColor Yellow
# *** Basket paths ***
$basketPath = $rootPath + "\src\Services\Basket\Basket.API"
$basketPathToProject = $basketPath + "\Basket.API.csproj"
Write-Host "basketPathToProject is $basketPathToProject" -ForegroundColor Yellow
$basketPathToPub = $basketPath + "\obj\Docker\publish"
Write-Host "basketPathToPub is $basketPathToPub" -ForegroundColor Yellow
########################################################################################
# Delete old eShop dotnet publish bits
########################################################################################
# Write-Host "Deleting previous dotnet publish bits from all projects" -ForegroundColor Blue
remove-item -path $WebMVCPathToPub -Force -Recurse -ErrorAction SilentlyContinue
remove-item -path $webSPAPathToPub -Force -Recurse -ErrorAction SilentlyContinue
remove-item -path $identitySvcPathToPub -Force -Recurse -ErrorAction SilentlyContinue
remove-item -path $catalogPathToPub -Force -Recurse -ErrorAction SilentlyContinue
remove-item -path $orderingPathToPub -Force -Recurse -ErrorAction SilentlyContinue
remove-item -path $basketPathToPub -Force -Recurse -ErrorAction SilentlyContinue
########################################################################################
# Building DotNet bits
########################################################################################
# WebMVC: Build dotnet bits
Write-Host "WebMVC: Restore Dependencies, dotnet build and dotnet publish" -ForegroundColor Blue
dotnet restore $WebMVCPathToProject
dotnet build $WebMVCPathToProject
dotnet publish $WebMVCPathToProject -o $WebMVCPathToPub
# WebSPA: Build dotnet bits
Write-Host "WebSPA: Installing npm dependencies"
#TEMP COMMENT--- Start-Process -WorkingDirectory $webSPAPath -NoNewWindow -Wait npm i
Write-Host "WebSPA: Restore Dependencies, dotnet build and dotnet publish" -ForegroundColor Blue
dotnet restore $webSPAPathToProject
dotnet build $webSPAPathToProject
dotnet publish $webSPAPathToProject -o $webSPAPathToPub
# Identity Service: Build dotnet bits
Write-Host "Identity Service: Restore Dependencies, dotnet build and dotnet publish" -ForegroundColor Blue
dotnet restore $identitySvcToProject
dotnet build $identitySvcToProject
dotnet publish $identitySvcToProject -o $identitySvcPathToPub
# Catalog Service: Build dotnet bits
Write-Host "Catalog Service: Restore Dependencies, dotnet build and dotnet publish" -ForegroundColor Blue
dotnet restore $catalogPathToProject
dotnet build $catalogPathToProject
dotnet publish $catalogPathToProject -o $catalogPathToPub
# Ordering Service: Build dotnet bits
Write-Host "Ordering Service: Restore Dependencies, dotnet build and dotnet publish" -ForegroundColor Blue
dotnet restore $orderingPathToProject
dotnet build $orderingPathToProject
dotnet publish $orderingPathToProject -o $orderingPathToPub
# Basket Service: Build dotnet bits
Write-Host "Basket Service: Restore Dependencies, dotnet build and dotnet publish" -ForegroundColor Blue
dotnet restore $basketPathToProject
dotnet build $basketPathToProject
dotnet publish $basketPathToProject -o $basketPathToPub
########################################################################################
# Delete old eShop Docker images
########################################################################################
$imagesToDelete = docker images --filter=reference="eshop/*" -q
If (-Not $imagesToDelete) {Write-Host "Not deleting eShop images as there are no eShop images in the current local Docker repo."}
Else
{
# Delete all containers
Write-Host "Deleting all containers in local Docker Host"
docker rm $(docker ps -a -q) -f
# Delete all eshop images
Write-Host "Deleting eShop images in local Docker repo"
Write-Host $imagesToDelete
docker rmi $(docker images --filter=reference="eshop/*" -q) -f
}
########################################################################################
# Build new eShop images
########################################################################################
# WE DON'T NEED DOCKER BUILD AS WE CAN RUN "DOCKER-COMPOSE BUILD" OR "DOCKER-COMPOSE UP" AND IT WILL BUILD ALL THE IMAGES IN THE .YML FOR US
#*** build docker images ***
# docker build -t eshop/web $webPathToPub
# docker build -t eshop/catalog.api $catalogPathToPub
# docker build -t eshop/ordering.api $orderingApiPathToPub
# docker build -t eshop/basket.api $basketPathToPub
# docker build -t eshop/webspa $webSPAPathToPub
# docker build -t eshop/identity $identitySvcPathToPub

+ 2
- 1
cli-windows/build-bits.ps1 View File

@ -15,13 +15,14 @@ $projectPaths =
@{Path="$rootPath\src\Services\Catalog\Catalog.API";Prj="Catalog.API.csproj"}, @{Path="$rootPath\src\Services\Catalog\Catalog.API";Prj="Catalog.API.csproj"},
@{Path="$rootPath\src\Services\Ordering\Ordering.API";Prj="Ordering.API.csproj"}, @{Path="$rootPath\src\Services\Ordering\Ordering.API";Prj="Ordering.API.csproj"},
@{Path="$rootPath\src\Services\Basket\Basket.API";Prj="Basket.API.csproj"} @{Path="$rootPath\src\Services\Basket\Basket.API";Prj="Basket.API.csproj"}
@{Path="$rootPath\src\Web\WebStatus";Prj="WebStatus.csproj"}
$projectPaths | foreach { $projectPaths | foreach {
$projectPath = $_.Path $projectPath = $_.Path
$projectFile = $_.Prj $projectFile = $_.Prj
$outPath = $_.Path + "\obj\Docker\publish" $outPath = $_.Path + "\obj\Docker\publish"
$projectPathAndFile = "$projectPath\$projectFile" $projectPathAndFile = "$projectPath\$projectFile"
Write-Host "Deleting $outPath" -ForegroundColor Yellow
Write-Host "Deleting old publish files in $outPath" -ForegroundColor Yellow
remove-item -path $outPath -Force -Recurse -ErrorAction SilentlyContinue remove-item -path $outPath -Force -Recurse -ErrorAction SilentlyContinue
Write-Host "Publishing $projectPathAndFile to $outPath" -ForegroundColor Yellow Write-Host "Publishing $projectPathAndFile to $outPath" -ForegroundColor Yellow
dotnet restore $projectPathAndFile dotnet restore $projectPathAndFile


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

@ -33,6 +33,7 @@ services:
- ASPNETCORE_ENVIRONMENT=Development - ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=http://0.0.0.0:5105 - ASPNETCORE_URLS=http://0.0.0.0:5105
- SpaClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5104 - SpaClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5104
- XamarinCallback=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5105/xamarincallback #localhost do not work for UWP login, so we have to use "external" IP always
- ConnectionStrings__DefaultConnection=Server=sql.data;Database=Microsoft.eShopOnContainers.Service.IdentityDb;User Id=sa;Password=Pass@word - ConnectionStrings__DefaultConnection=Server=sql.data;Database=Microsoft.eShopOnContainers.Service.IdentityDb;User Id=sa;Password=Pass@word
- MvcClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5100 #Local: You need to open your local dev-machine firewall at range 5100-5105. - MvcClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5100 #Local: You need to open your local dev-machine firewall at range 5100-5105.
ports: ports:
@ -86,7 +87,7 @@ services:
- CatalogUrl=http://catalog.api:5101/hc - CatalogUrl=http://catalog.api:5101/hc
- OrderingUrl=http://ordering.api:5102/hc - OrderingUrl=http://ordering.api:5102/hc
- BasketUrl=http://basket.api:5103/hc - BasketUrl=http://basket.api:5103/hc
- IdentityUrl=http://10.0.75.1:5105/hc
- IdentityUrl=http://identity.api:5105/hc
- mvc=http://webmvc:5100/hc - mvc=http://webmvc:5100/hc
- spa=http://webspa:5104/hc - spa=http://webspa:5104/hc
ports: ports:


+ 1
- 0
docker-compose.prod.yml View File

@ -40,6 +40,7 @@ services:
- SpaClient=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5104 - SpaClient=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5104
- ConnectionStrings__DefaultConnection=Server=sql.data;Database=Microsoft.eShopOnContainers.Service.IdentityDb;User Id=sa;Password=Pass@word - ConnectionStrings__DefaultConnection=Server=sql.data;Database=Microsoft.eShopOnContainers.Service.IdentityDb;User Id=sa;Password=Pass@word
- MvcClient=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5100 #Local: You need to open your host's firewall at range 5100-5105. - MvcClient=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5100 #Local: You need to open your host's firewall at range 5100-5105.
- XamarinCallback=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5105/xamarincallback
ports: ports:
- "5105:5105" - "5105:5105"


+ 55
- 1
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.26228.0
VisualStudioVersion = 15.0.26228.12
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
@ -84,6 +84,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Health
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebStatus", "src\Web\WebStatus\WebStatus.csproj", "{C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebStatus", "src\Web\WebStatus\WebStatus.csproj", "{C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Resilience", "Resilience", "{FBF43D93-F2E7-4FF8-B4AB-186895949B88}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Resilience.Http", "src\BuildingBlocks\Resilience\Resilience.Http\Resilience.Http.csproj", "{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
@ -914,6 +918,54 @@ Global
{C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Release|x64.Build.0 = Release|Any CPU {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Release|x64.Build.0 = Release|Any CPU
{C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Release|x86.ActiveCfg = Release|Any CPU {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Release|x86.ActiveCfg = Release|Any CPU
{C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Release|x86.Build.0 = Release|Any CPU {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Release|x86.Build.0 = Release|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.AppStore|ARM.ActiveCfg = Debug|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.AppStore|ARM.Build.0 = Debug|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.AppStore|iPhone.Build.0 = Debug|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.AppStore|x64.ActiveCfg = Debug|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.AppStore|x64.Build.0 = Debug|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.AppStore|x86.ActiveCfg = Debug|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.AppStore|x86.Build.0 = Debug|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Debug|ARM.ActiveCfg = Debug|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Debug|ARM.Build.0 = Debug|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Debug|iPhone.Build.0 = Debug|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Debug|x64.ActiveCfg = Debug|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Debug|x64.Build.0 = Debug|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Debug|x86.ActiveCfg = Debug|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Debug|x86.Build.0 = Debug|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Release|Any CPU.Build.0 = Release|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Release|ARM.ActiveCfg = Release|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Release|ARM.Build.0 = Release|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Release|iPhone.ActiveCfg = Release|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Release|iPhone.Build.0 = Release|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Release|x64.ActiveCfg = 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.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -947,5 +999,7 @@ Global
{942ED6E8-0050-495F-A0EA-01E97F63760C} = {A81ECBC2-6B00-4DCD-8388-469174033379} {942ED6E8-0050-495F-A0EA-01E97F63760C} = {A81ECBC2-6B00-4DCD-8388-469174033379}
{7804FC60-23E6-490C-8E08-F9FEF829F184} = {A81ECBC2-6B00-4DCD-8388-469174033379} {7804FC60-23E6-490C-8E08-F9FEF829F184} = {A81ECBC2-6B00-4DCD-8388-469174033379}
{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}
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502} = {FBF43D93-F2E7-4FF8-B4AB-186895949B88}
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal

BIN
img/eShopOnContainers_Architecture_Diagram.png View File

Before After
Width: 2034  |  Height: 1078  |  Size: 427 KiB Width: 1830  |  Height: 1078  |  Size: 405 KiB

BIN
img/eShopOnContainers_Architecture_Diagram_old.png View File

Before After
Width: 2034  |  Height: 1078  |  Size: 427 KiB

BIN
img/eShopOnContainers_Types_Of_Microservices.png View File

Before After
Width: 1249  |  Height: 581  |  Size: 156 KiB

+ 37
- 0
src/BuildingBlocks/EventBus/IntegrationEventLogEF/Utilities/ResilientTransaction.cs View File

@ -0,0 +1,37 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services;
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Utilities
{
public class ResilientTransaction
{
private DbContext _context;
private ResilientTransaction(DbContext context) =>
_context = context ?? throw new ArgumentNullException(nameof(context));
public static ResilientTransaction New (DbContext context) =>
new ResilientTransaction(context);
public async Task ExecuteAsync(Func<Task> action)
{
//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
var strategy = _context.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync(async () =>
{
using (var transaction = _context.Database.BeginTransaction())
{
await action();
transaction.Commit();
}
});
}
}
}

src/Web/WebMVC/Services/Utilities/IHttpClient.cs → src/BuildingBlocks/Resilience/Resilience.Http/IHttpClient.cs View File

@ -4,7 +4,7 @@ using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace WebMVC.Services.Utilities
namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http
{ {
public interface IHttpClient public interface IHttpClient
{ {

+ 14
- 0
src/BuildingBlocks/Resilience/Resilience.Http/Resilience.Http.csproj View File

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.4</TargetFramework>
<RootNamespace>Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="1.1.0" />
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
<PackageReference Include="Polly" Version="5.0.6" />
</ItemGroup>
</Project>

+ 10
- 0
src/BuildingBlocks/Resilience/Resilience.Http/ResiliencePolicy.cs View File

@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http
{
public class ResiliencePolicy
{
}
}

src/Web/WebMVC/Services/Utilities/ResilientHttpClient.cs → src/BuildingBlocks/Resilience/Resilience.Http/ResilientHttpClient.cs View File

@ -1,14 +1,14 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json; using Newtonsoft.Json;
using Polly; using Polly;
using Polly.Wrap; using Polly.Wrap;
using System; using System;
using System.Collections.Generic;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace WebMVC.Services.Utilities
namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http
{ {
/// <summary> /// <summary>
/// HttpClient wrapper that integrates Retry and Circuit /// HttpClient wrapper that integrates Retry and Circuit
@ -21,55 +21,15 @@ namespace WebMVC.Services.Utilities
private PolicyWrap _policyWrapper; private PolicyWrap _policyWrapper;
private ILogger<ResilientHttpClient> _logger; private ILogger<ResilientHttpClient> _logger;
public HttpClient Inst => _client; public HttpClient Inst => _client;
public ResilientHttpClient(ILogger<ResilientHttpClient> logger)
public ResilientHttpClient(Policy[] policies, ILogger<ResilientHttpClient> logger)
{ {
_client = new HttpClient(); _client = new HttpClient();
_logger = logger; _logger = logger;
// Add Policies to be applied // Add Policies to be applied
_policyWrapper = Policy.WrapAsync(
CreateRetryPolicy(),
CreateCircuitBreakerPolicy()
);
}
private Policy CreateRetryPolicy() =>
Policy.Handle<HttpRequestException>()
.WaitAndRetryAsync(
// number of retries
6,
// exponential backofff
retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
// on retry
(exception, timeSpan, retryCount, context) =>
{
var msg = $"Retry {retryCount} implemented with Polly's RetryPolicy " +
$"of {context.PolicyKey} " +
$"at {context.ExecutionKey}, " +
$"due to: {exception}.";
_logger.LogWarning(msg);
_logger.LogDebug(msg);
}
);
private Policy CreateCircuitBreakerPolicy() =>
Policy.Handle<HttpRequestException>()
.CircuitBreakerAsync(
// number of exceptions before breaking circuit
5,
// time circuit opened before retry
TimeSpan.FromMinutes(1),
(exception, duration) =>
{
// on circuit opened
_logger.LogTrace("Circuit breaker opened");
},
() =>
{
// on circuit closed
_logger.LogTrace("Circuit breaker reset");
}
);
_policyWrapper = Policy.WrapAsync(policies);
}
public Task<string> GetStringAsync(string uri) => public Task<string> GetStringAsync(string uri) =>
HttpInvoker(() => HttpInvoker(() =>

src/Web/WebMVC/Services/Utilities/StandardHttpClient.cs → src/BuildingBlocks/Resilience/Resilience.Http/StandardHttpClient.cs View File

@ -4,7 +4,7 @@ using System;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace WebMVC.Services.Utilities
namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http
{ {
public class StandardHttpClient : IHttpClient public class StandardHttpClient : IHttpClient
{ {

+ 3
- 2
src/Mobile/eShopOnContainers/eShopOnContainers.Core/App.xaml View File

@ -108,8 +108,9 @@
<converters:InverseBoolConverter x:Key="InverseBoolConverter" /> <converters:InverseBoolConverter x:Key="InverseBoolConverter" />
<converters:ItemsToHeightConverter x:Key="ItemsToHeightConverter" /> <converters:ItemsToHeightConverter x:Key="ItemsToHeightConverter" />
<converters:ToUpperConverter x:Key="ToUpperConverter" /> <converters:ToUpperConverter x:Key="ToUpperConverter" />
<converters:WebNavigatingEventArgsConverter x:Key="WebNavigatingEventArgsConverter" />
<converters:WebNavigatingEventArgsConverter x:Key="WebNavigatingEventArgsConverter" />
<converters:WebNavigatedEventArgsConverter x:Key="WebNavigatedEventArgsConverter" />
<!-- STYLES --> <!-- STYLES -->
<Style x:Key="ValidationErrorLabelStyle" <Style x:Key="ValidationErrorLabelStyle"
TargetType="{x:Type Label}"> TargetType="{x:Type Label}">


+ 23
- 0
src/Mobile/eShopOnContainers/eShopOnContainers.Core/Converters/WebNavigatedEventArgsConverter.cs View File

@ -0,0 +1,23 @@
using System;
using System.Globalization;
using Xamarin.Forms;
namespace eShopOnContainers.Core.Converters
{
public class WebNavigatedEventArgsConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var eventArgs = value as WebNavigatedEventArgs;
if (eventArgs == null)
throw new ArgumentException("Expected WebNavigatedEventArgs as value", "value");
return eventArgs.Url;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

+ 1
- 1
src/Mobile/eShopOnContainers/eShopOnContainers.Core/GlobalSettings.cs View File

@ -60,7 +60,7 @@
IdentityEndpoint = string.Format("{0}:5105/connect/authorize", baseEndpoint); IdentityEndpoint = string.Format("{0}:5105/connect/authorize", baseEndpoint);
UserInfoEndpoint = string.Format("{0}:5105/connect/userinfo", baseEndpoint); UserInfoEndpoint = string.Format("{0}:5105/connect/userinfo", baseEndpoint);
LogoutEndpoint = string.Format("{0}:5105/connect/endsession", baseEndpoint); LogoutEndpoint = string.Format("{0}:5105/connect/endsession", baseEndpoint);
IdentityCallback = "http://eshopxamarin/callback.html";
IdentityCallback = string.Format("{0}:5105/xamarincallback", baseEndpoint);
LogoutCallback = string.Format("{0}:5105/Account/Redirecting", baseEndpoint); LogoutCallback = string.Format("{0}:5105/Account/Redirecting", baseEndpoint);
} }
} }

+ 10
- 0
src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Basket/BasketItem.cs View File

@ -15,6 +15,16 @@ namespace eShopOnContainers.Core.Models.Basket
public decimal UnitPrice { get; set; } public decimal UnitPrice { get; set; }
public decimal OldUnitPrice { get; set; }
public bool HasNewPrice
{
get
{
return OldUnitPrice != 0.0m;
}
}
public int Quantity public int Quantity
{ {
get { return _quantity; } get { return _quantity; }


+ 3
- 0
src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Orders/Order.cs View File

@ -34,6 +34,9 @@ namespace eShopOnContainers.Core.Models.Orders
[JsonProperty("country")] [JsonProperty("country")]
public string ShippingCountry { get; set; } public string ShippingCountry { get; set; }
[JsonProperty("zipCode")]
public string ShippingZipCode { get; set; }
public int CardTypeId { get; set; } public int CardTypeId { get; set; }
public string CardNumber { get; set; } public string CardNumber { get; set; }


+ 2
- 0
src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/BasketViewModel.cs View File

@ -76,7 +76,9 @@ namespace eShopOnContainers.Core.ViewModels
if (basket != null && basket.Items != null && basket.Items.Any()) if (basket != null && basket.Items != null && basket.Items.Any())
{ {
BadgeCount = 0;
BasketItems.Clear(); BasketItems.Clear();
foreach (var basketItem in basket.Items) foreach (var basketItem in basket.Items)
{ {
BadgeCount += basketItem.Quantity; BadgeCount += basketItem.Quantity;


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

@ -90,7 +90,7 @@ namespace eShopOnContainers.Core.ViewModels
ZipCode = userInfo?.ZipCode, ZipCode = userInfo?.ZipCode,
State = userInfo?.State, State = userInfo?.State,
Country = userInfo?.Country, Country = userInfo?.Country,
City = string.Empty
City = userInfo?.Address
}; };
// Create Payment Info // Create Payment Info
@ -117,7 +117,8 @@ namespace eShopOnContainers.Core.ViewModels
ShippingState = _shippingAddress.State, ShippingState = _shippingAddress.State,
ShippingCountry = _shippingAddress.Country, ShippingCountry = _shippingAddress.Country,
ShippingStreet = _shippingAddress.Street, ShippingStreet = _shippingAddress.Street,
ShippingCity = _shippingAddress.City,
ShippingCity = _shippingAddress.City,
ShippingZipCode = _shippingAddress.ZipCode,
Total = CalculateTotal(CreateOrderItems(orderItems)) Total = CalculateTotal(CreateOrderItems(orderItems))
}; };


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

@ -221,14 +221,16 @@ namespace eShopOnContainers.Core.ViewModels
private async Task NavigateAsync(string url) private async Task NavigateAsync(string url)
{ {
if (url.Equals(GlobalSetting.Instance.LogoutCallback))
var unescapedUrl = System.Net.WebUtility.UrlDecode(url);
if (unescapedUrl.Equals(GlobalSetting.Instance.LogoutCallback))
{ {
Settings.AuthAccessToken = string.Empty; Settings.AuthAccessToken = string.Empty;
Settings.AuthIdToken = string.Empty; Settings.AuthIdToken = string.Empty;
IsLogin = false; IsLogin = false;
LoginUrl = _identityService.CreateAuthorizeRequest(); LoginUrl = _identityService.CreateAuthorizeRequest();
} }
else if (url.Contains(GlobalSetting.Instance.IdentityCallback))
else if (unescapedUrl.Contains(GlobalSetting.Instance.IdentityCallback))
{ {
var authResponse = new AuthorizeResponse(url); var authResponse = new AuthorizeResponse(url);


+ 20
- 4
src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/LoginView.xaml View File

@ -345,10 +345,26 @@
AbsoluteLayout.LayoutBounds="0, 0, 1, 1" AbsoluteLayout.LayoutBounds="0, 0, 1, 1"
AbsoluteLayout.LayoutFlags="All"> AbsoluteLayout.LayoutFlags="All">
<WebView.Behaviors> <WebView.Behaviors>
<behaviors:EventToCommandBehavior
EventName="Navigating"
EventArgsConverter="{StaticResource WebNavigatingEventArgsConverter}"
Command="{Binding NavigateCommand}" />
<OnPlatform x:TypeArguments="Behavior">
<OnPlatform.Android>
<behaviors:EventToCommandBehavior
EventName="Navigating"
EventArgsConverter="{StaticResource WebNavigatingEventArgsConverter}"
Command="{Binding NavigateCommand}" />
</OnPlatform.Android>
<OnPlatform.iOS>
<behaviors:EventToCommandBehavior
EventName="Navigating"
EventArgsConverter="{StaticResource WebNavigatingEventArgsConverter}"
Command="{Binding NavigateCommand}" />
</OnPlatform.iOS>
<OnPlatform.WinPhone>
<behaviors:EventToCommandBehavior
EventName="Navigated"
EventArgsConverter="{StaticResource WebNavigatedEventArgsConverter}"
Command="{Binding NavigateCommand}" />
</OnPlatform.WinPhone>
</OnPlatform>
</WebView.Behaviors> </WebView.Behaviors>
</WebView> </WebView>
</AbsoluteLayout> </AbsoluteLayout>


+ 11
- 0
src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/MainView.xaml.cs View File

@ -35,5 +35,16 @@ namespace eShopOnContainers.Core.Views
await ((BasketViewModel)BasketView.BindingContext).InitializeAsync(null); await ((BasketViewModel)BasketView.BindingContext).InitializeAsync(null);
await ((ProfileViewModel)ProfileView.BindingContext).InitializeAsync(null); await ((ProfileViewModel)ProfileView.BindingContext).InitializeAsync(null);
} }
protected override async void OnCurrentPageChanged()
{
base.OnCurrentPageChanged();
if (CurrentPage is BasketView)
{
// Force basket view refresh every time we access it
await (BasketView.BindingContext as ViewModelBase).InitializeAsync(null);
}
}
} }
} }

+ 16
- 1
src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/Templates/BasketItemTemplate.xaml View File

@ -83,6 +83,7 @@
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="*" /> <RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="1" /> <RowDefinition Height="1" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<!-- IMAGE --> <!-- IMAGE -->
@ -131,10 +132,24 @@
Text="{Binding Total, StringFormat='${0:N}'}" Text="{Binding Total, StringFormat='${0:N}'}"
Style="{StaticResource OrderTotalStyle}"/> Style="{StaticResource OrderTotalStyle}"/>
</Grid> </Grid>
<Grid
<Grid
Grid.Column="0" Grid.Column="0"
Grid.ColumnSpan="2" Grid.ColumnSpan="2"
Grid.Row="1" Grid.Row="1"
IsVisible="{Binding HasNewPrice}"
BackgroundColor="#F0AD4E">
<Label
HorizontalOptions="Fill"
VerticalOptions="Fill"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center"
HeightRequest="60"
Text="{Binding OldUnitPrice, StringFormat='Note that the price of this article changed in our Catalog. The old price when you originally added it to the basket was $ {0:N2}'}" />
</Grid>
<Grid
Grid.Column="0"
Grid.ColumnSpan="2"
Grid.Row="2"
BackgroundColor="Gray"/> BackgroundColor="Gray"/>
</Grid> </Grid>
</ContentView.Content> </ContentView.Content>

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

@ -58,6 +58,7 @@
<Compile Include="Converters\ItemsToHeightConverter.cs" /> <Compile Include="Converters\ItemsToHeightConverter.cs" />
<Compile Include="Converters\ItemTappedEventArgsConverter.cs" /> <Compile Include="Converters\ItemTappedEventArgsConverter.cs" />
<Compile Include="Converters\ToUpperConverter.cs" /> <Compile Include="Converters\ToUpperConverter.cs" />
<Compile Include="Converters\WebNavigatedEventArgsConverter.cs" />
<Compile Include="Exceptions\ServiceAuthenticationException.cs" /> <Compile Include="Exceptions\ServiceAuthenticationException.cs" />
<Compile Include="Extensions\ObservableExtension.cs" /> <Compile Include="Extensions\ObservableExtension.cs" />
<Compile Include="GlobalSettings.cs" /> <Compile Include="GlobalSettings.cs" />


+ 1
- 0
src/Mobile/eShopOnContainers/eShopOnContainers.Windows/Package.appxmanifest View File

@ -24,5 +24,6 @@
</Applications> </Applications>
<Capabilities> <Capabilities>
<Capability Name="internetClient" /> <Capability Name="internetClient" />
<Capability Name="privateNetworkClientServer" />
</Capabilities> </Capabilities>
</Package> </Package>

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

@ -0,0 +1,27 @@
using Basket.API.IntegrationEvents.Events;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.Services.Basket.API.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Basket.API.IntegrationEvents.EventHandling
{
public class OrderStartedIntegrationEventHandler : IIntegrationEventHandler<OrderStartedIntegrationEvent>
{
private readonly IBasketRepository _repository;
public OrderStartedIntegrationEventHandler(IBasketRepository repository)
{
_repository = repository;
}
public async Task Handle(OrderStartedIntegrationEvent @event)
{
await _repository.DeleteBasketAsync(@event.UserId.ToString());
}
}
}

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

@ -0,0 +1,19 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Basket.API.IntegrationEvents.Events
{
// Integration Events notes:
// An Event is “something that has happened in the past”, therefore its name has to be
// An Integration Event is an event that can cause side effects to other microsrvices, Bounded-Contexts or external systems.
public class OrderStartedIntegrationEvent : IntegrationEvent
{
public string UserId { get; }
public OrderStartedIntegrationEvent(string userId) =>
UserId = userId;
}
}

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

@ -17,6 +17,8 @@ using System;
using Microsoft.Extensions.HealthChecks; using Microsoft.Extensions.HealthChecks;
using System.Threading.Tasks; using System.Threading.Tasks;
using Basket.API.Infrastructure.Filters; using Basket.API.Infrastructure.Filters;
using Basket.API.IntegrationEvents.Events;
using Basket.API.IntegrationEvents.EventHandling;
namespace Microsoft.eShopOnContainers.Services.Basket.API namespace Microsoft.eShopOnContainers.Services.Basket.API
{ {
@ -40,7 +42,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
services.AddHealthChecks(checks => services.AddHealthChecks(checks =>
{ {
checks.AddValueTaskCheck("Always OK", () => new ValueTask<IHealthCheckResult>(HealthCheckResult.Healthy("Ok")));
checks.AddValueTaskCheck("HTTP Endpoint", () => new ValueTask<IHealthCheckResult>(HealthCheckResult.Healthy("Ok")));
}); });
// Add framework services. // Add framework services.
@ -91,6 +93,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
services.AddTransient<IBasketRepository, RedisBasketRepository>(); services.AddTransient<IBasketRepository, RedisBasketRepository>();
services.AddTransient<IIntegrationEventHandler<ProductPriceChangedIntegrationEvent>, ProductPriceChangedIntegrationEventHandler>(); services.AddTransient<IIntegrationEventHandler<ProductPriceChangedIntegrationEvent>, ProductPriceChangedIntegrationEventHandler>();
services.AddTransient<IIntegrationEventHandler<OrderStartedIntegrationEvent>, OrderStartedIntegrationEventHandler>();
var serviceProvider = services.BuildServiceProvider(); var serviceProvider = services.BuildServiceProvider();
var configuration = serviceProvider.GetRequiredService<IOptionsSnapshot<BasketSettings>>().Value; var configuration = serviceProvider.GetRequiredService<IOptionsSnapshot<BasketSettings>>().Value;
@ -117,8 +120,10 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
.UseSwaggerUi(); .UseSwaggerUi();
var catalogPriceHandler = app.ApplicationServices.GetService<IIntegrationEventHandler<ProductPriceChangedIntegrationEvent>>(); 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>(catalogPriceHandler); eventBus.Subscribe<ProductPriceChangedIntegrationEvent>(catalogPriceHandler);
eventBus.Subscribe<OrderStartedIntegrationEvent>(orderStartedHandler);
} }


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

@ -15,6 +15,8 @@ using System.Data.Common;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Utilities;
using Catalog.API.IntegrationEvents;
namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
{ {
@ -23,15 +25,13 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
{ {
private readonly CatalogContext _catalogContext; private readonly CatalogContext _catalogContext;
private readonly IOptionsSnapshot<Settings> _settings; private readonly IOptionsSnapshot<Settings> _settings;
private readonly IEventBus _eventBus;
private readonly Func<DbConnection, IIntegrationEventLogService> _integrationEventLogServiceFactory;
private readonly ICatalogIntegrationEventService _catalogIntegrationEventService;
public CatalogController(CatalogContext Context, IOptionsSnapshot<Settings> settings, IEventBus eventBus, Func<DbConnection, IIntegrationEventLogService> integrationEventLogServiceFactory)
public CatalogController(CatalogContext Context, IOptionsSnapshot<Settings> settings, ICatalogIntegrationEventService catalogIntegrationEventService)
{ {
_catalogContext = Context; _catalogContext = Context;
_catalogIntegrationEventService = catalogIntegrationEventService;
_settings = settings; _settings = settings;
_eventBus = eventBus;
_integrationEventLogServiceFactory = integrationEventLogServiceFactory;
((DbContext)Context).ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; ((DbContext)Context).ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
} }
@ -145,51 +145,28 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
{ {
var catalogItem = await _catalogContext.CatalogItems.SingleOrDefaultAsync(i => i.Id == productToUpdate.Id); var catalogItem = await _catalogContext.CatalogItems.SingleOrDefaultAsync(i => i.Id == productToUpdate.Id);
if (catalogItem == null) return NotFound(); if (catalogItem == null) return NotFound();
bool raiseProductPriceChangedEvent = false;
IntegrationEvent priceChangedEvent = null;
if (catalogItem.Price != productToUpdate.Price) raiseProductPriceChangedEvent = true;
if (raiseProductPriceChangedEvent) // Create event if price has changed
{
var oldPrice = catalogItem.Price;
priceChangedEvent = new ProductPriceChangedIntegrationEvent(catalogItem.Id, productToUpdate.Price, oldPrice);
}
//Update current product
var raiseProductPriceChangedEvent = catalogItem.Price != productToUpdate.Price;
var oldPrice = catalogItem.Price;
// Update current product
catalogItem = productToUpdate; catalogItem = productToUpdate;
_catalogContext.CatalogItems.Update(catalogItem);
//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
var strategy = _catalogContext.Database.CreateExecutionStrategy();
var eventLogService = _integrationEventLogServiceFactory(_catalogContext.Database.GetDbConnection());
await strategy.ExecuteAsync(async () =>
if (raiseProductPriceChangedEvent) // Save and publish integration event if price has changed
{ {
//Create Integration Event to be published through the Event Bus
var priceChangedEvent = new ProductPriceChangedIntegrationEvent(catalogItem.Id, productToUpdate.Price, oldPrice);
// Achieving atomicity between original Catalog database operation and the IntegrationEventLog thanks to a local transaction // Achieving atomicity between original Catalog database operation and the IntegrationEventLog thanks to a local transaction
using (var transaction = _catalogContext.Database.BeginTransaction())
{
_catalogContext.CatalogItems.Update(catalogItem);
await _catalogContext.SaveChangesAsync();
//Save to EventLog only if product price changed
if (raiseProductPriceChangedEvent)
{
await eventLogService.SaveEventAsync(priceChangedEvent, _catalogContext.Database.CurrentTransaction.GetDbTransaction());
}
transaction.Commit();
}
});
//Publish to Event Bus only if product price changed
if (raiseProductPriceChangedEvent)
{
_eventBus.Publish(priceChangedEvent);
await eventLogService.MarkEventAsPublishedAsync(priceChangedEvent);
await _catalogIntegrationEventService.SaveEventAndCatalogContextChangesAsync(priceChangedEvent);
// Publish through the Event Bus and mark the saved event as published
await _catalogIntegrationEventService.PublishThroughEventBusAsync(priceChangedEvent);
} }
else // Save updated product
{
await _catalogContext.SaveChangesAsync();
}
return Ok(); return Ok();
} }


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

@ -13,14 +13,12 @@
public DbSet<CatalogItem> CatalogItems { get; set; } public DbSet<CatalogItem> CatalogItems { get; set; }
public DbSet<CatalogBrand> CatalogBrands { get; set; } public DbSet<CatalogBrand> CatalogBrands { get; set; }
public DbSet<CatalogType> CatalogTypes { get; set; } public DbSet<CatalogType> CatalogTypes { get; set; }
//public DbSet<IntegrationEventLogEntry> IntegrationEventLog { get; set; }
protected override void OnModelCreating(ModelBuilder builder) protected override void OnModelCreating(ModelBuilder builder)
{ {
builder.Entity<CatalogBrand>(ConfigureCatalogBrand); builder.Entity<CatalogBrand>(ConfigureCatalogBrand);
builder.Entity<CatalogType>(ConfigureCatalogType); builder.Entity<CatalogType>(ConfigureCatalogType);
builder.Entity<CatalogItem>(ConfigureCatalogItem); builder.Entity<CatalogItem>(ConfigureCatalogItem);
//builder.Entity<IntegrationEventLogEntry>(ConfigureIntegrationEventLogEntry);
} }
void ConfigureCatalogItem(EntityTypeBuilder<CatalogItem> builder) void ConfigureCatalogItem(EntityTypeBuilder<CatalogItem> builder)
@ -79,31 +77,5 @@
.IsRequired() .IsRequired()
.HasMaxLength(100); .HasMaxLength(100);
} }
//void ConfigureIntegrationEventLogEntry(EntityTypeBuilder<IntegrationEventLogEntry> builder)
//{
// builder.ToTable("IntegrationEventLog");
// builder.HasKey(e => e.EventId);
// builder.Property(e => e.EventId)
// .IsRequired();
// builder.Property(e => e.Content)
// .IsRequired();
// builder.Property(e => e.CreationTime)
// .IsRequired();
// builder.Property(e => e.State)
// .IsRequired();
// builder.Property(e => e.TimesSent)
// .IsRequired();
// builder.Property(e => e.EventTypeName)
// .IsRequired();
//}
} }
} }

+ 48
- 0
src/Services/Catalog/Catalog.API/IntegrationEvents/CatalogIntegrationEventService.cs View File

@ -0,0 +1,48 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services;
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Utilities;
using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure;
using System;
using System.Data.Common;
using System.Threading.Tasks;
namespace Catalog.API.IntegrationEvents
{
public class CatalogIntegrationEventService : ICatalogIntegrationEventService
{
private readonly Func<DbConnection, IIntegrationEventLogService> _integrationEventLogServiceFactory;
private readonly IEventBus _eventBus;
private readonly CatalogContext _catalogContext;
private readonly IIntegrationEventLogService _eventLogService;
public CatalogIntegrationEventService(IEventBus eventBus, CatalogContext catalogContext,
Func<DbConnection, IIntegrationEventLogService> integrationEventLogServiceFactory)
{
_catalogContext = catalogContext ?? throw new ArgumentNullException(nameof(catalogContext));
_integrationEventLogServiceFactory = integrationEventLogServiceFactory ?? throw new ArgumentNullException(nameof(integrationEventLogServiceFactory));
_eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus));
_eventLogService = _integrationEventLogServiceFactory(_catalogContext.Database.GetDbConnection());
}
public async Task PublishThroughEventBusAsync(IntegrationEvent evt)
{
_eventBus.Publish(evt);
await _eventLogService.MarkEventAsPublishedAsync(evt);
}
public async Task SaveEventAndCatalogContextChangesAsync(IntegrationEvent evt)
{
//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
await ResilientTransaction.New(_catalogContext)
.ExecuteAsync(async () => {
// Achieving atomicity between original catalog database operation and the IntegrationEventLog thanks to a local transaction
await _catalogContext.SaveChangesAsync();
await _eventLogService.SaveEventAsync(evt, _catalogContext.Database.CurrentTransaction.GetDbTransaction());
});
}
}
}

+ 14
- 0
src/Services/Catalog/Catalog.API/IntegrationEvents/ICatalogIntegrationEventService.cs View File

@ -0,0 +1,14 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Catalog.API.IntegrationEvents
{
public interface ICatalogIntegrationEventService
{
Task SaveEventAndCatalogContextChangesAsync(IntegrationEvent evt);
Task PublishThroughEventBusAsync(IntegrationEvent evt);
}
}

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

@ -20,6 +20,7 @@
using System.IO; using System.IO;
using System.Data.Common; using System.Data.Common;
using System.Reflection; using System.Reflection;
using global::Catalog.API.IntegrationEvents;
using System.Threading.Tasks; using System.Threading.Tasks;
public class Startup public class Startup
@ -49,7 +50,7 @@
services.AddHealthChecks(checks => services.AddHealthChecks(checks =>
{ {
checks.AddSqlCheck("Catalog_Db", Configuration["ConnectionString"]);
checks.AddSqlCheck("CatalogDb", Configuration["ConnectionString"]);
}); });
services.AddMvc(options => services.AddMvc(options =>
@ -98,10 +99,10 @@
}); });
services.AddTransient<Func<DbConnection, IIntegrationEventLogService>>( services.AddTransient<Func<DbConnection, IIntegrationEventLogService>>(
sp => (DbConnection c) => new IntegrationEventLogService(c));
sp => (DbConnection c) => new IntegrationEventLogService(c));
var serviceProvider = services.BuildServiceProvider(); var serviceProvider = services.BuildServiceProvider();
var configuration = serviceProvider.GetRequiredService<IOptionsSnapshot<Settings>>().Value; var configuration = serviceProvider.GetRequiredService<IOptionsSnapshot<Settings>>().Value;
services.AddTransient<ICatalogIntegrationEventService, CatalogIntegrationEventService>();
services.AddSingleton<IEventBus>(new EventBusRabbitMQ(configuration.EventBusConnection)); services.AddSingleton<IEventBus>(new EventBusRabbitMQ(configuration.EventBusConnection));
} }


+ 25
- 25
src/Services/Identity/Identity.API/Configuration/Config.cs View File

@ -1,31 +1,30 @@
using IdentityServer4.Models; using IdentityServer4.Models;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using System.Collections.Generic; using System.Collections.Generic;
using IdentityServer4;
namespace Identity.API.Configuration namespace Identity.API.Configuration
{ {
public class Config public class Config
{ {
// scopes define the resources in your system
public static IEnumerable<Scope> GetScopes()
// ApiResources define the apis in your system
public static IEnumerable<ApiResource> GetApis()
{ {
return new List<Scope>
return new List<ApiResource>
{ {
//Authentication OpenId uses this scopes;
StandardScopes.OpenId,
StandardScopes.Profile,
new ApiResource("orders", "Orders Service"),
new ApiResource("basket", "Basket Service")
};
}
//Each api we want to securice;
new Scope
{
Name = "orders",
Description = "Orders Service"
},
new Scope
{
Name = "basket",
Description = "Basket Service"
}
// Identity resources are data like user ID, name, or email address of a user
// see: http://docs.identityserver.io/en/release/configuration/resources.html
public static IEnumerable<IdentityResource> GetResources()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile()
}; };
} }
@ -47,8 +46,8 @@ namespace Identity.API.Configuration
AllowedCorsOrigins = { $"{clientsUrl["Spa"]}" }, AllowedCorsOrigins = { $"{clientsUrl["Spa"]}" },
AllowedScopes = AllowedScopes =
{ {
StandardScopes.OpenId.Name,
StandardScopes.Profile.Name,
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"orders", "orders",
"basket" "basket"
} }
@ -59,14 +58,14 @@ namespace Identity.API.Configuration
ClientName = "eShop Xamarin OpenId Client", ClientName = "eShop Xamarin OpenId Client",
AllowedGrantTypes = GrantTypes.Implicit, AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true, AllowAccessTokensViaBrowser = true,
RedirectUris = { "http://eshopxamarin/callback.html" },
RedirectUris = { clientsUrl["Xamarin"] },
RequireConsent = false, RequireConsent = false,
PostLogoutRedirectUris = { "http://13.88.8.119:5105/Account/Redirecting", "http://10.6.1.234:5105/Account/Redirecting" }, PostLogoutRedirectUris = { "http://13.88.8.119:5105/Account/Redirecting", "http://10.6.1.234:5105/Account/Redirecting" },
AllowedCorsOrigins = { "http://eshopxamarin" }, AllowedCorsOrigins = { "http://eshopxamarin" },
AllowedScopes = AllowedScopes =
{ {
StandardScopes.OpenId.Name,
StandardScopes.Profile.Name,
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"orders", "orders",
"basket" "basket"
} }
@ -82,6 +81,7 @@ namespace Identity.API.Configuration
ClientUri = $"{clientsUrl["Mvc"]}", // public uri of the client ClientUri = $"{clientsUrl["Mvc"]}", // public uri of the client
AllowedGrantTypes = GrantTypes.Hybrid, AllowedGrantTypes = GrantTypes.Hybrid,
RequireConsent = false, RequireConsent = false,
AllowOfflineAccess = true,
RedirectUris = new List<string> RedirectUris = new List<string>
{ {
$"{clientsUrl["Mvc"]}/signin-oidc", $"{clientsUrl["Mvc"]}/signin-oidc",
@ -96,9 +96,9 @@ namespace Identity.API.Configuration
}, },
AllowedScopes = new List<string> AllowedScopes = new List<string>
{ {
StandardScopes.OpenId.Name,
StandardScopes.Profile.Name,
StandardScopes.OfflineAccess.Name,
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.OfflineAccess,
"orders", "orders",
"basket", "basket",
}, },


+ 0
- 1
src/Services/Identity/Identity.API/Controllers/AccountController.cs View File

@ -5,7 +5,6 @@
using IdentityModel; using IdentityModel;
using IdentityServer4.Quickstart.UI.Models; using IdentityServer4.Quickstart.UI.Models;
using IdentityServer4.Services; using IdentityServer4.Services;
using IdentityServer4.Services.InMemory;
using Microsoft.AspNetCore.Http.Authentication; using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using System; using System;


+ 6
- 6
src/Services/Identity/Identity.API/Controllers/ConsentController.cs View File

@ -22,7 +22,7 @@ namespace IdentityServer4.Quickstart.UI.Controllers
{ {
private readonly ILogger<ConsentController> _logger; private readonly ILogger<ConsentController> _logger;
private readonly IClientStore _clientStore; private readonly IClientStore _clientStore;
private readonly IScopeStore _scopeStore;
private readonly IResourceStore _resourceStore;
private readonly IIdentityServerInteractionService _interaction; private readonly IIdentityServerInteractionService _interaction;
@ -30,12 +30,12 @@ namespace IdentityServer4.Quickstart.UI.Controllers
ILogger<ConsentController> logger, ILogger<ConsentController> logger,
IIdentityServerInteractionService interaction, IIdentityServerInteractionService interaction,
IClientStore clientStore, IClientStore clientStore,
IScopeStore scopeStore)
IResourceStore resourceStore)
{ {
_logger = logger; _logger = logger;
_interaction = interaction; _interaction = interaction;
_clientStore = clientStore; _clientStore = clientStore;
_scopeStore = scopeStore;
_resourceStore = resourceStore;
} }
/// <summary> /// <summary>
@ -120,10 +120,10 @@ namespace IdentityServer4.Quickstart.UI.Controllers
var client = await _clientStore.FindEnabledClientByIdAsync(request.ClientId); var client = await _clientStore.FindEnabledClientByIdAsync(request.ClientId);
if (client != null) if (client != null)
{ {
var scopes = await _scopeStore.FindEnabledScopesAsync(request.ScopesRequested);
if (scopes != null && scopes.Any())
var resources = await _resourceStore.FindEnabledResourcesByScopeAsync(request.ScopesRequested);
if (resources != null && (resources.IdentityResources.Any() || resources.ApiResources.Any()))
{ {
return new ConsentViewModel(model, returnUrl, request, client, scopes);
return new ConsentViewModel(model, returnUrl, request, client, resources);
} }
else else
{ {


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

@ -41,8 +41,8 @@
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="1.1.0-msbuild3-final"> <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="1.1.0-msbuild3-final">
<PrivateAssets>All</PrivateAssets> <PrivateAssets>All</PrivateAssets>
</PackageReference> </PackageReference>
<PackageReference Include="IdentityServer4.AspNetIdentity" Version="1.0.0-rc3" />
<PackageReference Include="IdentityServer4.EntityFramework" Version="1.0.0-rc3" />
<PackageReference Include="IdentityServer4.AspNetIdentity" Version="1.0.0" />
<PackageReference Include="IdentityServer4.EntityFramework" Version="1.0.0" />
</ItemGroup> </ItemGroup>
<Target Name="PrepublishScript" BeforeTargets="PrepareForPublish"> <Target Name="PrepublishScript" BeforeTargets="PrepareForPublish">


+ 13
- 3
src/Services/Identity/Identity.API/Models/AccountViewModels/ConsentViewModel.cs View File

@ -10,7 +10,7 @@ namespace Identity.API.Models.AccountViewModels
{ {
public class ConsentViewModel : ConsentInputModel public class ConsentViewModel : ConsentInputModel
{ {
public ConsentViewModel(ConsentInputModel model, string returnUrl, AuthorizationRequest request, Client client, IEnumerable<Scope> scopes)
public ConsentViewModel(ConsentInputModel model, string returnUrl, AuthorizationRequest request, Client client, Resources resources)
{ {
RememberConsent = model?.RememberConsent ?? true; RememberConsent = model?.RememberConsent ?? true;
ScopesConsented = model?.ScopesConsented ?? Enumerable.Empty<string>(); ScopesConsented = model?.ScopesConsented ?? Enumerable.Empty<string>();
@ -22,8 +22,8 @@ namespace Identity.API.Models.AccountViewModels
ClientLogoUrl = client.LogoUri; ClientLogoUrl = client.LogoUri;
AllowRememberConsent = client.AllowRememberConsent; AllowRememberConsent = client.AllowRememberConsent;
IdentityScopes = scopes.Where(x => x.Type == ScopeType.Identity).Select(x => new ScopeViewModel(x, ScopesConsented.Contains(x.Name) || model == null)).ToArray();
ResourceScopes = scopes.Where(x => x.Type == ScopeType.Resource).Select(x => new ScopeViewModel(x, ScopesConsented.Contains(x.Name) || model == null)).ToArray();
IdentityScopes = resources.IdentityResources.Select(x => new ScopeViewModel(x, ScopesConsented.Contains(x.Name) || model == null)).ToArray();
ResourceScopes = resources.ApiResources.SelectMany(x => x.Scopes).Select(x => new ScopeViewModel(x, ScopesConsented.Contains(x.Name) || model == null)).ToArray();
} }
public string ClientName { get; set; } public string ClientName { get; set; }
@ -47,6 +47,16 @@ namespace Identity.API.Models.AccountViewModels
Checked = check || scope.Required; Checked = check || scope.Required;
} }
public ScopeViewModel(IdentityResource identity, bool check)
{
Name = identity.Name;
DisplayName = identity.DisplayName;
Description = identity.Description;
Emphasize = identity.Emphasize;
Required = identity.Required;
Checked = check || identity.Required;
}
public string Name { get; set; } public string Name { get; set; }
public string DisplayName { get; set; } public string DisplayName { get; set; }
public string Description { get; set; } public string Description { get; set; }


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

@ -73,11 +73,13 @@ namespace eShopOnContainers.Identity
Dictionary<string, string> clientUrls = new Dictionary<string, string>(); Dictionary<string, string> clientUrls = new Dictionary<string, string>();
clientUrls.Add("Mvc", Configuration.GetValue<string>("MvcClient")); clientUrls.Add("Mvc", Configuration.GetValue<string>("MvcClient"));
clientUrls.Add("Spa", Configuration.GetValue<string>("SpaClient")); clientUrls.Add("Spa", Configuration.GetValue<string>("SpaClient"));
clientUrls.Add("Xamarin", Configuration.GetValue<string>("XamarinCallback"));
// Adds IdentityServer // Adds IdentityServer
services.AddIdentityServer(x => x.IssuerUri = "null") services.AddIdentityServer(x => x.IssuerUri = "null")
.AddSigningCredential(Certificate.Get()) .AddSigningCredential(Certificate.Get())
.AddInMemoryScopes(Config.GetScopes())
.AddInMemoryApiResources(Config.GetApis())
.AddInMemoryIdentityResources(Config.GetResources())
.AddInMemoryClients(Config.GetClients(clientUrls)) .AddInMemoryClients(Config.GetClients(clientUrls))
.AddAspNetIdentity<ApplicationUser>() .AddAspNetIdentity<ApplicationUser>()
.Services.AddTransient<IProfileService, ProfileService>(); .Services.AddTransient<IProfileService, ProfileService>();


+ 1
- 0
src/Services/Identity/Identity.API/appsettings.json View File

@ -4,6 +4,7 @@
}, },
"MvcClient": "http://localhost:5100", "MvcClient": "http://localhost:5100",
"SpaClient": "http://localhost:5104", "SpaClient": "http://localhost:5104",
"XamarinCallback": "http://localhost:5105/xamarincallback",
"Logging": { "Logging": {
"IncludeScopes": false, "IncludeScopes": false,
"LogLevel": { "LogLevel": {


+ 23
- 8
src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/BuyerAndPaymentMethodVerified/UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler.cs View File

@ -1,6 +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.Domain.Events; using Ordering.Domain.Events;
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -11,10 +13,15 @@ namespace Ordering.API.Application.DomainEventHandlers.BuyerAndPaymentMethodVeri
: IAsyncNotificationHandler<BuyerAndPaymentMethodVerifiedDomainEvent> : IAsyncNotificationHandler<BuyerAndPaymentMethodVerifiedDomainEvent>
{ {
private readonly IOrderRepository _orderRepository; private readonly IOrderRepository _orderRepository;
private readonly ILoggerFactory _logger;
public UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler(IOrderRepository orderRepository, ILoggerFactory logger)
private readonly IOrderingIntegrationEventService _orderingIntegrationEventService;
private readonly ILoggerFactory _logger;
public UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler(
IOrderRepository orderRepository, ILoggerFactory logger,
IOrderingIntegrationEventService orderingIntegrationEventService)
{ {
_orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository)); _orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository));
_orderingIntegrationEventService = orderingIntegrationEventService ?? throw new ArgumentNullException(nameof(orderingIntegrationEventService));
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); _logger = logger ?? throw new ArgumentNullException(nameof(logger));
} }
@ -26,12 +33,20 @@ 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); orderToUpdate.SetPaymentId(buyerPaymentMethodVerifiedEvent.Payment.Id);
await _orderRepository.UnitOfWork
.SaveEntitiesAsync();
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);
_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/Infrastructure/IntegrationEventMigrations/20170330131634_IntegrationEventInitial.Designer.cs View File

@ -0,0 +1,43 @@
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF;
namespace Ordering.API.Infrastructure.IntegrationEventMigrations
{
[DbContext(typeof(IntegrationEventLogContext))]
[Migration("20170330131634_IntegrationEventInitial")]
partial class IntegrationEventInitial
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
modelBuilder
.HasAnnotation("ProductVersion", "1.1.1")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.IntegrationEventLogEntry", b =>
{
b.Property<Guid>("EventId")
.ValueGeneratedOnAdd();
b.Property<string>("Content")
.IsRequired();
b.Property<DateTime>("CreationTime");
b.Property<string>("EventTypeName")
.IsRequired();
b.Property<int>("State");
b.Property<int>("TimesSent");
b.HasKey("EventId");
b.ToTable("IntegrationEventLog");
});
}
}
}

+ 34
- 0
src/Services/Ordering/Ordering.API/Infrastructure/IntegrationEventMigrations/20170330131634_IntegrationEventInitial.cs View File

@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Ordering.API.Infrastructure.IntegrationEventMigrations
{
public partial class IntegrationEventInitial : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "IntegrationEventLog",
columns: table => new
{
EventId = table.Column<Guid>(nullable: false),
Content = table.Column<string>(nullable: false),
CreationTime = table.Column<DateTime>(nullable: false),
EventTypeName = table.Column<string>(nullable: false),
State = table.Column<int>(nullable: false),
TimesSent = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_IntegrationEventLog", x => x.EventId);
});
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "IntegrationEventLog");
}
}
}

+ 42
- 0
src/Services/Ordering/Ordering.API/Infrastructure/IntegrationEventMigrations/IntegrationEventLogContextModelSnapshot.cs View File

@ -0,0 +1,42 @@
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF;
namespace Ordering.API.Infrastructure.IntegrationEventMigrations
{
[DbContext(typeof(IntegrationEventLogContext))]
partial class IntegrationEventLogContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
modelBuilder
.HasAnnotation("ProductVersion", "1.1.1")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.IntegrationEventLogEntry", b =>
{
b.Property<Guid>("EventId")
.ValueGeneratedOnAdd();
b.Property<string>("Content")
.IsRequired();
b.Property<DateTime>("CreationTime");
b.Property<string>("EventTypeName")
.IsRequired();
b.Property<int>("State");
b.Property<int>("TimesSent");
b.HasKey("EventId");
b.ToTable("IntegrationEventLog");
});
}
}
}

+ 244
- 0
src/Services/Ordering/Ordering.API/Infrastructure/Migrations/20170403082405_NoBuyerPropertyInOrder.Designer.cs View File

@ -0,0 +1,244 @@
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure;
namespace Ordering.API.Migrations
{
[DbContext(typeof(OrderingContext))]
[Migration("20170403082405_NoBuyerPropertyInOrder")]
partial class NoBuyerPropertyInOrder
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
modelBuilder
.HasAnnotation("ProductVersion", "1.1.0-rtm-22752")
.HasAnnotation("SqlServer:Sequence:.orderitemseq", "'orderitemseq', '', '1', '10', '', '', 'Int64', 'False'")
.HasAnnotation("SqlServer:Sequence:ordering.buyerseq", "'buyerseq', 'ordering', '1', '10', '', '', 'Int64', 'False'")
.HasAnnotation("SqlServer:Sequence:ordering.orderseq", "'orderseq', 'ordering', '1', '10', '', '', 'Int64', 'False'")
.HasAnnotation("SqlServer:Sequence:ordering.paymentseq", "'paymentseq', 'ordering', '1', '10', '', '', 'Int64', 'False'")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.Buyer", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:HiLoSequenceName", "buyerseq")
.HasAnnotation("SqlServer:HiLoSequenceSchema", "ordering")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
b.Property<string>("IdentityGuid")
.IsRequired()
.HasMaxLength(200);
b.HasKey("Id");
b.HasIndex("IdentityGuid")
.IsUnique();
b.ToTable("buyers","ordering");
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.CardType", b =>
{
b.Property<int>("Id")
.HasDefaultValue(1);
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(200);
b.HasKey("Id");
b.ToTable("cardtypes","ordering");
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.PaymentMethod", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:HiLoSequenceName", "paymentseq")
.HasAnnotation("SqlServer:HiLoSequenceSchema", "ordering")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
b.Property<string>("Alias")
.IsRequired()
.HasMaxLength(200);
b.Property<int>("BuyerId");
b.Property<string>("CardHolderName")
.IsRequired()
.HasMaxLength(200);
b.Property<string>("CardNumber")
.IsRequired()
.HasMaxLength(25);
b.Property<int>("CardTypeId");
b.Property<DateTime>("Expiration");
b.HasKey("Id");
b.HasIndex("BuyerId");
b.HasIndex("CardTypeId");
b.ToTable("paymentmethods","ordering");
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Address", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("City");
b.Property<string>("Country");
b.Property<string>("State");
b.Property<string>("Street");
b.Property<string>("ZipCode");
b.HasKey("Id");
b.ToTable("address","ordering");
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Order", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:HiLoSequenceName", "orderseq")
.HasAnnotation("SqlServer:HiLoSequenceSchema", "ordering")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
b.Property<int?>("AddressId");
b.Property<int?>("BuyerId");
b.Property<DateTime>("OrderDate");
b.Property<int>("OrderStatusId");
b.Property<int?>("PaymentMethodId");
b.HasKey("Id");
b.HasIndex("AddressId");
b.HasIndex("BuyerId");
b.HasIndex("OrderStatusId");
b.HasIndex("PaymentMethodId");
b.ToTable("orders","ordering");
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.OrderItem", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:HiLoSequenceName", "orderitemseq")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
b.Property<decimal>("Discount");
b.Property<int>("OrderId");
b.Property<string>("PictureUrl");
b.Property<int>("ProductId");
b.Property<string>("ProductName")
.IsRequired();
b.Property<decimal>("UnitPrice");
b.Property<int>("Units");
b.HasKey("Id");
b.HasIndex("OrderId");
b.ToTable("orderItems","ordering");
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.OrderStatus", b =>
{
b.Property<int>("Id")
.HasDefaultValue(1);
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(200);
b.HasKey("Id");
b.ToTable("orderstatus","ordering");
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.ClientRequest", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Name")
.IsRequired();
b.Property<DateTime>("Time");
b.HasKey("Id");
b.ToTable("requests","ordering");
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.PaymentMethod", b =>
{
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.Buyer")
.WithMany("PaymentMethods")
.HasForeignKey("BuyerId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.CardType", "CardType")
.WithMany()
.HasForeignKey("CardTypeId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Order", b =>
{
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Address", "Address")
.WithMany()
.HasForeignKey("AddressId");
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.Buyer")
.WithMany()
.HasForeignKey("BuyerId");
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.OrderStatus", "OrderStatus")
.WithMany()
.HasForeignKey("OrderStatusId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.PaymentMethod", "PaymentMethod")
.WithMany()
.HasForeignKey("PaymentMethodId");
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.OrderItem", b =>
{
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Order")
.WithMany("OrderItems")
.HasForeignKey("OrderId")
.OnDelete(DeleteBehavior.Cascade);
});
}
}
}

+ 19
- 0
src/Services/Ordering/Ordering.API/Infrastructure/Migrations/20170403082405_NoBuyerPropertyInOrder.cs View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Ordering.API.Migrations
{
public partial class NoBuyerPropertyInOrder : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
}
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}

+ 244
- 0
src/Services/Ordering/Ordering.API/Infrastructure/Migrations/20170405110939_NoPaymentMethodPropertyInOrder.Designer.cs View File

@ -0,0 +1,244 @@
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure;
namespace Ordering.API.Migrations
{
[DbContext(typeof(OrderingContext))]
[Migration("20170405110939_NoPaymentMethodPropertyInOrder")]
partial class NoPaymentMethodPropertyInOrder
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
modelBuilder
.HasAnnotation("ProductVersion", "1.1.1")
.HasAnnotation("SqlServer:Sequence:.orderitemseq", "'orderitemseq', '', '1', '10', '', '', 'Int64', 'False'")
.HasAnnotation("SqlServer:Sequence:ordering.buyerseq", "'buyerseq', 'ordering', '1', '10', '', '', 'Int64', 'False'")
.HasAnnotation("SqlServer:Sequence:ordering.orderseq", "'orderseq', 'ordering', '1', '10', '', '', 'Int64', 'False'")
.HasAnnotation("SqlServer:Sequence:ordering.paymentseq", "'paymentseq', 'ordering', '1', '10', '', '', 'Int64', 'False'")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.Buyer", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:HiLoSequenceName", "buyerseq")
.HasAnnotation("SqlServer:HiLoSequenceSchema", "ordering")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
b.Property<string>("IdentityGuid")
.IsRequired()
.HasMaxLength(200);
b.HasKey("Id");
b.HasIndex("IdentityGuid")
.IsUnique();
b.ToTable("buyers","ordering");
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.CardType", b =>
{
b.Property<int>("Id")
.HasDefaultValue(1);
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(200);
b.HasKey("Id");
b.ToTable("cardtypes","ordering");
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.PaymentMethod", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:HiLoSequenceName", "paymentseq")
.HasAnnotation("SqlServer:HiLoSequenceSchema", "ordering")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
b.Property<string>("Alias")
.IsRequired()
.HasMaxLength(200);
b.Property<int>("BuyerId");
b.Property<string>("CardHolderName")
.IsRequired()
.HasMaxLength(200);
b.Property<string>("CardNumber")
.IsRequired()
.HasMaxLength(25);
b.Property<int>("CardTypeId");
b.Property<DateTime>("Expiration");
b.HasKey("Id");
b.HasIndex("BuyerId");
b.HasIndex("CardTypeId");
b.ToTable("paymentmethods","ordering");
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Address", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("City");
b.Property<string>("Country");
b.Property<string>("State");
b.Property<string>("Street");
b.Property<string>("ZipCode");
b.HasKey("Id");
b.ToTable("address","ordering");
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Order", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:HiLoSequenceName", "orderseq")
.HasAnnotation("SqlServer:HiLoSequenceSchema", "ordering")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
b.Property<int?>("AddressId");
b.Property<int?>("BuyerId");
b.Property<DateTime>("OrderDate");
b.Property<int>("OrderStatusId");
b.Property<int?>("PaymentMethodId");
b.HasKey("Id");
b.HasIndex("AddressId");
b.HasIndex("BuyerId");
b.HasIndex("OrderStatusId");
b.HasIndex("PaymentMethodId");
b.ToTable("orders","ordering");
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.OrderItem", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:HiLoSequenceName", "orderitemseq")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
b.Property<decimal>("Discount");
b.Property<int>("OrderId");
b.Property<string>("PictureUrl");
b.Property<int>("ProductId");
b.Property<string>("ProductName")
.IsRequired();
b.Property<decimal>("UnitPrice");
b.Property<int>("Units");
b.HasKey("Id");
b.HasIndex("OrderId");
b.ToTable("orderItems","ordering");
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.OrderStatus", b =>
{
b.Property<int>("Id")
.HasDefaultValue(1);
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(200);
b.HasKey("Id");
b.ToTable("orderstatus","ordering");
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.ClientRequest", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Name")
.IsRequired();
b.Property<DateTime>("Time");
b.HasKey("Id");
b.ToTable("requests","ordering");
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.PaymentMethod", b =>
{
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.Buyer")
.WithMany("PaymentMethods")
.HasForeignKey("BuyerId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.CardType", "CardType")
.WithMany()
.HasForeignKey("CardTypeId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Order", b =>
{
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Address", "Address")
.WithMany()
.HasForeignKey("AddressId");
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.Buyer")
.WithMany()
.HasForeignKey("BuyerId");
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.OrderStatus", "OrderStatus")
.WithMany()
.HasForeignKey("OrderStatusId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.PaymentMethod")
.WithMany()
.HasForeignKey("PaymentMethodId");
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.OrderItem", b =>
{
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Order")
.WithMany("OrderItems")
.HasForeignKey("OrderId")
.OnDelete(DeleteBehavior.Cascade);
});
}
}
}

+ 19
- 0
src/Services/Ordering/Ordering.API/Infrastructure/Migrations/20170405110939_NoPaymentMethodPropertyInOrder.cs View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Ordering.API.Migrations
{
public partial class NoPaymentMethodPropertyInOrder : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
}
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}

+ 3
- 3
src/Services/Ordering/Ordering.API/Infrastructure/Migrations/OrderingContextModelSnapshot.cs View File

@ -13,7 +13,7 @@ namespace Ordering.API.Migrations
protected override void BuildModel(ModelBuilder modelBuilder) protected override void BuildModel(ModelBuilder modelBuilder)
{ {
modelBuilder modelBuilder
.HasAnnotation("ProductVersion", "1.1.0-rtm-22752")
.HasAnnotation("ProductVersion", "1.1.1")
.HasAnnotation("SqlServer:Sequence:.orderitemseq", "'orderitemseq', '', '1', '10', '', '', 'Int64', 'False'") .HasAnnotation("SqlServer:Sequence:.orderitemseq", "'orderitemseq', '', '1', '10', '', '', 'Int64', 'False'")
.HasAnnotation("SqlServer:Sequence:ordering.buyerseq", "'buyerseq', 'ordering', '1', '10', '', '', 'Int64', 'False'") .HasAnnotation("SqlServer:Sequence:ordering.buyerseq", "'buyerseq', 'ordering', '1', '10', '', '', 'Int64', 'False'")
.HasAnnotation("SqlServer:Sequence:ordering.orderseq", "'orderseq', 'ordering', '1', '10', '', '', 'Int64', 'False'") .HasAnnotation("SqlServer:Sequence:ordering.orderseq", "'orderseq', 'ordering', '1', '10', '', '', 'Int64', 'False'")
@ -217,7 +217,7 @@ namespace Ordering.API.Migrations
.WithMany() .WithMany()
.HasForeignKey("AddressId"); .HasForeignKey("AddressId");
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.Buyer", "Buyer")
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.Buyer")
.WithMany() .WithMany()
.HasForeignKey("BuyerId"); .HasForeignKey("BuyerId");
@ -226,7 +226,7 @@ namespace Ordering.API.Migrations
.HasForeignKey("OrderStatusId") .HasForeignKey("OrderStatusId")
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.Cascade);
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.PaymentMethod", "PaymentMethod")
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.PaymentMethod")
.WithMany() .WithMany()
.HasForeignKey("PaymentMethodId"); .HasForeignKey("PaymentMethodId");
}); });


+ 19
- 0
src/Services/Ordering/Ordering.API/IntegrationEvents/Events/OrderStartedIntegrationEvent.cs View File

@ -0,0 +1,19 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Ordering.API.IntegrationEvents.Events
{
// Integration Events notes:
// An Event is “something that has happened in the past”, therefore its name has to be
// An Integration Event is an event that can cause side effects to other microsrvices, Bounded-Contexts or external systems.
public class OrderStartedIntegrationEvent : IntegrationEvent
{
public string UserId { get; }
public OrderStartedIntegrationEvent(string userId) =>
UserId = userId;
}
}

+ 14
- 0
src/Services/Ordering/Ordering.API/IntegrationEvents/IOrderingIntegrationEventService.cs View File

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

+ 49
- 0
src/Services/Ordering/Ordering.API/IntegrationEvents/OrderingIntegrationEventService.cs View File

@ -0,0 +1,49 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services;
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Utilities;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure;
using System;
using System.Data.Common;
using System.Threading.Tasks;
namespace Ordering.API.IntegrationEvents
{
public class OrderingIntegrationEventService : IOrderingIntegrationEventService
{
private readonly Func<DbConnection, IIntegrationEventLogService> _integrationEventLogServiceFactory;
private readonly IEventBus _eventBus;
private readonly OrderingContext _orderingContext;
private readonly IIntegrationEventLogService _eventLogService;
public OrderingIntegrationEventService (IEventBus eventBus, OrderingContext orderingContext,
Func<DbConnection, IIntegrationEventLogService> integrationEventLogServiceFactory)
{
_orderingContext = orderingContext ?? throw new ArgumentNullException(nameof(orderingContext));
_integrationEventLogServiceFactory = integrationEventLogServiceFactory ?? throw new ArgumentNullException(nameof(integrationEventLogServiceFactory));
_eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus));
_eventLogService = _integrationEventLogServiceFactory(_orderingContext.Database.GetDbConnection());
}
public async Task PublishThroughEventBusAsync(IntegrationEvent evt)
{
_eventBus.Publish(evt);
await _eventLogService.MarkEventAsPublishedAsync(evt);
}
public async Task SaveEventAndOrderingContextChangesAsync(IntegrationEvent evt)
{
//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
await ResilientTransaction.New(_orderingContext)
.ExecuteAsync(async () => {
// Achieving atomicity between original ordering database operation and the IntegrationEventLog thanks to a local transaction
await _orderingContext.SaveChangesAsync();
await _eventLogService.SaveEventAsync(evt, _orderingContext.Database.CurrentTransaction.GetDbTransaction());
});
}
}
}

+ 13
- 3
src/Services/Ordering/Ordering.API/Ordering.API.csproj View File

@ -23,6 +23,9 @@
</ItemGroup> </ItemGroup>
<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="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.AspNetCore.HealthChecks\Microsoft.AspNetCore.HealthChecks.csproj" /> <ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.AspNetCore.HealthChecks\Microsoft.AspNetCore.HealthChecks.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.Data\Microsoft.Extensions.HealthChecks.Data.csproj" /> <ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.Data\Microsoft.Extensions.HealthChecks.Data.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks\Microsoft.Extensions.HealthChecks.csproj" /> <ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks\Microsoft.Extensions.HealthChecks.csproj" />
@ -38,16 +41,18 @@
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.0" /> <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="1.1.0" /> <PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="1.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="1.0.2" /> <PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="1.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="1.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="1.1.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="1.1.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="1.1.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="1.1.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="1.1.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="1.1.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="1.1.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="1.1.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="1.1.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="1.1.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="1.1.0" /> <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="1.1.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.1.0" /> <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.1.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="1.1.0" /> <PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="1.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="1.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="1.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="1.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="1.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.Design" Version="1.1.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.Design" Version="1.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="1.1.0" /> <PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="1.1.0" />
@ -73,4 +78,9 @@
</None> </None>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Infrastructure\IntegrationEventMigrations\" />
<Folder Include="IntegrationEvents\EventHandling\" />
</ItemGroup>
</Project> </Project>

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

@ -4,6 +4,7 @@
using Autofac; using Autofac;
using Autofac.Extensions.DependencyInjection; using Autofac.Extensions.DependencyInjection;
using global::Ordering.API.Infrastructure.Middlewares; using global::Ordering.API.Infrastructure.Middlewares;
using global::Ordering.API.IntegrationEvents;
using Infrastructure; using Infrastructure;
using Infrastructure.Auth; using Infrastructure.Auth;
using Infrastructure.AutofacModules; using Infrastructure.AutofacModules;
@ -12,12 +13,17 @@
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ;
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF;
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.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;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Ordering.Infrastructure; using Ordering.Infrastructure;
using System; using System;
using System.Data.Common;
using System.Reflection; using System.Reflection;
public class Startup public class Startup
@ -53,9 +59,9 @@
services.AddHealthChecks(checks => services.AddHealthChecks(checks =>
{ {
checks.AddSqlCheck("Ordering_Db", Configuration["ConnectionString"]);
checks.AddSqlCheck("OrderingDb", Configuration["ConnectionString"]);
}); });
services.AddEntityFrameworkSqlServer() services.AddEntityFrameworkSqlServer()
.AddDbContext<OrderingContext>(options => .AddDbContext<OrderingContext>(options =>
{ {
@ -95,7 +101,11 @@
// Add application services. // Add application services.
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddTransient<IIdentityService, IdentityService>(); services.AddTransient<IIdentityService, IdentityService>();
services.AddTransient<Func<DbConnection, IIntegrationEventLogService>>(
sp => (DbConnection c) => new IntegrationEventLogService(c));
var serviceProvider = services.BuildServiceProvider();
services.AddTransient<IOrderingIntegrationEventService, OrderingIntegrationEventService>();
services.AddSingleton<IEventBus>(new EventBusRabbitMQ(Configuration["EventBusConnection"]));
services.AddOptions(); services.AddOptions();
//configure autofac //configure autofac
@ -126,6 +136,12 @@
.UseSwaggerUi(); .UseSwaggerUi();
OrderingContextSeed.SeedAsync(app).Wait(); OrderingContextSeed.SeedAsync(app).Wait();
var integrationEventLogContext = new IntegrationEventLogContext(
new DbContextOptionsBuilder<IntegrationEventLogContext>()
.UseSqlServer(Configuration["ConnectionString"], b => b.MigrationsAssembly("Ordering.API"))
.Options);
integrationEventLogContext.Database.Migrate();
} }
protected virtual void ConfigureAuth(IApplicationBuilder app) protected virtual void ConfigureAuth(IApplicationBuilder app)


+ 0
- 1
src/Services/Ordering/Ordering.Domain/AggregatesModel/BuyerAggregate/PaymentMethod.cs View File

@ -7,7 +7,6 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.B
public class PaymentMethod public class PaymentMethod
: Entity : Entity
{ {
private int _buyerId;
private string _alias; private string _alias;
private string _cardNumber; private string _cardNumber;
private string _securityNumber; private string _securityNumber;


+ 0
- 2
src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs View File

@ -18,7 +18,6 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O
public Address Address { get; private set; } public Address Address { get; private set; }
public Buyer Buyer { get; private set; }
private int? _buyerId; private int? _buyerId;
public OrderStatus OrderStatus { get; private set; } public OrderStatus OrderStatus { get; private set; }
@ -37,7 +36,6 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O
// It's much cheaper than .ToList() because it will not have to copy all items in a new collection. (Just one heap alloc for the wrapper instance) // It's much cheaper than .ToList() because it will not have to copy all items in a new collection. (Just one heap alloc for the wrapper instance)
//https://msdn.microsoft.com/en-us/library/e78dcd75(v=vs.110).aspx //https://msdn.microsoft.com/en-us/library/e78dcd75(v=vs.110).aspx
public PaymentMethod PaymentMethod { get; private set; }
private int? _paymentMethodId; private int? _paymentMethodId;
protected Order() { } protected Order() { }


+ 0
- 1
src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/OrderItem.cs View File

@ -12,7 +12,6 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O
// aligned with DDD Aggregates and Domain Entities (Instead of properties and property collections) // aligned with DDD Aggregates and Domain Entities (Instead of properties and property collections)
private string _productName; private string _productName;
private string _pictureUrl; private string _pictureUrl;
private int _orderId;
private decimal _unitPrice; private decimal _unitPrice;
private decimal _discount; private decimal _discount;
private int _units; private int _units;


+ 3
- 2
src/Services/Ordering/Ordering.Infrastructure/Ordering.Infrastructure.csproj View File

@ -16,8 +16,9 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="1.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="1.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="1.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="1.1.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>

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

@ -62,6 +62,10 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
{ {
addressConfiguration.ToTable("address", DEFAULT_SCHEMA); addressConfiguration.ToTable("address", DEFAULT_SCHEMA);
// DDD Pattern comment: Implementing the Address Id as "Shadow property"
// becuase the Address is a Value-Object (VO) and an Id (Identity) is not desired for a VO
// EF Core just needs the Id so it is capable to store it in a database table
// See: https://docs.microsoft.com/en-us/ef/core/modeling/shadow-properties
addressConfiguration.Property<int>("Id") addressConfiguration.Property<int>("Id")
.IsRequired(); .IsRequired();
@ -154,13 +158,13 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
//Set as Field (New since EF 1.1) to access the OrderItem collection property through its field //Set as Field (New since EF 1.1) to access the OrderItem collection property through its field
navigation.SetPropertyAccessMode(PropertyAccessMode.Field); navigation.SetPropertyAccessMode(PropertyAccessMode.Field);
orderConfiguration.HasOne(o => o.PaymentMethod)
orderConfiguration.HasOne<PaymentMethod>()
.WithMany() .WithMany()
.HasForeignKey("PaymentMethodId") .HasForeignKey("PaymentMethodId")
.IsRequired(false) .IsRequired(false)
.OnDelete(DeleteBehavior.Restrict); .OnDelete(DeleteBehavior.Restrict);
orderConfiguration.HasOne(o => o.Buyer)
orderConfiguration.HasOne<Buyer>()
.WithMany() .WithMany()
.IsRequired(false) .IsRequired(false)
.HasForeignKey("BuyerId"); .HasForeignKey("BuyerId");


+ 1
- 4
src/Web/WebMVC/Controllers/OrderController.cs View File

@ -45,14 +45,11 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers
var user = _appUserParser.Parse(HttpContext.User); var user = _appUserParser.Parse(HttpContext.User);
await _orderSvc.CreateOrder(model); await _orderSvc.CreateOrder(model);
//Empty basket for current user.
await _basketSvc.CleanBasket(user);
//Redirect to historic list. //Redirect to historic list.
return RedirectToAction("Index"); return RedirectToAction("Index");
} }
} }
catch(BrokenCircuitException ex)
catch(BrokenCircuitException)
{ {
ModelState.AddModelError("Error", "It was not possible to create a new order, please try later on"); ModelState.AddModelError("Error", "It was not possible to create a new order, please try later on");
} }


+ 10
- 0
src/Web/WebMVC/Infrastructure/IResilientHttpClientFactory.cs View File

@ -0,0 +1,10 @@
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http;
using System;
namespace Microsoft.eShopOnContainers.WebMVC.Infrastructure
{
public interface IResilientHttpClientFactory
{
ResilientHttpClient CreateResilientHttpClient();
}
}

+ 59
- 0
src/Web/WebMVC/Infrastructure/ResilientHttpClientFactory.cs View File

@ -0,0 +1,59 @@
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Polly;
using System.Net.Http;
namespace Microsoft.eShopOnContainers.WebMVC.Infrastructure
{
public class ResilientHttpClientFactory : IResilientHttpClientFactory
{
private readonly ILogger<ResilientHttpClient> _logger;
public ResilientHttpClientFactory(ILogger<ResilientHttpClient> logger)
=>_logger = logger;
public ResilientHttpClient CreateResilientHttpClient()
=> new ResilientHttpClient(CreatePolicies(), _logger);
private Policy[] CreatePolicies()
=> new Policy[]
{
Policy.Handle<HttpRequestException>()
.WaitAndRetryAsync(
// number of retries
6,
// exponential backofff
retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
// on retry
(exception, timeSpan, retryCount, context) =>
{
var msg = $"Retry {retryCount} implemented with Polly's RetryPolicy " +
$"of {context.PolicyKey} " +
$"at {context.ExecutionKey}, " +
$"due to: {exception}.";
_logger.LogWarning(msg);
_logger.LogDebug(msg);
}),
Policy.Handle<HttpRequestException>()
.CircuitBreakerAsync(
// number of exceptions before breaking circuit
5,
// time circuit opened before retry
TimeSpan.FromMinutes(1),
(exception, duration) =>
{
// on circuit opened
_logger.LogTrace("Circuit breaker opened");
},
() =>
{
// on circuit closed
_logger.LogTrace("Circuit breaker reset");
})};
}
}

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

@ -1,5 +1,6 @@
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http;
using Microsoft.eShopOnContainers.WebMVC.ViewModels; using Microsoft.eShopOnContainers.WebMVC.ViewModels;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -7,7 +8,6 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using WebMVC.Services.Utilities;
namespace Microsoft.eShopOnContainers.WebMVC.Services namespace Microsoft.eShopOnContainers.WebMVC.Services
{ {


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

@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http;
using Microsoft.eShopOnContainers.WebMVC.ViewModels; using Microsoft.eShopOnContainers.WebMVC.ViewModels;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
@ -6,7 +7,6 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using WebMVC.Services.Utilities;
namespace Microsoft.eShopOnContainers.WebMVC.Services namespace Microsoft.eShopOnContainers.WebMVC.Services
{ {


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

@ -8,7 +8,7 @@ using Microsoft.Extensions.Options;
using System.Net.Http; using System.Net.Http;
using Newtonsoft.Json; using Newtonsoft.Json;
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
using WebMVC.Services.Utilities;
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http;
namespace Microsoft.eShopOnContainers.WebMVC.Services namespace Microsoft.eShopOnContainers.WebMVC.Services
{ {


+ 8
- 11
src/Web/WebMVC/Startup.cs View File

@ -14,8 +14,9 @@ using Microsoft.IdentityModel.Tokens;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using System.Threading; using System.Threading;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using WebMVC.Services.Utilities;
using Microsoft.Extensions.HealthChecks; using Microsoft.Extensions.HealthChecks;
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http;
using Microsoft.eShopOnContainers.WebMVC.Infrastructure;
namespace Microsoft.eShopOnContainers.WebMVC namespace Microsoft.eShopOnContainers.WebMVC
{ {
@ -61,15 +62,16 @@ namespace Microsoft.eShopOnContainers.WebMVC
services.AddTransient<IBasketService, BasketService>(); services.AddTransient<IBasketService, BasketService>();
services.AddTransient<IIdentityParser<ApplicationUser>, IdentityParser>(); services.AddTransient<IIdentityParser<ApplicationUser>, IdentityParser>();
if(Configuration.GetValue<string>("ActivateCircuitBreaker") == bool.TrueString)
if (Configuration.GetValue<string>("UseResilientHttp") == bool.TrueString)
{ {
services.AddTransient<IHttpClient, ResilientHttpClient>();
services.AddTransient<IResilientHttpClientFactory, ResilientHttpClientFactory>();
services.AddTransient<IHttpClient, ResilientHttpClient>(sp => sp.GetService<IResilientHttpClientFactory>().CreateResilientHttpClient());
} }
else else
{ {
services.AddTransient<IHttpClient, StandardHttpClient>(); services.AddTransient<IHttpClient, StandardHttpClient>();
} }
}
}
// 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.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
@ -112,15 +114,10 @@ namespace Microsoft.eShopOnContainers.WebMVC
ResponseType = "code id_token", ResponseType = "code id_token",
SaveTokens = true, SaveTokens = true,
GetClaimsFromUserInfoEndpoint = true, GetClaimsFromUserInfoEndpoint = true,
RequireHttpsMetadata = false,
RequireHttpsMetadata = false,
Scope = { "openid", "profile", "orders", "basket" }
}; };
oidcOptions.Scope.Clear();
oidcOptions.Scope.Add("openid");
oidcOptions.Scope.Add("profile");
oidcOptions.Scope.Add("orders");
oidcOptions.Scope.Add("basket");
//Wait untill identity service is ready on compose. //Wait untill identity service is ready on compose.
app.UseOpenIdConnectAuthentication(oidcOptions); app.UseOpenIdConnectAuthentication(oidcOptions);


+ 1
- 1
src/Web/WebMVC/Views/Shared/Components/CartList/Default.cshtml View File

@ -38,7 +38,7 @@
<div class="esh-basket-items--border row"> <div class="esh-basket-items--border row">
@if (item.OldUnitPrice != 0) @if (item.OldUnitPrice != 0)
{ {
<div class="alert alert-warning esh-basket-margin12" role="alert">&nbsp;Note that the price of this article changed in our Catalog. The old price when you originally added it to the basket was @item.OldUnitPrice $</div>
<div class="alert alert-warning esh-basket-margin12" role="alert">&nbsp;Note that the price of this article changed in our Catalog. The old price when you originally added it to the basket was $ @item.OldUnitPrice </div>
} }
</div> </div>
<br/> <br/>


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

@ -13,6 +13,13 @@
<DockerComposeProjectPath>..\..\..\docker-compose.dcproj</DockerComposeProjectPath> <DockerComposeProjectPath>..\..\..\docker-compose.dcproj</DockerComposeProjectPath>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<Compile Remove="wwwroot\lib\bootstrap\**" />
<Content Remove="wwwroot\lib\bootstrap\**" />
<EmbeddedResource Remove="wwwroot\lib\bootstrap\**" />
<None Remove="wwwroot\lib\bootstrap\**" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.Cookies" Version="1.1.0" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.Cookies" Version="1.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="1.1.0" /> <PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="1.1.0" />
@ -32,7 +39,6 @@
<PrivateAssets>All</PrivateAssets> <PrivateAssets>All</PrivateAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
<PackageReference Include="Polly" Version="5.0.6" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="5.1.0" /> <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="5.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="1.1.0" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="1.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="1.1.0" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="1.1.0" />
@ -55,6 +61,7 @@
<ProjectReference Include="..\..\BuildingBlocks\HealthChecks\src\Microsoft.AspNetCore.HealthChecks\Microsoft.AspNetCore.HealthChecks.csproj" /> <ProjectReference Include="..\..\BuildingBlocks\HealthChecks\src\Microsoft.AspNetCore.HealthChecks\Microsoft.AspNetCore.HealthChecks.csproj" />
<ProjectReference Include="..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.Data\Microsoft.Extensions.HealthChecks.Data.csproj" /> <ProjectReference Include="..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.Data\Microsoft.Extensions.HealthChecks.Data.csproj" />
<ProjectReference Include="..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks\Microsoft.Extensions.HealthChecks.csproj" /> <ProjectReference Include="..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks\Microsoft.Extensions.HealthChecks.csproj" />
<ProjectReference Include="..\..\BuildingBlocks\Resilience\Resilience.Http\Resilience.Http.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>


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

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


+ 1
- 1
src/Web/WebSPA/Client/modules/basket/basket.component.html View File

@ -29,7 +29,7 @@
</article> </article>
<br/> <br/>
<div class="esh-basket-items-margin-left1 row"> <div class="esh-basket-items-margin-left1 row">
<div class="alert alert-warning" role="alert" *ngIf="item.oldUnitPrice > 0">&nbsp;Note that the price of this article changed in our Catalog. The old price when you originally added it to the basket was {{item.oldUnitPrice}} $</div>
<div class="alert alert-warning" role="alert" *ngIf="item.oldUnitPrice > 0">&nbsp;Note that the price of this article changed in our Catalog. The old price when you originally added it to the basket was $ {{item.oldUnitPrice}} </div>
</div> </div>
</div> </div>
</div> </div>


+ 2
- 5
src/Web/WebSPA/Client/modules/orders/orders-new/orders-new.component.ts View File

@ -18,7 +18,7 @@ export class OrdersNewComponent implements OnInit {
private errorReceived: Boolean; private errorReceived: Boolean;
private order: IOrder; private order: IOrder;
constructor(private service: OrdersService, fb: FormBuilder, private router: Router, private basketEvents: BasketWrapperService) {
constructor(private service: OrdersService, fb: FormBuilder, private router: Router) {
// Obtain user profile information // Obtain user profile information
this.order = service.mapBasketAndIdentityInfoNewOrder(); this.order = service.mapBasketAndIdentityInfoNewOrder();
this.newOrderForm = fb.group({ this.newOrderForm = fb.group({
@ -54,10 +54,7 @@ export class OrdersNewComponent implements OnInit {
return Observable.throw(errMessage); return Observable.throw(errMessage);
}) })
.subscribe(res => { .subscribe(res => {
// this will emit an observable. Basket service is subscribed to this observable, and will react deleting the basket for the current user.
this.basketEvents.orderCreated();
this.router.navigate(['orders']);
this.router.navigate(['orders']);
}); });
this.errorReceived = false; this.errorReceived = false;
this.isOrderProcessing = true; this.isOrderProcessing = true;


+ 1
- 0
src/Web/WebSPA/Program.cs View File

@ -9,6 +9,7 @@ namespace eShopConContainers.WebSPA
{ {
var host = new WebHostBuilder() var host = new WebHostBuilder()
.UseKestrel() .UseKestrel()
.UseHealthChecks("/hc")
.UseContentRoot(Directory.GetCurrentDirectory()) .UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration() .UseIISIntegration()
.UseStartup<Startup>() .UseStartup<Startup>()


+ 5
- 1
src/Web/WebSPA/Startup.cs View File

@ -10,6 +10,7 @@ using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Serialization; using Newtonsoft.Json.Serialization;
using eShopOnContainers.WebSPA; using eShopOnContainers.WebSPA;
using Microsoft.Extensions.HealthChecks; using Microsoft.Extensions.HealthChecks;
using System.Threading.Tasks;
namespace eShopConContainers.WebSPA namespace eShopConContainers.WebSPA
{ {
@ -42,7 +43,10 @@ namespace eShopConContainers.WebSPA
{ {
services.AddHealthChecks(checks => services.AddHealthChecks(checks =>
{ {
checks.AddUrlCheck(Configuration["CallBackUrl"]);
checks.AddUrlCheck(Configuration["CatalogUrl"]);
checks.AddUrlCheck(Configuration["OrderingUrl"]);
checks.AddUrlCheck(Configuration["BasketUrl"]);
checks.AddUrlCheck(Configuration["IdentityUrl"]);
}); });
services.Configure<AppSettings>(Configuration); services.Configure<AppSettings>(Configuration);


+ 1
- 1
src/Web/WebStatus/Controllers/HomeController.cs View File

@ -25,7 +25,7 @@ namespace WebStatus.Controllers
foreach (var checkResult in result.Results) foreach (var checkResult in result.Results)
{ {
data.AddResult(checkResult.Value);
data.AddResult(checkResult.Key, checkResult.Value);
} }
return View(data); return View(data);


+ 4
- 4
src/Web/WebStatus/Viewmodels/HealthStatusViewModel.cs View File

@ -9,13 +9,13 @@ namespace WebStatus.Viewmodels
public class HealthStatusViewModel public class HealthStatusViewModel
{ {
private readonly CheckStatus _overall; private readonly CheckStatus _overall;
private readonly List<IHealthCheckResult> _results;
private readonly Dictionary<string, IHealthCheckResult> _results;
public CheckStatus OverallStatus => _overall; public CheckStatus OverallStatus => _overall;
public IEnumerable<IHealthCheckResult> Results => _results;
private HealthStatusViewModel() => _results = new List<IHealthCheckResult>();
public IEnumerable<NamedCheckResult> Results => _results.Select(kvp => new NamedCheckResult(kvp.Key, kvp.Value));
private HealthStatusViewModel() => _results = new Dictionary<string, IHealthCheckResult>();
public HealthStatusViewModel(CheckStatus overall) : this() => _overall = overall; public HealthStatusViewModel(CheckStatus overall) : this() => _overall = overall;
public void AddResult(IHealthCheckResult result) => _results.Add(result);
public void AddResult(string name, IHealthCheckResult result) => _results.Add(name, result);
} }


+ 20
- 0
src/Web/WebStatus/Viewmodels/NamedCheckResult.cs View File

@ -0,0 +1,20 @@
using Microsoft.Extensions.HealthChecks;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace WebStatus.Viewmodels
{
public class NamedCheckResult
{
public string Name { get; }
public IHealthCheckResult Result { get; }
public NamedCheckResult(string name, IHealthCheckResult result)
{
Name = name;
Result = result;
}
}
}

+ 14
- 10
src/Web/WebStatus/Views/Home/Index.cshtml View File

@ -16,26 +16,30 @@
{ {
<div class="row list-group-status-item"> <div class="row list-group-status-item">
<div class="col-md-10"> <div class="col-md-10">
<h4 class="list-group-status-item-title">@result.Data["url"]</h4>
<p class="list-group-item-text">@result.Description</p>
<h4 class="list-group-status-item-title">@result.Name</h4>
<p class="list-group-item-text">
@if (result.Result.Data.ContainsKey("url")) {
<p>@result.Result.Data["url"]</p>
}
@result.Result.Description
</p>
</div> </div>
<div class="col-md-2 list-group-status-item-label"> <div class="col-md-2 list-group-status-item-label">
@if (@result.CheckStatus == Microsoft.Extensions.HealthChecks.CheckStatus.Healthy)
@if (@result.Result.CheckStatus == Microsoft.Extensions.HealthChecks.CheckStatus.Healthy)
{ {
<span class="label label-success">@result.CheckStatus</span>
<span class="label label-success">@result.Result.CheckStatus</span>
} }
else if (@result.CheckStatus == Microsoft.Extensions.HealthChecks.CheckStatus.Unhealthy)
else if (@result.Result.CheckStatus == Microsoft.Extensions.HealthChecks.CheckStatus.Unhealthy)
{ {
<span class="label label-danger">@result.CheckStatus</span>
<span class="label label-danger">@result.Result.CheckStatus</span>
} }
else if (@result.CheckStatus == Microsoft.Extensions.HealthChecks.CheckStatus.Warning)
else if (@result.Result.CheckStatus == Microsoft.Extensions.HealthChecks.CheckStatus.Warning)
{ {
<span class="label label-warning">@result.CheckStatus</span>
<span class="label label-warning">@result.Result.CheckStatus</span>
} }
else else
{ {
<span class="label label-default">@result.CheckStatus</span>
<span class="label label-default">@result.Result.CheckStatus</span>
} }
</div> </div>
</div> </div>


+ 11
- 1
test/Services/FunctionalTests/FunctionalTests.csproj View File

@ -4,6 +4,16 @@
<TargetFramework>netcoreapp1.1</TargetFramework> <TargetFramework>netcoreapp1.1</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<None Remove="Services\Catalog\settings.json" />
</ItemGroup>
<ItemGroup>
<Content Include="Services\Catalog\settings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.DotNet.InternalAbstractions" Version="1.0.0" /> <PackageReference Include="Microsoft.DotNet.InternalAbstractions" Version="1.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0-preview-20170106-08" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0-preview-20170106-08" />
@ -23,7 +33,7 @@
<None Update="appsettings.json"> <None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
<None Update="settings.json">
<None Update="Services\Ordering\settings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
</ItemGroup> </ItemGroup>


+ 1
- 1
test/Services/FunctionalTests/Services/Catalog/CatalogScenariosBase.cs View File

@ -14,7 +14,7 @@ namespace FunctionalTests.Services.Catalog
public TestServer CreateServer() public TestServer CreateServer()
{ {
var webHostBuilder = new WebHostBuilder(); var webHostBuilder = new WebHostBuilder();
webHostBuilder.UseContentRoot(Directory.GetCurrentDirectory());
webHostBuilder.UseContentRoot(Directory.GetCurrentDirectory() + "\\Services\\Catalog");
webHostBuilder.UseStartup<Startup>(); webHostBuilder.UseStartup<Startup>();
return new TestServer(webHostBuilder); return new TestServer(webHostBuilder);


+ 7
- 0
test/Services/FunctionalTests/Services/Catalog/settings.json View File

@ -0,0 +1,7 @@
{
"ConnectionString": "Server=tcp:127.0.0.1,5433;Initial Catalog=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word",
"ExternalCatalogBaseUrl": "http://localhost:5101",
"IdentityUrl": "http://localhost:5105",
"isTest": "true",
"EventBusConnection": "localhost"
}

+ 1
- 1
test/Services/FunctionalTests/Services/Ordering/OrderingScenariosBase.cs View File

@ -12,7 +12,7 @@ namespace FunctionalTests.Services.Ordering
public TestServer CreateServer() public TestServer CreateServer()
{ {
var webHostBuilder = new WebHostBuilder(); var webHostBuilder = new WebHostBuilder();
webHostBuilder.UseContentRoot(Directory.GetCurrentDirectory());
webHostBuilder.UseContentRoot(Directory.GetCurrentDirectory() + "\\Services\\Ordering");
webHostBuilder.UseStartup<OrderingTestsStartup>(); webHostBuilder.UseStartup<OrderingTestsStartup>();
return new TestServer(webHostBuilder); return new TestServer(webHostBuilder);


test/Services/FunctionalTests/settings.json → test/Services/FunctionalTests/Services/Ordering/settings.json View File


+ 9
- 2
test/Services/IntegrationTests/IntegrationTests.csproj View File

@ -12,9 +12,16 @@
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> <GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<None Remove="Services\Catalog\settings.json" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<!--<Content Include="settings.json;web.config">--> <!--<Content Include="settings.json;web.config">-->
<Content Include="settings.json">
<Content Include="Services\Catalog\settings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Services\Ordering\settings.json">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory> <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content> </Content>
</ItemGroup> </ItemGroup>
@ -35,7 +42,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Update="settings.json">
<Content Update="Services\Ordering\settings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
</ItemGroup> </ItemGroup>


+ 1
- 1
test/Services/IntegrationTests/Services/Catalog/CatalogScenarioBase.cs View File

@ -12,7 +12,7 @@ namespace IntegrationTests.Services.Catalog
public TestServer CreateServer() public TestServer CreateServer()
{ {
var webHostBuilder = new WebHostBuilder(); var webHostBuilder = new WebHostBuilder();
webHostBuilder.UseContentRoot(Directory.GetCurrentDirectory());
webHostBuilder.UseContentRoot(Directory.GetCurrentDirectory() + "\\Services\\Catalog");
webHostBuilder.UseStartup<Startup>(); webHostBuilder.UseStartup<Startup>();
return new TestServer(webHostBuilder); return new TestServer(webHostBuilder);


+ 7
- 0
test/Services/IntegrationTests/Services/Catalog/settings.json View File

@ -0,0 +1,7 @@
{
"ConnectionString": "Server=tcp:127.0.0.1,5433;Initial Catalog=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word",
"ExternalCatalogBaseUrl": "http://localhost:5101",
"IdentityUrl": "http://localhost:5105",
"isTest": "true",
"EventBusConnection": "localhost"
}

+ 1
- 1
test/Services/IntegrationTests/Services/Ordering/OrderingScenarioBase.cs View File

@ -10,7 +10,7 @@
public TestServer CreateServer() public TestServer CreateServer()
{ {
var webHostBuilder = new WebHostBuilder(); var webHostBuilder = new WebHostBuilder();
webHostBuilder.UseContentRoot(Directory.GetCurrentDirectory());
webHostBuilder.UseContentRoot(Directory.GetCurrentDirectory() + "\\Services\\Ordering");
webHostBuilder.UseStartup<OrderingTestsStartup>(); webHostBuilder.UseStartup<OrderingTestsStartup>();
return new TestServer(webHostBuilder); return new TestServer(webHostBuilder);


test/Services/IntegrationTests/settings.json → test/Services/IntegrationTests/Services/Ordering/settings.json View File

@ -2,5 +2,6 @@
"ConnectionString": "Server=tcp:127.0.0.1,5433;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word;", "ConnectionString": "Server=tcp:127.0.0.1,5433;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word;",
"ExternalCatalogBaseUrl": "http://localhost:5101", "ExternalCatalogBaseUrl": "http://localhost:5101",
"IdentityUrl": "http://localhost:5105", "IdentityUrl": "http://localhost:5105",
"isTest": "true"
"isTest": "true",
"EventBusConnection": "localhost"
} }

Loading…
Cancel
Save