Browse Source

Merge branch 'dev'

pull/223/head
Eduard Tomas 7 years ago
parent
commit
4bf1988d3f
108 changed files with 1516 additions and 1076 deletions
  1. +1
    -0
      .gitignore
  2. +18
    -19
      cli-linux/build-bits-linux.sh
  3. +14
    -14
      docker-compose-windows.yml
  4. +1
    -1
      docker-compose.ci.build.yml
  5. +154
    -103
      eShopOnContainers-ServicesAndWebApps.sln
  6. +22
    -0
      src/BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj
  7. +56
    -0
      src/BuildingBlocks/EventBus/EventBus.Tests/InMemory_SubscriptionManager_Tests.cs
  8. +11
    -0
      src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationEvent.cs
  9. +23
    -0
      src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationEventHandler.cs
  10. +23
    -0
      src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationOtherEventHandler.cs
  11. +8
    -2
      src/BuildingBlocks/EventBus/EventBus/Abstractions/IEventBus.cs
  12. +26
    -0
      src/BuildingBlocks/EventBus/EventBus/IEventBusSubscriptionsManager.cs
  13. +115
    -0
      src/BuildingBlocks/EventBus/EventBus/InMemoryEventBusSubscriptionsManager.cs
  14. +6
    -6
      src/BuildingBlocks/EventBus/EventBusRabbitMQ/DefaultRabbitMQPersisterConnection.cs
  15. +68
    -73
      src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs
  16. +1
    -2
      src/BuildingBlocks/EventBus/EventBusRabbitMQ/IRabbitMQPersisterConnection.cs
  17. +15
    -7
      src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckMiddleware.cs
  18. +11
    -4
      src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckStartupFilter.cs
  19. +16
    -5
      src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckWebHostBuilderExtension.cs
  20. +2
    -2
      src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.SqlServer/HealthCheckBuilderSqlServerExtensions.cs
  21. +0
    -0
      src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.SqlServer/Microsoft.Extensions.HealthChecks.SqlServer.csproj
  22. +109
    -0
      src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CachedHealthCheck.cs
  23. +19
    -0
      src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CachedHealthCheckExtensions.cs
  24. +30
    -24
      src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/AddCheck.cs
  25. +5
    -5
      src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/NumericChecks.cs
  26. +1
    -64
      src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/UrlChecks.cs
  27. +11
    -8
      src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CompositeHealthCheckResult.cs
  28. +15
    -49
      src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheck.cs
  29. +102
    -7
      src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckBuilder.cs
  30. +0
    -18
      src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckExtensions.cs
  31. +37
    -0
      src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckGroup.cs
  32. +0
    -1
      src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckResult.cs
  33. +91
    -26
      src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckService.cs
  34. +13
    -5
      src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckServiceCollectionExtensions.cs
  35. +0
    -38
      src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckServiceExtensions.cs
  36. +1
    -4
      src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/IHealthCheck.cs
  37. +46
    -1
      src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/IHealthCheckService.cs
  38. +18
    -42
      src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Internal/UrlChecker.cs
  39. +22
    -10
      src/BuildingBlocks/HealthChecks/src/common/Guard.cs
  40. +0
    -1
      src/Services/Basket/Basket.API/Basket.API.csproj
  41. +3
    -7
      src/Services/Basket/Basket.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs
  42. +20
    -11
      src/Services/Basket/Basket.API/Startup.cs
  43. +1
    -1
      src/Services/Catalog/Catalog.API/Catalog.API.csproj
  44. +5
    -3
      src/Services/Catalog/Catalog.API/Startup.cs
  45. +1
    -1
      src/Services/Identity/Identity.API/Identity.API.csproj
  46. +2
    -6
      src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommand.cs
  47. +1
    -1
      src/Services/Ordering/Ordering.API/Ordering.API.csproj
  48. +5
    -3
      src/Services/Ordering/Ordering.API/Startup.cs
  49. +1
    -1
      src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs
  50. +0
    -1
      src/Web/WebMVC/WebMVC.csproj
  51. +1
    -1
      src/Web/WebMVC/wwwroot/css/site.min.css
  52. +58
    -0
      src/Web/WebSPA/.angular-cli.json
  53. +1
    -4
      src/Web/WebSPA/.gitignore
  54. +0
    -0
      src/Web/WebSPA/Client/assets/.gitkeep
  55. +0
    -0
      src/Web/WebSPA/Client/assets/images/arrow-down.png
  56. +0
    -0
      src/Web/WebSPA/Client/assets/images/arrow-right.svg
  57. +0
    -0
      src/Web/WebSPA/Client/assets/images/brand.png
  58. +0
    -0
      src/Web/WebSPA/Client/assets/images/brand_dark.png
  59. +0
    -0
      src/Web/WebSPA/Client/assets/images/cart.png
  60. +0
    -0
      src/Web/WebSPA/Client/assets/images/logout.png
  61. +0
    -0
      src/Web/WebSPA/Client/assets/images/main_banner.png
  62. +0
    -0
      src/Web/WebSPA/Client/assets/images/main_banner_text.png
  63. +0
    -0
      src/Web/WebSPA/Client/assets/images/my_orders.png
  64. +0
    -2
      src/Web/WebSPA/Client/custom-typings.d.ts
  65. +3
    -0
      src/Web/WebSPA/Client/environments/environment.prod.ts
  66. +8
    -0
      src/Web/WebSPA/Client/environments/environment.ts
  67. +0
    -0
      src/Web/WebSPA/Client/favicon.ico
  68. +1
    -0
      src/Web/WebSPA/Client/globals.scss
  69. +18
    -0
      src/Web/WebSPA/Client/index.html
  70. +4
    -16
      src/Web/WebSPA/Client/main.ts
  71. +2
    -2
      src/Web/WebSPA/Client/modules/app.component.html
  72. +2
    -2
      src/Web/WebSPA/Client/modules/app.component.ts
  73. +1
    -1
      src/Web/WebSPA/Client/modules/basket/basket-status/basket-status.component.html
  74. +1
    -1
      src/Web/WebSPA/Client/modules/catalog/catalog.component.html
  75. +2
    -2
      src/Web/WebSPA/Client/modules/catalog/catalog.component.scss
  76. +5
    -6
      src/Web/WebSPA/Client/modules/orders/orders-detail/orders-detail.component.ts
  77. +4
    -4
      src/Web/WebSPA/Client/modules/orders/orders-new/orders-new.component.ts
  78. +2
    -1
      src/Web/WebSPA/Client/modules/orders/orders.service.ts
  79. +3
    -3
      src/Web/WebSPA/Client/modules/shared/components/identity/identity.html
  80. +1
    -1
      src/Web/WebSPA/Client/modules/shared/components/identity/identity.ts
  81. +14
    -0
      src/Web/WebSPA/Client/modules/shared/models/order-detail.model.ts
  82. +10
    -2
      src/Web/WebSPA/Client/modules/shared/shared.module.ts
  83. +66
    -21
      src/Web/WebSPA/Client/polyfills.ts
  84. +32
    -0
      src/Web/WebSPA/Client/test.ts
  85. +13
    -0
      src/Web/WebSPA/Client/tsconfig.app.json
  86. +20
    -0
      src/Web/WebSPA/Client/tsconfig.spec.json
  87. +5
    -0
      src/Web/WebSPA/Client/typings.d.ts
  88. +0
    -20
      src/Web/WebSPA/Client/vendor.ts
  89. +0
    -18
      src/Web/WebSPA/Server/Controllers/HomeController.cs
  90. +22
    -18
      src/Web/WebSPA/Startup.cs
  91. +0
    -12
      src/Web/WebSPA/Views/Home/Index.cshtml
  92. +0
    -16
      src/Web/WebSPA/Views/Shared/_Layout.cshtml
  93. +0
    -3
      src/Web/WebSPA/Views/_ViewImports.cshtml
  94. +0
    -3
      src/Web/WebSPA/Views/_ViewStart.cshtml
  95. +0
    -14
      src/Web/WebSPA/WebSPA.csproj
  96. +0
    -23
      src/Web/WebSPA/config/helpers.js
  97. +0
    -3
      src/Web/WebSPA/config/webpack.config.dev.js
  98. +0
    -77
      src/Web/WebSPA/config/webpack.config.js
  99. +0
    -23
      src/Web/WebSPA/config/webpack.config.prod.js
  100. +0
    -74
      src/Web/WebSPA/config/webpack.config.vendor.js

+ 1
- 0
.gitignore View File

@ -6,6 +6,7 @@
*.user
*.userosscache
*.sln.docstates
.vscode/
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs


+ 18
- 19
cli-linux/build-bits-linux.sh View File

@ -1,18 +1,17 @@
projectList=(
"/src/Services/Catalog/Catalog.API"
"/src/Services/Basket/Basket.API"
"/src/Services/Ordering/Ordering.API"
"/src/Services/Identity/Identity.API"
"/src/Web/WebMVC"
"/src/Web/WebSPA"
"/src/Web/WebStatus
#!/bin/bash
declare -a projectList=(
'../src/Services/Catalog/Catalog.API'
'../src/Services/Basket/Basket.API'
'../src/Services/Ordering/Ordering.API'
'../src/Services/Identity/Identity.API'
'../src/Web/WebMVC'
'../src/Web/WebSPA'
'../src/Web/WebStatus'
)
# Build SPA app
pushd $(pwd)/src/Web/WebSPA
npm rebuild node-sass
npm run build:prod
# pushd $(pwd)../src/Web/WebSPA
# npm run build:prod
for project in "${projectList[@]}"
do
@ -28,13 +27,13 @@ do
done
# remove old docker images:
#images=$(docker images --filter=reference="eshop/*" -q)
#if [ -n "$images" ]; then
# docker rm $(docker ps -a -q) -f
# echo "Deleting eShop images in local Docker repo"
# echo $images
# docker rmi $(docker images --filter=reference="eshop/*" -q) -f
#fi
images=$(docker images --filter=reference="eshop/*" -q)
if [ -n "$images" ]; then
docker rm $(docker ps -a -q) -f
echo "Deleting eShop images in local Docker repo"
echo $images
docker rmi $(docker images --filter=reference="eshop/*" -q) -f
fi
# 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.

+ 14
- 14
docker-compose-windows.yml View File

@ -2,7 +2,7 @@ version: '2.1'
services:
basket.api:
image: eshop/basket.api
image: eshop/basket.api-win
build:
context: ./src/Services/Basket/Basket.API
dockerfile: Dockerfile.nanowin
@ -11,7 +11,7 @@ services:
- identity.api
catalog.api:
image: eshop/catalog.api
image: eshop/catalog.api-win
build:
context: ./src/Services/Catalog/Catalog.API
dockerfile: Dockerfile.nanowin
@ -19,7 +19,7 @@ services:
- sql.data
identity.api:
image: eshop/identity.api
image: eshop/identity.api-win
build:
context: ./src/Services/Identity/Identity.API
dockerfile: Dockerfile.nanowin
@ -27,7 +27,7 @@ services:
- sql.data
ordering.api:
image: eshop/ordering.api
image: eshop/ordering.api-win
build:
context: ./src/Services/Ordering/Ordering.API
dockerfile: Dockerfile.nanowin
@ -35,7 +35,7 @@ services:
- sql.data
webspa:
image: eshop/webspa
image: eshop/webspa-win
build:
context: ./src/Web/WebSPA
dockerfile: Dockerfile.nanowin
@ -44,7 +44,7 @@ services:
- basket.api
webmvc:
image: eshop/webmvc
image: eshop/webmvc-win
build:
context: ./src/Web/WebMVC
dockerfile: Dockerfile.nanowin
@ -58,18 +58,18 @@ services:
image: microsoft/mssql-server-windows
basket.data:
image: redis
build:
context: ./_docker/redis
dockerfile: Dockerfile.nanowin
image: eshop/redis-win
# build:
# context: ./_docker/redis
# dockerfile: Dockerfile.nanowin
ports:
- "6379:6379"
rabbitmq:
image: rabbitmq
build:
context: ./_docker/rabbitmq
dockerfile: Dockerfile.nanowin
image: eshop/rabbitmq-win
# build:
# context: ./_docker/rabbitmq
# dockerfile: Dockerfile.nanowin
ports:
- "5672:5672"


+ 1
- 1
docker-compose.ci.build.yml View File

@ -6,5 +6,5 @@ services:
volumes:
- .:/src
working_dir: /src
command: /bin/bash -c "pushd ./src/Web/WebSPA && npm rebuild node-sass && pushd ./../../.. && dotnet restore ./eShopOnContainers-ServicesAndWebApps.sln && dotnet publish ./eShopOnContainers-ServicesAndWebApps.sln -c Release -o ./obj/Docker/publish"
command: /bin/bash -c "dotnet restore ./eShopOnContainers-ServicesAndWebApps.sln && dotnet publish ./eShopOnContainers-ServicesAndWebApps.sln -c Release -o ./obj/Docker/publish"

+ 154
- 103
eShopOnContainers-ServicesAndWebApps.sln View File

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26228.12
VisualStudioVersion = 15.0.26403.7
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{932D8224-11F6-4D07-B109-DA28AD288A63}"
EndProject
@ -62,18 +62,20 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IntegrationEventLogEF", "sr
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HealthChecks", "HealthChecks", "{A81ECBC2-6B00-4DCD-8388-469174033379}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.HealthChecks", "src\BuildingBlocks\HealthChecks\src\Microsoft.AspNetCore.HealthChecks\Microsoft.AspNetCore.HealthChecks.csproj", "{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.HealthChecks", "src\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks\Microsoft.Extensions.HealthChecks.csproj", "{942ED6E8-0050-495F-A0EA-01E97F63760C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.HealthChecks.Data", "src\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.Data\Microsoft.Extensions.HealthChecks.Data.csproj", "{7804FC60-23E6-490C-8E08-F9FEF829F184}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebStatus", "src\Web\WebStatus\WebStatus.csproj", "{C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}"
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
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.HealthChecks", "src\BuildingBlocks\HealthChecks\src\Microsoft.AspNetCore.HealthChecks\Microsoft.AspNetCore.HealthChecks.csproj", "{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.HealthChecks.SqlServer", "src\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.SqlServer\Microsoft.Extensions.HealthChecks.SqlServer.csproj", "{4BD76717-3102-4969-8C2C-BAAA3F0263B6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventBus.Tests", "src\BuildingBlocks\EventBus\EventBus.Tests\EventBus.Tests.csproj", "{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
@ -712,54 +714,6 @@ Global
{9EE28E45-1533-472B-8267-56C48855BA0E}.Release|x64.Build.0 = Release|Any CPU
{9EE28E45-1533-472B-8267-56C48855BA0E}.Release|x86.ActiveCfg = Release|Any CPU
{9EE28E45-1533-472B-8267-56C48855BA0E}.Release|x86.Build.0 = Release|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|ARM.ActiveCfg = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|ARM.Build.0 = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|iPhone.Build.0 = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|x64.ActiveCfg = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|x64.Build.0 = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|x86.ActiveCfg = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|x86.Build.0 = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|ARM.ActiveCfg = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|ARM.Build.0 = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|iPhone.Build.0 = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|x64.ActiveCfg = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|x64.Build.0 = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|x86.ActiveCfg = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|x86.Build.0 = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|Any CPU.Build.0 = Release|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|ARM.ActiveCfg = Release|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|ARM.Build.0 = Release|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|iPhone.ActiveCfg = Release|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|iPhone.Build.0 = Release|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|x64.ActiveCfg = Release|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|x64.Build.0 = Release|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|x86.ActiveCfg = Release|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|x86.Build.0 = Release|Any CPU
{942ED6E8-0050-495F-A0EA-01E97F63760C}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{942ED6E8-0050-495F-A0EA-01E97F63760C}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{942ED6E8-0050-495F-A0EA-01E97F63760C}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
@ -808,54 +762,6 @@ Global
{942ED6E8-0050-495F-A0EA-01E97F63760C}.Release|x64.Build.0 = Release|Any CPU
{942ED6E8-0050-495F-A0EA-01E97F63760C}.Release|x86.ActiveCfg = Release|Any CPU
{942ED6E8-0050-495F-A0EA-01E97F63760C}.Release|x86.Build.0 = Release|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|ARM.ActiveCfg = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|ARM.Build.0 = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|iPhone.Build.0 = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|x64.ActiveCfg = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|x64.Build.0 = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|x86.ActiveCfg = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|x86.Build.0 = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|ARM.ActiveCfg = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|ARM.Build.0 = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|iPhone.Build.0 = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|x64.ActiveCfg = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|x64.Build.0 = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|x86.ActiveCfg = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|x86.Build.0 = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|Any CPU.Build.0 = Release|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|ARM.ActiveCfg = Release|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|ARM.Build.0 = Release|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|iPhone.ActiveCfg = Release|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|iPhone.Build.0 = Release|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|x64.ActiveCfg = Release|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|x64.Build.0 = Release|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|x86.ActiveCfg = Release|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|x86.Build.0 = Release|Any CPU
{C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
@ -952,6 +858,150 @@ Global
{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
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|ARM.ActiveCfg = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|ARM.Build.0 = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|iPhone.Build.0 = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|x64.ActiveCfg = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|x64.Build.0 = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|x86.ActiveCfg = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|x86.Build.0 = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|ARM.ActiveCfg = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|ARM.Build.0 = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|iPhone.Build.0 = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|x64.ActiveCfg = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|x64.Build.0 = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|x86.ActiveCfg = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|x86.Build.0 = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|Any CPU.Build.0 = Release|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|ARM.ActiveCfg = Release|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|ARM.Build.0 = Release|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|iPhone.ActiveCfg = Release|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|iPhone.Build.0 = Release|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|x64.ActiveCfg = Release|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|x64.Build.0 = Release|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|x86.ActiveCfg = Release|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|x86.Build.0 = Release|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|ARM.ActiveCfg = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|ARM.Build.0 = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|iPhone.Build.0 = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|x64.ActiveCfg = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|x64.Build.0 = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|x86.ActiveCfg = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|x86.Build.0 = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|ARM.ActiveCfg = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|ARM.Build.0 = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|iPhone.Build.0 = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|x64.ActiveCfg = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|x64.Build.0 = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|x86.ActiveCfg = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|x86.Build.0 = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|Any CPU.Build.0 = Release|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|ARM.ActiveCfg = Release|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|ARM.Build.0 = Release|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|iPhone.ActiveCfg = Release|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|iPhone.Build.0 = Release|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|x64.ActiveCfg = Release|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|x64.Build.0 = Release|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|x86.ActiveCfg = Release|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|x86.Build.0 = Release|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|ARM.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|ARM.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|iPhone.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|x64.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|x64.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|x86.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|x86.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|ARM.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|ARM.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|iPhone.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|x64.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|x64.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|x86.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|x86.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|Any CPU.Build.0 = Release|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|ARM.ActiveCfg = Release|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|ARM.Build.0 = Release|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|iPhone.ActiveCfg = Release|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|iPhone.Build.0 = Release|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|x64.ActiveCfg = Release|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|x64.Build.0 = Release|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|x86.ActiveCfg = Release|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -981,11 +1031,12 @@ Global
{8088F3FC-6787-45FA-A924-816EC81CBFAC} = {807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F}
{9EE28E45-1533-472B-8267-56C48855BA0E} = {807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F}
{A81ECBC2-6B00-4DCD-8388-469174033379} = {DB0EFB20-B024-4E5E-A75C-52143C131D25}
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E} = {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}
{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}
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5} = {A81ECBC2-6B00-4DCD-8388-469174033379}
{4BD76717-3102-4969-8C2C-BAAA3F0263B6} = {A81ECBC2-6B00-4DCD-8388-469174033379}
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D} = {807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F}
EndGlobalSection
EndGlobal

+ 22
- 0
src/BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj View File

@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp1.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />
<PackageReference Include="xunit" Version="2.2.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\EventBusRabbitMQ\EventBusRabbitMQ.csproj" />
<ProjectReference Include="..\EventBus\EventBus.csproj" />
</ItemGroup>
<ItemGroup>
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
</ItemGroup>
</Project>

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

@ -0,0 +1,56 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus;
using System;
using System.Linq;
using Xunit;
namespace EventBus.Tests
{
public class InMemory_SubscriptionManager_Tests
{
[Fact]
public void After_Creation_Should_Be_Empty()
{
var manager = new InMemoryEventBusSubscriptionsManager();
Assert.True(manager.IsEmpty);
}
[Fact]
public void After_One_Event_Subscription_Should_Contain_The_Event()
{
var manager = new InMemoryEventBusSubscriptionsManager();
manager.AddSubscription<TestIntegrationEvent,TestIntegrationEventHandler>(() => new TestIntegrationEventHandler());
Assert.True(manager.HasSubscriptionsForEvent<TestIntegrationEvent>());
}
[Fact]
public void After_All_Subscriptions_Are_Deleted_Event_Should_No_Longer_Exists()
{
var manager = new InMemoryEventBusSubscriptionsManager();
manager.AddSubscription<TestIntegrationEvent, TestIntegrationEventHandler>(() => new TestIntegrationEventHandler());
manager.RemoveSubscription<TestIntegrationEvent, TestIntegrationEventHandler>();
Assert.False(manager.HasSubscriptionsForEvent<TestIntegrationEvent>());
}
[Fact]
public void Deleting_Last_Subscription_Should_Raise_On_Deleted_Event()
{
bool raised = false;
var manager = new InMemoryEventBusSubscriptionsManager();
manager.OnEventRemoved += (o, e) => raised = true;
manager.AddSubscription<TestIntegrationEvent, TestIntegrationEventHandler>(() => new TestIntegrationEventHandler());
manager.RemoveSubscription<TestIntegrationEvent, TestIntegrationEventHandler>();
Assert.True(raised);
}
[Fact]
public void Get_Handlers_For_Event_Should_Return_All_Handlers()
{
var manager = new InMemoryEventBusSubscriptionsManager();
manager.AddSubscription<TestIntegrationEvent, TestIntegrationEventHandler>(() => new TestIntegrationEventHandler());
manager.AddSubscription<TestIntegrationEvent, TestIntegrationOtherEventHandler>(() => new TestIntegrationOtherEventHandler());
var handlers = manager.GetHandlersForEvent<TestIntegrationEvent>();
Assert.Equal(2, handlers.Count());
}
}
}

+ 11
- 0
src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationEvent.cs View File

@ -0,0 +1,11 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using System;
using System.Collections.Generic;
using System.Text;
namespace EventBus.Tests
{
public class TestIntegrationEvent : IntegrationEvent
{
}
}

+ 23
- 0
src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationEventHandler.cs View File

@ -0,0 +1,23 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace EventBus.Tests
{
public class TestIntegrationOtherEventHandler : IIntegrationEventHandler<TestIntegrationEvent>
{
public bool Handled { get; private set; }
public TestIntegrationOtherEventHandler()
{
Handled = false;
}
public async Task Handle(TestIntegrationEvent @event)
{
Handled = true;
}
}
}

+ 23
- 0
src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationOtherEventHandler.cs View File

@ -0,0 +1,23 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace EventBus.Tests
{
public class TestIntegrationEventHandler : IIntegrationEventHandler<TestIntegrationEvent>
{
public bool Handled { get; private set; }
public TestIntegrationEventHandler()
{
Handled = false;
}
public async Task Handle(TestIntegrationEvent @event)
{
Handled = true;
}
}
}

+ 8
- 2
src/BuildingBlocks/EventBus/EventBus/Abstractions/IEventBus.cs View File

@ -1,11 +1,17 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using System;
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions
{
public interface IEventBus
{
void Subscribe<T>(IIntegrationEventHandler<T> handler) where T: IntegrationEvent;
void Unsubscribe<T>(IIntegrationEventHandler<T> handler) where T : IntegrationEvent;
void Subscribe<T, TH>(Func<TH> handler)
where T : IntegrationEvent
where TH : IIntegrationEventHandler<T>;
void Unsubscribe<T, TH>()
where TH : IIntegrationEventHandler<T>
where T : IntegrationEvent;
void Publish(IntegrationEvent @event);
}
}

+ 26
- 0
src/BuildingBlocks/EventBus/EventBus/IEventBusSubscriptionsManager.cs View File

@ -0,0 +1,26 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using System;
using System.Collections.Generic;
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus
{
public interface IEventBusSubscriptionsManager
{
bool IsEmpty { get; }
event EventHandler<string> OnEventRemoved;
void AddSubscription<T, TH>(Func<TH> handler)
where T : IntegrationEvent
where TH : IIntegrationEventHandler<T>;
void RemoveSubscription<T, TH>()
where TH : IIntegrationEventHandler<T>
where T : IntegrationEvent;
bool HasSubscriptionsForEvent<T>() where T : IntegrationEvent;
bool HasSubscriptionsForEvent(string eventName);
Type GetEventTypeByName(string eventName);
void Clear();
IEnumerable<Delegate> GetHandlersForEvent<T>() where T : IntegrationEvent;
IEnumerable<Delegate> GetHandlersForEvent(string eventName);
}
}

+ 115
- 0
src/BuildingBlocks/EventBus/EventBus/InMemoryEventBusSubscriptionsManager.cs View File

@ -0,0 +1,115 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus
{
public class InMemoryEventBusSubscriptionsManager : IEventBusSubscriptionsManager
{
private readonly Dictionary<string, List<Delegate>> _handlers;
private readonly List<Type> _eventTypes;
public event EventHandler<string> OnEventRemoved;
public InMemoryEventBusSubscriptionsManager()
{
_handlers = new Dictionary<string, List<Delegate>>();
_eventTypes = new List<Type>();
}
public bool IsEmpty => !_handlers.Keys.Any();
public void Clear() => _handlers.Clear();
public void AddSubscription<T, TH>(Func<TH> handler)
where T : IntegrationEvent
where TH : IIntegrationEventHandler<T>
{
var key = GetEventKey<T>();
if (!HasSubscriptionsForEvent<T>())
{
_handlers.Add(key, new List<Delegate>());
}
_handlers[key].Add(handler);
_eventTypes.Add(typeof(T));
}
public void RemoveSubscription<T, TH>()
where TH : IIntegrationEventHandler<T>
where T : IntegrationEvent
{
var handlerToRemove = FindHandlerToRemove<T, TH>();
if (handlerToRemove != null)
{
var key = GetEventKey<T>();
_handlers[key].Remove(handlerToRemove);
if (!_handlers[key].Any())
{
_handlers.Remove(key);
var eventType = _eventTypes.SingleOrDefault(e => e.Name == key);
if (eventType != null)
{
_eventTypes.Remove(eventType);
RaiseOnEventRemoved(eventType.Name);
}
}
}
}
public IEnumerable<Delegate> GetHandlersForEvent<T>() where T : IntegrationEvent
{
var key = GetEventKey<T>();
return GetHandlersForEvent(key);
}
public IEnumerable<Delegate> GetHandlersForEvent(string eventName) => _handlers[eventName];
private void RaiseOnEventRemoved(string eventName)
{
var handler = OnEventRemoved;
if (handler != null)
{
OnEventRemoved(this, eventName);
}
}
private Delegate FindHandlerToRemove<T, TH>()
where T : IntegrationEvent
where TH : IIntegrationEventHandler<T>
{
if (!HasSubscriptionsForEvent<T>())
{
return null;
}
var key = GetEventKey<T>();
foreach (var func in _handlers[key])
{
var genericArgs = func.GetType().GetGenericArguments();
if (genericArgs.SingleOrDefault() == typeof(TH))
{
return func;
}
}
return null;
}
public bool HasSubscriptionsForEvent<T>() where T : IntegrationEvent
{
var key = GetEventKey<T>();
return HasSubscriptionsForEvent(key);
}
public bool HasSubscriptionsForEvent(string eventName) => _handlers.ContainsKey(eventName);
public Type GetEventTypeByName(string eventName) => _eventTypes.Single(t => t.Name == eventName);
private string GetEventKey<T>()
{
return typeof(T).Name;
}
}
}

+ 6
- 6
src/BuildingBlocks/EventBus/EventBusRabbitMQ/DefaultRabbitMQPersisterConnection.cs View File

@ -10,18 +10,18 @@ using System.Net.Sockets;
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
{
public class DefaultRabbitMQPersisterConnection
: IRabbitMQPersisterConnection
public class DefaultRabbitMQPersistentConnection
: IRabbitMQPersistentConnection
{
private readonly IConnectionFactory _connectionFactory;
private readonly ILogger<DefaultRabbitMQPersisterConnection> _logger;
private readonly ILogger<DefaultRabbitMQPersistentConnection> _logger;
IConnection _connection;
bool _disposed;
object sync_root = new object();
public DefaultRabbitMQPersisterConnection(IConnectionFactory connectionFactory,ILogger<DefaultRabbitMQPersisterConnection> logger)
public DefaultRabbitMQPersistentConnection(IConnectionFactory connectionFactory,ILogger<DefaultRabbitMQPersistentConnection> logger)
{
_connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
@ -87,13 +87,13 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
_connection.CallbackException += OnCallbackException;
_connection.ConnectionBlocked += OnConnectionBlocked;
_logger.LogInformation($"RabbitMQ persister connection acquire a connection {_connection.Endpoint.HostName} and is subscribed to failure events");
_logger.LogInformation($"RabbitMQ persistent connection acquired a connection {_connection.Endpoint.HostName} and is subscribed to failure events");
return true;
}
else
{
_logger.LogCritical("FATAL ERROR: RabbitMQ connections can't be created and opened");
_logger.LogCritical("FATAL ERROR: RabbitMQ connections could not be created and opened");
return false;
}


+ 68
- 73
src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs View File

@ -1,4 +1,5 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
@ -21,32 +22,50 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
{
const string BROKER_NAME = "eshop_event_bus";
private readonly IRabbitMQPersisterConnection _persisterConnection;
private readonly IRabbitMQPersistentConnection _persistentConnection;
private readonly ILogger<EventBusRabbitMQ> _logger;
private readonly Dictionary<string, List<IIntegrationEventHandler>> _handlers
= new Dictionary<string, List<IIntegrationEventHandler>>();
private readonly List<Type> _eventTypes
= new List<Type>();
private readonly IEventBusSubscriptionsManager _subsManager;
private IModel _consumerChannel;
private string _queueName;
public EventBusRabbitMQ(IRabbitMQPersisterConnection persisterConnection, ILogger<EventBusRabbitMQ> logger)
public EventBusRabbitMQ(IRabbitMQPersistentConnection persistentConnection, ILogger<EventBusRabbitMQ> logger, IEventBusSubscriptionsManager subsManager)
{
_persisterConnection = persisterConnection ?? throw new ArgumentNullException(nameof(persisterConnection));
_persistentConnection = persistentConnection ?? throw new ArgumentNullException(nameof(persistentConnection));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_subsManager = subsManager ?? new InMemoryEventBusSubscriptionsManager();
_consumerChannel = CreateConsumerChannel();
_subsManager.OnEventRemoved += SubsManager_OnEventRemoved;
}
private void SubsManager_OnEventRemoved(object sender, string eventName)
{
if (!_persistentConnection.IsConnected)
{
_persistentConnection.TryConnect();
}
using (var channel = _persistentConnection.CreateModel())
{
channel.QueueUnbind(queue: _queueName,
exchange: BROKER_NAME,
routingKey: eventName);
if (_subsManager.IsEmpty)
{
_queueName = string.Empty;
_consumerChannel.Close();
}
}
}
public void Publish(IntegrationEvent @event)
{
if (!_persisterConnection.IsConnected)
if (!_persistentConnection.IsConnected)
{
_persisterConnection.TryConnect();
_persistentConnection.TryConnect();
}
var policy = RetryPolicy.Handle<BrokerUnreachableException>()
@ -56,7 +75,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
_logger.LogWarning(ex.ToString());
});
using (var channel = _persisterConnection.CreateModel())
using (var channel = _persistentConnection.CreateModel())
{
var eventName = @event.GetType()
.Name;
@ -77,75 +96,49 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
}
}
public void Subscribe<T>(IIntegrationEventHandler<T> handler) where T : IntegrationEvent
public void Subscribe<T, TH>(Func<TH> handler)
where T : IntegrationEvent
where TH : IIntegrationEventHandler<T>
{
var eventName = typeof(T).Name;
if (_handlers.ContainsKey(eventName))
{
_handlers[eventName].Add(handler);
}
else
var containsKey = _subsManager.HasSubscriptionsForEvent<T>();
if (!containsKey)
{
if (!_persisterConnection.IsConnected)
if (!_persistentConnection.IsConnected)
{
_persisterConnection.TryConnect();
_persistentConnection.TryConnect();
}
using (var channel = _persisterConnection.CreateModel())
using (var channel = _persistentConnection.CreateModel())
{
channel.QueueBind(queue: _queueName,
exchange: BROKER_NAME,
routingKey: eventName);
_handlers.Add(eventName, new List<IIntegrationEventHandler>());
_handlers[eventName].Add(handler);
_eventTypes.Add(typeof(T));
}
}
_subsManager.AddSubscription<T, TH>(handler);
}
public void Unsubscribe<T>(IIntegrationEventHandler<T> handler) where T : IntegrationEvent
public void Unsubscribe<T, TH>()
where TH : IIntegrationEventHandler<T>
where T : IntegrationEvent
{
var eventName = typeof(T).Name;
_subsManager.RemoveSubscription<T, TH>();
}
if (_handlers.ContainsKey(eventName) && _handlers[eventName].Contains(handler))
private static Func<IIntegrationEventHandler> FindHandlerByType(Type handlerType, IEnumerable<Func<IIntegrationEventHandler>> handlers)
{
foreach (var func in handlers)
{
_handlers[eventName].Remove(handler);
if (_handlers[eventName].Count == 0)
if (func.GetMethodInfo().ReturnType == handlerType)
{
_handlers.Remove(eventName);
var eventType = _eventTypes.SingleOrDefault(e => e.Name == eventName);
if (eventType != null)
{
_eventTypes.Remove(eventType);
if (!_persisterConnection.IsConnected)
{
_persisterConnection.TryConnect();
}
using (var channel = _persisterConnection.CreateModel())
{
channel.QueueUnbind(queue: _queueName,
exchange: BROKER_NAME,
routingKey: eventName);
if (_handlers.Keys.Count == 0)
{
_queueName = string.Empty;
_consumerChannel.Close();
}
}
}
return func;
}
}
return null;
}
public void Dispose()
@ -154,18 +147,18 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
{
_consumerChannel.Dispose();
}
_handlers.Clear();
_subsManager.Clear();
}
private IModel CreateConsumerChannel()
{
if (!_persisterConnection.IsConnected)
if (!_persistentConnection.IsConnected)
{
_persisterConnection.TryConnect();
_persistentConnection.TryConnect();
}
var channel = _persisterConnection.CreateModel();
var channel = _persistentConnection.CreateModel();
channel.ExchangeDeclare(exchange: BROKER_NAME,
type: "direct");
@ -196,15 +189,17 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
private async Task ProcessEvent(string eventName, string message)
{
if (_handlers.ContainsKey(eventName))
{
Type eventType = _eventTypes.Single(t => t.Name == eventName);
if (_subsManager.HasSubscriptionsForEvent(eventName))
{
var eventType = _subsManager.GetEventTypeByName(eventName);
var integrationEvent = JsonConvert.DeserializeObject(message, eventType);
var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType);
var handlers = _handlers[eventName];
var handlers = _subsManager.GetHandlersForEvent(eventName);
foreach (var handler in handlers)
foreach (var handlerfactory in handlers)
{
var handler = handlerfactory.DynamicInvoke();
var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType);
await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent });
}
}


+ 1
- 2
src/BuildingBlocks/EventBus/EventBusRabbitMQ/IRabbitMQPersisterConnection.cs View File

@ -3,8 +3,7 @@ using System;
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
{
public interface IRabbitMQPersisterConnection
public interface IRabbitMQPersistentConnection
: IDisposable
{
bool IsConnected { get; }


+ 15
- 7
src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckMiddleware.cs View File

@ -1,6 +1,8 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
@ -11,30 +13,34 @@ namespace Microsoft.AspNetCore.HealthChecks
{
public class HealthCheckMiddleware
{
private RequestDelegate _next;
private string _path;
private int? _port;
private IHealthCheckService _service;
private readonly RequestDelegate _next;
private readonly string _path;
private readonly int? _port;
private readonly IHealthCheckService _service;
private readonly TimeSpan _timeout;
public HealthCheckMiddleware(RequestDelegate next, IHealthCheckService service, int port)
public HealthCheckMiddleware(RequestDelegate next, IHealthCheckService service, int port, TimeSpan timeout)
{
_port = port;
_service = service;
_next = next;
_timeout = timeout;
}
public HealthCheckMiddleware(RequestDelegate next, IHealthCheckService service, string path)
public HealthCheckMiddleware(RequestDelegate next, IHealthCheckService service, string path, TimeSpan timeout)
{
_path = path;
_service = service;
_next = next;
_timeout = timeout;
}
public async Task Invoke(HttpContext context)
{
if (IsHealthCheckRequest(context))
{
var result = await _service.CheckHealthAsync();
var timeoutTokenSource = new CancellationTokenSource(_timeout);
var result = await _service.CheckHealthAsync(timeoutTokenSource.Token);
var status = result.CheckStatus;
if (status != CheckStatus.Healthy)
@ -60,7 +66,9 @@ namespace Microsoft.AspNetCore.HealthChecks
}
if (context.Request.Path == _path)
{
return true;
}
return false;
}


+ 11
- 4
src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckStartupFilter.cs View File

@ -11,15 +11,18 @@ namespace Microsoft.AspNetCore.HealthChecks
{
private string _path;
private int? _port;
private TimeSpan _timeout;
public HealthCheckStartupFilter(int port)
public HealthCheckStartupFilter(int port, TimeSpan timeout)
{
_port = port;
_timeout = timeout;
}
public HealthCheckStartupFilter(string path)
public HealthCheckStartupFilter(string path, TimeSpan timeout)
{
_path = path;
_timeout = timeout;
}
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
@ -27,9 +30,13 @@ namespace Microsoft.AspNetCore.HealthChecks
return app =>
{
if (_port.HasValue)
app.UseMiddleware<HealthCheckMiddleware>(_port);
{
app.UseMiddleware<HealthCheckMiddleware>(_port, _timeout);
}
else
app.UseMiddleware<HealthCheckMiddleware>(_path);
{
app.UseMiddleware<HealthCheckMiddleware>(_path, _timeout);
}
next(app);
};


+ 16
- 5
src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckWebHostBuilderExtension.cs View File

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.HealthChecks;
using Microsoft.Extensions.DependencyInjection;
@ -8,28 +9,38 @@ namespace Microsoft.AspNetCore.Hosting
{
public static class HealthCheckWebHostBuilderExtension
{
public static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(10);
public static IWebHostBuilder UseHealthChecks(this IWebHostBuilder builder, int port)
=> UseHealthChecks(builder, port, DefaultTimeout);
public static IWebHostBuilder UseHealthChecks(this IWebHostBuilder builder, int port, TimeSpan timeout)
{
Guard.ArgumentValid(port > 0 && port < 65536, nameof(port), "Port must be a value between 1 and 65535");
Guard.ArgumentValid(port > 0 && port < 65536, nameof(port), "Port must be a value between 1 and 65535.");
Guard.ArgumentValid(timeout > TimeSpan.Zero, nameof(timeout), "Health check timeout must be a positive time span.");
builder.ConfigureServices(services =>
{
var existingUrl = builder.GetSetting(WebHostDefaults.ServerUrlsKey);
builder.UseSetting(WebHostDefaults.ServerUrlsKey, $"{existingUrl};http://localhost:{port}");
services.AddSingleton<IStartupFilter>(new HealthCheckStartupFilter(port));
services.AddSingleton<IStartupFilter>(new HealthCheckStartupFilter(port, timeout));
});
return builder;
}
public static IWebHostBuilder UseHealthChecks(this IWebHostBuilder builder, string path)
=> UseHealthChecks(builder, path, DefaultTimeout);
public static IWebHostBuilder UseHealthChecks(this IWebHostBuilder builder, string path, TimeSpan timeout)
{
Guard.ArgumentNotNull(nameof(path), path);
// REVIEW: Is there a better URL path validator somewhere?
Guard.ArgumentValid(!path.Contains("?"), nameof(path), "Path cannot contain query string values");
Guard.ArgumentValid(path.StartsWith("/"), nameof(path), "Path should start with /");
Guard.ArgumentValid(!path.Contains("?"), nameof(path), "Path cannot contain query string values.");
Guard.ArgumentValid(path.StartsWith("/"), nameof(path), "Path should start with '/'.");
Guard.ArgumentValid(timeout > TimeSpan.Zero, nameof(timeout), "Health check timeout must be a positive time span.");
builder.ConfigureServices(services => services.AddSingleton<IStartupFilter>(new HealthCheckStartupFilter(path)));
builder.ConfigureServices(services => services.AddSingleton<IStartupFilter>(new HealthCheckStartupFilter(path, timeout)));
return builder;
}
}


src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.Data/HealthCheckBuilderDataExtensions.cs → src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.SqlServer/HealthCheckBuilderSqlServerExtensions.cs View File

@ -7,7 +7,7 @@ using System.Data.SqlClient;
namespace Microsoft.Extensions.HealthChecks
{
public static class HealthCheckBuilderDataExtensions
public static class HealthCheckBuilderSqlServerExtensions
{
public static HealthCheckBuilder AddSqlCheck(this HealthCheckBuilder builder, string name, string connectionString)
{
@ -33,7 +33,7 @@ namespace Microsoft.Extensions.HealthChecks
}
}
}
catch(Exception ex)
catch (Exception ex)
{
return HealthCheckResult.Unhealthy($"SqlCheck({name}): Exception during check: {ex.GetType().FullName}");
}

src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.Data/Microsoft.Extensions.HealthChecks.Data.csproj → src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.SqlServer/Microsoft.Extensions.HealthChecks.SqlServer.csproj View File


+ 109
- 0
src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CachedHealthCheck.cs View File

@ -0,0 +1,109 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.Extensions.HealthChecks
{
public abstract class CachedHealthCheck
{
private static readonly TypeInfo HealthCheckTypeInfo = typeof(IHealthCheck).GetTypeInfo();
private volatile int _writerCount;
public CachedHealthCheck(string name, TimeSpan cacheDuration)
{
Guard.ArgumentNotNullOrEmpty(nameof(name), name);
Guard.ArgumentValid(cacheDuration.TotalMilliseconds >= 0, nameof(cacheDuration), "Cache duration must be zero (disabled) or greater than zero.");
Name = name;
CacheDuration = cacheDuration;
}
public IHealthCheckResult CachedResult { get; internal set; }
public TimeSpan CacheDuration { get; }
public DateTimeOffset CacheExpiration { get; internal set; }
public string Name { get; }
protected virtual DateTimeOffset UtcNow => DateTimeOffset.UtcNow;
protected abstract IHealthCheck Resolve(IServiceProvider serviceProvider);
public async ValueTask<IHealthCheckResult> RunAsync(IServiceProvider serviceProvider, CancellationToken cancellationToken = default(CancellationToken))
{
while (CacheExpiration <= UtcNow)
{
// Can't use a standard lock here because of async, so we'll use this flag to determine when we should write a value,
// and the waiters who aren't allowed to write will just spin wait for the new value.
if (Interlocked.Exchange(ref _writerCount, 1) != 0)
{
await Task.Delay(5, cancellationToken).ConfigureAwait(false);
continue;
}
try
{
var check = Resolve(serviceProvider);
CachedResult = await check.CheckAsync(cancellationToken);
}
catch (OperationCanceledException)
{
CachedResult = HealthCheckResult.Unhealthy("The health check operation timed out");
}
catch (Exception ex)
{
CachedResult = HealthCheckResult.Unhealthy($"Exception during check: {ex.GetType().FullName}");
}
CacheExpiration = UtcNow + CacheDuration;
_writerCount = 0;
break;
}
return CachedResult;
}
public static CachedHealthCheck FromHealthCheck(string name, TimeSpan cacheDuration, IHealthCheck healthCheck)
{
Guard.ArgumentNotNull(nameof(healthCheck), healthCheck);
return new TypeOrHealthCheck_HealthCheck(name, cacheDuration, healthCheck);
}
public static CachedHealthCheck FromType(string name, TimeSpan cacheDuration, Type healthCheckType)
{
Guard.ArgumentNotNull(nameof(healthCheckType), healthCheckType);
Guard.ArgumentValid(HealthCheckTypeInfo.IsAssignableFrom(healthCheckType.GetTypeInfo()), nameof(healthCheckType), $"Health check must implement '{typeof(IHealthCheck).FullName}'.");
return new TypeOrHealthCheck_Type(name, cacheDuration, healthCheckType);
}
class TypeOrHealthCheck_HealthCheck : CachedHealthCheck
{
private readonly IHealthCheck _healthCheck;
public TypeOrHealthCheck_HealthCheck(string name, TimeSpan cacheDuration, IHealthCheck healthCheck) : base(name, cacheDuration)
=> _healthCheck = healthCheck;
protected override IHealthCheck Resolve(IServiceProvider serviceProvider) => _healthCheck;
}
class TypeOrHealthCheck_Type : CachedHealthCheck
{
private readonly Type _healthCheckType;
public TypeOrHealthCheck_Type(string name, TimeSpan cacheDuration, Type healthCheckType) : base(name, cacheDuration)
=> _healthCheckType = healthCheckType;
protected override IHealthCheck Resolve(IServiceProvider serviceProvider)
=> (IHealthCheck)serviceProvider.GetRequiredService(_healthCheckType);
}
}
}

+ 19
- 0
src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CachedHealthCheckExtensions.cs View File

@ -0,0 +1,19 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Extensions.HealthChecks
{
public static class CachedHealthCheckExtensions
{
public static ValueTask<IHealthCheckResult> RunAsync(this CachedHealthCheck check, IServiceProvider serviceProvider)
{
Guard.ArgumentNotNull(nameof(check), check);
return check.RunAsync(serviceProvider, CancellationToken.None);
}
}
}

+ 30
- 24
src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/AddCheck.cs View File

@ -15,96 +15,102 @@ namespace Microsoft.Extensions.HealthChecks
{
Guard.ArgumentNotNull(nameof(builder), builder);
builder.AddCheck(name, HealthCheck.FromCheck(check, builder.DefaultCacheDuration));
return builder;
return builder.AddCheck(name, HealthCheck.FromCheck(check), builder.DefaultCacheDuration);
}
public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func<CancellationToken, IHealthCheckResult> check)
{
Guard.ArgumentNotNull(nameof(builder), builder);
builder.AddCheck(name, HealthCheck.FromCheck(check, builder.DefaultCacheDuration));
return builder;
return builder.AddCheck(name, HealthCheck.FromCheck(check), builder.DefaultCacheDuration);
}
public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func<IHealthCheckResult> check, TimeSpan cacheDuration)
{
Guard.ArgumentNotNull(nameof(builder), builder);
builder.AddCheck(name, HealthCheck.FromCheck(check, cacheDuration));
return builder;
return builder.AddCheck(name, HealthCheck.FromCheck(check), cacheDuration);
}
public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func<CancellationToken, IHealthCheckResult> check, TimeSpan cacheDuration)
{
Guard.ArgumentNotNull(nameof(builder), builder);
builder.AddCheck(name, HealthCheck.FromCheck(check, cacheDuration));
return builder;
return builder.AddCheck(name, HealthCheck.FromCheck(check), cacheDuration);
}
public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func<Task<IHealthCheckResult>> check)
{
Guard.ArgumentNotNull(nameof(builder), builder);
builder.AddCheck(name, HealthCheck.FromTaskCheck(check, builder.DefaultCacheDuration));
return builder;
return builder.AddCheck(name, HealthCheck.FromTaskCheck(check), builder.DefaultCacheDuration);
}
public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func<CancellationToken, Task<IHealthCheckResult>> check)
{
Guard.ArgumentNotNull(nameof(builder), builder);
builder.AddCheck(name, HealthCheck.FromTaskCheck(check, builder.DefaultCacheDuration));
return builder;
return builder.AddCheck(name, HealthCheck.FromTaskCheck(check), builder.DefaultCacheDuration);
}
public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func<Task<IHealthCheckResult>> check, TimeSpan cacheDuration)
{
Guard.ArgumentNotNull(nameof(builder), builder);
builder.AddCheck(name, HealthCheck.FromTaskCheck(check, cacheDuration));
return builder;
return builder.AddCheck(name, HealthCheck.FromTaskCheck(check), cacheDuration);
}
public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func<CancellationToken, Task<IHealthCheckResult>> check, TimeSpan cacheDuration)
{
Guard.ArgumentNotNull(nameof(builder), builder);
builder.AddCheck(name, HealthCheck.FromTaskCheck(check, cacheDuration));
return builder;
return builder.AddCheck(name, HealthCheck.FromTaskCheck(check), cacheDuration);
}
public static HealthCheckBuilder AddValueTaskCheck(this HealthCheckBuilder builder, string name, Func<ValueTask<IHealthCheckResult>> check)
{
Guard.ArgumentNotNull(nameof(builder), builder);
builder.AddCheck(name, HealthCheck.FromValueTaskCheck(check, builder.DefaultCacheDuration));
return builder;
return builder.AddCheck(name, HealthCheck.FromValueTaskCheck(check), builder.DefaultCacheDuration);
}
public static HealthCheckBuilder AddValueTaskCheck(this HealthCheckBuilder builder, string name, Func<CancellationToken, ValueTask<IHealthCheckResult>> check)
{
Guard.ArgumentNotNull(nameof(builder), builder);
builder.AddCheck(name, HealthCheck.FromValueTaskCheck(check, builder.DefaultCacheDuration));
return builder;
return builder.AddCheck(name, HealthCheck.FromValueTaskCheck(check), builder.DefaultCacheDuration);
}
public static HealthCheckBuilder AddValueTaskCheck(this HealthCheckBuilder builder, string name, Func<ValueTask<IHealthCheckResult>> check, TimeSpan cacheDuration)
{
Guard.ArgumentNotNull(nameof(builder), builder);
builder.AddCheck(name, HealthCheck.FromValueTaskCheck(check, cacheDuration));
return builder;
return builder.AddCheck(name, HealthCheck.FromValueTaskCheck(check), cacheDuration);
}
public static HealthCheckBuilder AddValueTaskCheck(this HealthCheckBuilder builder, string name, Func<CancellationToken, ValueTask<IHealthCheckResult>> check, TimeSpan cacheDuration)
{
Guard.ArgumentNotNull(nameof(builder), builder);
builder.AddCheck(name, HealthCheck.FromValueTaskCheck(check, cacheDuration));
return builder;
return builder.AddCheck(name, HealthCheck.FromValueTaskCheck(check), cacheDuration);
}
// IHealthCheck versions of AddCheck
public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string checkName, IHealthCheck check)
{
Guard.ArgumentNotNull(nameof(builder), builder);
return builder.AddCheck(checkName, check, builder.DefaultCacheDuration);
}
// Type versions of AddCheck
public static HealthCheckBuilder AddCheck<TCheck>(this HealthCheckBuilder builder, string name) where TCheck : class, IHealthCheck
{
Guard.ArgumentNotNull(nameof(builder), builder);
return builder.AddCheck<TCheck>(name, builder.DefaultCacheDuration);
}
}
}

+ 5
- 5
src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/NumericChecks.cs View File

@ -14,7 +14,7 @@ namespace Microsoft.Extensions.HealthChecks
where T : IComparable<T>
{
Guard.ArgumentNotNull(nameof(builder), builder);
Guard.ArgumentNotNullOrWhitespace(nameof(name), name);
Guard.ArgumentNotNullOrEmpty(nameof(name), name);
Guard.ArgumentNotNull(nameof(currentValueFunc), currentValueFunc);
builder.AddCheck(name, () =>
@ -23,7 +23,7 @@ namespace Microsoft.Extensions.HealthChecks
var status = currentValue.CompareTo(minValue) >= 0 ? CheckStatus.Healthy : CheckStatus.Unhealthy;
return HealthCheckResult.FromStatus(
status,
$"{name}: min={minValue}, current={currentValue}",
$"min={minValue}, current={currentValue}",
new Dictionary<string, object> { { "min", minValue }, { "current", currentValue } }
);
});
@ -35,16 +35,16 @@ namespace Microsoft.Extensions.HealthChecks
where T : IComparable<T>
{
Guard.ArgumentNotNull(nameof(builder), builder);
Guard.ArgumentNotNullOrWhitespace(nameof(name), name);
Guard.ArgumentNotNullOrEmpty(nameof(name), name);
Guard.ArgumentNotNull(nameof(currentValueFunc), currentValueFunc);
builder.AddCheck($"{name}", () =>
builder.AddCheck(name, () =>
{
var currentValue = currentValueFunc();
var status = currentValue.CompareTo(maxValue) <= 0 ? CheckStatus.Healthy : CheckStatus.Unhealthy;
return HealthCheckResult.FromStatus(
status,
$"{name}: max={maxValue}, current={currentValue}",
$"max={maxValue}, current={currentValue}",
new Dictionary<string, object> { { "max", maxValue }, { "current", currentValue } }
);
});


+ 1
- 64
src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/UrlChecks.cs View File

@ -2,8 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.HealthChecks.Internal;
@ -37,73 +35,12 @@ namespace Microsoft.Extensions.HealthChecks
Func<HttpResponseMessage, ValueTask<IHealthCheckResult>> checkFunc)
{
Guard.ArgumentNotNull(nameof(builder), builder);
Guard.ArgumentNotNullOrWhitespace(nameof(url), url);
Guard.ArgumentNotNullOrEmpty(nameof(url), url);
Guard.ArgumentNotNull(nameof(checkFunc), checkFunc);
var urlCheck = new UrlChecker(checkFunc, url);
builder.AddCheck($"UrlCheck({url})", () => urlCheck.CheckAsync());
return builder;
}
public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable<string> urlItems, string groupName)
=> AddUrlChecks(builder, urlItems, groupName, CheckStatus.Warning, response => UrlChecker.DefaultUrlCheck(response));
public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable<string> urlItems, string groupName,
Func<HttpResponseMessage, IHealthCheckResult> checkFunc)
{
Guard.ArgumentNotNull(nameof(checkFunc), checkFunc);
return AddUrlChecks(builder, urlItems, groupName, CheckStatus.Warning, response => new ValueTask<IHealthCheckResult>(checkFunc(response)));
}
public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable<string> urlItems, string groupName,
Func<HttpResponseMessage, Task<IHealthCheckResult>> checkFunc)
{
Guard.ArgumentNotNull(nameof(checkFunc), checkFunc);
return AddUrlChecks(builder, urlItems, groupName, CheckStatus.Warning, response => new ValueTask<IHealthCheckResult>(checkFunc(response)));
}
public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable<string> urlItems, string groupName,
Func<HttpResponseMessage, ValueTask<IHealthCheckResult>> checkFunc)
{
Guard.ArgumentNotNull(nameof(checkFunc), checkFunc);
return AddUrlChecks(builder, urlItems, groupName, CheckStatus.Warning, response => checkFunc(response));
}
public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable<string> urlItems, string groupName,
CheckStatus partialSuccessStatus)
=> AddUrlChecks(builder, urlItems, groupName, partialSuccessStatus, response => UrlChecker.DefaultUrlCheck(response));
public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable<string> urlItems, string groupName,
CheckStatus partialSuccessStatus, Func<HttpResponseMessage, IHealthCheckResult> checkFunc)
{
Guard.ArgumentNotNull(nameof(checkFunc), checkFunc);
return AddUrlChecks(builder, urlItems, groupName, partialSuccessStatus, response => new ValueTask<IHealthCheckResult>(checkFunc(response)));
}
public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable<string> urlItems, string groupName,
CheckStatus partialSuccessStatus, Func<HttpResponseMessage, Task<IHealthCheckResult>> checkFunc)
{
Guard.ArgumentNotNull(nameof(checkFunc), checkFunc);
return AddUrlChecks(builder, urlItems, groupName, partialSuccessStatus, response => new ValueTask<IHealthCheckResult>(checkFunc(response)));
}
public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable<string> urlItems, string groupName,
CheckStatus partialSuccessStatus, Func<HttpResponseMessage, ValueTask<IHealthCheckResult>> checkFunc)
{
var urls = urlItems?.ToArray();
Guard.ArgumentNotNull(nameof(builder), builder);
Guard.ArgumentNotNullOrEmpty(nameof(urlItems), urls);
Guard.ArgumentNotNullOrWhitespace(nameof(groupName), groupName);
var urlChecker = new UrlChecker(checkFunc, urls) { PartiallyHealthyStatus = partialSuccessStatus };
builder.AddCheck($"UrlChecks({groupName})", () => urlChecker.CheckAsync());
return builder;
}
}
}

+ 11
- 8
src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CompositeHealthCheckResult.cs View File

@ -7,7 +7,6 @@ using System.Linq;
namespace Microsoft.Extensions.HealthChecks
{
// REVIEW: Does this need to be thread safe?
/// <summary>
/// Represents a composite health check result built from several results.
/// </summary>
@ -31,17 +30,23 @@ namespace Microsoft.Extensions.HealthChecks
{
var checkStatuses = new HashSet<CheckStatus>(_results.Select(x => x.Value.CheckStatus));
if (checkStatuses.Count == 0)
{
return _initialStatus;
}
if (checkStatuses.Count == 1)
{
return checkStatuses.First();
}
if (checkStatuses.Contains(CheckStatus.Healthy))
{
return _partiallyHealthyStatus;
}
return CheckStatus.Unhealthy;
}
}
public string Description => string.Join(Environment.NewLine, _results.Select(r => r.Value.Description));
public string Description => string.Join(Environment.NewLine, _results.Select(r => $"{r.Key}: {r.Value.Description}"));
public IReadOnlyDictionary<string, object> Data
{
@ -58,23 +63,21 @@ namespace Microsoft.Extensions.HealthChecks
public IReadOnlyDictionary<string, IHealthCheckResult> Results => _results;
// REVIEW: Should description be required? Seems redundant for success checks.
public void Add(string name, CheckStatus status, string description)
=> Add(name, status, description, null);
public void Add(string name, CheckStatus status, string description, Dictionary<string, object> data)
{
Guard.ArgumentNotNullOrWhitespace(nameof(name), name);
Guard.ArgumentValid(status != CheckStatus.Unknown, nameof(status), "Cannot add unknown status to composite health check result");
Guard.ArgumentNotNullOrWhitespace(nameof(description), description);
Guard.ArgumentNotNullOrEmpty(nameof(name), name);
Guard.ArgumentValid(status != CheckStatus.Unknown, nameof(status), "Cannot add 'Unknown' status to composite health check result.");
Guard.ArgumentNotNullOrEmpty(nameof(description), description);
_results.Add(name, HealthCheckResult.FromStatus(status, description, data));
}
public void Add(string name, IHealthCheckResult checkResult)
{
Guard.ArgumentNotNullOrWhitespace(nameof(name), name);
Guard.ArgumentNotNullOrEmpty(nameof(name), name);
Guard.ArgumentNotNull(nameof(checkResult), checkResult);
_results.Add(name, checkResult);


+ 15
- 49
src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheck.cs View File

@ -9,68 +9,34 @@ namespace Microsoft.Extensions.HealthChecks
{
public class HealthCheck : IHealthCheck
{
private DateTimeOffset _cacheExpiration;
private IHealthCheckResult _cachedResult;
private volatile int _writerCount;
protected HealthCheck(Func<CancellationToken, ValueTask<IHealthCheckResult>> check, TimeSpan cacheDuration)
protected HealthCheck(Func<CancellationToken, ValueTask<IHealthCheckResult>> check)
{
Guard.ArgumentNotNull(nameof(check), check);
Guard.ArgumentValid(cacheDuration >= TimeSpan.Zero, nameof(cacheDuration), "Cache duration must either be zero (disabled) or a positive value");
Check = check;
CacheDuration = cacheDuration;
}
public TimeSpan CacheDuration { get; }
protected Func<CancellationToken, ValueTask<IHealthCheckResult>> Check { get; }
protected virtual DateTimeOffset UtcNow => DateTimeOffset.UtcNow;
public async ValueTask<IHealthCheckResult> CheckAsync(CancellationToken cancellationToken)
{
while (_cacheExpiration <= UtcNow)
{
// Can't use a standard lock here because of async, so we'll use this flag to determine when we should write a value,
// and the waiters who aren't allowed to write will just spin wait for the new value.
if (Interlocked.Exchange(ref _writerCount, 1) != 0)
{
await Task.Delay(5, cancellationToken).ConfigureAwait(false);
continue;
}
try
{
_cachedResult = await Check(cancellationToken).ConfigureAwait(false);
_cacheExpiration = UtcNow + CacheDuration;
break;
}
finally
{
_writerCount = 0;
}
}
return _cachedResult;
}
public ValueTask<IHealthCheckResult> CheckAsync(CancellationToken cancellationToken = default(CancellationToken))
=> Check(cancellationToken);
public static HealthCheck FromCheck(Func<IHealthCheckResult> check, TimeSpan cacheDuration)
=> new HealthCheck(token => new ValueTask<IHealthCheckResult>(check()), cacheDuration);
public static HealthCheck FromCheck(Func<IHealthCheckResult> check)
=> new HealthCheck(token => new ValueTask<IHealthCheckResult>(check()));
public static HealthCheck FromCheck(Func<CancellationToken, IHealthCheckResult> check, TimeSpan cacheDuration)
=> new HealthCheck(token => new ValueTask<IHealthCheckResult>(check(token)), cacheDuration);
public static HealthCheck FromCheck(Func<CancellationToken, IHealthCheckResult> check)
=> new HealthCheck(token => new ValueTask<IHealthCheckResult>(check(token)));
public static HealthCheck FromTaskCheck(Func<Task<IHealthCheckResult>> check, TimeSpan cacheDuration)
=> new HealthCheck(token => new ValueTask<IHealthCheckResult>(check()), cacheDuration);
public static HealthCheck FromTaskCheck(Func<Task<IHealthCheckResult>> check)
=> new HealthCheck(token => new ValueTask<IHealthCheckResult>(check()));
public static HealthCheck FromTaskCheck(Func<CancellationToken, Task<IHealthCheckResult>> check, TimeSpan cacheDuration)
=> new HealthCheck(token => new ValueTask<IHealthCheckResult>(check(token)), cacheDuration);
public static HealthCheck FromTaskCheck(Func<CancellationToken, Task<IHealthCheckResult>> check)
=> new HealthCheck(token => new ValueTask<IHealthCheckResult>(check(token)));
public static HealthCheck FromValueTaskCheck(Func<ValueTask<IHealthCheckResult>> check, TimeSpan cacheDuration)
=> new HealthCheck(token => check(), cacheDuration);
public static HealthCheck FromValueTaskCheck(Func<ValueTask<IHealthCheckResult>> check)
=> new HealthCheck(token => check());
public static HealthCheck FromValueTaskCheck(Func<CancellationToken, ValueTask<IHealthCheckResult>> check, TimeSpan cacheDuration)
=> new HealthCheck(check, cacheDuration);
public static HealthCheck FromValueTaskCheck(Func<CancellationToken, ValueTask<IHealthCheckResult>> check)
=> new HealthCheck(check);
}
}

+ 102
- 7
src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckBuilder.cs View File

@ -8,33 +8,128 @@ namespace Microsoft.Extensions.HealthChecks
{
public class HealthCheckBuilder
{
private readonly Dictionary<string, IHealthCheck> _checks;
private readonly Dictionary<string, CachedHealthCheck> _checksByName;
private readonly HealthCheckGroup _currentGroup;
private readonly Dictionary<string, HealthCheckGroup> _groups;
public HealthCheckBuilder()
{
_checks = new Dictionary<string, IHealthCheck>(StringComparer.OrdinalIgnoreCase);
_checksByName = new Dictionary<string, CachedHealthCheck>(StringComparer.OrdinalIgnoreCase);
_currentGroup = new HealthCheckGroup(string.Empty, CheckStatus.Unhealthy);
_groups = new Dictionary<string, HealthCheckGroup>(StringComparer.OrdinalIgnoreCase)
{
[string.Empty] = _currentGroup
};
DefaultCacheDuration = TimeSpan.FromMinutes(5);
}
public IReadOnlyDictionary<string, IHealthCheck> Checks => _checks;
/// <summary>
/// This constructor should only be used when creating a grouped health check builder.
/// </summary>
public HealthCheckBuilder(HealthCheckBuilder rootBuilder, HealthCheckGroup currentGroup)
{
Guard.ArgumentNotNull(nameof(rootBuilder), rootBuilder);
Guard.ArgumentNotNull(nameof(currentGroup), currentGroup);
_checksByName = rootBuilder._checksByName;
_currentGroup = currentGroup;
_groups = rootBuilder._groups;
DefaultCacheDuration = rootBuilder.DefaultCacheDuration;
}
/// <summary>
/// Gets the registered checks, indexed by check name.
/// </summary>
public IReadOnlyDictionary<string, CachedHealthCheck> ChecksByName => _checksByName;
/// <summary>
/// Gets the current default cache duration used when registering checks.
/// </summary>
public TimeSpan DefaultCacheDuration { get; private set; }
public HealthCheckBuilder AddCheck(string name, IHealthCheck check)
/// <summary>
/// Gets the registered groups, indexed by group name. The root group's name is <see cref="string.Empty"/>.
/// </summary>
public IReadOnlyDictionary<string, HealthCheckGroup> Groups => _groups;
/// <summary>
/// Registers a health check type that will later be resolved via dependency
/// injection.
/// </summary>
public HealthCheckBuilder AddCheck<TCheck>(string checkName, TimeSpan cacheDuration) where TCheck : class, IHealthCheck
{
Guard.ArgumentNotNullOrWhitespace(nameof(name), name);
Guard.ArgumentNotNullOrEmpty(nameof(checkName), checkName);
Guard.ArgumentValid(!_checksByName.ContainsKey(checkName), nameof(checkName), $"A check with name '{checkName}' has already been registered.");
var namedCheck = CachedHealthCheck.FromType(checkName, cacheDuration, typeof(TCheck));
_checksByName.Add(checkName, namedCheck);
_currentGroup.ChecksInternal.Add(namedCheck);
return this;
}
/// <summary>
/// Registers a concrete health check to the builder.
/// </summary>
public HealthCheckBuilder AddCheck(string checkName, IHealthCheck check, TimeSpan cacheDuration)
{
Guard.ArgumentNotNullOrEmpty(nameof(checkName), checkName);
Guard.ArgumentNotNull(nameof(check), check);
Guard.ArgumentValid(!_checksByName.ContainsKey(checkName), nameof(checkName), $"A check with name '{checkName}' has already been registered.");
var namedCheck = CachedHealthCheck.FromHealthCheck(checkName, cacheDuration, check);
_checksByName.Add(checkName, namedCheck);
_currentGroup.ChecksInternal.Add(namedCheck);
return this;
}
/// <summary>
/// Creates a new health check group, to which you can add one or more health
/// checks. Uses <see cref="CheckStatus.Unhealthy"/> when the group is
/// partially successful.
/// </summary>
public HealthCheckBuilder AddHealthCheckGroup(string groupName, Action<HealthCheckBuilder> groupChecks)
=> AddHealthCheckGroup(groupName, groupChecks, CheckStatus.Unhealthy);
/// <summary>
/// Creates a new health check group, to which you can add one or more health
/// checks.
/// </summary>
public HealthCheckBuilder AddHealthCheckGroup(string groupName, Action<HealthCheckBuilder> groupChecks, CheckStatus partialSuccessStatus)
{
Guard.ArgumentNotNullOrEmpty(nameof(groupName), groupName);
Guard.ArgumentNotNull(nameof(groupChecks), groupChecks);
Guard.ArgumentValid(partialSuccessStatus != CheckStatus.Unknown, nameof(partialSuccessStatus), "Check status 'Unknown' is not valid for partial success.");
Guard.ArgumentValid(!_groups.ContainsKey(groupName), nameof(groupName), $"A group with name '{groupName}' has already been registered.");
Guard.OperationValid(_currentGroup.GroupName == string.Empty, "Nested groups are not supported by HealthCheckBuilder.");
var group = new HealthCheckGroup(groupName, partialSuccessStatus);
_groups.Add(groupName, group);
var innerBuilder = new HealthCheckBuilder(this, group);
groupChecks(innerBuilder);
_checks.Add(name, check);
return this;
}
public HealthCheckBuilder WithDefaultCacheDuration(TimeSpan duration)
{
Guard.ArgumentValid(duration >= TimeSpan.Zero, nameof(duration), "Duration must be zero (disabled) or a positive duration");
Guard.ArgumentValid(duration >= TimeSpan.Zero, nameof(duration), "Duration must be zero (disabled) or a positive duration.");
DefaultCacheDuration = duration;
return this;
}
public HealthCheckBuilder WithPartialSuccessStatus(CheckStatus partiallyHealthyStatus)
{
_currentGroup.PartiallyHealthyStatus = partiallyHealthyStatus;
return this;
}
}
}

+ 0
- 18
src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckExtensions.cs View File

@ -1,18 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Extensions.HealthChecks
{
public static class HealthCheckExtensions
{
public static ValueTask<IHealthCheckResult> CheckAsync(this IHealthCheck healthCheck)
{
Guard.ArgumentNotNull(nameof(healthCheck), healthCheck);
return healthCheck.CheckAsync(CancellationToken.None);
}
}
}

+ 37
- 0
src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckGroup.cs View File

@ -0,0 +1,37 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
namespace Microsoft.Extensions.HealthChecks
{
public class HealthCheckGroup
{
private CheckStatus _partialSuccessStatus;
public HealthCheckGroup(string groupName, CheckStatus partialSuccessStatus)
{
Guard.ArgumentNotNull(nameof(groupName), groupName);
GroupName = groupName;
PartiallyHealthyStatus = partialSuccessStatus;
}
public IReadOnlyList<CachedHealthCheck> Checks => ChecksInternal.AsReadOnly();
internal List<CachedHealthCheck> ChecksInternal { get; } = new List<CachedHealthCheck>();
public string GroupName { get; }
public CheckStatus PartiallyHealthyStatus
{
get => _partialSuccessStatus;
internal set
{
Guard.ArgumentValid(value != CheckStatus.Unknown, nameof(value), "Check status 'Unknown' is not valid for partial success.");
_partialSuccessStatus = value;
}
}
}
}

+ 0
- 1
src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckResult.cs View File

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Linq;
namespace Microsoft.Extensions.HealthChecks
{


+ 91
- 26
src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckService.cs View File

@ -3,52 +3,117 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.Extensions.HealthChecks
{
public class HealthCheckService : IHealthCheckService
{
public IReadOnlyDictionary<string, IHealthCheck> _checks;
private readonly HealthCheckBuilder _builder;
private readonly IReadOnlyList<HealthCheckGroup> _groups;
private readonly HealthCheckGroup _root;
private readonly IServiceProvider _serviceProvider;
private readonly IServiceScopeFactory _serviceScopeFactory;
private ILogger<HealthCheckService> _logger;
public HealthCheckService(HealthCheckBuilder builder, ILogger<HealthCheckService> logger)
public HealthCheckService(HealthCheckBuilder builder, IServiceProvider serviceProvider, IServiceScopeFactory serviceScopeFactory)
{
_checks = builder.Checks;
_logger = logger;
_builder = builder;
_groups = GetGroups().Where(group => group.GroupName != string.Empty).ToList();
_root = GetGroup(string.Empty);
_serviceProvider = serviceProvider;
_serviceScopeFactory = serviceScopeFactory;
}
public async Task<CompositeHealthCheckResult> CheckHealthAsync(CheckStatus partiallyHealthyStatus, CancellationToken cancellationToken)
public async Task<CompositeHealthCheckResult> CheckHealthAsync(CancellationToken cancellationToken = default(CancellationToken))
{
var logMessage = new StringBuilder();
var result = new CompositeHealthCheckResult(partiallyHealthyStatus);
foreach (var check in _checks)
using (var scope = GetServiceScope())
{
try
{
var healthCheckResult = await check.Value.CheckAsync().ConfigureAwait(false);
logMessage.AppendLine($"HealthCheck: {check.Key} : {healthCheckResult.CheckStatus}");
result.Add(check.Key, healthCheckResult);
}
catch (Exception ex)
var scopeServiceProvider = scope.ServiceProvider;
var groupTasks = _groups.Select(group => new { Group = group, Task = RunGroupAsync(scopeServiceProvider, group, cancellationToken) }).ToList();
var result = await RunGroupAsync(scopeServiceProvider, _root, cancellationToken).ConfigureAwait(false);
await Task.WhenAll(groupTasks.Select(x => x.Task));
foreach (var groupTask in groupTasks)
{
logMessage.AppendLine($"HealthCheck: {check.Key} : Exception {ex.GetType().FullName} thrown");
result.Add(check.Key, CheckStatus.Unhealthy, $"Exception during check: {ex.GetType().FullName}");
result.Add($"Group({groupTask.Group.GroupName})", groupTask.Task.Result);
}
return result;
}
}
public IReadOnlyList<CachedHealthCheck> GetAllChecks()
=> _builder.ChecksByName.Values.ToList().AsReadOnly();
if (logMessage.Length == 0)
logMessage.AppendLine("HealthCheck: No checks have been registered");
public CachedHealthCheck GetCheck(string checkName)
=> _builder.ChecksByName[checkName];
public HealthCheckGroup GetGroup(string groupName)
=> _builder.Groups[groupName];
public IReadOnlyList<HealthCheckGroup> GetGroups()
=> _builder.Groups.Values.ToList().AsReadOnly();
private IServiceScope GetServiceScope()
=> _serviceScopeFactory == null ? new UnscopedServiceProvider(_serviceProvider) : _serviceScopeFactory.CreateScope();
public async ValueTask<IHealthCheckResult> RunCheckAsync(CachedHealthCheck healthCheck, CancellationToken cancellationToken = default(CancellationToken))
{
using (var scope = GetServiceScope())
{
return await RunCheckAsync(scope.ServiceProvider, healthCheck, cancellationToken).ConfigureAwait(false);
}
}
/// <summary>
/// Uses the provided service provider and executes the provided check.
/// </summary>
public ValueTask<IHealthCheckResult> RunCheckAsync(IServiceProvider serviceProvider, CachedHealthCheck healthCheck, CancellationToken cancellationToken = default(CancellationToken))
{
Guard.ArgumentNotNull(nameof(serviceProvider), serviceProvider);
Guard.ArgumentNotNull(nameof(healthCheck), healthCheck);
return healthCheck.RunAsync(serviceProvider, cancellationToken);
}
/// <summary>
/// Creates a new resolution scope from the default service provider and executes the checks in the given group.
/// </summary>
public async Task<CompositeHealthCheckResult> RunGroupAsync(HealthCheckGroup group, CancellationToken cancellationToken = default(CancellationToken))
{
using (var scope = GetServiceScope())
return await RunGroupAsync(scope.ServiceProvider, group, cancellationToken).ConfigureAwait(false);
}
/// <summary>
/// Uses the provided service provider and executes the checks in the given group.
/// </summary>
public async Task<CompositeHealthCheckResult> RunGroupAsync(IServiceProvider serviceProvider, HealthCheckGroup group, CancellationToken cancellationToken = default(CancellationToken))
{
var result = new CompositeHealthCheckResult(group.PartiallyHealthyStatus);
var checkTasks = group.Checks.Select(check => new { Check = check, Task = check.RunAsync(serviceProvider, cancellationToken).AsTask() }).ToList();
await Task.WhenAll(checkTasks.Select(checkTask => checkTask.Task));
foreach (var checkTask in checkTasks)
{
result.Add(checkTask.Check.Name, checkTask.Task.Result);
}
_logger.Log((result.CheckStatus == CheckStatus.Healthy ? LogLevel.Information : LogLevel.Error), 0, logMessage.ToString(), null, MessageFormatter);
return result;
}
private static string MessageFormatter(string state, Exception error) => state;
private class UnscopedServiceProvider : IServiceScope
{
public UnscopedServiceProvider(IServiceProvider serviceProvider)
=> ServiceProvider = serviceProvider;
public IServiceProvider ServiceProvider { get; }
public void Dispose() { }
}
}
}

+ 13
- 5
src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckServiceCollectionExtensions.cs View File

@ -2,20 +2,28 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Linq;
using Microsoft.Extensions.HealthChecks;
namespace Microsoft.Extensions.DependencyInjection
{
public static class HealthCheckServiceCollectionExtensions
{
public static IServiceCollection AddHealthChecks(this IServiceCollection services, Action<HealthCheckBuilder> checkupAction)
private static readonly Type HealthCheckServiceInterface = typeof(IHealthCheckService);
public static IServiceCollection AddHealthChecks(this IServiceCollection services, Action<HealthCheckBuilder> checks)
{
var checkupBuilder = new HealthCheckBuilder();
Guard.OperationValid(!services.Any(descriptor => descriptor.ServiceType == HealthCheckServiceInterface), "AddHealthChecks may only be called once.");
var builder = new HealthCheckBuilder();
checkupAction.Invoke(checkupBuilder);
services.AddSingleton<IHealthCheckService, HealthCheckService>(serviceProvider =>
{
var serviceScopeFactory = serviceProvider.GetService<IServiceScopeFactory>();
return new HealthCheckService(builder, serviceProvider, serviceScopeFactory);
});
services.AddSingleton(checkupBuilder);
services.AddSingleton<IHealthCheckService, HealthCheckService>();
checks(builder);
return services;
}
}


+ 0
- 38
src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckServiceExtensions.cs View File

@ -1,38 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Extensions.HealthChecks
{
public static class HealthCheckServiceExtensions
{
public static Task<CompositeHealthCheckResult> CheckHealthAsync(this IHealthCheckService service)
{
Guard.ArgumentNotNull(nameof(service), service);
return service.CheckHealthAsync(CheckStatus.Unhealthy, CancellationToken.None);
}
public static Task<CompositeHealthCheckResult> CheckHealthAsync(this IHealthCheckService service, CheckStatus partiallyHealthyStatus)
{
Guard.ArgumentNotNull(nameof(service), service);
return service.CheckHealthAsync(partiallyHealthyStatus, CancellationToken.None);
}
public static Task<CompositeHealthCheckResult> CheckHealthAsync(this IHealthCheckService service, CancellationToken cancellationToken)
{
Guard.ArgumentNotNull(nameof(service), service);
return service.CheckHealthAsync(CheckStatus.Unhealthy, cancellationToken);
}
public static Task<CompositeHealthCheckResult> CheckHealthAsync(this IHealthCheckService service, CheckStatus partiallyHealthyStatus, CancellationToken cancellationToken)
{
Guard.ArgumentNotNull(nameof(service), service);
return service.CheckHealthAsync(partiallyHealthyStatus, cancellationToken);
}
}
}

+ 1
- 4
src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/IHealthCheck.cs View File

@ -1,7 +1,6 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading;
using System.Threading.Tasks;
@ -9,8 +8,6 @@ namespace Microsoft.Extensions.HealthChecks
{
public interface IHealthCheck
{
TimeSpan CacheDuration { get; }
ValueTask<IHealthCheckResult> CheckAsync(CancellationToken cancellationToken);
ValueTask<IHealthCheckResult> CheckAsync(CancellationToken cancellationToken = default(CancellationToken));
}
}

+ 46
- 1
src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/IHealthCheckService.cs View File

@ -1,6 +1,8 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
@ -8,6 +10,49 @@ namespace Microsoft.Extensions.HealthChecks
{
public interface IHealthCheckService
{
Task<CompositeHealthCheckResult> CheckHealthAsync(CheckStatus partiallyHealthyStatus, CancellationToken cancellationToken);
/// <summary>
/// Runs all registered health checks.
/// </summary>
Task<CompositeHealthCheckResult> CheckHealthAsync(CancellationToken cancellationToken = default(CancellationToken));
/// <summary>
/// Gets all registered health checks as a flat list.
/// </summary>
IReadOnlyList<CachedHealthCheck> GetAllChecks();
/// <summary>
/// Gets a health check by name.
/// </summary>
CachedHealthCheck GetCheck(string checkName);
/// <summary>
/// Gets all health checks in a group.
/// </summary>
HealthCheckGroup GetGroup(string groupName);
/// <summary>
/// Gets all the health check groups.
/// </summary>
IReadOnlyList<HealthCheckGroup> GetGroups();
/// <summary>
/// Creates a new resolution scope from the default service provider and executes the provided check.
/// </summary>
ValueTask<IHealthCheckResult> RunCheckAsync(CachedHealthCheck healthCheck, CancellationToken cancellationToken = default(CancellationToken));
/// <summary>
/// Uses the provided service provider and executes the provided check.
/// </summary>
ValueTask<IHealthCheckResult> RunCheckAsync(IServiceProvider serviceProvider, CachedHealthCheck healthCheck, CancellationToken cancellationToken = default(CancellationToken));
/// <summary>
/// Creates a new resolution scope from the default service provider and executes the checks in the given group.
/// </summary>
Task<CompositeHealthCheckResult> RunGroupAsync(HealthCheckGroup group, CancellationToken cancellationToken = default(CancellationToken));
/// <summary>
/// Uses the provided service provider and executes the checks in the given group.
/// </summary>
Task<CompositeHealthCheckResult> RunGroupAsync(IServiceProvider serviceProvider, HealthCheckGroup group, CancellationToken cancellationToken = default(CancellationToken));
}
}

+ 18
- 42
src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Internal/UrlChecker.cs View File

@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
@ -13,55 +12,33 @@ namespace Microsoft.Extensions.HealthChecks.Internal
public class UrlChecker
{
private readonly Func<HttpResponseMessage, ValueTask<IHealthCheckResult>> _checkFunc;
private readonly string[] _urls;
private readonly string _url;
// REVIEW: Cache timeout here?
public UrlChecker(Func<HttpResponseMessage, ValueTask<IHealthCheckResult>> checkFunc, params string[] urls)
public UrlChecker(Func<HttpResponseMessage, ValueTask<IHealthCheckResult>> checkFunc, string url)
{
Guard.ArgumentNotNull(nameof(checkFunc), checkFunc);
Guard.ArgumentNotNullOrEmpty(nameof(urls), urls);
Guard.ArgumentNotNullOrEmpty(nameof(url), url);
_checkFunc = checkFunc;
_urls = urls;
_url = url;
}
public CheckStatus PartiallyHealthyStatus { get; set; } = CheckStatus.Warning;
public Task<IHealthCheckResult> CheckAsync()
=> _urls.Length == 1 ? CheckSingleAsync() : CheckMultiAsync();
public async Task<IHealthCheckResult> CheckSingleAsync()
{
var httpClient = CreateHttpClient();
var result = default(IHealthCheckResult);
await CheckUrlAsync(httpClient, _urls[0], (_, checkResult) => result = checkResult).ConfigureAwait(false);
return result;
}
public async Task<IHealthCheckResult> CheckMultiAsync()
public async Task<IHealthCheckResult> CheckAsync()
{
var composite = new CompositeHealthCheckResult(PartiallyHealthyStatus);
var httpClient = CreateHttpClient();
// REVIEW: Should these be done in parallel?
foreach (var url in _urls)
await CheckUrlAsync(httpClient, url, (name, checkResult) => composite.Add(name, checkResult)).ConfigureAwait(false);
return composite;
}
private async Task CheckUrlAsync(HttpClient httpClient, string url, Action<string, IHealthCheckResult> adder)
{
var name = $"UrlCheck({url})";
try
{
var response = await httpClient.GetAsync(url).ConfigureAwait(false);
var result = await _checkFunc(response);
adder(name, result);
}
catch (Exception ex)
using (var httpClient = CreateHttpClient())
{
adder(name, HealthCheckResult.Unhealthy($"Exception during check: {ex.GetType().FullName}"));
try
{
var response = await httpClient.GetAsync(_url).ConfigureAwait(false);
return await _checkFunc(response);
}
catch (Exception ex)
{
var data = new Dictionary<string, object> { { "url", _url } };
return HealthCheckResult.Unhealthy($"Exception during check: {ex.GetType().FullName}", data);
}
}
}
@ -74,8 +51,7 @@ namespace Microsoft.Extensions.HealthChecks.Internal
public static async ValueTask<IHealthCheckResult> DefaultUrlCheck(HttpResponseMessage response)
{
// REVIEW: Should this be an explicit 200 check, or just an "is success" check?
var status = response.StatusCode == HttpStatusCode.OK ? CheckStatus.Healthy : CheckStatus.Unhealthy;
var status = response.IsSuccessStatusCode ? CheckStatus.Healthy : CheckStatus.Unhealthy;
var data = new Dictionary<string, object>
{
{ "url", response.RequestMessage.RequestUri.ToString() },
@ -83,7 +59,7 @@ namespace Microsoft.Extensions.HealthChecks.Internal
{ "reason", response.ReasonPhrase },
{ "body", await response.Content?.ReadAsStringAsync() }
};
return HealthCheckResult.FromStatus(status, $"UrlCheck({response.RequestMessage.RequestUri}): status code {response.StatusCode} ({(int)response.StatusCode})", data);
return HealthCheckResult.FromStatus(status, $"status code {response.StatusCode} ({(int)response.StatusCode})", data);
}
protected virtual HttpClient GetHttpClient()


+ 22
- 10
src/BuildingBlocks/HealthChecks/src/common/Guard.cs View File

@ -9,37 +9,49 @@ static class Guard
public static void ArgumentNotNull(string argumentName, object value)
{
if (value == null)
{
throw new ArgumentNullException(argumentName);
}
}
public static void ArgumentNotNullOrEmpty<T>(string argumentName, string value)
public static void ArgumentNotNullOrEmpty(string argumentName, string value)
{
if (value == null)
{
throw new ArgumentNullException(argumentName);
}
if (string.IsNullOrEmpty(value))
throw new ArgumentException("Value cannot be an empty string", argumentName);
{
throw new ArgumentException("Value cannot be an empty string.", argumentName);
}
}
// Use IReadOnlyCollection<T> instead of IEnumerable<T> to discourage double enumeration
public static void ArgumentNotNullOrEmpty<T>(string argumentName, IReadOnlyCollection<T> items)
{
if (items == null)
{
throw new ArgumentNullException(argumentName);
}
if (items.Count == 0)
throw new ArgumentException("Collection must contain at least one item", argumentName);
{
throw new ArgumentException("Collection must contain at least one item.", argumentName);
}
}
public static void ArgumentNotNullOrWhitespace(string argumentName, string value)
public static void ArgumentValid(bool valid, string argumentName, string exceptionMessage)
{
if (value == null)
throw new ArgumentNullException(argumentName);
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("Value must contain a non-whitespace value", argumentName);
if (!valid)
{
throw new ArgumentException(exceptionMessage, argumentName);
}
}
public static void ArgumentValid(bool valid, string argumentName, string exceptionMessage)
public static void OperationValid(bool valid, string exceptionMessage)
{
if (!valid)
throw new ArgumentException(exceptionMessage, argumentName);
{
throw new InvalidOperationException(exceptionMessage);
}
}
}

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

@ -43,7 +43,6 @@
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBusRabbitMQ\EventBusRabbitMQ.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBus\EventBus.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\Microsoft.Extensions.HealthChecks.csproj" />
</ItemGroup>


+ 3
- 7
src/Services/Basket/Basket.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs View File

@ -4,11 +4,7 @@ using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
namespace Basket.API.Infrastructure.Filters
{
@ -43,12 +39,12 @@ namespace Basket.API.Infrastructure.Filters
{
var json = new JsonErrorResponse
{
Messages = new[] { "An error ocurr.Try it again." }
Messages = new[] { "An error occurred. Try it again." }
};
if (env.IsDevelopment())
{
json.DeveloperMeesage = context.Exception;
json.DeveloperMessage = context.Exception;
}
context.Result = new InternalServerErrorObjectResult(json);
@ -61,7 +57,7 @@ namespace Basket.API.Infrastructure.Filters
{
public string[] Messages { get; set; }
public object DeveloperMeesage { get; set; }
public object DeveloperMessage { get; set; }
}
}
}

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

@ -3,6 +3,7 @@ using Basket.API.IntegrationEvents.EventHandling;
using Basket.API.IntegrationEvents.Events;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ;
using Microsoft.eShopOnContainers.Services.Basket.API.Auth.Server;
@ -19,6 +20,7 @@ using StackExchange.Redis;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using System;
namespace Microsoft.eShopOnContainers.Services.Basket.API
{
@ -68,20 +70,18 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
});
services.AddSingleton<IRabbitMQPersisterConnection>(sp =>
services.AddSingleton<IRabbitMQPersistentConnection>(sp =>
{
var settings = sp.GetRequiredService<IOptions<BasketSettings>>().Value;
var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersisterConnection>>();
var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersistentConnection>>();
var factory = new ConnectionFactory()
{
HostName = settings.EventBusConnection
};
return new DefaultRabbitMQPersisterConnection(factory, logger);
return new DefaultRabbitMQPersistentConnection(factory, logger);
});
services.AddSingleton<IEventBus, EventBusRabbitMQ>();
services.AddSwaggerGen();
services.ConfigureSwaggerGen(options =>
@ -108,9 +108,16 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
});
services.AddTransient<IBasketRepository, RedisBasketRepository>();
services.AddTransient<IIntegrationEventHandler<ProductPriceChangedIntegrationEvent>, ProductPriceChangedIntegrationEventHandler>();
services.AddTransient<IIntegrationEventHandler<OrderStartedIntegrationEvent>, OrderStartedIntegrationEventHandler>();
RegisterServiceBus(services);
}
private void RegisterServiceBus(IServiceCollection services)
{
services.AddSingleton<IEventBus, EventBusRabbitMQ>();
services.AddSingleton<IEventBusSubscriptionsManager, InMemoryEventBusSubscriptionsManager>();
services.AddTransient<ProductPriceChangedIntegrationEventHandler>();
services.AddTransient<OrderStartedIntegrationEventHandler>();
}
@ -155,11 +162,13 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
var orderStartedHandler = app.ApplicationServices
.GetService<IIntegrationEventHandler<OrderStartedIntegrationEvent>>();
var eventBus = app.ApplicationServices
.GetRequiredService<IEventBus>();
var eventBus = app.ApplicationServices.GetRequiredService<IEventBus>();
eventBus.Subscribe<ProductPriceChangedIntegrationEvent, ProductPriceChangedIntegrationEventHandler>
(() => app.ApplicationServices.GetRequiredService<ProductPriceChangedIntegrationEventHandler>());
eventBus.Subscribe<ProductPriceChangedIntegrationEvent>(catalogPriceHandler);
eventBus.Subscribe<OrderStartedIntegrationEvent>(orderStartedHandler);
eventBus.Subscribe<OrderStartedIntegrationEvent, OrderStartedIntegrationEventHandler>
(() => app.ApplicationServices.GetRequiredService<OrderStartedIntegrationEventHandler>());
}
}
}

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

@ -59,7 +59,7 @@
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBusRabbitMQ\EventBusRabbitMQ.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBus\EventBus.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.SqlServer\Microsoft.Extensions.HealthChecks.SqlServer.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks\Microsoft.Extensions.HealthChecks.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\IntegrationEventLogEF\IntegrationEventLogEF.csproj" />
</ItemGroup>


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

@ -6,6 +6,7 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ;
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF;
@ -103,18 +104,19 @@
services.AddTransient<ICatalogIntegrationEventService, CatalogIntegrationEventService>();
services.AddSingleton<IRabbitMQPersisterConnection>(sp =>
services.AddSingleton<IRabbitMQPersistentConnection>(sp =>
{
var settings = sp.GetRequiredService<IOptions<CatalogSettings>>().Value;
var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersisterConnection>>();
var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersistentConnection>>();
var factory = new ConnectionFactory()
{
HostName = settings.EventBusConnection
};
return new DefaultRabbitMQPersisterConnection(factory, logger);
return new DefaultRabbitMQPersistentConnection(factory, logger);
});
services.AddSingleton<IEventBusSubscriptionsManager, InMemoryEventBusSubscriptionsManager>();
services.AddSingleton<IEventBus, EventBusRabbitMQ>();
}


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

@ -63,7 +63,7 @@
<ItemGroup>
<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.SqlServer\Microsoft.Extensions.HealthChecks.SqlServer.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks\Microsoft.Extensions.HealthChecks.csproj" />
</ItemGroup>


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

@ -61,20 +61,16 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands
[DataMember]
public IEnumerable<OrderItemDTO> OrderItems => _orderItems;
public void AddOrderItem(OrderItemDTO item)
{
_orderItems.Add(item);
}
public CreateOrderCommand()
{
_orderItems = new List<OrderItemDTO>();
}
public CreateOrderCommand(string city, string street, string state, string country, string zipcode,
public CreateOrderCommand(List<OrderItemDTO> orderItems, string city, string street, string state, string country, string zipcode,
string cardNumber, string cardHolderName, DateTime cardExpiration,
string cardSecurityNumber, int cardTypeId, int paymentId, int buyerId) : this()
{
_orderItems = orderItems;
City = city;
Street = street;
State = state;


+ 1
- 1
src/Services/Ordering/Ordering.API/Ordering.API.csproj View File

@ -27,7 +27,7 @@
<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.Extensions.HealthChecks.Data\Microsoft.Extensions.HealthChecks.Data.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.SqlServer\Microsoft.Extensions.HealthChecks.SqlServer.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks\Microsoft.Extensions.HealthChecks.csproj" />
<ProjectReference Include="..\Ordering.Domain\Ordering.Domain.csproj" />
<ProjectReference Include="..\Ordering.Infrastructure\Ordering.Infrastructure.csproj" />


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

@ -13,6 +13,7 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ;
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF;
@ -107,18 +108,19 @@
var serviceProvider = services.BuildServiceProvider();
services.AddTransient<IOrderingIntegrationEventService, OrderingIntegrationEventService>();
services.AddSingleton<IRabbitMQPersisterConnection>(sp =>
services.AddSingleton<IRabbitMQPersistentConnection>(sp =>
{
var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersisterConnection>>();
var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersistentConnection>>();
var factory = new ConnectionFactory()
{
HostName = Configuration["EventBusConnection"]
};
return new DefaultRabbitMQPersisterConnection(factory, logger);
return new DefaultRabbitMQPersistentConnection(factory, logger);
});
services.AddSingleton<IEventBusSubscriptionsManager, InMemoryEventBusSubscriptionsManager>();
services.AddSingleton<IEventBus, EventBusRabbitMQ>();
services.AddOptions();


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

@ -252,7 +252,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
// After executing this line all the changes (from the Command Handler and Domain Event Handlers)
// performed thought the DbContext will be commited
// performed throught the DbContext will be commited
var result = await base.SaveChangesAsync();
return true;


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

@ -59,7 +59,6 @@
<ItemGroup>
<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\Microsoft.Extensions.HealthChecks.csproj" />
<ProjectReference Include="..\..\BuildingBlocks\Resilience\Resilience.Http\Resilience.Http.csproj" />
</ItemGroup>


+ 1
- 1
src/Web/WebMVC/wwwroot/css/site.min.css
File diff suppressed because it is too large
View File


+ 58
- 0
src/Web/WebSPA/.angular-cli.json View File

@ -0,0 +1,58 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"project": {
"name": "WebSPA"
},
"apps": [
{
"root": "Client",
"outDir": "wwwroot",
"assets": [
"assets",
"favicon.ico"
],
"index": "index.html",
"main": "main.ts",
"polyfills": "polyfills.ts",
"test": "test.ts",
"tsconfig": "tsconfig.app.json",
"testTsconfig": "tsconfig.spec.json",
"prefix": "app",
"styles": [
"globals.scss",
"../node_modules/bootstrap/scss/bootstrap.scss"
],
"scripts": [],
"environmentSource": "environments/environment.ts",
"environments": {
"dev": "environments/environment.ts",
"prod": "environments/environment.prod.ts"
}
}
],
"e2e": {
"protractor": {
"config": "./protractor.conf.js"
}
},
"lint": [
{
"project": "Client/tsconfig.app.json"
},
{
"project": "Client/tsconfig.spec.json"
},
{
"project": "e2e/tsconfig.e2e.json"
}
],
"test": {
"karma": {
"config": "./karma.conf.js"
}
},
"defaults": {
"styleExt": "scss",
"component": {}
}
}

+ 1
- 4
src/Web/WebSPA/.gitignore View File

@ -177,10 +177,7 @@ ClientBin/
*.publishsettings
node_modules/
bower_components/
**/wwwroot/tmp/
**/wwwroot/*.bundle.map
**/wwwroot/*.js
/wwwroot/dist/
wwwroot/
orleans.codegen.cs


+ 0
- 0
src/Web/WebSPA/Client/assets/.gitkeep View File


src/Web/WebSPA/Client/images/arrow-down.png → src/Web/WebSPA/Client/assets/images/arrow-down.png View File


src/Web/WebSPA/Client/images/arrow-right.svg → src/Web/WebSPA/Client/assets/images/arrow-right.svg View File


src/Web/WebSPA/Client/images/brand.png → src/Web/WebSPA/Client/assets/images/brand.png View File


src/Web/WebSPA/Client/images/brand_dark.png → src/Web/WebSPA/Client/assets/images/brand_dark.png View File


src/Web/WebSPA/Client/images/cart.png → src/Web/WebSPA/Client/assets/images/cart.png View File


src/Web/WebSPA/Client/images/logout.png → src/Web/WebSPA/Client/assets/images/logout.png View File


src/Web/WebSPA/Client/images/main_banner.png → src/Web/WebSPA/Client/assets/images/main_banner.png View File


src/Web/WebSPA/Client/images/main_banner_text.png → src/Web/WebSPA/Client/assets/images/main_banner_text.png View File


src/Web/WebSPA/Client/images/my_orders.png → src/Web/WebSPA/Client/assets/images/my_orders.png View File


+ 0
- 2
src/Web/WebSPA/Client/custom-typings.d.ts View File

@ -1,2 +0,0 @@
// Extra variables that live on Global that will be replaced by webpack DefinePlugin
// declare var process: any;

+ 3
- 0
src/Web/WebSPA/Client/environments/environment.prod.ts View File

@ -0,0 +1,3 @@
export const environment = {
production: true
};

+ 8
- 0
src/Web/WebSPA/Client/environments/environment.ts View File

@ -0,0 +1,8 @@
// The file contents for the current environment will overwrite these during build.
// The build system defaults to the dev environment which uses `environment.ts`, but if you do
// `ng build --env=prod` then `environment.prod.ts` will be used instead.
// The list of which env maps to which file can be found in `.angular-cli.json`.
export const environment = {
production: false
};

src/Web/WebSPA/wwwroot/favicon.ico → src/Web/WebSPA/Client/favicon.ico View File


+ 1
- 0
src/Web/WebSPA/Client/globals.scss View File

@ -1,3 +1,4 @@
/* You can add global styles to this file, and also import other style files */
@import './modules/variables';
$dist: './fonts/Montserrat-Regular';


+ 18
- 0
src/Web/WebSPA/Client/index.html View File

@ -0,0 +1,18 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>eShopConContainers.WebSPA</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<esh-app>
<div class="preloading">
<i class="fa fa-spinner fa-spin fa-5x" aria-hidden="true"></i>
</div>
</esh-app>
</body>
</html>

+ 4
- 16
src/Web/WebSPA/Client/main.ts View File

@ -1,23 +1,11 @@
import './polyfills';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './modules/app.module';
import { AppModule } from './modules/app.module';
import { environment } from './environments/environment';
if (process.env.ENV === 'Development') {
// Development
} else {
// Production
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule);
// Basic hot reloading support. Automatically reloads and restarts the Angular 2 app each time
// you modify source files. This will not preserve any application state other than the URL.
declare var module: any;
if (module.hot) {
module.hot.accept();
}

+ 2
- 2
src/Web/WebSPA/Client/modules/app.component.html View File

@ -4,7 +4,7 @@
<section class="col-lg-7 col-md-6 col-xs-12">
<a class="navbar-brand" routerLink="catalog">
<img src="../images/brand.png" />
<img src="assets/images/brand.png" />
</a>
</section>
@ -28,7 +28,7 @@
<article class="row">
<section class="col-sm-6">
<img class="esh-app-footer-brand" src="../images/brand_dark.png" />
<img class="esh-app-footer-brand" src="assets/images/brand_dark.png" />
</section>
<section class="col-sm-6">


+ 2
- 2
src/Web/WebSPA/Client/modules/app.component.ts View File

@ -13,12 +13,12 @@ import { ConfigurationService } from './shared/services/configuration.service';
*/
@Component({
selector: 'esh-app.esh-app',
selector: 'esh-app',
styleUrls: ['./app.component.scss'],
templateUrl: './app.component.html'
})
export class AppComponent implements OnInit {
private Authenticated: boolean = false;
Authenticated: boolean = false;
subscription: Subscription;
constructor(private titleService: Title, private securityService: SecurityService, private configurationService: ConfigurationService) {


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

@ -3,7 +3,7 @@
[routerLink]="['basket']">
<div class="esh-basketstatus-image">
<img src="../../../images/cart.png" />
<img src="assets/images/cart.png" />
</div>
<div class="esh-basketstatus-badge">
{{badge}}


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

@ -16,7 +16,7 @@
<option *ngFor="let type of types" [value]="type.id">{{type.type}}</option>
</select>
</label>
<img class="esh-catalog-send" (click)="onFilterApplied($event)" src="../../images/arrow-right.svg" />
<img class="esh-catalog-send" (click)="onFilterApplied($event)" src="/assets/images/arrow-right.svg" />
</div>
</section>


+ 2
- 2
src/Web/WebSPA/Client/modules/catalog/catalog.component.scss View File

@ -4,7 +4,7 @@
$banner-height: 260px;
&-hero {
background-image: url('../../images/main_banner.png');
background-image: url('../../assets/images/main_banner.png');
background-size: cover;
height: $banner-height;
width: 100%;
@ -61,7 +61,7 @@
}
&::after {
background-image: url('../../images/arrow-down.png');
background-image: url('../../assets/images/arrow-down.png');
content: '';
height: 7px; //png height
position: absolute;


+ 5
- 6
src/Web/WebSPA/Client/modules/orders/orders-detail/orders-detail.component.ts View File

@ -1,6 +1,6 @@
import { Component, OnInit } from '@angular/core';
import { OrdersService } from '../orders.service';
import { IOrder } from '../../shared/models/order.model';
import { Component, OnInit } from '@angular/core';
import { OrdersService } from '../orders.service';
import { IOrderDetail } from '../../shared/models/order-detail.model';
import { ActivatedRoute } from '@angular/router';
@Component({
@ -9,7 +9,7 @@ import { ActivatedRoute } from '@angular/router';
templateUrl: './orders-detail.component.html'
})
export class OrdersDetailComponent implements OnInit {
order = {}; // new order
public order: IOrderDetail = <IOrderDetail>{};
constructor(private service: OrdersService, private route: ActivatedRoute) { }
@ -27,5 +27,4 @@ export class OrdersDetailComponent implements OnInit {
console.log(this.order);
});
}
}
}

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

@ -13,10 +13,10 @@ import { Router } from '@angular/router';
templateUrl: './orders-new.component.html'
})
export class OrdersNewComponent implements OnInit {
private newOrderForm: FormGroup; // new order form
private isOrderProcessing: Boolean;
private errorReceived: Boolean;
private order: IOrder;
newOrderForm: FormGroup; // new order form
isOrderProcessing: boolean;
errorReceived: boolean;
order: IOrder;
constructor(private service: OrdersService, fb: FormBuilder, private router: Router) {
// Obtain user profile information


+ 2
- 1
src/Web/WebSPA/Client/modules/orders/orders.service.ts View File

@ -4,6 +4,7 @@ import { Response } from '@angular/http';
import { DataService } from '../shared/services/data.service';
import { IOrder } from '../shared/models/order.model';
import { IOrderItem } from '../shared/models/orderItem.model';
import { IOrderDetail } from "../shared/models/order-detail.model";
import { SecurityService } from '../shared/services/security.service';
import { ConfigurationService } from '../shared/services/configuration.service';
import { BasketWrapperService } from '../shared/services/basket.wrapper.service';
@ -35,7 +36,7 @@ export class OrdersService {
});
}
getOrder(id: number): Observable<IOrder> {
getOrder(id: number): Observable<IOrderDetail> {
let url = this.ordersUrl + '/api/v1/orders/' + id;
return this.service.get(url).map((response: Response) => {


+ 3
- 3
src/Web/WebSPA/Client/modules/shared/components/identity/identity.html View File

@ -12,7 +12,7 @@
*ngIf="authenticated">
<div class="esh-identity-name">{{userName}}</div>
<img class="esh-identity-image" src="../../../../images/arrow-down.png">
<img class="esh-identity-image" src="assets/images/arrow-down.png">
</section>
<section class="esh-identity-drop"
@ -22,14 +22,14 @@
[routerLink]="['orders']">
<div class="esh-identity-name esh-identity-name--upper">My orders</div>
<img class="esh-identity-image" src="../../../../images/my_orders.png">
<img class="esh-identity-image" src="assets/images/my_orders.png">
</div>
<div class="esh-identity-item"
(click)="logoutClicked($event)">
<div class="esh-identity-name esh-identity-name--upper">Log Out</div>
<img class="esh-identity-image" src="../../../../images/logout.png">
<img class="esh-identity-image" src="assets/images/logout.png">
</div>
</section>
</div>

+ 1
- 1
src/Web/WebSPA/Client/modules/shared/components/identity/identity.ts View File

@ -10,7 +10,7 @@ import { SecurityService } from '../../services/security.service';
styleUrls: ['./identity.scss']
})
export class Identity implements OnInit {
private authenticated: boolean = false;
authenticated: boolean = false;
private subscription: Subscription;
private userName: string = '';


+ 14
- 0
src/Web/WebSPA/Client/modules/shared/models/order-detail.model.ts View File

@ -0,0 +1,14 @@
import {IOrderItem} from './orderItem.model';
export interface IOrderDetail {
ordernumber: string;
status: string;
street: string;
date: Date;
city: number;
state: string;
zipcode: string;
country: number;
total: number;
orderitems: IOrderItem[];
}

+ 10
- 2
src/Web/WebSPA/Client/modules/shared/shared.module.ts View File

@ -16,6 +16,10 @@ import { StorageService } from './services/storage.service';
import { Pager } from './components/pager/pager';
import { Header } from './components/header/header';
import { Identity } from './components/identity/identity';
import { PageNotFoundComponent } from './components/page-not-found/page-not-found.component';
// Pipes:
import { UppercasePipe } from './pipes/uppercase.pipe';
@NgModule({
imports: [
@ -31,7 +35,9 @@ import { Identity } from './components/identity/identity';
declarations: [
Pager,
Header,
Identity
Identity,
PageNotFoundComponent,
UppercasePipe
],
exports: [
// Modules
@ -43,7 +49,9 @@ import { Identity } from './components/identity/identity';
// Providers, Components, directive, pipes
Pager,
Header,
Identity
Identity,
PageNotFoundComponent,
UppercasePipe
]
})
export class SharedModule {


+ 66
- 21
src/Web/WebSPA/Client/polyfills.ts View File

@ -1,23 +1,68 @@
// Added parts of es6 which are necessary for your project or your browser support requirements.
import 'core-js/es6/symbol';
import 'core-js/es6/object';
import 'core-js/es6/function';
import 'core-js/es6/parse-int';
import 'core-js/es6/parse-float';
import 'core-js/es6/number';
import 'core-js/es6/math';
import 'core-js/es6/string';
import 'core-js/es6/date';
import 'core-js/es6/array';
import 'core-js/es6/regexp';
import 'core-js/es6/map';
import 'core-js/es6/set';
import 'core-js/es6/weak-map';
import 'core-js/es6/weak-set';
import 'core-js/es6/typed';
import 'core-js/es6/reflect';
// see issue https://github.com/AngularClass/angular2-webpack-starter/issues/709
// import 'core-js/es6/promise';
/**
* This file includes polyfills needed by Angular and is loaded before the app.
* You can add your own extra polyfills to this file.
*
* This file is divided into 2 sections:
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
* file.
*
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
*
* Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
*/
/***************************************************************************************************
* BROWSER POLYFILLS
*/
/** IE9, IE10 and IE11 requires all of the following polyfills. **/
// import 'core-js/es6/symbol';
// import 'core-js/es6/object';
// import 'core-js/es6/function';
// import 'core-js/es6/parse-int';
// import 'core-js/es6/parse-float';
// import 'core-js/es6/number';
// import 'core-js/es6/math';
// import 'core-js/es6/string';
// import 'core-js/es6/date';
// import 'core-js/es6/array';
// import 'core-js/es6/regexp';
// import 'core-js/es6/map';
// import 'core-js/es6/set';
/** IE10 and IE11 requires the following for NgClass support on SVG elements */
// import 'classlist.js'; // Run `npm install --save classlist.js`.
/** IE10 and IE11 requires the following to support `@angular/animation`. */
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/** Evergreen browsers require these. **/
import 'core-js/es6/reflect';
import 'core-js/es7/reflect';
import 'zone.js/dist/zone';
/** ALL Firefox browsers require the following to support `@angular/animation`. **/
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/***************************************************************************************************
* Zone JS is required by Angular itself.
*/
import 'zone.js/dist/zone'; // Included with Angular CLI.
/***************************************************************************************************
* APPLICATION IMPORTS
*/
/**
* Date, currency, decimal and percent pipes.
* Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10
*/
// import 'intl'; // Run `npm install --save intl`.

+ 32
- 0
src/Web/WebSPA/Client/test.ts View File

@ -0,0 +1,32 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/dist/long-stack-trace-zone';
import 'zone.js/dist/proxy.js';
import 'zone.js/dist/sync-test';
import 'zone.js/dist/jasmine-patch';
import 'zone.js/dist/async-test';
import 'zone.js/dist/fake-async-test';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';
// Unfortunately there's no typing for the `__karma__` variable. Just declare it as any.
declare var __karma__: any;
declare var require: any;
// Prevent Karma from running prematurely.
__karma__.loaded = function () {};
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);
// Finally, start Karma to run the tests.
__karma__.start();

+ 13
- 0
src/Web/WebSPA/Client/tsconfig.app.json View File

@ -0,0 +1,13 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/app",
"module": "es2015",
"baseUrl": "",
"types": []
},
"exclude": [
"test.ts",
"**/*.spec.ts"
]
}

+ 20
- 0
src/Web/WebSPA/Client/tsconfig.spec.json View File

@ -0,0 +1,20 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/spec",
"module": "commonjs",
"target": "es5",
"baseUrl": "",
"types": [
"jasmine",
"node"
]
},
"files": [
"test.ts"
],
"include": [
"**/*.spec.ts",
"**/*.d.ts"
]
}

+ 5
- 0
src/Web/WebSPA/Client/typings.d.ts View File

@ -0,0 +1,5 @@
/* SystemJS module definition */
declare var module: NodeModule;
interface NodeModule {
id: string;
}

+ 0
- 20
src/Web/WebSPA/Client/vendor.ts View File

@ -1,20 +0,0 @@
// For vendors for example jQuery, Lodash, angular2-jwt just import them here unless you plan on
// chunking vendors files for async loading. You would need to import the async loaded vendors
// at the entry point of the async loaded file. Also see custom-typings.d.ts as you also need to
// run `typings install x` where `x` is your module
// Angular 2
import '@angular/platform-browser';
import '@angular/platform-browser-dynamic';
import '@angular/core';
import '@angular/common';
import '@angular/forms';
import '@angular/http';
import '@angular/router';
// RxJS
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/finally';
import 'rxjs/add/observable/throw';

+ 0
- 18
src/Web/WebSPA/Server/Controllers/HomeController.cs View File

@ -1,6 +1,5 @@
// For more information on enabling MVC for empty projects, visit http://go.microsoft.com/fwlink/?LinkID=397860
using System.Linq;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
@ -18,23 +17,6 @@ namespace eShopConContainers.WebSPA.Server.Controllers
_env = env;
_settings = settings;
}
public IActionResult Index()
{
ViewBag.HashedMain = GetHashedMainDotJs();
return View();
}
public string GetHashedMainDotJs()
{
var basePath = _env.WebRootPath + "//dist//";
var info = new System.IO.DirectoryInfo(basePath);
var file = info.GetFiles().Where(f => f.Name.StartsWith("main.") && !f.Name.EndsWith("bundle.map")).FirstOrDefault();
return file.Name;
}
public IActionResult Configuration()
{
return Json(_settings.Value);


+ 22
- 18
src/Web/WebSPA/Startup.cs View File

@ -1,16 +1,15 @@
using System;
using System.IO;
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.SpaServices.Webpack;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.HealthChecks;
using Newtonsoft.Json.Serialization;
using eShopOnContainers.WebSPA;
using Microsoft.Extensions.HealthChecks;
using System.Threading.Tasks;
namespace eShopConContainers.WebSPA
{
@ -76,29 +75,34 @@ namespace eShopConContainers.WebSPA
// Configure XSRF middleware, This pattern is for SPA style applications where XSRF token is added on Index page
// load and passed back token on every subsequent async request
// app.Use(async (context, next) =>
// {
// if (string.Equals(context.Request.Path.Value, "/", StringComparison.OrdinalIgnoreCase))
// {
// var tokens = antiforgery.GetAndStoreTokens(context);
// context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken, new CookieOptions() { HttpOnly = false });
// }
// await next.Invoke();
// });
app.Use(async (context, next) =>
{
if (string.Equals(context.Request.Path.Value, "/", StringComparison.OrdinalIgnoreCase))
await next();
// If there's no available file and the request doesn't contain an extension, we're probably trying to access a page.
// Rewrite request to use app root
if (context.Response.StatusCode == 404 && !Path.HasExtension(context.Request.Path.Value) && !context.Request.Path.Value.StartsWith("/api"))
{
var tokens = antiforgery.GetAndStoreTokens(context);
context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken, new CookieOptions() { HttpOnly = false });
context.Request.Path = "/index.html";
context.Response.StatusCode = 200; // Make sure we update the status code, otherwise it returns 404
await next();
}
await next.Invoke();
});
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
routes.MapSpaFallbackRoute(
name: "spa-fallback",
defaults: new { controller = "Home", action = "Index" });
});
app.UseMvcWithDefaultRoute();
}
}
}

+ 0
- 12
src/Web/WebSPA/Views/Home/Index.cshtml View File

@ -1,12 +0,0 @@
<esh-app class="esh-app" asp-prerender-webpack-config="config/webpack.config.js">
<div class="preloading">
<i class="fa fa-spinner fa-spin fa-5x" aria-hidden="true"></i>
</div>
</esh-app>
<script>
window.user = '@ViewBag.user';
</script>
<script src="~/dist/vendor.js" asp-append-version="true"></script>
<script src="~/dist/@ViewBag.HashedMain" asp-append-version="true"></script>

+ 0
- 16
src/Web/WebSPA/Views/Shared/_Layout.cshtml View File

@ -1,16 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>eShopConContainers.WebSPA</title>
<base href="/" />
<link rel="stylesheet" href="~/dist/vendor.css" asp-append-version="true" />
</head>
<body>
@RenderBody()
</body>
</html>

+ 0
- 3
src/Web/WebSPA/Views/_ViewImports.cshtml View File

@ -1,3 +0,0 @@
@addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers"
@addTagHelper "*, Microsoft.AspNetCore.SpaServices"

+ 0
- 3
src/Web/WebSPA/Views/_ViewStart.cshtml View File

@ -1,3 +0,0 @@
@{
Layout = "_Layout";
}

+ 0
- 14
src/Web/WebSPA/WebSPA.csproj View File

@ -22,21 +22,9 @@
<Content Update="appsettings.json;">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Update="Client\**\*;">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Update="Views\**\*;">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Update="tsconfig.json;">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Update="web.config;">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Update="config\**\*;">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Update="wwwroot\**\*;">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
@ -51,7 +39,6 @@
<PackageReference Include="Microsoft.AspNetCore.Antiforgery" Version="1.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="1.1.0" />
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
<PackageReference Include="Webpack" Version="3.0.0" />
<PackageReference Include="Microsoft.AspNetCore.AngularServices" Version="1.0.0-beta-000014" />
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="1.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="1.1.0" />
@ -87,7 +74,6 @@
<ItemGroup>
<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\Microsoft.Extensions.HealthChecks.csproj" />
</ItemGroup>


+ 0
- 23
src/Web/WebSPA/config/helpers.js View File

@ -1,23 +0,0 @@
/**
* @author: @AngularClass
*/
var path = require('path');
// Helper functions
var ROOT = path.resolve(__dirname, '..');
console.log('root directory:', root() + '\n');
function hasProcessFlag(flag) {
return process.argv.join('').indexOf(flag) > -1;
}
function root(args) {
args = Array.prototype.slice.call(arguments, 0);
return path.join.apply(path, [ROOT].concat(args));
}
exports.hasProcessFlag = hasProcessFlag;
exports.root = root;

+ 0
- 3
src/Web/WebSPA/config/webpack.config.dev.js View File

@ -1,3 +0,0 @@
module.exports = {
//devtool: 'cheap-module-source-map'
};

+ 0
- 77
src/Web/WebSPA/config/webpack.config.js View File

@ -1,77 +0,0 @@
var path = require('path');
var webpack = require('webpack');
var merge = require('extendify')({ isDeep: true, arrays: 'concat' });
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var extractCSS = new ExtractTextPlugin('styles.css');
var ForkCheckerPlugin = require('awesome-typescript-loader').ForkCheckerPlugin;
var devConfig = require('./webpack.config.dev');
var prodConfig = require('./webpack.config.prod');
var CopyWebpackPlugin = require('copy-webpack-plugin');
var isDevelopment = process.env.ASPNETCORE_ENVIRONMENT === 'Development';
console.log("==========Dev Mode = " + isDevelopment + " ============" )
module.exports = merge({
resolve: {
extensions: ['.js', '.ts']
},
module: {
rules: [
{
test: /\.ts$/, exclude: [/\.(spec|e2e)\.ts$/],
loaders: ['awesome-typescript-loader?forkChecker=true ', 'angular2-template-loader', 'angular2-router-loader']
},
{ test: /\.html$/, loader: "html" },
{ test: /\.scss$/, loader: 'exports-loader?module.exports.toString()!css-loader!sass-loader' },
{ test: /\.json$/, loader: 'json-loader' },
{
test: /\.woff(\?v=\d+\.\d+\.\d+)?$/,
loader: "file-loader"
}, {
test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/,
loader: "file-loader"
}, {
test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
loader: "file-loader"
}, {
test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
loader: "file-loader"
},
{
test: /\.(png|jpg|gif|svg)$/,
loader: "file-loader?name=images/[name].[ext]"
}
]
},
entry: {
'main': './Client/main.ts'
},
output: {
path: path.join(__dirname, '../wwwroot', 'dist'),
filename: '[name].js',
publicPath: '/dist/'
},
profile: true,
plugins: [
extractCSS,
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('../wwwroot/dist/vendor-manifest.json')
}),
// To eliminate warning
// https://github.com/AngularClass/angular2-webpack-starter/issues/993
new webpack.ContextReplacementPlugin(
/angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/,
__dirname
),
new ForkCheckerPlugin(),
new webpack.DefinePlugin({
'process.env': {
'ENV': JSON.stringify(process.env.ASPNETCORE_ENVIRONMENT)
}
}),
new CopyWebpackPlugin([
{ from: 'Client/fonts', to: 'fonts' }
])
]
}, isDevelopment ? devConfig : prodConfig);

+ 0
- 23
src/Web/WebSPA/config/webpack.config.prod.js View File

@ -1,23 +0,0 @@
var webpack = require('webpack');
const WebpackMd5Hash = require('webpack-md5-hash');
module.exports = {
devtool: 'source-map',
output: {
filename: '[name].[chunkhash].bundle.js',
sourceMapFilename: '[name].[chunkhash].bundle.map',
chunkFilename: '[id].[chunkhash].chunk.js'
},
plugins: [
// new webpack.LoaderOptionsPlugin({
// minimize: true,
// debug: false
// }),
new WebpackMd5Hash(),
new webpack.optimize.UglifyJsPlugin({
beautify: false,
comments: false,
sourceMap: true
})
]
};

+ 0
- 74
src/Web/WebSPA/config/webpack.config.vendor.js View File

@ -1,74 +0,0 @@
var path = require('path');
var webpack = require('webpack');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var extractCSS = new ExtractTextPlugin('vendor.css');
var isDevelopment = process.env.ASPNETCORE_ENVIRONMENT === 'Development';
module.exports = {
resolve: {
extensions: ['.js']
},
module: {
rules: [
{ test: /\.(png|woff|woff2|eot|ttf|svg)$/, loader: 'url-loader?limit=100000' },
{ test: /\.scss$/i, loader: extractCSS.extract(['css?minimize', 'sass']) },
{ test: /\.json$/, loader: 'json-loader' }
]
},
entry: {
// polyfills: [
// 'core-js/es6/symbol',
// 'core-js/es6/object',
// 'core-js/es6/function',
// 'core-js/es6/parse-int',
// 'core-js/es6/parse-float',
// 'core-js/es6/number',
// 'core-js/es6/math',
// 'core-js/es6/string',
// 'core-js/es6/date',
// 'core-js/es6/array',
// 'core-js/es6/regexp',
// 'core-js/es6/map',
// 'core-js/es6/set',
// 'core-js/es6/reflect',
// 'core-js/es7/reflect',
// 'zone.js/dist/zone'
// ],
vendor: [
'font-awesome/scss/font-awesome.scss',
'bootstrap/scss/bootstrap.scss',
'@angular/common',
'@angular/compiler',
'@angular/core',
'@angular/http',
'@angular/forms',
'@angular/platform-browser',
'@angular/platform-browser-dynamic',
'@angular/router',
'./Client/globals.scss'
]
},
output: {
path: path.join(__dirname, '../wwwroot', 'dist'),
filename: '[name].js',
library: '[name]_[hash]',
},
plugins: [
extractCSS,
// To eliminate warning
// https://github.com/AngularClass/angular2-webpack-starter/issues/993
new webpack.ContextReplacementPlugin(
/angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/,
__dirname
),
new webpack.DllPlugin({
path: path.join(__dirname, '../wwwroot', 'dist', '[name]-manifest.json'),
name: '[name]_[hash]'
})
].concat(isDevelopment ? [] : [
new webpack.optimize.UglifyJsPlugin({
beautify: false,
comments: false
})
])
};

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

Loading…
Cancel
Save