diff --git a/.gitignore b/.gitignore
index 75d3a4bd2..d7f16d65c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,7 @@
*.user
*.userosscache
*.sln.docstates
+.vscode/
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
diff --git a/cli-linux/build-bits-linux.sh b/cli-linux/build-bits-linux.sh
old mode 100644
new mode 100755
index e99df361b..7cda26d8c
--- a/cli-linux/build-bits-linux.sh
+++ b/cli-linux/build-bits-linux.sh
@@ -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.
diff --git a/docker-compose-windows.yml b/docker-compose-windows.yml
index b8eaabfc8..5a08a9302 100644
--- a/docker-compose-windows.yml
+++ b/docker-compose-windows.yml
@@ -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"
diff --git a/docker-compose.ci.build.yml b/docker-compose.ci.build.yml
index ef9705c8e..546b7690f 100644
--- a/docker-compose.ci.build.yml
+++ b/docker-compose.ci.build.yml
@@ -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"
diff --git a/eShopOnContainers-ServicesAndWebApps.sln b/eShopOnContainers-ServicesAndWebApps.sln
index 53a9ceada..cf2be1c26 100644
--- a/eShopOnContainers-ServicesAndWebApps.sln
+++ b/eShopOnContainers-ServicesAndWebApps.sln
@@ -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
diff --git a/src/BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj b/src/BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj
new file mode 100644
index 000000000..1387a74dd
--- /dev/null
+++ b/src/BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj
@@ -0,0 +1,22 @@
+
+
+
+ netcoreapp1.1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/BuildingBlocks/EventBus/EventBus.Tests/InMemory_SubscriptionManager_Tests.cs b/src/BuildingBlocks/EventBus/EventBus.Tests/InMemory_SubscriptionManager_Tests.cs
new file mode 100644
index 000000000..dd5f7f5b4
--- /dev/null
+++ b/src/BuildingBlocks/EventBus/EventBus.Tests/InMemory_SubscriptionManager_Tests.cs
@@ -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(() => new TestIntegrationEventHandler());
+ Assert.True(manager.HasSubscriptionsForEvent());
+ }
+
+ [Fact]
+ public void After_All_Subscriptions_Are_Deleted_Event_Should_No_Longer_Exists()
+ {
+ var manager = new InMemoryEventBusSubscriptionsManager();
+ manager.AddSubscription(() => new TestIntegrationEventHandler());
+ manager.RemoveSubscription();
+ Assert.False(manager.HasSubscriptionsForEvent());
+ }
+
+ [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(() => new TestIntegrationEventHandler());
+ manager.RemoveSubscription();
+ Assert.True(raised);
+ }
+
+ [Fact]
+ public void Get_Handlers_For_Event_Should_Return_All_Handlers()
+ {
+ var manager = new InMemoryEventBusSubscriptionsManager();
+ manager.AddSubscription(() => new TestIntegrationEventHandler());
+ manager.AddSubscription(() => new TestIntegrationOtherEventHandler());
+ var handlers = manager.GetHandlersForEvent();
+ Assert.Equal(2, handlers.Count());
+ }
+
+ }
+}
diff --git a/src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationEvent.cs b/src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationEvent.cs
new file mode 100644
index 000000000..a77f3ef6f
--- /dev/null
+++ b/src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationEvent.cs
@@ -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
+ {
+ }
+}
diff --git a/src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationEventHandler.cs b/src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationEventHandler.cs
new file mode 100644
index 000000000..0b5b793ee
--- /dev/null
+++ b/src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationEventHandler.cs
@@ -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
+ {
+ public bool Handled { get; private set; }
+
+ public TestIntegrationOtherEventHandler()
+ {
+ Handled = false;
+ }
+
+ public async Task Handle(TestIntegrationEvent @event)
+ {
+ Handled = true;
+ }
+ }
+}
diff --git a/src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationOtherEventHandler.cs b/src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationOtherEventHandler.cs
new file mode 100644
index 000000000..72e1ed2cd
--- /dev/null
+++ b/src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationOtherEventHandler.cs
@@ -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
+ {
+ public bool Handled { get; private set; }
+
+ public TestIntegrationEventHandler()
+ {
+ Handled = false;
+ }
+
+ public async Task Handle(TestIntegrationEvent @event)
+ {
+ Handled = true;
+ }
+ }
+}
diff --git a/src/BuildingBlocks/EventBus/EventBus/Abstractions/IEventBus.cs b/src/BuildingBlocks/EventBus/EventBus/Abstractions/IEventBus.cs
index 63f9f1b99..9ab7a4499 100644
--- a/src/BuildingBlocks/EventBus/EventBus/Abstractions/IEventBus.cs
+++ b/src/BuildingBlocks/EventBus/EventBus/Abstractions/IEventBus.cs
@@ -1,11 +1,17 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
+using System;
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions
{
public interface IEventBus
{
- void Subscribe(IIntegrationEventHandler handler) where T: IntegrationEvent;
- void Unsubscribe(IIntegrationEventHandler handler) where T : IntegrationEvent;
+ void Subscribe(Func handler)
+ where T : IntegrationEvent
+ where TH : IIntegrationEventHandler;
+ void Unsubscribe()
+ where TH : IIntegrationEventHandler
+ where T : IntegrationEvent;
+
void Publish(IntegrationEvent @event);
}
}
diff --git a/src/BuildingBlocks/EventBus/EventBus/IEventBusSubscriptionsManager.cs b/src/BuildingBlocks/EventBus/EventBus/IEventBusSubscriptionsManager.cs
new file mode 100644
index 000000000..2fdefc039
--- /dev/null
+++ b/src/BuildingBlocks/EventBus/EventBus/IEventBusSubscriptionsManager.cs
@@ -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 OnEventRemoved;
+ void AddSubscription(Func handler)
+ where T : IntegrationEvent
+ where TH : IIntegrationEventHandler;
+
+ void RemoveSubscription()
+ where TH : IIntegrationEventHandler
+ where T : IntegrationEvent;
+ bool HasSubscriptionsForEvent() where T : IntegrationEvent;
+ bool HasSubscriptionsForEvent(string eventName);
+ Type GetEventTypeByName(string eventName);
+ void Clear();
+ IEnumerable GetHandlersForEvent() where T : IntegrationEvent;
+ IEnumerable GetHandlersForEvent(string eventName);
+ }
+}
\ No newline at end of file
diff --git a/src/BuildingBlocks/EventBus/EventBus/InMemoryEventBusSubscriptionsManager.cs b/src/BuildingBlocks/EventBus/EventBus/InMemoryEventBusSubscriptionsManager.cs
new file mode 100644
index 000000000..11fdba3c5
--- /dev/null
+++ b/src/BuildingBlocks/EventBus/EventBus/InMemoryEventBusSubscriptionsManager.cs
@@ -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> _handlers;
+ private readonly List _eventTypes;
+
+ public event EventHandler OnEventRemoved;
+
+ public InMemoryEventBusSubscriptionsManager()
+ {
+ _handlers = new Dictionary>();
+ _eventTypes = new List();
+ }
+
+ public bool IsEmpty => !_handlers.Keys.Any();
+ public void Clear() => _handlers.Clear();
+
+ public void AddSubscription(Func handler)
+ where T : IntegrationEvent
+ where TH : IIntegrationEventHandler
+ {
+ var key = GetEventKey();
+ if (!HasSubscriptionsForEvent())
+ {
+ _handlers.Add(key, new List());
+ }
+ _handlers[key].Add(handler);
+ _eventTypes.Add(typeof(T));
+ }
+
+ public void RemoveSubscription()
+ where TH : IIntegrationEventHandler
+ where T : IntegrationEvent
+ {
+ var handlerToRemove = FindHandlerToRemove();
+ if (handlerToRemove != null)
+ {
+ var key = GetEventKey();
+ _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 GetHandlersForEvent() where T : IntegrationEvent
+ {
+ var key = GetEventKey();
+ return GetHandlersForEvent(key);
+ }
+ public IEnumerable GetHandlersForEvent(string eventName) => _handlers[eventName];
+
+ private void RaiseOnEventRemoved(string eventName)
+ {
+ var handler = OnEventRemoved;
+ if (handler != null)
+ {
+ OnEventRemoved(this, eventName);
+ }
+ }
+
+ private Delegate FindHandlerToRemove()
+ where T : IntegrationEvent
+ where TH : IIntegrationEventHandler
+ {
+ if (!HasSubscriptionsForEvent())
+ {
+ return null;
+ }
+
+ var key = GetEventKey();
+ foreach (var func in _handlers[key])
+ {
+ var genericArgs = func.GetType().GetGenericArguments();
+ if (genericArgs.SingleOrDefault() == typeof(TH))
+ {
+ return func;
+ }
+ }
+
+ return null;
+ }
+
+ public bool HasSubscriptionsForEvent() where T : IntegrationEvent
+ {
+ var key = GetEventKey();
+ 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()
+ {
+ return typeof(T).Name;
+ }
+ }
+}
diff --git a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/DefaultRabbitMQPersisterConnection.cs b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/DefaultRabbitMQPersisterConnection.cs
index 894afb4e4..0aafaf90a 100644
--- a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/DefaultRabbitMQPersisterConnection.cs
+++ b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/DefaultRabbitMQPersisterConnection.cs
@@ -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 _logger;
+ private readonly ILogger _logger;
IConnection _connection;
bool _disposed;
object sync_root = new object();
- public DefaultRabbitMQPersisterConnection(IConnectionFactory connectionFactory,ILogger logger)
+ public DefaultRabbitMQPersistentConnection(IConnectionFactory connectionFactory,ILogger 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;
}
diff --git a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs
index e7a493c10..adbc52ad1 100644
--- a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs
+++ b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs
@@ -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 _logger;
-
- private readonly Dictionary> _handlers
- = new Dictionary>();
-
- private readonly List _eventTypes
- = new List();
+ private readonly IEventBusSubscriptionsManager _subsManager;
+
private IModel _consumerChannel;
private string _queueName;
- public EventBusRabbitMQ(IRabbitMQPersisterConnection persisterConnection, ILogger logger)
+ public EventBusRabbitMQ(IRabbitMQPersistentConnection persistentConnection, ILogger 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()
@@ -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(IIntegrationEventHandler handler) where T : IntegrationEvent
+ public void Subscribe(Func handler)
+ where T : IntegrationEvent
+ where TH : IIntegrationEventHandler
{
var eventName = typeof(T).Name;
-
- if (_handlers.ContainsKey(eventName))
+ var containsKey = _subsManager.HasSubscriptionsForEvent();
+ if (!containsKey)
{
- _handlers[eventName].Add(handler);
- }
- else
- {
- 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());
- _handlers[eventName].Add(handler);
- _eventTypes.Add(typeof(T));
}
-
}
+ _subsManager.AddSubscription(handler);
+
}
- public void Unsubscribe(IIntegrationEventHandler handler) where T : IntegrationEvent
+ public void Unsubscribe()
+ where TH : IIntegrationEventHandler
+ where T : IntegrationEvent
{
- var eventName = typeof(T).Name;
+ _subsManager.RemoveSubscription();
+ }
- if (_handlers.ContainsKey(eventName) && _handlers[eventName].Contains(handler))
+ private static Func FindHandlerByType(Type handlerType, IEnumerable> 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);
- var integrationEvent = JsonConvert.DeserializeObject(message, eventType);
- var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType);
- var handlers = _handlers[eventName];
- foreach (var handler in handlers)
+ if (_subsManager.HasSubscriptionsForEvent(eventName))
+ {
+ var eventType = _subsManager.GetEventTypeByName(eventName);
+ var integrationEvent = JsonConvert.DeserializeObject(message, eventType);
+ var handlers = _subsManager.GetHandlersForEvent(eventName);
+
+ foreach (var handlerfactory in handlers)
{
+ var handler = handlerfactory.DynamicInvoke();
+ var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType);
await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent });
}
}
diff --git a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/IRabbitMQPersisterConnection.cs b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/IRabbitMQPersisterConnection.cs
index b9debe743..5893791c5 100644
--- a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/IRabbitMQPersisterConnection.cs
+++ b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/IRabbitMQPersisterConnection.cs
@@ -3,8 +3,7 @@ using System;
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
{
-
- public interface IRabbitMQPersisterConnection
+ public interface IRabbitMQPersistentConnection
: IDisposable
{
bool IsConnected { get; }
diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckMiddleware.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckMiddleware.cs
index 64b4f48c2..f8e68c957 100644
--- a/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckMiddleware.cs
+++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckMiddleware.cs
@@ -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;
}
diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckStartupFilter.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckStartupFilter.cs
index 4aa91b070..cac4b1188 100644
--- a/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckStartupFilter.cs
+++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckStartupFilter.cs
@@ -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 Configure(Action next)
@@ -27,9 +30,13 @@ namespace Microsoft.AspNetCore.HealthChecks
return app =>
{
if (_port.HasValue)
- app.UseMiddleware(_port);
+ {
+ app.UseMiddleware(_port, _timeout);
+ }
else
- app.UseMiddleware(_path);
+ {
+ app.UseMiddleware(_path, _timeout);
+ }
next(app);
};
diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckWebHostBuilderExtension.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckWebHostBuilderExtension.cs
index 0b806ca06..467293137 100644
--- a/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckWebHostBuilderExtension.cs
+++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckWebHostBuilderExtension.cs
@@ -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(new HealthCheckStartupFilter(port));
+ services.AddSingleton(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(new HealthCheckStartupFilter(path)));
+ builder.ConfigureServices(services => services.AddSingleton(new HealthCheckStartupFilter(path, timeout)));
return builder;
}
}
diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.Data/HealthCheckBuilderDataExtensions.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.SqlServer/HealthCheckBuilderSqlServerExtensions.cs
similarity index 94%
rename from src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.Data/HealthCheckBuilderDataExtensions.cs
rename to src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.SqlServer/HealthCheckBuilderSqlServerExtensions.cs
index e209dfdaf..4998c91ed 100644
--- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.Data/HealthCheckBuilderDataExtensions.cs
+++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.SqlServer/HealthCheckBuilderSqlServerExtensions.cs
@@ -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}");
}
diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.Data/Microsoft.Extensions.HealthChecks.Data.csproj b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.SqlServer/Microsoft.Extensions.HealthChecks.SqlServer.csproj
similarity index 100%
rename from src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.Data/Microsoft.Extensions.HealthChecks.Data.csproj
rename to src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.SqlServer/Microsoft.Extensions.HealthChecks.SqlServer.csproj
diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CachedHealthCheck.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CachedHealthCheck.cs
new file mode 100644
index 000000000..39ed087eb
--- /dev/null
+++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CachedHealthCheck.cs
@@ -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 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);
+ }
+ }
+}
diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CachedHealthCheckExtensions.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CachedHealthCheckExtensions.cs
new file mode 100644
index 000000000..2c3388709
--- /dev/null
+++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CachedHealthCheckExtensions.cs
@@ -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 RunAsync(this CachedHealthCheck check, IServiceProvider serviceProvider)
+ {
+ Guard.ArgumentNotNull(nameof(check), check);
+
+ return check.RunAsync(serviceProvider, CancellationToken.None);
+ }
+ }
+}
diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/AddCheck.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/AddCheck.cs
index cd10fb93d..5b7b49af0 100644
--- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/AddCheck.cs
+++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/AddCheck.cs
@@ -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 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 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 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> 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> 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> 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> 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> 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> 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> 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> 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(this HealthCheckBuilder builder, string name) where TCheck : class, IHealthCheck
+ {
+ Guard.ArgumentNotNull(nameof(builder), builder);
+
+ return builder.AddCheck(name, builder.DefaultCacheDuration);
}
}
}
diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/NumericChecks.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/NumericChecks.cs
index cb97eec46..f3c795629 100644
--- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/NumericChecks.cs
+++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/NumericChecks.cs
@@ -14,7 +14,7 @@ namespace Microsoft.Extensions.HealthChecks
where T : IComparable
{
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 { { "min", minValue }, { "current", currentValue } }
);
});
@@ -35,16 +35,16 @@ namespace Microsoft.Extensions.HealthChecks
where T : IComparable
{
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 { { "max", maxValue }, { "current", currentValue } }
);
});
diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/UrlChecks.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/UrlChecks.cs
index 6ab393547..d7df58def 100644
--- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/UrlChecks.cs
+++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/UrlChecks.cs
@@ -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> 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 urlItems, string groupName)
- => AddUrlChecks(builder, urlItems, groupName, CheckStatus.Warning, response => UrlChecker.DefaultUrlCheck(response));
-
- public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable urlItems, string groupName,
- Func checkFunc)
- {
- Guard.ArgumentNotNull(nameof(checkFunc), checkFunc);
-
- return AddUrlChecks(builder, urlItems, groupName, CheckStatus.Warning, response => new ValueTask(checkFunc(response)));
- }
-
- public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable urlItems, string groupName,
- Func> checkFunc)
- {
- Guard.ArgumentNotNull(nameof(checkFunc), checkFunc);
-
- return AddUrlChecks(builder, urlItems, groupName, CheckStatus.Warning, response => new ValueTask(checkFunc(response)));
- }
-
- public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable urlItems, string groupName,
- Func> checkFunc)
- {
- Guard.ArgumentNotNull(nameof(checkFunc), checkFunc);
-
- return AddUrlChecks(builder, urlItems, groupName, CheckStatus.Warning, response => checkFunc(response));
- }
-
- public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable urlItems, string groupName,
- CheckStatus partialSuccessStatus)
- => AddUrlChecks(builder, urlItems, groupName, partialSuccessStatus, response => UrlChecker.DefaultUrlCheck(response));
-
- public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable urlItems, string groupName,
- CheckStatus partialSuccessStatus, Func checkFunc)
- {
- Guard.ArgumentNotNull(nameof(checkFunc), checkFunc);
-
- return AddUrlChecks(builder, urlItems, groupName, partialSuccessStatus, response => new ValueTask(checkFunc(response)));
- }
-
- public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable urlItems, string groupName,
- CheckStatus partialSuccessStatus, Func> checkFunc)
- {
- Guard.ArgumentNotNull(nameof(checkFunc), checkFunc);
-
- return AddUrlChecks(builder, urlItems, groupName, partialSuccessStatus, response => new ValueTask(checkFunc(response)));
- }
-
- public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable urlItems, string groupName,
- CheckStatus partialSuccessStatus, Func> 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;
- }
}
}
diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CompositeHealthCheckResult.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CompositeHealthCheckResult.cs
index 5a3367843..6894ce85f 100644
--- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CompositeHealthCheckResult.cs
+++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CompositeHealthCheckResult.cs
@@ -7,7 +7,6 @@ using System.Linq;
namespace Microsoft.Extensions.HealthChecks
{
- // REVIEW: Does this need to be thread safe?
///
/// Represents a composite health check result built from several results.
///
@@ -31,17 +30,23 @@ namespace Microsoft.Extensions.HealthChecks
{
var checkStatuses = new HashSet(_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 Data
{
@@ -58,23 +63,21 @@ namespace Microsoft.Extensions.HealthChecks
public IReadOnlyDictionary 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 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);
diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheck.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheck.cs
index d068a7d05..5e1caa2ff 100644
--- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheck.cs
+++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheck.cs
@@ -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> check, TimeSpan cacheDuration)
+ protected HealthCheck(Func> 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> Check { get; }
- protected virtual DateTimeOffset UtcNow => DateTimeOffset.UtcNow;
+ public ValueTask CheckAsync(CancellationToken cancellationToken = default(CancellationToken))
+ => Check(cancellationToken);
- public async ValueTask 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;
- }
+ public static HealthCheck FromCheck(Func check)
+ => new HealthCheck(token => new ValueTask(check()));
- try
- {
- _cachedResult = await Check(cancellationToken).ConfigureAwait(false);
- _cacheExpiration = UtcNow + CacheDuration;
- break;
- }
- finally
- {
- _writerCount = 0;
- }
- }
+ public static HealthCheck FromCheck(Func check)
+ => new HealthCheck(token => new ValueTask(check(token)));
- return _cachedResult;
- }
+ public static HealthCheck FromTaskCheck(Func> check)
+ => new HealthCheck(token => new ValueTask(check()));
- public static HealthCheck FromCheck(Func check, TimeSpan cacheDuration)
- => new HealthCheck(token => new ValueTask(check()), cacheDuration);
+ public static HealthCheck FromTaskCheck(Func> check)
+ => new HealthCheck(token => new ValueTask(check(token)));
- public static HealthCheck FromCheck(Func check, TimeSpan cacheDuration)
- => new HealthCheck(token => new ValueTask(check(token)), cacheDuration);
+ public static HealthCheck FromValueTaskCheck(Func> check)
+ => new HealthCheck(token => check());
- public static HealthCheck FromTaskCheck(Func> check, TimeSpan cacheDuration)
- => new HealthCheck(token => new ValueTask(check()), cacheDuration);
-
- public static HealthCheck FromTaskCheck(Func> check, TimeSpan cacheDuration)
- => new HealthCheck(token => new ValueTask(check(token)), cacheDuration);
-
- public static HealthCheck FromValueTaskCheck(Func> check, TimeSpan cacheDuration)
- => new HealthCheck(token => check(), cacheDuration);
-
- public static HealthCheck FromValueTaskCheck(Func> check, TimeSpan cacheDuration)
- => new HealthCheck(check, cacheDuration);
+ public static HealthCheck FromValueTaskCheck(Func> check)
+ => new HealthCheck(check);
}
}
diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckBuilder.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckBuilder.cs
index f6cc17304..006e4a6ef 100644
--- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckBuilder.cs
+++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckBuilder.cs
@@ -8,33 +8,128 @@ namespace Microsoft.Extensions.HealthChecks
{
public class HealthCheckBuilder
{
- private readonly Dictionary _checks;
+ private readonly Dictionary _checksByName;
+ private readonly HealthCheckGroup _currentGroup;
+ private readonly Dictionary _groups;
public HealthCheckBuilder()
{
- _checks = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ _checksByName = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ _currentGroup = new HealthCheckGroup(string.Empty, CheckStatus.Unhealthy);
+ _groups = new Dictionary(StringComparer.OrdinalIgnoreCase)
+ {
+ [string.Empty] = _currentGroup
+ };
+
DefaultCacheDuration = TimeSpan.FromMinutes(5);
}
- public IReadOnlyDictionary Checks => _checks;
+ ///
+ /// This constructor should only be used when creating a grouped health check builder.
+ ///
+ 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;
+ }
+
+ ///
+ /// Gets the registered checks, indexed by check name.
+ ///
+ public IReadOnlyDictionary ChecksByName => _checksByName;
+
+ ///
+ /// Gets the current default cache duration used when registering checks.
+ ///
public TimeSpan DefaultCacheDuration { get; private set; }
- public HealthCheckBuilder AddCheck(string name, IHealthCheck check)
- {
- Guard.ArgumentNotNullOrWhitespace(nameof(name), name);
- Guard.ArgumentNotNull(nameof(check), check);
+ ///
+ /// Gets the registered groups, indexed by group name. The root group's name is .
+ ///
+ public IReadOnlyDictionary Groups => _groups;
+
+ ///
+ /// Registers a health check type that will later be resolved via dependency
+ /// injection.
+ ///
+ public HealthCheckBuilder AddCheck(string checkName, TimeSpan cacheDuration) where TCheck : class, IHealthCheck
+ {
+ 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;
+ }
+
+ ///
+ /// Registers a concrete health check to the builder.
+ ///
+ 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;
+ }
+
+ ///
+ /// Creates a new health check group, to which you can add one or more health
+ /// checks. Uses when the group is
+ /// partially successful.
+ ///
+ public HealthCheckBuilder AddHealthCheckGroup(string groupName, Action groupChecks)
+ => AddHealthCheckGroup(groupName, groupChecks, CheckStatus.Unhealthy);
+
+ ///
+ /// Creates a new health check group, to which you can add one or more health
+ /// checks.
+ ///
+ public HealthCheckBuilder AddHealthCheckGroup(string groupName, Action 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;
+ }
}
}
diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckExtensions.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckExtensions.cs
deleted file mode 100644
index 2669afc77..000000000
--- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckExtensions.cs
+++ /dev/null
@@ -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 CheckAsync(this IHealthCheck healthCheck)
- {
- Guard.ArgumentNotNull(nameof(healthCheck), healthCheck);
-
- return healthCheck.CheckAsync(CancellationToken.None);
- }
- }
-}
diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckGroup.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckGroup.cs
new file mode 100644
index 000000000..18c55132b
--- /dev/null
+++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckGroup.cs
@@ -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 Checks => ChecksInternal.AsReadOnly();
+
+ internal List ChecksInternal { get; } = new List();
+
+ 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;
+ }
+ }
+ }
+}
diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckResult.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckResult.cs
index 7ea31b42a..d8ef80dc4 100644
--- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckResult.cs
+++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckResult.cs
@@ -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
{
diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckService.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckService.cs
index 6e16bed61..1d2934e0e 100644
--- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckService.cs
+++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckService.cs
@@ -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 _checks;
+ private readonly HealthCheckBuilder _builder;
+ private readonly IReadOnlyList _groups;
+ private readonly HealthCheckGroup _root;
+ private readonly IServiceProvider _serviceProvider;
+ private readonly IServiceScopeFactory _serviceScopeFactory;
- private ILogger _logger;
-
- public HealthCheckService(HealthCheckBuilder builder, ILogger 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 CheckHealthAsync(CheckStatus partiallyHealthyStatus, CancellationToken cancellationToken)
+ public async Task 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 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)
{
- var healthCheckResult = await check.Value.CheckAsync().ConfigureAwait(false);
- logMessage.AppendLine($"HealthCheck: {check.Key} : {healthCheckResult.CheckStatus}");
- result.Add(check.Key, healthCheckResult);
- }
- catch (Exception ex)
- {
- 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 GetAllChecks()
+ => _builder.ChecksByName.Values.ToList().AsReadOnly();
+
+ public CachedHealthCheck GetCheck(string checkName)
+ => _builder.ChecksByName[checkName];
+
+ public HealthCheckGroup GetGroup(string groupName)
+ => _builder.Groups[groupName];
+
+ public IReadOnlyList GetGroups()
+ => _builder.Groups.Values.ToList().AsReadOnly();
+
+ private IServiceScope GetServiceScope()
+ => _serviceScopeFactory == null ? new UnscopedServiceProvider(_serviceProvider) : _serviceScopeFactory.CreateScope();
+
+ public async ValueTask RunCheckAsync(CachedHealthCheck healthCheck, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ using (var scope = GetServiceScope())
+ {
+ return await RunCheckAsync(scope.ServiceProvider, healthCheck, cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ ///
+ /// Uses the provided service provider and executes the provided check.
+ ///
+ public ValueTask RunCheckAsync(IServiceProvider serviceProvider, CachedHealthCheck healthCheck, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ Guard.ArgumentNotNull(nameof(serviceProvider), serviceProvider);
+ Guard.ArgumentNotNull(nameof(healthCheck), healthCheck);
+
+ return healthCheck.RunAsync(serviceProvider, cancellationToken);
+ }
+
+ ///
+ /// Creates a new resolution scope from the default service provider and executes the checks in the given group.
+ ///
+ public async Task RunGroupAsync(HealthCheckGroup group, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ using (var scope = GetServiceScope())
+ return await RunGroupAsync(scope.ServiceProvider, group, cancellationToken).ConfigureAwait(false);
+ }
+
+ ///
+ /// Uses the provided service provider and executes the checks in the given group.
+ ///
+ public async Task 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);
}
- if (logMessage.Length == 0)
- logMessage.AppendLine("HealthCheck: No checks have been registered");
-
- _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() { }
+ }
}
}
diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckServiceCollectionExtensions.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckServiceCollectionExtensions.cs
index cdd763d9f..678731737 100644
--- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckServiceCollectionExtensions.cs
+++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckServiceCollectionExtensions.cs
@@ -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 checkupAction)
+ private static readonly Type HealthCheckServiceInterface = typeof(IHealthCheckService);
+
+ public static IServiceCollection AddHealthChecks(this IServiceCollection services, Action checks)
{
- var checkupBuilder = new HealthCheckBuilder();
+ Guard.OperationValid(!services.Any(descriptor => descriptor.ServiceType == HealthCheckServiceInterface), "AddHealthChecks may only be called once.");
- checkupAction.Invoke(checkupBuilder);
+ var builder = new HealthCheckBuilder();
- services.AddSingleton(checkupBuilder);
- services.AddSingleton();
+ services.AddSingleton(serviceProvider =>
+ {
+ var serviceScopeFactory = serviceProvider.GetService();
+ return new HealthCheckService(builder, serviceProvider, serviceScopeFactory);
+ });
+
+ checks(builder);
return services;
}
}
diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckServiceExtensions.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckServiceExtensions.cs
deleted file mode 100644
index 98ab54b70..000000000
--- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckServiceExtensions.cs
+++ /dev/null
@@ -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 CheckHealthAsync(this IHealthCheckService service)
- {
- Guard.ArgumentNotNull(nameof(service), service);
-
- return service.CheckHealthAsync(CheckStatus.Unhealthy, CancellationToken.None);
- }
-
- public static Task CheckHealthAsync(this IHealthCheckService service, CheckStatus partiallyHealthyStatus)
- {
- Guard.ArgumentNotNull(nameof(service), service);
-
- return service.CheckHealthAsync(partiallyHealthyStatus, CancellationToken.None);
- }
-
- public static Task CheckHealthAsync(this IHealthCheckService service, CancellationToken cancellationToken)
- {
- Guard.ArgumentNotNull(nameof(service), service);
-
- return service.CheckHealthAsync(CheckStatus.Unhealthy, cancellationToken);
- }
- public static Task CheckHealthAsync(this IHealthCheckService service, CheckStatus partiallyHealthyStatus, CancellationToken cancellationToken)
- {
- Guard.ArgumentNotNull(nameof(service), service);
-
- return service.CheckHealthAsync(partiallyHealthyStatus, cancellationToken);
- }
- }
-}
diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/IHealthCheck.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/IHealthCheck.cs
index b5b95405f..e4aa45d28 100644
--- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/IHealthCheck.cs
+++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/IHealthCheck.cs
@@ -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 CheckAsync(CancellationToken cancellationToken);
+ ValueTask CheckAsync(CancellationToken cancellationToken = default(CancellationToken));
}
}
diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/IHealthCheckService.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/IHealthCheckService.cs
index 60e917264..17a49cb00 100644
--- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/IHealthCheckService.cs
+++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/IHealthCheckService.cs
@@ -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 CheckHealthAsync(CheckStatus partiallyHealthyStatus, CancellationToken cancellationToken);
+ ///
+ /// Runs all registered health checks.
+ ///
+ Task CheckHealthAsync(CancellationToken cancellationToken = default(CancellationToken));
+
+ ///
+ /// Gets all registered health checks as a flat list.
+ ///
+ IReadOnlyList GetAllChecks();
+
+ ///
+ /// Gets a health check by name.
+ ///
+ CachedHealthCheck GetCheck(string checkName);
+
+ ///
+ /// Gets all health checks in a group.
+ ///
+ HealthCheckGroup GetGroup(string groupName);
+
+ ///
+ /// Gets all the health check groups.
+ ///
+ IReadOnlyList GetGroups();
+
+ ///
+ /// Creates a new resolution scope from the default service provider and executes the provided check.
+ ///
+ ValueTask RunCheckAsync(CachedHealthCheck healthCheck, CancellationToken cancellationToken = default(CancellationToken));
+
+ ///
+ /// Uses the provided service provider and executes the provided check.
+ ///
+ ValueTask RunCheckAsync(IServiceProvider serviceProvider, CachedHealthCheck healthCheck, CancellationToken cancellationToken = default(CancellationToken));
+
+ ///
+ /// Creates a new resolution scope from the default service provider and executes the checks in the given group.
+ ///
+ Task RunGroupAsync(HealthCheckGroup group, CancellationToken cancellationToken = default(CancellationToken));
+
+ ///
+ /// Uses the provided service provider and executes the checks in the given group.
+ ///
+ Task RunGroupAsync(IServiceProvider serviceProvider, HealthCheckGroup group, CancellationToken cancellationToken = default(CancellationToken));
}
}
diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Internal/UrlChecker.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Internal/UrlChecker.cs
index ad021b149..56800d334 100644
--- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Internal/UrlChecker.cs
+++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Internal/UrlChecker.cs
@@ -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> _checkFunc;
- private readonly string[] _urls;
+ private readonly string _url;
- // REVIEW: Cache timeout here?
- public UrlChecker(Func> checkFunc, params string[] urls)
+ public UrlChecker(Func> 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 CheckAsync()
- => _urls.Length == 1 ? CheckSingleAsync() : CheckMultiAsync();
-
- public async Task CheckSingleAsync()
+ public async Task CheckAsync()
{
- var httpClient = CreateHttpClient();
- var result = default(IHealthCheckResult);
- await CheckUrlAsync(httpClient, _urls[0], (_, checkResult) => result = checkResult).ConfigureAwait(false);
- return result;
- }
-
- public async Task CheckMultiAsync()
- {
- 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 adder)
- {
- var name = $"UrlCheck({url})";
- try
+ using (var httpClient = CreateHttpClient())
{
- var response = await httpClient.GetAsync(url).ConfigureAwait(false);
- var result = await _checkFunc(response);
- adder(name, result);
- }
- catch (Exception ex)
- {
- 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 { { "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 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
{
{ "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()
diff --git a/src/BuildingBlocks/HealthChecks/src/common/Guard.cs b/src/BuildingBlocks/HealthChecks/src/common/Guard.cs
index 8ed91054e..9f394be51 100644
--- a/src/BuildingBlocks/HealthChecks/src/common/Guard.cs
+++ b/src/BuildingBlocks/HealthChecks/src/common/Guard.cs
@@ -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(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 instead of IEnumerable to discourage double enumeration
public static void ArgumentNotNullOrEmpty(string argumentName, IReadOnlyCollection items)
{
if (items == null)
+ {
throw new ArgumentNullException(argumentName);
+ }
if (items.Count == 0)
- throw new ArgumentException("Collection must contain at least one item", argumentName);
- }
-
- public static void ArgumentNotNullOrWhitespace(string argumentName, string value)
- {
- if (value == null)
- throw new ArgumentNullException(argumentName);
- if (string.IsNullOrWhiteSpace(value))
- throw new ArgumentException("Value must contain a non-whitespace value", argumentName);
+ {
+ throw new ArgumentException("Collection must contain at least one item.", argumentName);
+ }
}
public static void ArgumentValid(bool valid, string argumentName, string exceptionMessage)
{
if (!valid)
+ {
throw new ArgumentException(exceptionMessage, argumentName);
+ }
+ }
+
+ public static void OperationValid(bool valid, string exceptionMessage)
+ {
+ if (!valid)
+ {
+ throw new InvalidOperationException(exceptionMessage);
+ }
}
}
diff --git a/src/Services/Basket/Basket.API/Basket.API.csproj b/src/Services/Basket/Basket.API/Basket.API.csproj
index ce20513cc..b3ba97b10 100644
--- a/src/Services/Basket/Basket.API/Basket.API.csproj
+++ b/src/Services/Basket/Basket.API/Basket.API.csproj
@@ -43,7 +43,6 @@
-
diff --git a/src/Services/Basket/Basket.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs b/src/Services/Basket/Basket.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs
index 5acd0bbdc..ab7989973 100644
--- a/src/Services/Basket/Basket.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs
+++ b/src/Services/Basket/Basket.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs
@@ -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; }
}
}
}
diff --git a/src/Services/Basket/Basket.API/Startup.cs b/src/Services/Basket/Basket.API/Startup.cs
index 60fc46de2..855312a65 100644
--- a/src/Services/Basket/Basket.API/Startup.cs
+++ b/src/Services/Basket/Basket.API/Startup.cs
@@ -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(sp =>
+ services.AddSingleton(sp =>
{
var settings = sp.GetRequiredService>().Value;
- var logger = sp.GetRequiredService>();
+ var logger = sp.GetRequiredService>();
var factory = new ConnectionFactory()
{
HostName = settings.EventBusConnection
};
- return new DefaultRabbitMQPersisterConnection(factory, logger);
+ return new DefaultRabbitMQPersistentConnection(factory, logger);
});
- services.AddSingleton();
-
services.AddSwaggerGen();
services.ConfigureSwaggerGen(options =>
@@ -108,9 +108,16 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
});
services.AddTransient();
- services.AddTransient, ProductPriceChangedIntegrationEventHandler>();
- services.AddTransient, OrderStartedIntegrationEventHandler>();
+ RegisterServiceBus(services);
+ }
+ private void RegisterServiceBus(IServiceCollection services)
+ {
+ services.AddSingleton();
+ services.AddSingleton();
+
+ services.AddTransient();
+ services.AddTransient();
}
@@ -155,11 +162,13 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
var orderStartedHandler = app.ApplicationServices
.GetService>();
- var eventBus = app.ApplicationServices
- .GetRequiredService();
+ var eventBus = app.ApplicationServices.GetRequiredService();
- eventBus.Subscribe(catalogPriceHandler);
- eventBus.Subscribe(orderStartedHandler);
+ eventBus.Subscribe
+ (() => app.ApplicationServices.GetRequiredService());
+
+ eventBus.Subscribe
+ (() => app.ApplicationServices.GetRequiredService());
}
}
}
diff --git a/src/Services/Catalog/Catalog.API/Catalog.API.csproj b/src/Services/Catalog/Catalog.API/Catalog.API.csproj
index d805e06e0..4306d6922 100644
--- a/src/Services/Catalog/Catalog.API/Catalog.API.csproj
+++ b/src/Services/Catalog/Catalog.API/Catalog.API.csproj
@@ -59,7 +59,7 @@
-
+
diff --git a/src/Services/Catalog/Catalog.API/Startup.cs b/src/Services/Catalog/Catalog.API/Startup.cs
index c13ac2d1b..9eb195674 100644
--- a/src/Services/Catalog/Catalog.API/Startup.cs
+++ b/src/Services/Catalog/Catalog.API/Startup.cs
@@ -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();
- services.AddSingleton(sp =>
+ services.AddSingleton(sp =>
{
var settings = sp.GetRequiredService>().Value;
- var logger = sp.GetRequiredService>();
+ var logger = sp.GetRequiredService>();
var factory = new ConnectionFactory()
{
HostName = settings.EventBusConnection
};
- return new DefaultRabbitMQPersisterConnection(factory, logger);
+ return new DefaultRabbitMQPersistentConnection(factory, logger);
});
+ services.AddSingleton();
services.AddSingleton();
}
diff --git a/src/Services/Identity/Identity.API/Identity.API.csproj b/src/Services/Identity/Identity.API/Identity.API.csproj
index 043595232..dda24079e 100644
--- a/src/Services/Identity/Identity.API/Identity.API.csproj
+++ b/src/Services/Identity/Identity.API/Identity.API.csproj
@@ -63,7 +63,7 @@
| | | |