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 @@ - + diff --git a/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommand.cs b/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommand.cs index 1485a536f..950c4bdc5 100644 --- a/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommand.cs +++ b/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommand.cs @@ -61,20 +61,16 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands [DataMember] public IEnumerable OrderItems => _orderItems; - public void AddOrderItem(OrderItemDTO item) - { - _orderItems.Add(item); - } - public CreateOrderCommand() { _orderItems = new List(); } - public CreateOrderCommand(string city, string street, string state, string country, string zipcode, + public CreateOrderCommand(List orderItems, string city, string street, string state, string country, string zipcode, string cardNumber, string cardHolderName, DateTime cardExpiration, string cardSecurityNumber, int cardTypeId, int paymentId, int buyerId) : this() { + _orderItems = orderItems; City = city; Street = street; State = state; diff --git a/src/Services/Ordering/Ordering.API/Ordering.API.csproj b/src/Services/Ordering/Ordering.API/Ordering.API.csproj index 6eed3a13c..975645ce4 100644 --- a/src/Services/Ordering/Ordering.API/Ordering.API.csproj +++ b/src/Services/Ordering/Ordering.API/Ordering.API.csproj @@ -27,7 +27,7 @@ - + diff --git a/src/Services/Ordering/Ordering.API/Startup.cs b/src/Services/Ordering/Ordering.API/Startup.cs index 0d6e222b6..58d8f1cbe 100644 --- a/src/Services/Ordering/Ordering.API/Startup.cs +++ b/src/Services/Ordering/Ordering.API/Startup.cs @@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.EntityFrameworkCore; + using Microsoft.eShopOnContainers.BuildingBlocks.EventBus; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ; using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF; @@ -107,18 +108,19 @@ var serviceProvider = services.BuildServiceProvider(); services.AddTransient(); - services.AddSingleton(sp => + services.AddSingleton(sp => { - var logger = sp.GetRequiredService>(); + var logger = sp.GetRequiredService>(); var factory = new ConnectionFactory() { HostName = Configuration["EventBusConnection"] }; - return new DefaultRabbitMQPersisterConnection(factory, logger); + return new DefaultRabbitMQPersistentConnection(factory, logger); }); + services.AddSingleton(); services.AddSingleton(); services.AddOptions(); diff --git a/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs b/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs index 5b95ee23c..18d66534b 100644 --- a/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs +++ b/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs @@ -252,7 +252,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure // After executing this line all the changes (from the Command Handler and Domain Event Handlers) - // performed thought the DbContext will be commited + // performed throught the DbContext will be commited var result = await base.SaveChangesAsync(); return true; diff --git a/src/Web/WebMVC/WebMVC.csproj b/src/Web/WebMVC/WebMVC.csproj index 20a8baca0..3f0f6b84b 100644 --- a/src/Web/WebMVC/WebMVC.csproj +++ b/src/Web/WebMVC/WebMVC.csproj @@ -59,7 +59,6 @@ - diff --git a/src/Web/WebMVC/wwwroot/css/site.min.css b/src/Web/WebMVC/wwwroot/css/site.min.css index 4d03fa783..f5fc90999 100644 --- a/src/Web/WebMVC/wwwroot/css/site.min.css +++ b/src/Web/WebMVC/wwwroot/css/site.min.css @@ -1 +1 @@ -.esh-app-footer{background-color:#000;border-top:1px solid #eee;margin-top:2.5rem;padding-bottom:2.5rem;padding-top:2.5rem;width:100%}.esh-app-footer-brand{height:50px;width:230px}.esh-app-footer-text{color:#83d01b;line-height:50px;text-align:right;width:100%}@font-face{font-family:Montserrat;font-weight:400;src:url("../fonts/Montserrat-Regular.eot?") format("eot"),url("../fonts/Montserrat-Regular.woff") format("woff"),url("../fonts/Montserrat-Regular.ttf") format("truetype"),url("../fonts/Montserrat-Regular.svg#Montserrat") format("svg")}@font-face{font-family:Montserrat;font-weight:700;src:url("../fonts/Montserrat-Bold.eot?") format("eot"),url("../fonts/Montserrat-Bold.woff") format("woff"),url("../fonts/Montserrat-Bold.ttf") format("truetype"),url("../fonts/Montserrat-Bold.svg#Montserrat") format("svg")}html,body{font-family:Montserrat,sans-serif;font-size:16px;font-weight:400;z-index:10}*,*::after,*::before{box-sizing:border-box}.preloading{color:#00a69c;display:block;font-size:1.5rem;left:50%;position:fixed;top:50%;transform:translate(-50%,-50%)}select::-ms-expand{display:none}@media screen and (min-width:992px){.form-input{max-width:360px;width:360px}}.form-input{border-radius:0;height:45px;padding:10px}.form-input-small{max-width:100px !important}.form-input-medium{width:150px !important}.alert{padding-left:0}.alert-danger{background-color:transparent;border:0;color:#fb0d0d;font-size:12px}a,a:active,a:hover,a:visited{color:#000;text-decoration:none;transition:color .35s}a:hover,a:active{color:#75b918;transition:color .35s}.esh-basket{min-height:80vh}.esh-basket-titles{padding-bottom:1rem;padding-top:2rem}.esh-basket-titles--clean{padding-bottom:0;padding-top:0}.esh-basket-title{text-transform:uppercase}.esh-basket-items--border{border-bottom:1px solid #eee;padding:.5rem 0}.esh-basket-items--border:last-of-type{border-color:transparent}.esh-basket-item{font-size:1rem;font-weight:300}.esh-basket-item--middle{line-height:8rem}@media screen and (max-width:1024px){.esh-basket-item--middle{line-height:1rem}}.esh-basket-item--mark{color:#00a69c}.esh-basket-image{height:8rem}.esh-basket-input{line-height:1rem;width:100%}.esh-basket-checkout{border:none;border-radius:0;background-color:#83d01b;color:#fff;display:inline-block;font-size:1rem;font-weight:400;margin-top:1rem;padding:1rem 1.5rem;text-align:center;text-transform:uppercase;transition:all .35s}.esh-basket-checkout:hover{background-color:#4a760f;transition:all .35s}.esh-basket-margin12{margin-left:12px}.esh-basketstatus{cursor:pointer;display:inline-block;float:right;position:relative;transition:all .35s}.esh-basketstatus.is-disabled{opacity:.5;pointer-events:none}.esh-basketstatus-image{height:36px;margin-top:.5rem}.esh-basketstatus-badge{background-color:#83d01b;border-radius:50%;color:#fff;display:block;height:1.5rem;left:50%;position:absolute;text-align:center;top:0;transform:translateX(-38%);transition:all .35s;width:1.5rem}.esh-basketstatus:hover .esh-basketstatus-badge{background-color:transparent;color:#75b918;transition:all .35s}.esh-catalog-hero{background-image:url("../images/main_banner.png");background-size:cover;height:260px;width:100%}.esh-catalog-title{position:relative;top:74.28571px}.esh-catalog-filters{background-color:#00a69c;height:65px}.esh-catalog-filter{background-color:transparent;border-color:#00d9cc;color:#fff;cursor:pointer;margin-right:1rem;margin-top:.5rem;outline-color:#83d01b;padding-bottom:0;padding-left:.5rem;padding-right:.5rem;padding-top:1.5rem;min-width:140px;-webkit-appearance:none}.esh-catalog-filter option{background-color:#00a69c}.esh-catalog-label{display:inline-block;position:relative;z-index:0}.esh-catalog-label::before{color:rgba(255,255,255,.5);content:attr(data-title);font-size:.65rem;margin-top:.65rem;margin-left:.5rem;position:absolute;text-transform:uppercase;z-index:1}.esh-catalog-label::after{background-image:url("../images/arrow-down.png");height:7px;content:'';position:absolute;right:1.5rem;top:2.5rem;width:10px;z-index:1}.esh-catalog-send{background-color:#83d01b;color:#fff;cursor:pointer;font-size:1rem;transform:translateY(.5rem);padding:.5rem;transition:all .35s}.esh-catalog-send:hover{background-color:#4a760f;transition:all .35s}.esh-catalog-items{margin-top:1rem}.esh-catalog-item{text-align:center;margin-bottom:1.5rem;width:33%;display:inline-block;float:none !important}@media screen and (max-width:1024px){.esh-catalog-item{width:50%}}@media screen and (max-width:768px){.esh-catalog-item{width:100%}}.esh-catalog-thumbnail{max-width:370px;width:100%}.esh-catalog-button{background-color:#83d01b;border:none;color:#fff;cursor:pointer;font-size:1rem;height:3rem;margin-top:1rem;transition:all .35s;width:80%}.esh-catalog-button.is-disabled{opacity:.5;pointer-events:none}.esh-catalog-button:hover{background-color:#4a760f;transition:all .35s}.esh-catalog-name{font-size:1rem;font-weight:300;margin-top:.5rem;text-align:center;text-transform:uppercase}.esh-catalog-price{text-align:center;font-weight:900;font-size:28px}.esh-catalog-price::before{content:'$'}.esh-orders{min-height:80vh;overflow-x:hidden}.esh-orders-header{background-color:#00a69c;height:4rem}.esh-orders-back{color:rgba(255,255,255,.4);line-height:4rem;text-transform:uppercase;text-decoration:none;transition:color .35s}.esh-orders-back:hover{color:#fff;transition:color .35s}.esh-orders-titles{padding-bottom:1rem;padding-top:2rem}.esh-orders-title{text-transform:uppercase}.esh-orders-items{height:2rem;line-height:2rem;position:relative}.esh-orders-items:nth-of-type(2n+1):before{background-color:#eef;content:'';height:100%;left:0;margin-left:-100vw;position:absolute;top:0;width:200vw;z-index:-1}.esh-orders-item{font-weight:300}.esh-orders-item--hover{opacity:0;pointer-events:none}.esh-orders-items:hover .esh-orders-item--hover{opacity:1;pointer-events:all}.esh-orders-link{color:#83d01b;text-decoration:none;transition:color .35s}.esh-orders-link:hover{color:#75b918;transition:color .35s}.esh-orders_detail{min-height:80vh}.esh-orders_detail-section{padding:1rem 0}.esh-orders_detail-section--right{text-align:right}.esh-orders_detail-titles{padding-bottom:1rem;padding-top:2rem}.esh-orders_detail-title{text-transform:uppercase}.esh-orders_detail-items--border{border-bottom:1px solid #eee;padding:.5rem 0}.esh-orders_detail-items--border:last-of-type{border-color:transparent}.esh-orders_detail-item{font-size:1rem;font-weight:300}.esh-orders_detail-item--middle{line-height:8rem}@media screen and (max-width:768px){.esh-orders_detail-item--middle{line-height:1rem}}.esh-orders_detail-item--mark{color:#83d01b}.esh-orders_detail-image{height:8rem}.esh-orders_new{min-height:80vh}.esh-orders_new-header{background-color:#00a69c;height:4rem}.esh-orders_new-back{color:rgba(255,255,255,.4);line-height:4rem;text-decoration:none;text-transform:uppercase;transition:color .35s}.esh-orders_new-back:hover{color:#fff;transition:color .35s}.esh-orders_new-section{padding:1rem 0}.esh-orders_new-section--right{text-align:right}.esh-orders_new-placeOrder{background-color:#83d01b;border:0;border-radius:0;color:#fff;display:inline-block;font-size:1rem;font-weight:400;margin-top:1rem;padding:1rem 1.5rem;text-align:center;text-transform:uppercase;transition:all .35s}.esh-orders_new-placeOrder:hover{background-color:#4a760f;transition:all .35s}.esh-orders_new-titles{padding-bottom:1rem;padding-top:2rem}.esh-orders_new-title{font-size:1.25rem;text-transform:uppercase}.esh-orders_new-items--border{border-bottom:1px solid #eee;padding:.5rem 0}.esh-orders_new-items--border:last-of-type{border-color:transparent}.esh-orders_new-item{font-size:1rem;font-weight:300}.esh-orders_new-item--middle{line-height:8rem}@media screen and (max-width:768px){.esh-orders_new-item--middle{line-height:1rem}}.esh-orders_new-item--mark{color:#83d01b}.esh-orders_new-image{height:8rem}.esh-header{background-color:#00a69c;height:4rem}.esh-header-back{color:rgba(255,255,255,.5) !important;line-height:4rem;text-transform:uppercase;text-decoration:none;transition:color .35s}.esh-header-back:hover{color:#fff !important;transition:color .35s}.esh-identity{line-height:3rem;position:relative;text-align:right}.esh-identity-section{display:inline-block;width:100%}.esh-identity-name{display:inline-block}.esh-identity-name--upper{text-transform:uppercase}@media screen and (max-width:768px){.esh-identity-name{font-size:.85rem}}.esh-identity-image{display:inline-block}.esh-identity-drop{background:#fff;height:0;min-width:14rem;right:0;overflow:hidden;padding:.5rem;position:absolute;top:2.5rem;transition:height .35s}.esh-identity:hover .esh-identity-drop{border:1px solid #eee;height:7rem;transition:height .35s}.esh-identity-item{cursor:pointer;display:block;transition:color .35s}.esh-identity-item:hover{color:#75b918;transition:color .35s}.esh-pager-wrapper{padding-top:1rem;text-align:center}.esh-pager-item{margin:0 5vw}.esh-pager-item--navigable{display:inline-block;cursor:pointer}.esh-pager-item--navigable.is-disabled{opacity:0;pointer-events:none}.esh-pager-item--navigable:hover{color:#83d01b}@media screen and (max-width:1280px){.esh-pager-item{font-size:.85rem}}@media screen and (max-width:1024px){.esh-pager-item{margin:0 4vw}} \ No newline at end of file +.esh-app-footer{background-color:#000;border-top:1px solid #eee;margin-top:2.5rem;padding-bottom:2.5rem;padding-top:2.5rem;width:100%}.esh-app-footer-brand{height:50px;width:230px}.esh-app-footer-text{color:#83d01b;line-height:50px;text-align:right;width:100%}@font-face{font-family:Montserrat;font-weight:400;src:url("../fonts/Montserrat-Regular.eot?") format("eot"),url("../fonts/Montserrat-Regular.woff") format("woff"),url("../fonts/Montserrat-Regular.ttf") format("truetype"),url("../fonts/Montserrat-Regular.svg#Montserrat") format("svg")}@font-face{font-family:Montserrat;font-weight:700;src:url("../fonts/Montserrat-Bold.eot?") format("eot"),url("../fonts/Montserrat-Bold.woff") format("woff"),url("../fonts/Montserrat-Bold.ttf") format("truetype"),url("../fonts/Montserrat-Bold.svg#Montserrat") format("svg")}html,body{font-family:Montserrat,sans-serif;font-size:16px;font-weight:400;z-index:10}*,*::after,*::before{box-sizing:border-box}.preloading{color:#00a69c;display:block;font-size:1.5rem;left:50%;position:fixed;top:50%;transform:translate(-50%,-50%)}select::-ms-expand{display:none}@media screen and (min-width:992px){.form-input{max-width:360px;width:360px}}.form-input{border-radius:0;height:45px;padding:10px}.form-input-small{max-width:100px !important}.form-input-medium{width:150px !important}.alert{padding-left:0}.alert-danger{background-color:transparent;border:0;color:#fb0d0d;font-size:12px}a,a:active,a:hover,a:visited{color:#000;text-decoration:none;transition:color .35s}a:hover,a:active{color:#75b918;transition:color .35s}.esh-basket{min-height:80vh}.esh-basket-titles{padding-bottom:1rem;padding-top:2rem}.esh-basket-titles--clean{padding-bottom:0;padding-top:0}.esh-basket-title{text-transform:uppercase}.esh-basket-items--border{border-bottom:1px solid #eee;padding:.5rem 0}.esh-basket-items--border:last-of-type{border-color:transparent}.esh-basket-item{font-size:1rem;font-weight:300}.esh-basket-item--middle{line-height:8rem}@media screen and (max-width:1024px){.esh-basket-item--middle{line-height:1rem}}.esh-basket-item--mark{color:#00a69c}.esh-basket-image{height:8rem}.esh-basket-input{line-height:1rem;width:100%}.esh-basket-checkout{border:none;border-radius:0;background-color:#83d01b;color:#fff;display:inline-block;font-size:1rem;font-weight:400;margin-top:1rem;padding:1rem 1.5rem;text-align:center;text-transform:uppercase;transition:all .35s}.esh-basket-checkout:hover{background-color:#4a760f;transition:all .35s}.esh-basket-margin12{margin-left:12px}.esh-basketstatus{cursor:pointer;display:inline-block;float:right;position:relative;transition:all .35s}.esh-basketstatus.is-disabled{opacity:.5;pointer-events:none}.esh-basketstatus-image{height:36px;margin-top:.5rem}.esh-basketstatus-badge{background-color:#83d01b;border-radius:50%;color:#fff;display:block;height:1.5rem;left:50%;position:absolute;text-align:center;top:0;transform:translateX(-38%);transition:all .35s;width:1.5rem}.esh-basketstatus:hover .esh-basketstatus-badge{background-color:transparent;color:#75b918;transition:all .35s}.esh-catalog-hero{background-image:url("../images/main_banner.png");background-size:cover;height:260px;width:100%}.esh-catalog-title{position:relative;top:74.28571px}.esh-catalog-filters{background-color:#00a69c;height:65px}.esh-catalog-filter{background-color:transparent;border-color:#00d9cc;color:#fff;cursor:pointer;margin-right:1rem;margin-top:.5rem;outline-color:#83d01b;padding-bottom:0;padding-left:.5rem;padding-right:.5rem;padding-top:1.5rem;min-width:140px;-webkit-appearance:none}.esh-catalog-filter option{background-color:#00a69c}.esh-catalog-label{display:inline-block;position:relative;z-index:0}.esh-catalog-label::before{color:rgba(255,255,255,.5);content:attr(data-title);font-size:.65rem;margin-top:.65rem;margin-left:.5rem;position:absolute;text-transform:uppercase;z-index:1}.esh-catalog-label::after{background-image:url("../images/arrow-down.png");height:7px;content:'';position:absolute;right:1.5rem;top:2.5rem;width:10px;z-index:1}.esh-catalog-send{background-color:#83d01b;color:#fff;cursor:pointer;font-size:1rem;transform:translateY(.5rem);padding:.5rem;transition:all .35s}.esh-catalog-send:hover{background-color:#4a760f;transition:all .35s}.esh-catalog-items{margin-top:1rem}.esh-catalog-item{text-align:center;margin-bottom:1.5rem;width:33%;display:inline-block;float:none !important}@media screen and (max-width:1024px){.esh-catalog-item{width:50%}}@media screen and (max-width:768px){.esh-catalog-item{width:100%}}.esh-catalog-thumbnail{max-width:370px;width:100%}.esh-catalog-button{background-color:#83d01b;border:none;color:#fff;cursor:pointer;font-size:1rem;height:3rem;margin-top:1rem;transition:all .35s;width:80%}.esh-catalog-button.is-disabled{opacity:.5;pointer-events:none}.esh-catalog-button:hover{background-color:#4a760f;transition:all .35s}.esh-catalog-name{font-size:1rem;font-weight:300;margin-top:.5rem;text-align:center;text-transform:uppercase}.esh-catalog-price{text-align:center;font-weight:900;font-size:28px}.esh-catalog-price::before{content:'$'}.esh-orders{min-height:80vh;overflow-x:hidden}.esh-orders-header{background-color:#00a69c;height:4rem}.esh-orders-back{color:rgba(255,255,255,.4);line-height:4rem;text-transform:uppercase;text-decoration:none;transition:color .35s}.esh-orders-back:hover{color:#fff;transition:color .35s}.esh-orders-titles{padding-bottom:1rem;padding-top:2rem}.esh-orders-title{text-transform:uppercase}.esh-orders-items{height:2rem;line-height:2rem;position:relative}.esh-orders-items:nth-of-type(2n+1):before{background-color:#eef;content:'';height:100%;left:0;margin-left:-100vw;position:absolute;top:0;width:200vw;z-index:-1}.esh-orders-item{font-weight:300}.esh-orders-item--hover{opacity:0;pointer-events:none}.esh-orders-items:hover .esh-orders-item--hover{opacity:1;pointer-events:all}.esh-orders-link{color:#83d01b;text-decoration:none;transition:color .35s}.esh-orders-link:hover{color:#75b918;transition:color .35s}.esh-orders_new{min-height:80vh}.esh-orders_new-header{background-color:#00a69c;height:4rem}.esh-orders_new-back{color:rgba(255,255,255,.4);line-height:4rem;text-decoration:none;text-transform:uppercase;transition:color .35s}.esh-orders_new-back:hover{color:#fff;transition:color .35s}.esh-orders_new-section{padding:1rem 0}.esh-orders_new-section--right{text-align:right}.esh-orders_new-placeOrder{background-color:#83d01b;border:0;border-radius:0;color:#fff;display:inline-block;font-size:1rem;font-weight:400;margin-top:1rem;padding:1rem 1.5rem;text-align:center;text-transform:uppercase;transition:all .35s}.esh-orders_new-placeOrder:hover{background-color:#4a760f;transition:all .35s}.esh-orders_new-titles{padding-bottom:1rem;padding-top:2rem}.esh-orders_new-title{font-size:1.25rem;text-transform:uppercase}.esh-orders_new-items--border{border-bottom:1px solid #eee;padding:.5rem 0}.esh-orders_new-items--border:last-of-type{border-color:transparent}.esh-orders_new-item{font-size:1rem;font-weight:300}.esh-orders_new-item--middle{line-height:8rem}@media screen and (max-width:768px){.esh-orders_new-item--middle{line-height:1rem}}.esh-orders_new-item--mark{color:#83d01b}.esh-orders_new-image{height:8rem}.esh-orders_detail{min-height:80vh}.esh-orders_detail-section{padding:1rem 0}.esh-orders_detail-section--right{text-align:right}.esh-orders_detail-titles{padding-bottom:1rem;padding-top:2rem}.esh-orders_detail-title{text-transform:uppercase}.esh-orders_detail-items--border{border-bottom:1px solid #eee;padding:.5rem 0}.esh-orders_detail-items--border:last-of-type{border-color:transparent}.esh-orders_detail-item{font-size:1rem;font-weight:300}.esh-orders_detail-item--middle{line-height:8rem}@media screen and (max-width:768px){.esh-orders_detail-item--middle{line-height:1rem}}.esh-orders_detail-item--mark{color:#83d01b}.esh-orders_detail-image{height:8rem}.esh-identity{line-height:3rem;position:relative;text-align:right}.esh-identity-section{display:inline-block;width:100%}.esh-identity-name{display:inline-block}.esh-identity-name--upper{text-transform:uppercase}@media screen and (max-width:768px){.esh-identity-name{font-size:.85rem}}.esh-identity-image{display:inline-block}.esh-identity-drop{background:#fff;height:0;min-width:14rem;right:0;overflow:hidden;padding:.5rem;position:absolute;top:2.5rem;transition:height .35s}.esh-identity:hover .esh-identity-drop{border:1px solid #eee;height:7rem;transition:height .35s}.esh-identity-item{cursor:pointer;display:block;transition:color .35s}.esh-identity-item:hover{color:#75b918;transition:color .35s}.esh-header{background-color:#00a69c;height:4rem}.esh-header-back{color:rgba(255,255,255,.5) !important;line-height:4rem;text-transform:uppercase;text-decoration:none;transition:color .35s}.esh-header-back:hover{color:#fff !important;transition:color .35s}.esh-pager-wrapper{padding-top:1rem;text-align:center}.esh-pager-item{margin:0 5vw}.esh-pager-item--navigable{display:inline-block;cursor:pointer}.esh-pager-item--navigable.is-disabled{opacity:0;pointer-events:none}.esh-pager-item--navigable:hover{color:#83d01b}@media screen and (max-width:1280px){.esh-pager-item{font-size:.85rem}}@media screen and (max-width:1024px){.esh-pager-item{margin:0 4vw}} \ No newline at end of file diff --git a/src/Web/WebSPA/.angular-cli.json b/src/Web/WebSPA/.angular-cli.json new file mode 100644 index 000000000..131f2897c --- /dev/null +++ b/src/Web/WebSPA/.angular-cli.json @@ -0,0 +1,58 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "project": { + "name": "WebSPA" + }, + "apps": [ + { + "root": "Client", + "outDir": "wwwroot", + "assets": [ + "assets", + "favicon.ico" + ], + "index": "index.html", + "main": "main.ts", + "polyfills": "polyfills.ts", + "test": "test.ts", + "tsconfig": "tsconfig.app.json", + "testTsconfig": "tsconfig.spec.json", + "prefix": "app", + "styles": [ + "globals.scss", + "../node_modules/bootstrap/scss/bootstrap.scss" + ], + "scripts": [], + "environmentSource": "environments/environment.ts", + "environments": { + "dev": "environments/environment.ts", + "prod": "environments/environment.prod.ts" + } + } + ], + "e2e": { + "protractor": { + "config": "./protractor.conf.js" + } + }, + "lint": [ + { + "project": "Client/tsconfig.app.json" + }, + { + "project": "Client/tsconfig.spec.json" + }, + { + "project": "e2e/tsconfig.e2e.json" + } + ], + "test": { + "karma": { + "config": "./karma.conf.js" + } + }, + "defaults": { + "styleExt": "scss", + "component": {} + } +} diff --git a/src/Web/WebSPA/.gitignore b/src/Web/WebSPA/.gitignore index 280332f79..a4cb1babf 100644 --- a/src/Web/WebSPA/.gitignore +++ b/src/Web/WebSPA/.gitignore @@ -177,10 +177,7 @@ ClientBin/ *.publishsettings node_modules/ bower_components/ -**/wwwroot/tmp/ -**/wwwroot/*.bundle.map -**/wwwroot/*.js -/wwwroot/dist/ +wwwroot/ orleans.codegen.cs diff --git a/src/Web/WebSPA/Client/assets/.gitkeep b/src/Web/WebSPA/Client/assets/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/src/Web/WebSPA/Client/images/arrow-down.png b/src/Web/WebSPA/Client/assets/images/arrow-down.png similarity index 100% rename from src/Web/WebSPA/Client/images/arrow-down.png rename to src/Web/WebSPA/Client/assets/images/arrow-down.png diff --git a/src/Web/WebSPA/Client/images/arrow-right.svg b/src/Web/WebSPA/Client/assets/images/arrow-right.svg similarity index 100% rename from src/Web/WebSPA/Client/images/arrow-right.svg rename to src/Web/WebSPA/Client/assets/images/arrow-right.svg diff --git a/src/Web/WebSPA/Client/images/brand.png b/src/Web/WebSPA/Client/assets/images/brand.png similarity index 100% rename from src/Web/WebSPA/Client/images/brand.png rename to src/Web/WebSPA/Client/assets/images/brand.png diff --git a/src/Web/WebSPA/Client/images/brand_dark.png b/src/Web/WebSPA/Client/assets/images/brand_dark.png similarity index 100% rename from src/Web/WebSPA/Client/images/brand_dark.png rename to src/Web/WebSPA/Client/assets/images/brand_dark.png diff --git a/src/Web/WebSPA/Client/images/cart.png b/src/Web/WebSPA/Client/assets/images/cart.png similarity index 100% rename from src/Web/WebSPA/Client/images/cart.png rename to src/Web/WebSPA/Client/assets/images/cart.png diff --git a/src/Web/WebSPA/Client/images/logout.png b/src/Web/WebSPA/Client/assets/images/logout.png similarity index 100% rename from src/Web/WebSPA/Client/images/logout.png rename to src/Web/WebSPA/Client/assets/images/logout.png diff --git a/src/Web/WebSPA/Client/images/main_banner.png b/src/Web/WebSPA/Client/assets/images/main_banner.png similarity index 100% rename from src/Web/WebSPA/Client/images/main_banner.png rename to src/Web/WebSPA/Client/assets/images/main_banner.png diff --git a/src/Web/WebSPA/Client/images/main_banner_text.png b/src/Web/WebSPA/Client/assets/images/main_banner_text.png similarity index 100% rename from src/Web/WebSPA/Client/images/main_banner_text.png rename to src/Web/WebSPA/Client/assets/images/main_banner_text.png diff --git a/src/Web/WebSPA/Client/images/my_orders.png b/src/Web/WebSPA/Client/assets/images/my_orders.png similarity index 100% rename from src/Web/WebSPA/Client/images/my_orders.png rename to src/Web/WebSPA/Client/assets/images/my_orders.png diff --git a/src/Web/WebSPA/Client/custom-typings.d.ts b/src/Web/WebSPA/Client/custom-typings.d.ts deleted file mode 100644 index 8e46a4e30..000000000 --- a/src/Web/WebSPA/Client/custom-typings.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -// Extra variables that live on Global that will be replaced by webpack DefinePlugin -// declare var process: any; diff --git a/src/Web/WebSPA/Client/environments/environment.prod.ts b/src/Web/WebSPA/Client/environments/environment.prod.ts new file mode 100644 index 000000000..3612073bc --- /dev/null +++ b/src/Web/WebSPA/Client/environments/environment.prod.ts @@ -0,0 +1,3 @@ +export const environment = { + production: true +}; diff --git a/src/Web/WebSPA/Client/environments/environment.ts b/src/Web/WebSPA/Client/environments/environment.ts new file mode 100644 index 000000000..b7f639aec --- /dev/null +++ b/src/Web/WebSPA/Client/environments/environment.ts @@ -0,0 +1,8 @@ +// The file contents for the current environment will overwrite these during build. +// The build system defaults to the dev environment which uses `environment.ts`, but if you do +// `ng build --env=prod` then `environment.prod.ts` will be used instead. +// The list of which env maps to which file can be found in `.angular-cli.json`. + +export const environment = { + production: false +}; diff --git a/src/Web/WebSPA/wwwroot/favicon.ico b/src/Web/WebSPA/Client/favicon.ico similarity index 100% rename from src/Web/WebSPA/wwwroot/favicon.ico rename to src/Web/WebSPA/Client/favicon.ico diff --git a/src/Web/WebSPA/Client/globals.scss b/src/Web/WebSPA/Client/globals.scss index fde578e3d..e69b955dc 100644 --- a/src/Web/WebSPA/Client/globals.scss +++ b/src/Web/WebSPA/Client/globals.scss @@ -1,3 +1,4 @@ +/* You can add global styles to this file, and also import other style files */ @import './modules/variables'; $dist: './fonts/Montserrat-Regular'; diff --git a/src/Web/WebSPA/Client/index.html b/src/Web/WebSPA/Client/index.html new file mode 100644 index 000000000..5d9432f70 --- /dev/null +++ b/src/Web/WebSPA/Client/index.html @@ -0,0 +1,18 @@ + + + + + eShopConContainers.WebSPA + + + + + + + +
+ +
+
+ + diff --git a/src/Web/WebSPA/Client/main.ts b/src/Web/WebSPA/Client/main.ts index ff50b8628..1f87381f8 100644 --- a/src/Web/WebSPA/Client/main.ts +++ b/src/Web/WebSPA/Client/main.ts @@ -1,23 +1,11 @@ -import './polyfills'; - -import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { enableProdMode } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; -import { AppModule } from './modules/app.module'; +import { AppModule } from './modules/app.module'; +import { environment } from './environments/environment'; -if (process.env.ENV === 'Development') { - // Development -} else { - // Production +if (environment.production) { enableProdMode(); } platformBrowserDynamic().bootstrapModule(AppModule); - -// Basic hot reloading support. Automatically reloads and restarts the Angular 2 app each time -// you modify source files. This will not preserve any application state other than the URL. -declare var module: any; - -if (module.hot) { - module.hot.accept(); -} diff --git a/src/Web/WebSPA/Client/modules/app.component.html b/src/Web/WebSPA/Client/modules/app.component.html index bd50dc066..14e322e61 100644 --- a/src/Web/WebSPA/Client/modules/app.component.html +++ b/src/Web/WebSPA/Client/modules/app.component.html @@ -4,7 +4,7 @@
- +
@@ -28,7 +28,7 @@
- +
diff --git a/src/Web/WebSPA/Client/modules/app.component.ts b/src/Web/WebSPA/Client/modules/app.component.ts index 00abe4ad4..37bba914b 100644 --- a/src/Web/WebSPA/Client/modules/app.component.ts +++ b/src/Web/WebSPA/Client/modules/app.component.ts @@ -13,12 +13,12 @@ import { ConfigurationService } from './shared/services/configuration.service'; */ @Component({ - selector: 'esh-app.esh-app', + selector: 'esh-app', styleUrls: ['./app.component.scss'], templateUrl: './app.component.html' }) export class AppComponent implements OnInit { - private Authenticated: boolean = false; + Authenticated: boolean = false; subscription: Subscription; constructor(private titleService: Title, private securityService: SecurityService, private configurationService: ConfigurationService) { diff --git a/src/Web/WebSPA/Client/modules/basket/basket-status/basket-status.component.html b/src/Web/WebSPA/Client/modules/basket/basket-status/basket-status.component.html index 7509a2281..b15127a60 100644 --- a/src/Web/WebSPA/Client/modules/basket/basket-status/basket-status.component.html +++ b/src/Web/WebSPA/Client/modules/basket/basket-status/basket-status.component.html @@ -3,7 +3,7 @@ [routerLink]="['basket']">
- +
{{badge}} diff --git a/src/Web/WebSPA/Client/modules/catalog/catalog.component.html b/src/Web/WebSPA/Client/modules/catalog/catalog.component.html index c0aa46ad5..d3c40af62 100644 --- a/src/Web/WebSPA/Client/modules/catalog/catalog.component.html +++ b/src/Web/WebSPA/Client/modules/catalog/catalog.component.html @@ -16,7 +16,7 @@ - +
diff --git a/src/Web/WebSPA/Client/modules/catalog/catalog.component.scss b/src/Web/WebSPA/Client/modules/catalog/catalog.component.scss index 663215ff6..9158bf858 100644 --- a/src/Web/WebSPA/Client/modules/catalog/catalog.component.scss +++ b/src/Web/WebSPA/Client/modules/catalog/catalog.component.scss @@ -4,7 +4,7 @@ $banner-height: 260px; &-hero { - background-image: url('../../images/main_banner.png'); + background-image: url('../../assets/images/main_banner.png'); background-size: cover; height: $banner-height; width: 100%; @@ -61,7 +61,7 @@ } &::after { - background-image: url('../../images/arrow-down.png'); + background-image: url('../../assets/images/arrow-down.png'); content: ''; height: 7px; //png height position: absolute; diff --git a/src/Web/WebSPA/Client/modules/orders/orders-detail/orders-detail.component.ts b/src/Web/WebSPA/Client/modules/orders/orders-detail/orders-detail.component.ts index 31da11594..c9c5c79c0 100644 --- a/src/Web/WebSPA/Client/modules/orders/orders-detail/orders-detail.component.ts +++ b/src/Web/WebSPA/Client/modules/orders/orders-detail/orders-detail.component.ts @@ -1,6 +1,6 @@ -import { Component, OnInit } from '@angular/core'; -import { OrdersService } from '../orders.service'; -import { IOrder } from '../../shared/models/order.model'; +import { Component, OnInit } from '@angular/core'; +import { OrdersService } from '../orders.service'; +import { IOrderDetail } from '../../shared/models/order-detail.model'; import { ActivatedRoute } from '@angular/router'; @Component({ @@ -9,7 +9,7 @@ import { ActivatedRoute } from '@angular/router'; templateUrl: './orders-detail.component.html' }) export class OrdersDetailComponent implements OnInit { - order = {}; // new order + public order: IOrderDetail = {}; constructor(private service: OrdersService, private route: ActivatedRoute) { } @@ -27,5 +27,4 @@ export class OrdersDetailComponent implements OnInit { console.log(this.order); }); } -} - +} \ No newline at end of file diff --git a/src/Web/WebSPA/Client/modules/orders/orders-new/orders-new.component.ts b/src/Web/WebSPA/Client/modules/orders/orders-new/orders-new.component.ts index 8f8a10eb0..4f6f82a9f 100644 --- a/src/Web/WebSPA/Client/modules/orders/orders-new/orders-new.component.ts +++ b/src/Web/WebSPA/Client/modules/orders/orders-new/orders-new.component.ts @@ -13,10 +13,10 @@ import { Router } from '@angular/router'; templateUrl: './orders-new.component.html' }) export class OrdersNewComponent implements OnInit { - private newOrderForm: FormGroup; // new order form - private isOrderProcessing: Boolean; - private errorReceived: Boolean; - private order: IOrder; + newOrderForm: FormGroup; // new order form + isOrderProcessing: boolean; + errorReceived: boolean; + order: IOrder; constructor(private service: OrdersService, fb: FormBuilder, private router: Router) { // Obtain user profile information diff --git a/src/Web/WebSPA/Client/modules/orders/orders.service.ts b/src/Web/WebSPA/Client/modules/orders/orders.service.ts index 24991056a..355670cdd 100644 --- a/src/Web/WebSPA/Client/modules/orders/orders.service.ts +++ b/src/Web/WebSPA/Client/modules/orders/orders.service.ts @@ -4,6 +4,7 @@ import { Response } from '@angular/http'; import { DataService } from '../shared/services/data.service'; import { IOrder } from '../shared/models/order.model'; import { IOrderItem } from '../shared/models/orderItem.model'; +import { IOrderDetail } from "../shared/models/order-detail.model"; import { SecurityService } from '../shared/services/security.service'; import { ConfigurationService } from '../shared/services/configuration.service'; import { BasketWrapperService } from '../shared/services/basket.wrapper.service'; @@ -35,7 +36,7 @@ export class OrdersService { }); } - getOrder(id: number): Observable { + getOrder(id: number): Observable { let url = this.ordersUrl + '/api/v1/orders/' + id; return this.service.get(url).map((response: Response) => { diff --git a/src/Web/WebSPA/Client/modules/shared/components/identity/identity.html b/src/Web/WebSPA/Client/modules/shared/components/identity/identity.html index f15dda395..9dce33adc 100644 --- a/src/Web/WebSPA/Client/modules/shared/components/identity/identity.html +++ b/src/Web/WebSPA/Client/modules/shared/components/identity/identity.html @@ -12,7 +12,7 @@ *ngIf="authenticated">
{{userName}}
- +
My orders
- +
Log Out
- +
diff --git a/src/Web/WebSPA/Client/modules/shared/components/identity/identity.ts b/src/Web/WebSPA/Client/modules/shared/components/identity/identity.ts index 606249c0a..505cdc05d 100644 --- a/src/Web/WebSPA/Client/modules/shared/components/identity/identity.ts +++ b/src/Web/WebSPA/Client/modules/shared/components/identity/identity.ts @@ -10,7 +10,7 @@ import { SecurityService } from '../../services/security.service'; styleUrls: ['./identity.scss'] }) export class Identity implements OnInit { - private authenticated: boolean = false; + authenticated: boolean = false; private subscription: Subscription; private userName: string = ''; diff --git a/src/Web/WebSPA/Client/modules/shared/models/order-detail.model.ts b/src/Web/WebSPA/Client/modules/shared/models/order-detail.model.ts new file mode 100644 index 000000000..25a869f9c --- /dev/null +++ b/src/Web/WebSPA/Client/modules/shared/models/order-detail.model.ts @@ -0,0 +1,14 @@ +import {IOrderItem} from './orderItem.model'; + +export interface IOrderDetail { + ordernumber: string; + status: string; + street: string; + date: Date; + city: number; + state: string; + zipcode: string; + country: number; + total: number; + orderitems: IOrderItem[]; +} diff --git a/src/Web/WebSPA/Client/modules/shared/shared.module.ts b/src/Web/WebSPA/Client/modules/shared/shared.module.ts index 64441396f..da7667df6 100644 --- a/src/Web/WebSPA/Client/modules/shared/shared.module.ts +++ b/src/Web/WebSPA/Client/modules/shared/shared.module.ts @@ -16,6 +16,10 @@ import { StorageService } from './services/storage.service'; import { Pager } from './components/pager/pager'; import { Header } from './components/header/header'; import { Identity } from './components/identity/identity'; +import { PageNotFoundComponent } from './components/page-not-found/page-not-found.component'; + +// Pipes: +import { UppercasePipe } from './pipes/uppercase.pipe'; @NgModule({ imports: [ @@ -31,7 +35,9 @@ import { Identity } from './components/identity/identity'; declarations: [ Pager, Header, - Identity + Identity, + PageNotFoundComponent, + UppercasePipe ], exports: [ // Modules @@ -43,7 +49,9 @@ import { Identity } from './components/identity/identity'; // Providers, Components, directive, pipes Pager, Header, - Identity + Identity, + PageNotFoundComponent, + UppercasePipe ] }) export class SharedModule { diff --git a/src/Web/WebSPA/Client/polyfills.ts b/src/Web/WebSPA/Client/polyfills.ts index 2cdf1a036..53bdaf1b8 100644 --- a/src/Web/WebSPA/Client/polyfills.ts +++ b/src/Web/WebSPA/Client/polyfills.ts @@ -1,23 +1,68 @@ -// Added parts of es6 which are necessary for your project or your browser support requirements. -import 'core-js/es6/symbol'; -import 'core-js/es6/object'; -import 'core-js/es6/function'; -import 'core-js/es6/parse-int'; -import 'core-js/es6/parse-float'; -import 'core-js/es6/number'; -import 'core-js/es6/math'; -import 'core-js/es6/string'; -import 'core-js/es6/date'; -import 'core-js/es6/array'; -import 'core-js/es6/regexp'; -import 'core-js/es6/map'; -import 'core-js/es6/set'; -import 'core-js/es6/weak-map'; -import 'core-js/es6/weak-set'; -import 'core-js/es6/typed'; -import 'core-js/es6/reflect'; -// see issue https://github.com/AngularClass/angular2-webpack-starter/issues/709 -// import 'core-js/es6/promise'; +/** + * This file includes polyfills needed by Angular and is loaded before the app. + * You can add your own extra polyfills to this file. + * + * This file is divided into 2 sections: + * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. + * 2. Application imports. Files imported after ZoneJS that should be loaded before your main + * file. + * + * The current setup is for so-called "evergreen" browsers; the last versions of browsers that + * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), + * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. + * + * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html + */ +/*************************************************************************************************** + * BROWSER POLYFILLS + */ + +/** IE9, IE10 and IE11 requires all of the following polyfills. **/ +// import 'core-js/es6/symbol'; +// import 'core-js/es6/object'; +// import 'core-js/es6/function'; +// import 'core-js/es6/parse-int'; +// import 'core-js/es6/parse-float'; +// import 'core-js/es6/number'; +// import 'core-js/es6/math'; +// import 'core-js/es6/string'; +// import 'core-js/es6/date'; +// import 'core-js/es6/array'; +// import 'core-js/es6/regexp'; +// import 'core-js/es6/map'; +// import 'core-js/es6/set'; + +/** IE10 and IE11 requires the following for NgClass support on SVG elements */ +// import 'classlist.js'; // Run `npm install --save classlist.js`. + +/** IE10 and IE11 requires the following to support `@angular/animation`. */ +// import 'web-animations-js'; // Run `npm install --save web-animations-js`. + + +/** Evergreen browsers require these. **/ +import 'core-js/es6/reflect'; import 'core-js/es7/reflect'; -import 'zone.js/dist/zone'; + + +/** ALL Firefox browsers require the following to support `@angular/animation`. **/ +// import 'web-animations-js'; // Run `npm install --save web-animations-js`. + + + +/*************************************************************************************************** + * Zone JS is required by Angular itself. + */ +import 'zone.js/dist/zone'; // Included with Angular CLI. + + + +/*************************************************************************************************** + * APPLICATION IMPORTS + */ + +/** + * Date, currency, decimal and percent pipes. + * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 + */ +// import 'intl'; // Run `npm install --save intl`. diff --git a/src/Web/WebSPA/Client/test.ts b/src/Web/WebSPA/Client/test.ts new file mode 100644 index 000000000..9bf72267e --- /dev/null +++ b/src/Web/WebSPA/Client/test.ts @@ -0,0 +1,32 @@ +// This file is required by karma.conf.js and loads recursively all the .spec and framework files + +import 'zone.js/dist/long-stack-trace-zone'; +import 'zone.js/dist/proxy.js'; +import 'zone.js/dist/sync-test'; +import 'zone.js/dist/jasmine-patch'; +import 'zone.js/dist/async-test'; +import 'zone.js/dist/fake-async-test'; +import { getTestBed } from '@angular/core/testing'; +import { + BrowserDynamicTestingModule, + platformBrowserDynamicTesting +} from '@angular/platform-browser-dynamic/testing'; + +// Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. +declare var __karma__: any; +declare var require: any; + +// Prevent Karma from running prematurely. +__karma__.loaded = function () {}; + +// First, initialize the Angular testing environment. +getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting() +); +// Then we find all the tests. +const context = require.context('./', true, /\.spec\.ts$/); +// And load the modules. +context.keys().map(context); +// Finally, start Karma to run the tests. +__karma__.start(); diff --git a/src/Web/WebSPA/Client/tsconfig.app.json b/src/Web/WebSPA/Client/tsconfig.app.json new file mode 100644 index 000000000..5e2507db5 --- /dev/null +++ b/src/Web/WebSPA/Client/tsconfig.app.json @@ -0,0 +1,13 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/app", + "module": "es2015", + "baseUrl": "", + "types": [] + }, + "exclude": [ + "test.ts", + "**/*.spec.ts" + ] +} diff --git a/src/Web/WebSPA/Client/tsconfig.spec.json b/src/Web/WebSPA/Client/tsconfig.spec.json new file mode 100644 index 000000000..510e3f1fd --- /dev/null +++ b/src/Web/WebSPA/Client/tsconfig.spec.json @@ -0,0 +1,20 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/spec", + "module": "commonjs", + "target": "es5", + "baseUrl": "", + "types": [ + "jasmine", + "node" + ] + }, + "files": [ + "test.ts" + ], + "include": [ + "**/*.spec.ts", + "**/*.d.ts" + ] +} diff --git a/src/Web/WebSPA/Client/typings.d.ts b/src/Web/WebSPA/Client/typings.d.ts new file mode 100644 index 000000000..ef5c7bd62 --- /dev/null +++ b/src/Web/WebSPA/Client/typings.d.ts @@ -0,0 +1,5 @@ +/* SystemJS module definition */ +declare var module: NodeModule; +interface NodeModule { + id: string; +} diff --git a/src/Web/WebSPA/Client/vendor.ts b/src/Web/WebSPA/Client/vendor.ts deleted file mode 100644 index 4f40d6b4a..000000000 --- a/src/Web/WebSPA/Client/vendor.ts +++ /dev/null @@ -1,20 +0,0 @@ -// For vendors for example jQuery, Lodash, angular2-jwt just import them here unless you plan on -// chunking vendors files for async loading. You would need to import the async loaded vendors -// at the entry point of the async loaded file. Also see custom-typings.d.ts as you also need to -// run `typings install x` where `x` is your module - -// Angular 2 -import '@angular/platform-browser'; -import '@angular/platform-browser-dynamic'; -import '@angular/core'; -import '@angular/common'; -import '@angular/forms'; -import '@angular/http'; -import '@angular/router'; - -// RxJS -import 'rxjs/add/operator/map'; -import 'rxjs/add/operator/mergeMap'; -import 'rxjs/add/operator/catch'; -import 'rxjs/add/operator/finally'; -import 'rxjs/add/observable/throw'; diff --git a/src/Web/WebSPA/Server/Controllers/HomeController.cs b/src/Web/WebSPA/Server/Controllers/HomeController.cs index 7e78cd41a..3bb021943 100644 --- a/src/Web/WebSPA/Server/Controllers/HomeController.cs +++ b/src/Web/WebSPA/Server/Controllers/HomeController.cs @@ -1,6 +1,5 @@ // For more information on enabling MVC for empty projects, visit http://go.microsoft.com/fwlink/?LinkID=397860 -using System.Linq; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; @@ -18,23 +17,6 @@ namespace eShopConContainers.WebSPA.Server.Controllers _env = env; _settings = settings; } - - public IActionResult Index() - { - ViewBag.HashedMain = GetHashedMainDotJs(); - - return View(); - } - - public string GetHashedMainDotJs() - { - var basePath = _env.WebRootPath + "//dist//"; - var info = new System.IO.DirectoryInfo(basePath); - var file = info.GetFiles().Where(f => f.Name.StartsWith("main.") && !f.Name.EndsWith("bundle.map")).FirstOrDefault(); - - return file.Name; - } - public IActionResult Configuration() { return Json(_settings.Value); diff --git a/src/Web/WebSPA/Startup.cs b/src/Web/WebSPA/Startup.cs index a572f7961..f06cb8ee1 100644 --- a/src/Web/WebSPA/Startup.cs +++ b/src/Web/WebSPA/Startup.cs @@ -1,16 +1,15 @@ using System; +using System.IO; using Microsoft.AspNetCore.Antiforgery; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.SpaServices.Webpack; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.HealthChecks; using Newtonsoft.Json.Serialization; using eShopOnContainers.WebSPA; -using Microsoft.Extensions.HealthChecks; -using System.Threading.Tasks; namespace eShopConContainers.WebSPA { @@ -76,29 +75,34 @@ namespace eShopConContainers.WebSPA // Configure XSRF middleware, This pattern is for SPA style applications where XSRF token is added on Index page // load and passed back token on every subsequent async request + // app.Use(async (context, next) => + // { + // if (string.Equals(context.Request.Path.Value, "/", StringComparison.OrdinalIgnoreCase)) + // { + // var tokens = antiforgery.GetAndStoreTokens(context); + // context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken, new CookieOptions() { HttpOnly = false }); + // } + // await next.Invoke(); + // }); + app.Use(async (context, next) => { - if (string.Equals(context.Request.Path.Value, "/", StringComparison.OrdinalIgnoreCase)) + await next(); + + // If there's no available file and the request doesn't contain an extension, we're probably trying to access a page. + // Rewrite request to use app root + if (context.Response.StatusCode == 404 && !Path.HasExtension(context.Request.Path.Value) && !context.Request.Path.Value.StartsWith("/api")) { - var tokens = antiforgery.GetAndStoreTokens(context); - context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken, new CookieOptions() { HttpOnly = false }); + context.Request.Path = "/index.html"; + context.Response.StatusCode = 200; // Make sure we update the status code, otherwise it returns 404 + await next(); } - await next.Invoke(); }); + app.UseDefaultFiles(); app.UseStaticFiles(); - - app.UseMvc(routes => - { - routes.MapRoute( - name: "default", - template: "{controller=Home}/{action=Index}/{id?}"); - - routes.MapSpaFallbackRoute( - name: "spa-fallback", - defaults: new { controller = "Home", action = "Index" }); - }); + app.UseMvcWithDefaultRoute(); } } } diff --git a/src/Web/WebSPA/Views/Home/Index.cshtml b/src/Web/WebSPA/Views/Home/Index.cshtml deleted file mode 100644 index 931ef55c7..000000000 --- a/src/Web/WebSPA/Views/Home/Index.cshtml +++ /dev/null @@ -1,12 +0,0 @@ - -
- -
-
- - - - - diff --git a/src/Web/WebSPA/Views/Shared/_Layout.cshtml b/src/Web/WebSPA/Views/Shared/_Layout.cshtml deleted file mode 100644 index c7d974e27..000000000 --- a/src/Web/WebSPA/Views/Shared/_Layout.cshtml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - eShopConContainers.WebSPA - - - - - - @RenderBody() - - - \ No newline at end of file diff --git a/src/Web/WebSPA/Views/_ViewImports.cshtml b/src/Web/WebSPA/Views/_ViewImports.cshtml deleted file mode 100644 index 1c0d391f9..000000000 --- a/src/Web/WebSPA/Views/_ViewImports.cshtml +++ /dev/null @@ -1,3 +0,0 @@ - -@addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers" -@addTagHelper "*, Microsoft.AspNetCore.SpaServices" diff --git a/src/Web/WebSPA/Views/_ViewStart.cshtml b/src/Web/WebSPA/Views/_ViewStart.cshtml deleted file mode 100644 index 820a2f6e0..000000000 --- a/src/Web/WebSPA/Views/_ViewStart.cshtml +++ /dev/null @@ -1,3 +0,0 @@ -@{ - Layout = "_Layout"; -} diff --git a/src/Web/WebSPA/WebSPA.csproj b/src/Web/WebSPA/WebSPA.csproj index d5161d753..cebe4377d 100644 --- a/src/Web/WebSPA/WebSPA.csproj +++ b/src/Web/WebSPA/WebSPA.csproj @@ -22,21 +22,9 @@ PreserveNewest - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - PreserveNewest - - PreserveNewest - PreserveNewest @@ -51,7 +39,6 @@ - @@ -87,7 +74,6 @@ - diff --git a/src/Web/WebSPA/config/helpers.js b/src/Web/WebSPA/config/helpers.js deleted file mode 100644 index 9d37e78a8..000000000 --- a/src/Web/WebSPA/config/helpers.js +++ /dev/null @@ -1,23 +0,0 @@ -/** - * @author: @AngularClass - */ - -var path = require('path'); - -// Helper functions -var ROOT = path.resolve(__dirname, '..'); - -console.log('root directory:', root() + '\n'); - -function hasProcessFlag(flag) { - return process.argv.join('').indexOf(flag) > -1; -} - -function root(args) { - args = Array.prototype.slice.call(arguments, 0); - return path.join.apply(path, [ROOT].concat(args)); -} - - -exports.hasProcessFlag = hasProcessFlag; -exports.root = root; diff --git a/src/Web/WebSPA/config/webpack.config.dev.js b/src/Web/WebSPA/config/webpack.config.dev.js deleted file mode 100644 index d87a27ee4..000000000 --- a/src/Web/WebSPA/config/webpack.config.dev.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - //devtool: 'cheap-module-source-map' -}; diff --git a/src/Web/WebSPA/config/webpack.config.js b/src/Web/WebSPA/config/webpack.config.js deleted file mode 100644 index 2bf3a804d..000000000 --- a/src/Web/WebSPA/config/webpack.config.js +++ /dev/null @@ -1,77 +0,0 @@ -var path = require('path'); -var webpack = require('webpack'); -var merge = require('extendify')({ isDeep: true, arrays: 'concat' }); -var ExtractTextPlugin = require('extract-text-webpack-plugin'); -var extractCSS = new ExtractTextPlugin('styles.css'); -var ForkCheckerPlugin = require('awesome-typescript-loader').ForkCheckerPlugin; -var devConfig = require('./webpack.config.dev'); -var prodConfig = require('./webpack.config.prod'); -var CopyWebpackPlugin = require('copy-webpack-plugin'); -var isDevelopment = process.env.ASPNETCORE_ENVIRONMENT === 'Development'; - -console.log("==========Dev Mode = " + isDevelopment + " ============" ) - -module.exports = merge({ - resolve: { - extensions: ['.js', '.ts'] - }, - module: { - rules: [ - { - test: /\.ts$/, exclude: [/\.(spec|e2e)\.ts$/], - loaders: ['awesome-typescript-loader?forkChecker=true ', 'angular2-template-loader', 'angular2-router-loader'] - }, - { test: /\.html$/, loader: "html" }, - { test: /\.scss$/, loader: 'exports-loader?module.exports.toString()!css-loader!sass-loader' }, - { test: /\.json$/, loader: 'json-loader' }, - { - test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, - loader: "file-loader" - }, { - test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, - loader: "file-loader" - }, { - test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, - loader: "file-loader" - }, { - test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, - loader: "file-loader" - }, - { - test: /\.(png|jpg|gif|svg)$/, - loader: "file-loader?name=images/[name].[ext]" - } - ] - }, - entry: { - 'main': './Client/main.ts' - }, - output: { - path: path.join(__dirname, '../wwwroot', 'dist'), - filename: '[name].js', - publicPath: '/dist/' - }, - profile: true, - plugins: [ - extractCSS, - new webpack.DllReferencePlugin({ - context: __dirname, - manifest: require('../wwwroot/dist/vendor-manifest.json') - }), - // To eliminate warning - // https://github.com/AngularClass/angular2-webpack-starter/issues/993 - new webpack.ContextReplacementPlugin( - /angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/, - __dirname - ), - new ForkCheckerPlugin(), - new webpack.DefinePlugin({ - 'process.env': { - 'ENV': JSON.stringify(process.env.ASPNETCORE_ENVIRONMENT) - } - }), - new CopyWebpackPlugin([ - { from: 'Client/fonts', to: 'fonts' } - ]) - ] -}, isDevelopment ? devConfig : prodConfig); diff --git a/src/Web/WebSPA/config/webpack.config.prod.js b/src/Web/WebSPA/config/webpack.config.prod.js deleted file mode 100644 index 95277c818..000000000 --- a/src/Web/WebSPA/config/webpack.config.prod.js +++ /dev/null @@ -1,23 +0,0 @@ -var webpack = require('webpack'); -const WebpackMd5Hash = require('webpack-md5-hash'); - -module.exports = { - devtool: 'source-map', - output: { - filename: '[name].[chunkhash].bundle.js', - sourceMapFilename: '[name].[chunkhash].bundle.map', - chunkFilename: '[id].[chunkhash].chunk.js' - }, - plugins: [ - // new webpack.LoaderOptionsPlugin({ - // minimize: true, - // debug: false - // }), - new WebpackMd5Hash(), - new webpack.optimize.UglifyJsPlugin({ - beautify: false, - comments: false, - sourceMap: true - }) - ] -}; diff --git a/src/Web/WebSPA/config/webpack.config.vendor.js b/src/Web/WebSPA/config/webpack.config.vendor.js deleted file mode 100644 index 9009e8ba6..000000000 --- a/src/Web/WebSPA/config/webpack.config.vendor.js +++ /dev/null @@ -1,74 +0,0 @@ -var path = require('path'); -var webpack = require('webpack'); -var ExtractTextPlugin = require('extract-text-webpack-plugin'); -var extractCSS = new ExtractTextPlugin('vendor.css'); -var isDevelopment = process.env.ASPNETCORE_ENVIRONMENT === 'Development'; - -module.exports = { - resolve: { - extensions: ['.js'] - }, - module: { - rules: [ - { test: /\.(png|woff|woff2|eot|ttf|svg)$/, loader: 'url-loader?limit=100000' }, - { test: /\.scss$/i, loader: extractCSS.extract(['css?minimize', 'sass']) }, - { test: /\.json$/, loader: 'json-loader' } - ] - }, - entry: { - // polyfills: [ - // 'core-js/es6/symbol', - // 'core-js/es6/object', - // 'core-js/es6/function', - // 'core-js/es6/parse-int', - // 'core-js/es6/parse-float', - // 'core-js/es6/number', - // 'core-js/es6/math', - // 'core-js/es6/string', - // 'core-js/es6/date', - // 'core-js/es6/array', - // 'core-js/es6/regexp', - // 'core-js/es6/map', - // 'core-js/es6/set', - // 'core-js/es6/reflect', - // 'core-js/es7/reflect', - // 'zone.js/dist/zone' - // ], - vendor: [ - 'font-awesome/scss/font-awesome.scss', - 'bootstrap/scss/bootstrap.scss', - '@angular/common', - '@angular/compiler', - '@angular/core', - '@angular/http', - '@angular/forms', - '@angular/platform-browser', - '@angular/platform-browser-dynamic', - '@angular/router', - './Client/globals.scss' - ] - }, - output: { - path: path.join(__dirname, '../wwwroot', 'dist'), - filename: '[name].js', - library: '[name]_[hash]', - }, - plugins: [ - extractCSS, - // To eliminate warning - // https://github.com/AngularClass/angular2-webpack-starter/issues/993 - new webpack.ContextReplacementPlugin( - /angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/, - __dirname - ), - new webpack.DllPlugin({ - path: path.join(__dirname, '../wwwroot', 'dist', '[name]-manifest.json'), - name: '[name]_[hash]' - }) - ].concat(isDevelopment ? [] : [ - new webpack.optimize.UglifyJsPlugin({ - beautify: false, - comments: false - }) - ]) -}; diff --git a/src/Web/WebSPA/package.json b/src/Web/WebSPA/package.json index b863e2f1c..003f220ac 100644 --- a/src/Web/WebSPA/package.json +++ b/src/Web/WebSPA/package.json @@ -16,90 +16,51 @@ "email": "cesardl@microsoft.com" }, "scripts": { + "ng": "ng", "rimraf": "rimraf", - "typings": "typings", - "webpack": "webpack", - "clean": "npm cache clean && npm run rimraf -- node_modules doc typings coverage wwwroot/dist", - "clean:dist": "npm run rimraf -- wwwroot/dist", - "preclean:install": "npm run clean", - "clean:install": "npm set progress=false && npm install", - "preclean:start": "npm run clean", - "clean:start": "npm start", - "build:vendor": "node node_modules/webpack/bin/webpack.js --config config/webpack.config.vendor.js", - "build:main": "node node_modules/webpack/bin/webpack.js --config config/webpack.config.js", - "setdev": "set ASPNETCORE_ENVIRONMENT=Development", - "setprod": "set ASPNETCORE_ENVIRONMENT=Production", - "build:dev": "npm run setdev && npm run clean:dist && npm run build:vendor && npm run build:main", - "build:prod": "npm run setprod && npm run clean:dist && npm run build:vendor && npm run build:main", - "version": "npm run build", + "clean": "npm cache clean && npm run rimraf -- node_modules doc typings coverage wwwroot", + "start": "ng serve", + "build:dev": "ng build", + "build:prod": "ng build --prod --aot --extract-css", "lint:sass": "sass-lint -c .sass-lint.yml Client/**/*.scss --verbose", "lint:ts": "tslint -c tslint.json Client/**/*.ts" }, "dependencies": { - "@angular/common": "2.1.2", - "@angular/compiler": "2.1.2", - "@angular/compiler-cli": "2.1.2", - "@angular/core": "2.1.2", - "@angular/forms": "2.1.2", - "@angular/http": "2.1.2", - "@angular/platform-browser": "2.1.2", - "@angular/platform-browser-dynamic": "2.1.2", - "@angular/platform-server": "2.1.2", - "@angular/router": "3.1.2", - "@ng-bootstrap/ng-bootstrap": "1.0.0-alpha.11", - "aspnet-prerendering": "1.0.7", - "aspnet-webpack": "1.0.24", + "@angular/common": "^4.0.0", + "@angular/compiler": "^4.0.0", + "@angular/core": "^4.0.0", + "@angular/forms": "^4.0.0", + "@angular/http": "^4.0.0", + "@angular/platform-browser": "^4.0.0", + "@angular/platform-browser-dynamic": "^4.0.0", + "@angular/router": "^4.0.0", + "@ng-bootstrap/ng-bootstrap": "1.0.0-alpha.22", "bootstrap": "4.0.0-alpha.5", - "core-js": "2.4.1", + "core-js": "^2.4.1", "file-loader": "0.9.0", "font-awesome": "4.6.3", "isomorphic-fetch": "2.2.1", "normalize.css": "5.0.0", "preboot": "4.5.2", - "rxjs": "5.0.0-beta.12", - "zone.js": "0.6.26" + "rxjs": "^5.1.0", + "zone.js": "^0.8.4" }, "devDependencies": { + "@angular/cli": "1.0.0", + "@angular/compiler-cli": "^4.0.0", + "@types/jasmine": "2.5.38", + "@types/node": "~6.0.60", "@types/core-js": "0.9.34", "@types/hammerjs": "2.0.33", - "@types/jasmine": "2.5.35", - "@types/node": "6.0.45", "@types/protractor": "1.5.20", "@types/selenium-webdriver": "2.44.26", - "@types/sinon": "1.16.31", - "@types/source-map": "0.1.28", - "@types/uglify-js": "2.6.28", - "@types/webpack": "1.12.35", - "angular2-router-loader": "0.3.4", - "angular2-template-loader": "0.6.0", - "awesome-typescript-loader": "2.2.4", - "codelyzer": "1.0.0-beta.3", - "copy-webpack-plugin": "4.0.1", - "css": "2.2.1", - "css-loader": "0.25.0", - "es6-promise": "3.2.1", - "es6-promise-loader": "1.0.2", - "exports-loader": "0.6.3", - "extendify": "1.0.0", - "extract-text-webpack-plugin": "2.0.0-beta.4", - "file-loader": "0.9.0", - "html-loader": "0.4.4", - "html-webpack-plugin": "2.24.1", - "json-loader": "0.5.4", - "node-sass": "4.5.0", - "parse5": "2.1.5", - "rimraf": "2.5.4", + "codelyzer": "~2.0.0", "sass-lint": "1.10.2", - "sass-loader": "4.0.2", "ts-helpers": "1.1.1", - "ts-node": "1.4.3", - "tslint": "3.15.1", + "ts-node": "~2.0.0", + "tslint": "~4.5.0", "typedoc": "0.5.0", - "typescript": "2.0.6", - "url-loader": "0.5.7", - "webpack": "2.1.0-beta.25", - "webpack-externals-plugin": "1.0.0", - "webpack-hot-middleware": "2.13.0", - "webpack-md5-hash": "0.0.5" + "typescript": "~2.2.0", + "url-loader": "0.5.7" } } diff --git a/src/Web/WebSPA/tsconfig.json b/src/Web/WebSPA/tsconfig.json index e04adfb21..a35a8ee3a 100644 --- a/src/Web/WebSPA/tsconfig.json +++ b/src/Web/WebSPA/tsconfig.json @@ -1,42 +1,20 @@ { + "compileOnSave": false, "compilerOptions": { - "target": "es5", - "module": "commonjs", - "moduleResolution": "node", + "outDir": "./dist/out-tsc", + "baseUrl": "src", + "sourceMap": true, "declaration": false, + "moduleResolution": "node", "emitDecoratorMetadata": true, "experimentalDecorators": true, - "allowSyntheticDefaultImports": true, - "sourceMap": true, - "strictNullChecks": false, - "baseUrl": "./src", - "paths": {}, - "lib": [ - "dom", - "es6" + "target": "es5", + "typeRoots": [ + "node_modules/@types" ], - "types": [ - "hammerjs", - "jasmine", - "node", - "protractor", - "selenium-webdriver", - "source-map", - "uglify-js", - "webpack" + "lib": [ + "es2016", + "dom" ] - }, - "exclude": [ - "node_modules", - "wwwroot" - ], - "awesomeTypescriptLoaderOptions": { - "forkChecker": true, - "useWebpackText": true - }, - "compileOnSave": false, - "buildOnSave": false, - "atom": { - "rewriteTsconfig": false } -} \ No newline at end of file +} diff --git a/src/Web/WebSPA/wwwroot/web.config b/src/Web/WebSPA/wwwroot/web.config deleted file mode 100644 index e70a7778d..000000000 --- a/src/Web/WebSPA/wwwroot/web.config +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/Web/WebStatus/WebStatus.csproj b/src/Web/WebStatus/WebStatus.csproj index 47a3e20f0..e3028722d 100644 --- a/src/Web/WebStatus/WebStatus.csproj +++ b/src/Web/WebStatus/WebStatus.csproj @@ -21,7 +21,6 @@ - diff --git a/test/Services/FunctionalTests/Services/Ordering/OrderingScenarios.cs b/test/Services/FunctionalTests/Services/Ordering/OrderingScenarios.cs index 5f52e1771..5b2424114 100644 --- a/test/Services/FunctionalTests/Services/Ordering/OrderingScenarios.cs +++ b/test/Services/FunctionalTests/Services/Ordering/OrderingScenarios.cs @@ -45,7 +45,19 @@ namespace FunctionalTests.Services.Ordering string BuildOrder() { + List orderItemsList = new List(); + orderItemsList.Add(new OrderItemDTO() + { + ProductId = 1, + Discount = 8M, + UnitPrice = 10, + Units = 1, + ProductName = "Some name" + } + ); + var order = new CreateOrderCommand( + orderItemsList, cardExpiration: DateTime.UtcNow.AddYears(1), cardNumber: "5145-555-5555", cardHolderName: "Jhon Senna", @@ -60,15 +72,6 @@ namespace FunctionalTests.Services.Ordering buyerId: 3 ); - order.AddOrderItem(new OrderItemDTO() - { - ProductId = 1, - Discount = 8M, - UnitPrice = 10, - Units = 1, - ProductName = "Some name" - }); - return JsonConvert.SerializeObject(order); } } diff --git a/test/Services/IntegrationTests/Services/Ordering/OrderingScenarios.cs b/test/Services/IntegrationTests/Services/Ordering/OrderingScenarios.cs index 3e2350c9d..49f04fa3b 100644 --- a/test/Services/IntegrationTests/Services/Ordering/OrderingScenarios.cs +++ b/test/Services/IntegrationTests/Services/Ordering/OrderingScenarios.cs @@ -9,7 +9,9 @@ using System.Text; using System.Threading.Tasks; using Xunit; + using System.Collections; using static Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands.CreateOrderCommand; + using System.Collections.Generic; public class OrderingScenarios : OrderingScenarioBase @@ -59,7 +61,19 @@ string BuildOrder() { + List orderItemsList = new List(); + orderItemsList.Add(new OrderItemDTO() + { + ProductId = 1, + Discount = 10M, + UnitPrice = 10, + Units = 1, + ProductName = "Some name" + } + ); + var order = new CreateOrderCommand( + orderItemsList, cardExpiration: DateTime.UtcNow.AddYears(1), cardNumber: "5145-555-5555", cardHolderName: "Jhon Senna", @@ -74,20 +88,12 @@ buyerId: 1 ); - order.AddOrderItem(new OrderItemDTO() - { - ProductId = 1, - Discount = 10M, - UnitPrice = 10, - Units = 1, - ProductName = "Some name" - }); - return JsonConvert.SerializeObject(order); } string BuildOrderWithInvalidExperationTime() { var order = new CreateOrderCommand( + null, cardExpiration: DateTime.UtcNow.AddYears(-1), cardNumber: "5145-555-5555", cardHolderName: "Jhon Senna", diff --git a/test/Services/UnitTest/Ordering/Application/IdentifierCommandHandlerTest.cs b/test/Services/UnitTest/Ordering/Application/IdentifierCommandHandlerTest.cs index 66070c497..2a4a356ec 100644 --- a/test/Services/UnitTest/Ordering/Application/IdentifierCommandHandlerTest.cs +++ b/test/Services/UnitTest/Ordering/Application/IdentifierCommandHandlerTest.cs @@ -70,6 +70,7 @@ namespace UnitTest.Ordering.Application private CreateOrderCommand FakeOrderRequest(Dictionary args = null) { return new CreateOrderCommand( + null, city: args != null && args.ContainsKey("city") ? (string)args["city"] : null, street: args != null && args.ContainsKey("street") ? (string)args["street"] : null, state: args != null && args.ContainsKey("state") ? (string)args["state"] : null, diff --git a/test/Services/UnitTest/Ordering/Application/NewOrderCommandHandlerTest.cs b/test/Services/UnitTest/Ordering/Application/NewOrderCommandHandlerTest.cs index 22760fc7d..9a4a70bf8 100644 --- a/test/Services/UnitTest/Ordering/Application/NewOrderCommandHandlerTest.cs +++ b/test/Services/UnitTest/Ordering/Application/NewOrderCommandHandlerTest.cs @@ -72,6 +72,7 @@ namespace UnitTest.Ordering.Application private CreateOrderCommand FakeOrderRequestWithBuyer(Dictionary args = null) { return new CreateOrderCommand( + null, city: args != null && args.ContainsKey("city") ? (string)args["city"] : null, street: args != null && args.ContainsKey("street") ? (string)args["street"] : null, state: args != null && args.ContainsKey("state") ? (string)args["state"] : null,