Merge branch 'dev'
1
.gitignore
vendored
@ -6,6 +6,7 @@
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
.vscode/
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
37
cli-linux/build-bits-linux.sh
Normal file → Executable file
@ -1,18 +1,17 @@
|
||||
|
||||
projectList=(
|
||||
"/src/Services/Catalog/Catalog.API"
|
||||
"/src/Services/Basket/Basket.API"
|
||||
"/src/Services/Ordering/Ordering.API"
|
||||
"/src/Services/Identity/Identity.API"
|
||||
"/src/Web/WebMVC"
|
||||
"/src/Web/WebSPA"
|
||||
"/src/Web/WebStatus
|
||||
#!/bin/bash
|
||||
declare -a projectList=(
|
||||
'../src/Services/Catalog/Catalog.API'
|
||||
'../src/Services/Basket/Basket.API'
|
||||
'../src/Services/Ordering/Ordering.API'
|
||||
'../src/Services/Identity/Identity.API'
|
||||
'../src/Web/WebMVC'
|
||||
'../src/Web/WebSPA'
|
||||
'../src/Web/WebStatus'
|
||||
)
|
||||
|
||||
# Build SPA app
|
||||
pushd $(pwd)/src/Web/WebSPA
|
||||
npm rebuild node-sass
|
||||
npm run build:prod
|
||||
# pushd $(pwd)../src/Web/WebSPA
|
||||
# npm run build:prod
|
||||
|
||||
for project in "${projectList[@]}"
|
||||
do
|
||||
@ -28,13 +27,13 @@ do
|
||||
done
|
||||
|
||||
# remove old docker images:
|
||||
#images=$(docker images --filter=reference="eshop/*" -q)
|
||||
#if [ -n "$images" ]; then
|
||||
# docker rm $(docker ps -a -q) -f
|
||||
# echo "Deleting eShop images in local Docker repo"
|
||||
# echo $images
|
||||
# docker rmi $(docker images --filter=reference="eshop/*" -q) -f
|
||||
#fi
|
||||
images=$(docker images --filter=reference="eshop/*" -q)
|
||||
if [ -n "$images" ]; then
|
||||
docker rm $(docker ps -a -q) -f
|
||||
echo "Deleting eShop images in local Docker repo"
|
||||
echo $images
|
||||
docker rmi $(docker images --filter=reference="eshop/*" -q) -f
|
||||
fi
|
||||
|
||||
# No need to build the images, docker build or docker compose will
|
||||
# do that using the images and containers defined in the docker-compose.yml file.
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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
|
||||
|
@ -0,0 +1,22 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp1.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />
|
||||
<PackageReference Include="xunit" Version="2.2.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\EventBusRabbitMQ\EventBusRabbitMQ.csproj" />
|
||||
<ProjectReference Include="..\EventBus\EventBus.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
@ -0,0 +1,56 @@
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace EventBus.Tests
|
||||
{
|
||||
public class InMemory_SubscriptionManager_Tests
|
||||
{
|
||||
[Fact]
|
||||
public void After_Creation_Should_Be_Empty()
|
||||
{
|
||||
var manager = new InMemoryEventBusSubscriptionsManager();
|
||||
Assert.True(manager.IsEmpty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void After_One_Event_Subscription_Should_Contain_The_Event()
|
||||
{
|
||||
var manager = new InMemoryEventBusSubscriptionsManager();
|
||||
manager.AddSubscription<TestIntegrationEvent,TestIntegrationEventHandler>(() => new TestIntegrationEventHandler());
|
||||
Assert.True(manager.HasSubscriptionsForEvent<TestIntegrationEvent>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void After_All_Subscriptions_Are_Deleted_Event_Should_No_Longer_Exists()
|
||||
{
|
||||
var manager = new InMemoryEventBusSubscriptionsManager();
|
||||
manager.AddSubscription<TestIntegrationEvent, TestIntegrationEventHandler>(() => new TestIntegrationEventHandler());
|
||||
manager.RemoveSubscription<TestIntegrationEvent, TestIntegrationEventHandler>();
|
||||
Assert.False(manager.HasSubscriptionsForEvent<TestIntegrationEvent>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Deleting_Last_Subscription_Should_Raise_On_Deleted_Event()
|
||||
{
|
||||
bool raised = false;
|
||||
var manager = new InMemoryEventBusSubscriptionsManager();
|
||||
manager.OnEventRemoved += (o, e) => raised = true;
|
||||
manager.AddSubscription<TestIntegrationEvent, TestIntegrationEventHandler>(() => new TestIntegrationEventHandler());
|
||||
manager.RemoveSubscription<TestIntegrationEvent, TestIntegrationEventHandler>();
|
||||
Assert.True(raised);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Get_Handlers_For_Event_Should_Return_All_Handlers()
|
||||
{
|
||||
var manager = new InMemoryEventBusSubscriptionsManager();
|
||||
manager.AddSubscription<TestIntegrationEvent, TestIntegrationEventHandler>(() => new TestIntegrationEventHandler());
|
||||
manager.AddSubscription<TestIntegrationEvent, TestIntegrationOtherEventHandler>(() => new TestIntegrationOtherEventHandler());
|
||||
var handlers = manager.GetHandlersForEvent<TestIntegrationEvent>();
|
||||
Assert.Equal(2, handlers.Count());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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
|
||||
{
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EventBus.Tests
|
||||
{
|
||||
public class TestIntegrationOtherEventHandler : IIntegrationEventHandler<TestIntegrationEvent>
|
||||
{
|
||||
public bool Handled { get; private set; }
|
||||
|
||||
public TestIntegrationOtherEventHandler()
|
||||
{
|
||||
Handled = false;
|
||||
}
|
||||
|
||||
public async Task Handle(TestIntegrationEvent @event)
|
||||
{
|
||||
Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EventBus.Tests
|
||||
{
|
||||
public class TestIntegrationEventHandler : IIntegrationEventHandler<TestIntegrationEvent>
|
||||
{
|
||||
public bool Handled { get; private set; }
|
||||
|
||||
public TestIntegrationEventHandler()
|
||||
{
|
||||
Handled = false;
|
||||
}
|
||||
|
||||
public async Task Handle(TestIntegrationEvent @event)
|
||||
{
|
||||
Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +1,17 @@
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
|
||||
using System;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions
|
||||
{
|
||||
public interface IEventBus
|
||||
{
|
||||
void Subscribe<T>(IIntegrationEventHandler<T> handler) where T: IntegrationEvent;
|
||||
void Unsubscribe<T>(IIntegrationEventHandler<T> handler) where T : IntegrationEvent;
|
||||
void Subscribe<T, TH>(Func<TH> handler)
|
||||
where T : IntegrationEvent
|
||||
where TH : IIntegrationEventHandler<T>;
|
||||
void Unsubscribe<T, TH>()
|
||||
where TH : IIntegrationEventHandler<T>
|
||||
where T : IntegrationEvent;
|
||||
|
||||
void Publish(IntegrationEvent @event);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,26 @@
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus
|
||||
{
|
||||
public interface IEventBusSubscriptionsManager
|
||||
{
|
||||
bool IsEmpty { get; }
|
||||
event EventHandler<string> OnEventRemoved;
|
||||
void AddSubscription<T, TH>(Func<TH> handler)
|
||||
where T : IntegrationEvent
|
||||
where TH : IIntegrationEventHandler<T>;
|
||||
|
||||
void RemoveSubscription<T, TH>()
|
||||
where TH : IIntegrationEventHandler<T>
|
||||
where T : IntegrationEvent;
|
||||
bool HasSubscriptionsForEvent<T>() where T : IntegrationEvent;
|
||||
bool HasSubscriptionsForEvent(string eventName);
|
||||
Type GetEventTypeByName(string eventName);
|
||||
void Clear();
|
||||
IEnumerable<Delegate> GetHandlersForEvent<T>() where T : IntegrationEvent;
|
||||
IEnumerable<Delegate> GetHandlersForEvent(string eventName);
|
||||
}
|
||||
}
|
@ -0,0 +1,115 @@
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus
|
||||
{
|
||||
public class InMemoryEventBusSubscriptionsManager : IEventBusSubscriptionsManager
|
||||
{
|
||||
private readonly Dictionary<string, List<Delegate>> _handlers;
|
||||
private readonly List<Type> _eventTypes;
|
||||
|
||||
public event EventHandler<string> OnEventRemoved;
|
||||
|
||||
public InMemoryEventBusSubscriptionsManager()
|
||||
{
|
||||
_handlers = new Dictionary<string, List<Delegate>>();
|
||||
_eventTypes = new List<Type>();
|
||||
}
|
||||
|
||||
public bool IsEmpty => !_handlers.Keys.Any();
|
||||
public void Clear() => _handlers.Clear();
|
||||
|
||||
public void AddSubscription<T, TH>(Func<TH> handler)
|
||||
where T : IntegrationEvent
|
||||
where TH : IIntegrationEventHandler<T>
|
||||
{
|
||||
var key = GetEventKey<T>();
|
||||
if (!HasSubscriptionsForEvent<T>())
|
||||
{
|
||||
_handlers.Add(key, new List<Delegate>());
|
||||
}
|
||||
_handlers[key].Add(handler);
|
||||
_eventTypes.Add(typeof(T));
|
||||
}
|
||||
|
||||
public void RemoveSubscription<T, TH>()
|
||||
where TH : IIntegrationEventHandler<T>
|
||||
where T : IntegrationEvent
|
||||
{
|
||||
var handlerToRemove = FindHandlerToRemove<T, TH>();
|
||||
if (handlerToRemove != null)
|
||||
{
|
||||
var key = GetEventKey<T>();
|
||||
_handlers[key].Remove(handlerToRemove);
|
||||
if (!_handlers[key].Any())
|
||||
{
|
||||
_handlers.Remove(key);
|
||||
var eventType = _eventTypes.SingleOrDefault(e => e.Name == key);
|
||||
if (eventType != null)
|
||||
{
|
||||
_eventTypes.Remove(eventType);
|
||||
RaiseOnEventRemoved(eventType.Name);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<Delegate> GetHandlersForEvent<T>() where T : IntegrationEvent
|
||||
{
|
||||
var key = GetEventKey<T>();
|
||||
return GetHandlersForEvent(key);
|
||||
}
|
||||
public IEnumerable<Delegate> GetHandlersForEvent(string eventName) => _handlers[eventName];
|
||||
|
||||
private void RaiseOnEventRemoved(string eventName)
|
||||
{
|
||||
var handler = OnEventRemoved;
|
||||
if (handler != null)
|
||||
{
|
||||
OnEventRemoved(this, eventName);
|
||||
}
|
||||
}
|
||||
|
||||
private Delegate FindHandlerToRemove<T, TH>()
|
||||
where T : IntegrationEvent
|
||||
where TH : IIntegrationEventHandler<T>
|
||||
{
|
||||
if (!HasSubscriptionsForEvent<T>())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var key = GetEventKey<T>();
|
||||
foreach (var func in _handlers[key])
|
||||
{
|
||||
var genericArgs = func.GetType().GetGenericArguments();
|
||||
if (genericArgs.SingleOrDefault() == typeof(TH))
|
||||
{
|
||||
return func;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool HasSubscriptionsForEvent<T>() where T : IntegrationEvent
|
||||
{
|
||||
var key = GetEventKey<T>();
|
||||
return HasSubscriptionsForEvent(key);
|
||||
}
|
||||
public bool HasSubscriptionsForEvent(string eventName) => _handlers.ContainsKey(eventName);
|
||||
|
||||
public Type GetEventTypeByName(string eventName) => _eventTypes.Single(t => t.Name == eventName);
|
||||
|
||||
private string GetEventKey<T>()
|
||||
{
|
||||
return typeof(T).Name;
|
||||
}
|
||||
}
|
||||
}
|
@ -10,18 +10,18 @@ using System.Net.Sockets;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
|
||||
{
|
||||
public class DefaultRabbitMQPersisterConnection
|
||||
: IRabbitMQPersisterConnection
|
||||
public class DefaultRabbitMQPersistentConnection
|
||||
: IRabbitMQPersistentConnection
|
||||
{
|
||||
private readonly IConnectionFactory _connectionFactory;
|
||||
private readonly ILogger<DefaultRabbitMQPersisterConnection> _logger;
|
||||
private readonly ILogger<DefaultRabbitMQPersistentConnection> _logger;
|
||||
|
||||
IConnection _connection;
|
||||
bool _disposed;
|
||||
|
||||
object sync_root = new object();
|
||||
|
||||
public DefaultRabbitMQPersisterConnection(IConnectionFactory connectionFactory,ILogger<DefaultRabbitMQPersisterConnection> logger)
|
||||
public DefaultRabbitMQPersistentConnection(IConnectionFactory connectionFactory,ILogger<DefaultRabbitMQPersistentConnection> logger)
|
||||
{
|
||||
_connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
@ -87,13 +87,13 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
|
||||
_connection.CallbackException += OnCallbackException;
|
||||
_connection.ConnectionBlocked += OnConnectionBlocked;
|
||||
|
||||
_logger.LogInformation($"RabbitMQ persister connection acquire a connection {_connection.Endpoint.HostName} and is subscribed to failure events");
|
||||
_logger.LogInformation($"RabbitMQ persistent connection acquired a connection {_connection.Endpoint.HostName} and is subscribed to failure events");
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogCritical("FATAL ERROR: RabbitMQ connections can't be created and opened");
|
||||
_logger.LogCritical("FATAL ERROR: RabbitMQ connections could not be created and opened");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus;
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
@ -21,32 +22,50 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
|
||||
{
|
||||
const string BROKER_NAME = "eshop_event_bus";
|
||||
|
||||
private readonly IRabbitMQPersisterConnection _persisterConnection;
|
||||
private readonly IRabbitMQPersistentConnection _persistentConnection;
|
||||
private readonly ILogger<EventBusRabbitMQ> _logger;
|
||||
|
||||
private readonly Dictionary<string, List<IIntegrationEventHandler>> _handlers
|
||||
= new Dictionary<string, List<IIntegrationEventHandler>>();
|
||||
|
||||
private readonly List<Type> _eventTypes
|
||||
= new List<Type>();
|
||||
private readonly IEventBusSubscriptionsManager _subsManager;
|
||||
|
||||
|
||||
private IModel _consumerChannel;
|
||||
private string _queueName;
|
||||
|
||||
public EventBusRabbitMQ(IRabbitMQPersisterConnection persisterConnection, ILogger<EventBusRabbitMQ> logger)
|
||||
public EventBusRabbitMQ(IRabbitMQPersistentConnection persistentConnection, ILogger<EventBusRabbitMQ> logger, IEventBusSubscriptionsManager subsManager)
|
||||
{
|
||||
_persisterConnection = persisterConnection ?? throw new ArgumentNullException(nameof(persisterConnection));
|
||||
_persistentConnection = persistentConnection ?? throw new ArgumentNullException(nameof(persistentConnection));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
_subsManager = subsManager ?? new InMemoryEventBusSubscriptionsManager();
|
||||
_consumerChannel = CreateConsumerChannel();
|
||||
|
||||
_subsManager.OnEventRemoved += SubsManager_OnEventRemoved;
|
||||
}
|
||||
|
||||
private void SubsManager_OnEventRemoved(object sender, string eventName)
|
||||
{
|
||||
if (!_persistentConnection.IsConnected)
|
||||
{
|
||||
_persistentConnection.TryConnect();
|
||||
}
|
||||
|
||||
using (var channel = _persistentConnection.CreateModel())
|
||||
{
|
||||
channel.QueueUnbind(queue: _queueName,
|
||||
exchange: BROKER_NAME,
|
||||
routingKey: eventName);
|
||||
|
||||
if (_subsManager.IsEmpty)
|
||||
{
|
||||
_queueName = string.Empty;
|
||||
_consumerChannel.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Publish(IntegrationEvent @event)
|
||||
{
|
||||
if (!_persisterConnection.IsConnected)
|
||||
if (!_persistentConnection.IsConnected)
|
||||
{
|
||||
_persisterConnection.TryConnect();
|
||||
_persistentConnection.TryConnect();
|
||||
}
|
||||
|
||||
var policy = RetryPolicy.Handle<BrokerUnreachableException>()
|
||||
@ -56,7 +75,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
|
||||
_logger.LogWarning(ex.ToString());
|
||||
});
|
||||
|
||||
using (var channel = _persisterConnection.CreateModel())
|
||||
using (var channel = _persistentConnection.CreateModel())
|
||||
{
|
||||
var eventName = @event.GetType()
|
||||
.Name;
|
||||
@ -77,75 +96,49 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
|
||||
}
|
||||
}
|
||||
|
||||
public void Subscribe<T>(IIntegrationEventHandler<T> handler) where T : IntegrationEvent
|
||||
public void Subscribe<T, TH>(Func<TH> handler)
|
||||
where T : IntegrationEvent
|
||||
where TH : IIntegrationEventHandler<T>
|
||||
{
|
||||
var eventName = typeof(T).Name;
|
||||
|
||||
if (_handlers.ContainsKey(eventName))
|
||||
var containsKey = _subsManager.HasSubscriptionsForEvent<T>();
|
||||
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<IIntegrationEventHandler>());
|
||||
_handlers[eventName].Add(handler);
|
||||
_eventTypes.Add(typeof(T));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
_subsManager.AddSubscription<T, TH>(handler);
|
||||
|
||||
}
|
||||
|
||||
public void Unsubscribe<T>(IIntegrationEventHandler<T> handler) where T : IntegrationEvent
|
||||
public void Unsubscribe<T, TH>()
|
||||
where TH : IIntegrationEventHandler<T>
|
||||
where T : IntegrationEvent
|
||||
{
|
||||
var eventName = typeof(T).Name;
|
||||
_subsManager.RemoveSubscription<T, TH>();
|
||||
}
|
||||
|
||||
if (_handlers.ContainsKey(eventName) && _handlers[eventName].Contains(handler))
|
||||
private static Func<IIntegrationEventHandler> FindHandlerByType(Type handlerType, IEnumerable<Func<IIntegrationEventHandler>> handlers)
|
||||
{
|
||||
foreach (var func in handlers)
|
||||
{
|
||||
_handlers[eventName].Remove(handler);
|
||||
|
||||
if (_handlers[eventName].Count == 0)
|
||||
if (func.GetMethodInfo().ReturnType == handlerType)
|
||||
{
|
||||
_handlers.Remove(eventName);
|
||||
|
||||
var eventType = _eventTypes.SingleOrDefault(e => e.Name == eventName);
|
||||
|
||||
if (eventType != null)
|
||||
{
|
||||
_eventTypes.Remove(eventType);
|
||||
|
||||
if (!_persisterConnection.IsConnected)
|
||||
{
|
||||
_persisterConnection.TryConnect();
|
||||
}
|
||||
|
||||
using (var channel = _persisterConnection.CreateModel())
|
||||
{
|
||||
channel.QueueUnbind(queue: _queueName,
|
||||
exchange: BROKER_NAME,
|
||||
routingKey: eventName);
|
||||
|
||||
if (_handlers.Keys.Count == 0)
|
||||
{
|
||||
_queueName = string.Empty;
|
||||
|
||||
_consumerChannel.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
return func;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
@ -154,18 +147,18 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
|
||||
{
|
||||
_consumerChannel.Dispose();
|
||||
}
|
||||
|
||||
_handlers.Clear();
|
||||
|
||||
_subsManager.Clear();
|
||||
}
|
||||
|
||||
private IModel CreateConsumerChannel()
|
||||
{
|
||||
if (!_persisterConnection.IsConnected)
|
||||
if (!_persistentConnection.IsConnected)
|
||||
{
|
||||
_persisterConnection.TryConnect();
|
||||
_persistentConnection.TryConnect();
|
||||
}
|
||||
|
||||
var channel = _persisterConnection.CreateModel();
|
||||
var channel = _persistentConnection.CreateModel();
|
||||
|
||||
channel.ExchangeDeclare(exchange: BROKER_NAME,
|
||||
type: "direct");
|
||||
@ -196,15 +189,17 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
|
||||
|
||||
private async Task ProcessEvent(string eventName, string message)
|
||||
{
|
||||
if (_handlers.ContainsKey(eventName))
|
||||
{
|
||||
Type eventType = _eventTypes.Single(t => t.Name == eventName);
|
||||
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 });
|
||||
}
|
||||
}
|
||||
|
@ -3,8 +3,7 @@ using System;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
|
||||
{
|
||||
|
||||
public interface IRabbitMQPersisterConnection
|
||||
public interface IRabbitMQPersistentConnection
|
||||
: IDisposable
|
||||
{
|
||||
bool IsConnected { get; }
|
||||
|
@ -1,6 +1,8 @@
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
@ -11,30 +13,34 @@ namespace Microsoft.AspNetCore.HealthChecks
|
||||
{
|
||||
public class HealthCheckMiddleware
|
||||
{
|
||||
private RequestDelegate _next;
|
||||
private string _path;
|
||||
private int? _port;
|
||||
private IHealthCheckService _service;
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly string _path;
|
||||
private readonly int? _port;
|
||||
private readonly IHealthCheckService _service;
|
||||
private readonly TimeSpan _timeout;
|
||||
|
||||
public HealthCheckMiddleware(RequestDelegate next, IHealthCheckService service, int port)
|
||||
public HealthCheckMiddleware(RequestDelegate next, IHealthCheckService service, int port, TimeSpan timeout)
|
||||
{
|
||||
_port = port;
|
||||
_service = service;
|
||||
_next = next;
|
||||
_timeout = timeout;
|
||||
}
|
||||
|
||||
public HealthCheckMiddleware(RequestDelegate next, IHealthCheckService service, string path)
|
||||
public HealthCheckMiddleware(RequestDelegate next, IHealthCheckService service, string path, TimeSpan timeout)
|
||||
{
|
||||
_path = path;
|
||||
_service = service;
|
||||
_next = next;
|
||||
_timeout = timeout;
|
||||
}
|
||||
|
||||
public async Task Invoke(HttpContext context)
|
||||
{
|
||||
if (IsHealthCheckRequest(context))
|
||||
{
|
||||
var result = await _service.CheckHealthAsync();
|
||||
var timeoutTokenSource = new CancellationTokenSource(_timeout);
|
||||
var result = await _service.CheckHealthAsync(timeoutTokenSource.Token);
|
||||
var status = result.CheckStatus;
|
||||
|
||||
if (status != CheckStatus.Healthy)
|
||||
@ -60,7 +66,9 @@ namespace Microsoft.AspNetCore.HealthChecks
|
||||
}
|
||||
|
||||
if (context.Request.Path == _path)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -11,15 +11,18 @@ namespace Microsoft.AspNetCore.HealthChecks
|
||||
{
|
||||
private string _path;
|
||||
private int? _port;
|
||||
private TimeSpan _timeout;
|
||||
|
||||
public HealthCheckStartupFilter(int port)
|
||||
public HealthCheckStartupFilter(int port, TimeSpan timeout)
|
||||
{
|
||||
_port = port;
|
||||
_timeout = timeout;
|
||||
}
|
||||
|
||||
public HealthCheckStartupFilter(string path)
|
||||
public HealthCheckStartupFilter(string path, TimeSpan timeout)
|
||||
{
|
||||
_path = path;
|
||||
_timeout = timeout;
|
||||
}
|
||||
|
||||
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
|
||||
@ -27,9 +30,13 @@ namespace Microsoft.AspNetCore.HealthChecks
|
||||
return app =>
|
||||
{
|
||||
if (_port.HasValue)
|
||||
app.UseMiddleware<HealthCheckMiddleware>(_port);
|
||||
{
|
||||
app.UseMiddleware<HealthCheckMiddleware>(_port, _timeout);
|
||||
}
|
||||
else
|
||||
app.UseMiddleware<HealthCheckMiddleware>(_path);
|
||||
{
|
||||
app.UseMiddleware<HealthCheckMiddleware>(_path, _timeout);
|
||||
}
|
||||
|
||||
next(app);
|
||||
};
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.HealthChecks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
@ -8,28 +9,38 @@ namespace Microsoft.AspNetCore.Hosting
|
||||
{
|
||||
public static class HealthCheckWebHostBuilderExtension
|
||||
{
|
||||
public static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(10);
|
||||
|
||||
public static IWebHostBuilder UseHealthChecks(this IWebHostBuilder builder, int port)
|
||||
=> UseHealthChecks(builder, port, DefaultTimeout);
|
||||
|
||||
public static IWebHostBuilder UseHealthChecks(this IWebHostBuilder builder, int port, TimeSpan timeout)
|
||||
{
|
||||
Guard.ArgumentValid(port > 0 && port < 65536, nameof(port), "Port must be a value between 1 and 65535");
|
||||
Guard.ArgumentValid(port > 0 && port < 65536, nameof(port), "Port must be a value between 1 and 65535.");
|
||||
Guard.ArgumentValid(timeout > TimeSpan.Zero, nameof(timeout), "Health check timeout must be a positive time span.");
|
||||
|
||||
builder.ConfigureServices(services =>
|
||||
{
|
||||
var existingUrl = builder.GetSetting(WebHostDefaults.ServerUrlsKey);
|
||||
builder.UseSetting(WebHostDefaults.ServerUrlsKey, $"{existingUrl};http://localhost:{port}");
|
||||
|
||||
services.AddSingleton<IStartupFilter>(new HealthCheckStartupFilter(port));
|
||||
services.AddSingleton<IStartupFilter>(new HealthCheckStartupFilter(port, timeout));
|
||||
});
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static IWebHostBuilder UseHealthChecks(this IWebHostBuilder builder, string path)
|
||||
=> UseHealthChecks(builder, path, DefaultTimeout);
|
||||
|
||||
public static IWebHostBuilder UseHealthChecks(this IWebHostBuilder builder, string path, TimeSpan timeout)
|
||||
{
|
||||
Guard.ArgumentNotNull(nameof(path), path);
|
||||
// REVIEW: Is there a better URL path validator somewhere?
|
||||
Guard.ArgumentValid(!path.Contains("?"), nameof(path), "Path cannot contain query string values");
|
||||
Guard.ArgumentValid(path.StartsWith("/"), nameof(path), "Path should start with /");
|
||||
Guard.ArgumentValid(!path.Contains("?"), nameof(path), "Path cannot contain query string values.");
|
||||
Guard.ArgumentValid(path.StartsWith("/"), nameof(path), "Path should start with '/'.");
|
||||
Guard.ArgumentValid(timeout > TimeSpan.Zero, nameof(timeout), "Health check timeout must be a positive time span.");
|
||||
|
||||
builder.ConfigureServices(services => services.AddSingleton<IStartupFilter>(new HealthCheckStartupFilter(path)));
|
||||
builder.ConfigureServices(services => services.AddSingleton<IStartupFilter>(new HealthCheckStartupFilter(path, timeout)));
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
@ -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}");
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.Extensions.HealthChecks
|
||||
{
|
||||
public abstract class CachedHealthCheck
|
||||
{
|
||||
private static readonly TypeInfo HealthCheckTypeInfo = typeof(IHealthCheck).GetTypeInfo();
|
||||
|
||||
private volatile int _writerCount;
|
||||
|
||||
public CachedHealthCheck(string name, TimeSpan cacheDuration)
|
||||
{
|
||||
Guard.ArgumentNotNullOrEmpty(nameof(name), name);
|
||||
Guard.ArgumentValid(cacheDuration.TotalMilliseconds >= 0, nameof(cacheDuration), "Cache duration must be zero (disabled) or greater than zero.");
|
||||
|
||||
Name = name;
|
||||
CacheDuration = cacheDuration;
|
||||
}
|
||||
|
||||
public IHealthCheckResult CachedResult { get; internal set; }
|
||||
|
||||
public TimeSpan CacheDuration { get; }
|
||||
|
||||
public DateTimeOffset CacheExpiration { get; internal set; }
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
protected virtual DateTimeOffset UtcNow => DateTimeOffset.UtcNow;
|
||||
|
||||
protected abstract IHealthCheck Resolve(IServiceProvider serviceProvider);
|
||||
|
||||
public async ValueTask<IHealthCheckResult> RunAsync(IServiceProvider serviceProvider, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
while (CacheExpiration <= UtcNow)
|
||||
{
|
||||
// Can't use a standard lock here because of async, so we'll use this flag to determine when we should write a value,
|
||||
// and the waiters who aren't allowed to write will just spin wait for the new value.
|
||||
if (Interlocked.Exchange(ref _writerCount, 1) != 0)
|
||||
{
|
||||
await Task.Delay(5, cancellationToken).ConfigureAwait(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var check = Resolve(serviceProvider);
|
||||
CachedResult = await check.CheckAsync(cancellationToken);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
CachedResult = HealthCheckResult.Unhealthy("The health check operation timed out");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CachedResult = HealthCheckResult.Unhealthy($"Exception during check: {ex.GetType().FullName}");
|
||||
}
|
||||
|
||||
CacheExpiration = UtcNow + CacheDuration;
|
||||
_writerCount = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
return CachedResult;
|
||||
}
|
||||
|
||||
public static CachedHealthCheck FromHealthCheck(string name, TimeSpan cacheDuration, IHealthCheck healthCheck)
|
||||
{
|
||||
Guard.ArgumentNotNull(nameof(healthCheck), healthCheck);
|
||||
|
||||
return new TypeOrHealthCheck_HealthCheck(name, cacheDuration, healthCheck);
|
||||
}
|
||||
|
||||
public static CachedHealthCheck FromType(string name, TimeSpan cacheDuration, Type healthCheckType)
|
||||
{
|
||||
Guard.ArgumentNotNull(nameof(healthCheckType), healthCheckType);
|
||||
Guard.ArgumentValid(HealthCheckTypeInfo.IsAssignableFrom(healthCheckType.GetTypeInfo()), nameof(healthCheckType), $"Health check must implement '{typeof(IHealthCheck).FullName}'.");
|
||||
|
||||
return new TypeOrHealthCheck_Type(name, cacheDuration, healthCheckType);
|
||||
}
|
||||
|
||||
class TypeOrHealthCheck_HealthCheck : CachedHealthCheck
|
||||
{
|
||||
private readonly IHealthCheck _healthCheck;
|
||||
|
||||
public TypeOrHealthCheck_HealthCheck(string name, TimeSpan cacheDuration, IHealthCheck healthCheck) : base(name, cacheDuration)
|
||||
=> _healthCheck = healthCheck;
|
||||
|
||||
protected override IHealthCheck Resolve(IServiceProvider serviceProvider) => _healthCheck;
|
||||
}
|
||||
|
||||
class TypeOrHealthCheck_Type : CachedHealthCheck
|
||||
{
|
||||
private readonly Type _healthCheckType;
|
||||
|
||||
public TypeOrHealthCheck_Type(string name, TimeSpan cacheDuration, Type healthCheckType) : base(name, cacheDuration)
|
||||
=> _healthCheckType = healthCheckType;
|
||||
|
||||
protected override IHealthCheck Resolve(IServiceProvider serviceProvider)
|
||||
=> (IHealthCheck)serviceProvider.GetRequiredService(_healthCheckType);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Extensions.HealthChecks
|
||||
{
|
||||
public static class CachedHealthCheckExtensions
|
||||
{
|
||||
public static ValueTask<IHealthCheckResult> RunAsync(this CachedHealthCheck check, IServiceProvider serviceProvider)
|
||||
{
|
||||
Guard.ArgumentNotNull(nameof(check), check);
|
||||
|
||||
return check.RunAsync(serviceProvider, CancellationToken.None);
|
||||
}
|
||||
}
|
||||
}
|
@ -15,96 +15,102 @@ namespace Microsoft.Extensions.HealthChecks
|
||||
{
|
||||
Guard.ArgumentNotNull(nameof(builder), builder);
|
||||
|
||||
builder.AddCheck(name, HealthCheck.FromCheck(check, builder.DefaultCacheDuration));
|
||||
return builder;
|
||||
return builder.AddCheck(name, HealthCheck.FromCheck(check), builder.DefaultCacheDuration);
|
||||
}
|
||||
|
||||
public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func<CancellationToken, IHealthCheckResult> check)
|
||||
{
|
||||
Guard.ArgumentNotNull(nameof(builder), builder);
|
||||
|
||||
builder.AddCheck(name, HealthCheck.FromCheck(check, builder.DefaultCacheDuration));
|
||||
return builder;
|
||||
return builder.AddCheck(name, HealthCheck.FromCheck(check), builder.DefaultCacheDuration);
|
||||
}
|
||||
|
||||
public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func<IHealthCheckResult> check, TimeSpan cacheDuration)
|
||||
{
|
||||
Guard.ArgumentNotNull(nameof(builder), builder);
|
||||
|
||||
builder.AddCheck(name, HealthCheck.FromCheck(check, cacheDuration));
|
||||
return builder;
|
||||
return builder.AddCheck(name, HealthCheck.FromCheck(check), cacheDuration);
|
||||
}
|
||||
|
||||
public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func<CancellationToken, IHealthCheckResult> check, TimeSpan cacheDuration)
|
||||
{
|
||||
Guard.ArgumentNotNull(nameof(builder), builder);
|
||||
|
||||
builder.AddCheck(name, HealthCheck.FromCheck(check, cacheDuration));
|
||||
return builder;
|
||||
return builder.AddCheck(name, HealthCheck.FromCheck(check), cacheDuration);
|
||||
}
|
||||
|
||||
public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func<Task<IHealthCheckResult>> check)
|
||||
{
|
||||
Guard.ArgumentNotNull(nameof(builder), builder);
|
||||
|
||||
builder.AddCheck(name, HealthCheck.FromTaskCheck(check, builder.DefaultCacheDuration));
|
||||
return builder;
|
||||
return builder.AddCheck(name, HealthCheck.FromTaskCheck(check), builder.DefaultCacheDuration);
|
||||
}
|
||||
|
||||
public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func<CancellationToken, Task<IHealthCheckResult>> check)
|
||||
{
|
||||
Guard.ArgumentNotNull(nameof(builder), builder);
|
||||
|
||||
builder.AddCheck(name, HealthCheck.FromTaskCheck(check, builder.DefaultCacheDuration));
|
||||
return builder;
|
||||
return builder.AddCheck(name, HealthCheck.FromTaskCheck(check), builder.DefaultCacheDuration);
|
||||
}
|
||||
|
||||
public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func<Task<IHealthCheckResult>> check, TimeSpan cacheDuration)
|
||||
{
|
||||
Guard.ArgumentNotNull(nameof(builder), builder);
|
||||
|
||||
builder.AddCheck(name, HealthCheck.FromTaskCheck(check, cacheDuration));
|
||||
return builder;
|
||||
return builder.AddCheck(name, HealthCheck.FromTaskCheck(check), cacheDuration);
|
||||
}
|
||||
|
||||
public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func<CancellationToken, Task<IHealthCheckResult>> check, TimeSpan cacheDuration)
|
||||
{
|
||||
Guard.ArgumentNotNull(nameof(builder), builder);
|
||||
|
||||
builder.AddCheck(name, HealthCheck.FromTaskCheck(check, cacheDuration));
|
||||
return builder;
|
||||
return builder.AddCheck(name, HealthCheck.FromTaskCheck(check), cacheDuration);
|
||||
}
|
||||
|
||||
public static HealthCheckBuilder AddValueTaskCheck(this HealthCheckBuilder builder, string name, Func<ValueTask<IHealthCheckResult>> check)
|
||||
{
|
||||
Guard.ArgumentNotNull(nameof(builder), builder);
|
||||
|
||||
builder.AddCheck(name, HealthCheck.FromValueTaskCheck(check, builder.DefaultCacheDuration));
|
||||
return builder;
|
||||
return builder.AddCheck(name, HealthCheck.FromValueTaskCheck(check), builder.DefaultCacheDuration);
|
||||
}
|
||||
|
||||
public static HealthCheckBuilder AddValueTaskCheck(this HealthCheckBuilder builder, string name, Func<CancellationToken, ValueTask<IHealthCheckResult>> check)
|
||||
{
|
||||
Guard.ArgumentNotNull(nameof(builder), builder);
|
||||
|
||||
builder.AddCheck(name, HealthCheck.FromValueTaskCheck(check, builder.DefaultCacheDuration));
|
||||
return builder;
|
||||
return builder.AddCheck(name, HealthCheck.FromValueTaskCheck(check), builder.DefaultCacheDuration);
|
||||
}
|
||||
|
||||
public static HealthCheckBuilder AddValueTaskCheck(this HealthCheckBuilder builder, string name, Func<ValueTask<IHealthCheckResult>> check, TimeSpan cacheDuration)
|
||||
{
|
||||
Guard.ArgumentNotNull(nameof(builder), builder);
|
||||
|
||||
builder.AddCheck(name, HealthCheck.FromValueTaskCheck(check, cacheDuration));
|
||||
return builder;
|
||||
return builder.AddCheck(name, HealthCheck.FromValueTaskCheck(check), cacheDuration);
|
||||
}
|
||||
|
||||
public static HealthCheckBuilder AddValueTaskCheck(this HealthCheckBuilder builder, string name, Func<CancellationToken, ValueTask<IHealthCheckResult>> check, TimeSpan cacheDuration)
|
||||
{
|
||||
Guard.ArgumentNotNull(nameof(builder), builder);
|
||||
|
||||
builder.AddCheck(name, HealthCheck.FromValueTaskCheck(check, cacheDuration));
|
||||
return builder;
|
||||
return builder.AddCheck(name, HealthCheck.FromValueTaskCheck(check), cacheDuration);
|
||||
}
|
||||
|
||||
// IHealthCheck versions of AddCheck
|
||||
|
||||
public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string checkName, IHealthCheck check)
|
||||
{
|
||||
Guard.ArgumentNotNull(nameof(builder), builder);
|
||||
|
||||
return builder.AddCheck(checkName, check, builder.DefaultCacheDuration);
|
||||
}
|
||||
|
||||
// Type versions of AddCheck
|
||||
|
||||
public static HealthCheckBuilder AddCheck<TCheck>(this HealthCheckBuilder builder, string name) where TCheck : class, IHealthCheck
|
||||
{
|
||||
Guard.ArgumentNotNull(nameof(builder), builder);
|
||||
|
||||
return builder.AddCheck<TCheck>(name, builder.DefaultCacheDuration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ namespace Microsoft.Extensions.HealthChecks
|
||||
where T : IComparable<T>
|
||||
{
|
||||
Guard.ArgumentNotNull(nameof(builder), builder);
|
||||
Guard.ArgumentNotNullOrWhitespace(nameof(name), name);
|
||||
Guard.ArgumentNotNullOrEmpty(nameof(name), name);
|
||||
Guard.ArgumentNotNull(nameof(currentValueFunc), currentValueFunc);
|
||||
|
||||
builder.AddCheck(name, () =>
|
||||
@ -23,7 +23,7 @@ namespace Microsoft.Extensions.HealthChecks
|
||||
var status = currentValue.CompareTo(minValue) >= 0 ? CheckStatus.Healthy : CheckStatus.Unhealthy;
|
||||
return HealthCheckResult.FromStatus(
|
||||
status,
|
||||
$"{name}: min={minValue}, current={currentValue}",
|
||||
$"min={minValue}, current={currentValue}",
|
||||
new Dictionary<string, object> { { "min", minValue }, { "current", currentValue } }
|
||||
);
|
||||
});
|
||||
@ -35,16 +35,16 @@ namespace Microsoft.Extensions.HealthChecks
|
||||
where T : IComparable<T>
|
||||
{
|
||||
Guard.ArgumentNotNull(nameof(builder), builder);
|
||||
Guard.ArgumentNotNullOrWhitespace(nameof(name), name);
|
||||
Guard.ArgumentNotNullOrEmpty(nameof(name), name);
|
||||
Guard.ArgumentNotNull(nameof(currentValueFunc), currentValueFunc);
|
||||
|
||||
builder.AddCheck($"{name}", () =>
|
||||
builder.AddCheck(name, () =>
|
||||
{
|
||||
var currentValue = currentValueFunc();
|
||||
var status = currentValue.CompareTo(maxValue) <= 0 ? CheckStatus.Healthy : CheckStatus.Unhealthy;
|
||||
return HealthCheckResult.FromStatus(
|
||||
status,
|
||||
$"{name}: max={maxValue}, current={currentValue}",
|
||||
$"max={maxValue}, current={currentValue}",
|
||||
new Dictionary<string, object> { { "max", maxValue }, { "current", currentValue } }
|
||||
);
|
||||
});
|
||||
|
@ -2,8 +2,6 @@
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.HealthChecks.Internal;
|
||||
@ -37,73 +35,12 @@ namespace Microsoft.Extensions.HealthChecks
|
||||
Func<HttpResponseMessage, ValueTask<IHealthCheckResult>> checkFunc)
|
||||
{
|
||||
Guard.ArgumentNotNull(nameof(builder), builder);
|
||||
Guard.ArgumentNotNullOrWhitespace(nameof(url), url);
|
||||
Guard.ArgumentNotNullOrEmpty(nameof(url), url);
|
||||
Guard.ArgumentNotNull(nameof(checkFunc), checkFunc);
|
||||
|
||||
var urlCheck = new UrlChecker(checkFunc, url);
|
||||
builder.AddCheck($"UrlCheck({url})", () => urlCheck.CheckAsync());
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable<string> urlItems, string groupName)
|
||||
=> AddUrlChecks(builder, urlItems, groupName, CheckStatus.Warning, response => UrlChecker.DefaultUrlCheck(response));
|
||||
|
||||
public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable<string> urlItems, string groupName,
|
||||
Func<HttpResponseMessage, IHealthCheckResult> checkFunc)
|
||||
{
|
||||
Guard.ArgumentNotNull(nameof(checkFunc), checkFunc);
|
||||
|
||||
return AddUrlChecks(builder, urlItems, groupName, CheckStatus.Warning, response => new ValueTask<IHealthCheckResult>(checkFunc(response)));
|
||||
}
|
||||
|
||||
public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable<string> urlItems, string groupName,
|
||||
Func<HttpResponseMessage, Task<IHealthCheckResult>> checkFunc)
|
||||
{
|
||||
Guard.ArgumentNotNull(nameof(checkFunc), checkFunc);
|
||||
|
||||
return AddUrlChecks(builder, urlItems, groupName, CheckStatus.Warning, response => new ValueTask<IHealthCheckResult>(checkFunc(response)));
|
||||
}
|
||||
|
||||
public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable<string> urlItems, string groupName,
|
||||
Func<HttpResponseMessage, ValueTask<IHealthCheckResult>> checkFunc)
|
||||
{
|
||||
Guard.ArgumentNotNull(nameof(checkFunc), checkFunc);
|
||||
|
||||
return AddUrlChecks(builder, urlItems, groupName, CheckStatus.Warning, response => checkFunc(response));
|
||||
}
|
||||
|
||||
public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable<string> urlItems, string groupName,
|
||||
CheckStatus partialSuccessStatus)
|
||||
=> AddUrlChecks(builder, urlItems, groupName, partialSuccessStatus, response => UrlChecker.DefaultUrlCheck(response));
|
||||
|
||||
public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable<string> urlItems, string groupName,
|
||||
CheckStatus partialSuccessStatus, Func<HttpResponseMessage, IHealthCheckResult> checkFunc)
|
||||
{
|
||||
Guard.ArgumentNotNull(nameof(checkFunc), checkFunc);
|
||||
|
||||
return AddUrlChecks(builder, urlItems, groupName, partialSuccessStatus, response => new ValueTask<IHealthCheckResult>(checkFunc(response)));
|
||||
}
|
||||
|
||||
public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable<string> urlItems, string groupName,
|
||||
CheckStatus partialSuccessStatus, Func<HttpResponseMessage, Task<IHealthCheckResult>> checkFunc)
|
||||
{
|
||||
Guard.ArgumentNotNull(nameof(checkFunc), checkFunc);
|
||||
|
||||
return AddUrlChecks(builder, urlItems, groupName, partialSuccessStatus, response => new ValueTask<IHealthCheckResult>(checkFunc(response)));
|
||||
}
|
||||
|
||||
public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable<string> urlItems, string groupName,
|
||||
CheckStatus partialSuccessStatus, Func<HttpResponseMessage, ValueTask<IHealthCheckResult>> checkFunc)
|
||||
{
|
||||
var urls = urlItems?.ToArray();
|
||||
|
||||
Guard.ArgumentNotNull(nameof(builder), builder);
|
||||
Guard.ArgumentNotNullOrEmpty(nameof(urlItems), urls);
|
||||
Guard.ArgumentNotNullOrWhitespace(nameof(groupName), groupName);
|
||||
|
||||
var urlChecker = new UrlChecker(checkFunc, urls) { PartiallyHealthyStatus = partialSuccessStatus };
|
||||
builder.AddCheck($"UrlChecks({groupName})", () => urlChecker.CheckAsync());
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ using System.Linq;
|
||||
|
||||
namespace Microsoft.Extensions.HealthChecks
|
||||
{
|
||||
// REVIEW: Does this need to be thread safe?
|
||||
/// <summary>
|
||||
/// Represents a composite health check result built from several results.
|
||||
/// </summary>
|
||||
@ -31,17 +30,23 @@ namespace Microsoft.Extensions.HealthChecks
|
||||
{
|
||||
var checkStatuses = new HashSet<CheckStatus>(_results.Select(x => x.Value.CheckStatus));
|
||||
if (checkStatuses.Count == 0)
|
||||
{
|
||||
return _initialStatus;
|
||||
}
|
||||
if (checkStatuses.Count == 1)
|
||||
{
|
||||
return checkStatuses.First();
|
||||
}
|
||||
if (checkStatuses.Contains(CheckStatus.Healthy))
|
||||
{
|
||||
return _partiallyHealthyStatus;
|
||||
}
|
||||
|
||||
return CheckStatus.Unhealthy;
|
||||
}
|
||||
}
|
||||
|
||||
public string Description => string.Join(Environment.NewLine, _results.Select(r => r.Value.Description));
|
||||
public string Description => string.Join(Environment.NewLine, _results.Select(r => $"{r.Key}: {r.Value.Description}"));
|
||||
|
||||
public IReadOnlyDictionary<string, object> Data
|
||||
{
|
||||
@ -58,23 +63,21 @@ namespace Microsoft.Extensions.HealthChecks
|
||||
|
||||
public IReadOnlyDictionary<string, IHealthCheckResult> Results => _results;
|
||||
|
||||
// REVIEW: Should description be required? Seems redundant for success checks.
|
||||
|
||||
public void Add(string name, CheckStatus status, string description)
|
||||
=> Add(name, status, description, null);
|
||||
|
||||
public void Add(string name, CheckStatus status, string description, Dictionary<string, object> data)
|
||||
{
|
||||
Guard.ArgumentNotNullOrWhitespace(nameof(name), name);
|
||||
Guard.ArgumentValid(status != CheckStatus.Unknown, nameof(status), "Cannot add unknown status to composite health check result");
|
||||
Guard.ArgumentNotNullOrWhitespace(nameof(description), description);
|
||||
Guard.ArgumentNotNullOrEmpty(nameof(name), name);
|
||||
Guard.ArgumentValid(status != CheckStatus.Unknown, nameof(status), "Cannot add 'Unknown' status to composite health check result.");
|
||||
Guard.ArgumentNotNullOrEmpty(nameof(description), description);
|
||||
|
||||
_results.Add(name, HealthCheckResult.FromStatus(status, description, data));
|
||||
}
|
||||
|
||||
public void Add(string name, IHealthCheckResult checkResult)
|
||||
{
|
||||
Guard.ArgumentNotNullOrWhitespace(nameof(name), name);
|
||||
Guard.ArgumentNotNullOrEmpty(nameof(name), name);
|
||||
Guard.ArgumentNotNull(nameof(checkResult), checkResult);
|
||||
|
||||
_results.Add(name, checkResult);
|
||||
|
@ -9,68 +9,34 @@ namespace Microsoft.Extensions.HealthChecks
|
||||
{
|
||||
public class HealthCheck : IHealthCheck
|
||||
{
|
||||
private DateTimeOffset _cacheExpiration;
|
||||
private IHealthCheckResult _cachedResult;
|
||||
private volatile int _writerCount;
|
||||
|
||||
protected HealthCheck(Func<CancellationToken, ValueTask<IHealthCheckResult>> check, TimeSpan cacheDuration)
|
||||
protected HealthCheck(Func<CancellationToken, ValueTask<IHealthCheckResult>> check)
|
||||
{
|
||||
Guard.ArgumentNotNull(nameof(check), check);
|
||||
Guard.ArgumentValid(cacheDuration >= TimeSpan.Zero, nameof(cacheDuration), "Cache duration must either be zero (disabled) or a positive value");
|
||||
|
||||
Check = check;
|
||||
CacheDuration = cacheDuration;
|
||||
}
|
||||
|
||||
public TimeSpan CacheDuration { get; }
|
||||
|
||||
protected Func<CancellationToken, ValueTask<IHealthCheckResult>> Check { get; }
|
||||
|
||||
protected virtual DateTimeOffset UtcNow => DateTimeOffset.UtcNow;
|
||||
public ValueTask<IHealthCheckResult> CheckAsync(CancellationToken cancellationToken = default(CancellationToken))
|
||||
=> Check(cancellationToken);
|
||||
|
||||
public async ValueTask<IHealthCheckResult> CheckAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
while (_cacheExpiration <= UtcNow)
|
||||
{
|
||||
// Can't use a standard lock here because of async, so we'll use this flag to determine when we should write a value,
|
||||
// and the waiters who aren't allowed to write will just spin wait for the new value.
|
||||
if (Interlocked.Exchange(ref _writerCount, 1) != 0)
|
||||
{
|
||||
await Task.Delay(5, cancellationToken).ConfigureAwait(false);
|
||||
continue;
|
||||
}
|
||||
public static HealthCheck FromCheck(Func<IHealthCheckResult> check)
|
||||
=> new HealthCheck(token => new ValueTask<IHealthCheckResult>(check()));
|
||||
|
||||
try
|
||||
{
|
||||
_cachedResult = await Check(cancellationToken).ConfigureAwait(false);
|
||||
_cacheExpiration = UtcNow + CacheDuration;
|
||||
break;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_writerCount = 0;
|
||||
}
|
||||
}
|
||||
public static HealthCheck FromCheck(Func<CancellationToken, IHealthCheckResult> check)
|
||||
=> new HealthCheck(token => new ValueTask<IHealthCheckResult>(check(token)));
|
||||
|
||||
return _cachedResult;
|
||||
}
|
||||
public static HealthCheck FromTaskCheck(Func<Task<IHealthCheckResult>> check)
|
||||
=> new HealthCheck(token => new ValueTask<IHealthCheckResult>(check()));
|
||||
|
||||
public static HealthCheck FromCheck(Func<IHealthCheckResult> check, TimeSpan cacheDuration)
|
||||
=> new HealthCheck(token => new ValueTask<IHealthCheckResult>(check()), cacheDuration);
|
||||
public static HealthCheck FromTaskCheck(Func<CancellationToken, Task<IHealthCheckResult>> check)
|
||||
=> new HealthCheck(token => new ValueTask<IHealthCheckResult>(check(token)));
|
||||
|
||||
public static HealthCheck FromCheck(Func<CancellationToken, IHealthCheckResult> check, TimeSpan cacheDuration)
|
||||
=> new HealthCheck(token => new ValueTask<IHealthCheckResult>(check(token)), cacheDuration);
|
||||
public static HealthCheck FromValueTaskCheck(Func<ValueTask<IHealthCheckResult>> check)
|
||||
=> new HealthCheck(token => check());
|
||||
|
||||
public static HealthCheck FromTaskCheck(Func<Task<IHealthCheckResult>> check, TimeSpan cacheDuration)
|
||||
=> new HealthCheck(token => new ValueTask<IHealthCheckResult>(check()), cacheDuration);
|
||||
|
||||
public static HealthCheck FromTaskCheck(Func<CancellationToken, Task<IHealthCheckResult>> check, TimeSpan cacheDuration)
|
||||
=> new HealthCheck(token => new ValueTask<IHealthCheckResult>(check(token)), cacheDuration);
|
||||
|
||||
public static HealthCheck FromValueTaskCheck(Func<ValueTask<IHealthCheckResult>> check, TimeSpan cacheDuration)
|
||||
=> new HealthCheck(token => check(), cacheDuration);
|
||||
|
||||
public static HealthCheck FromValueTaskCheck(Func<CancellationToken, ValueTask<IHealthCheckResult>> check, TimeSpan cacheDuration)
|
||||
=> new HealthCheck(check, cacheDuration);
|
||||
public static HealthCheck FromValueTaskCheck(Func<CancellationToken, ValueTask<IHealthCheckResult>> check)
|
||||
=> new HealthCheck(check);
|
||||
}
|
||||
}
|
||||
|
@ -8,33 +8,128 @@ namespace Microsoft.Extensions.HealthChecks
|
||||
{
|
||||
public class HealthCheckBuilder
|
||||
{
|
||||
private readonly Dictionary<string, IHealthCheck> _checks;
|
||||
private readonly Dictionary<string, CachedHealthCheck> _checksByName;
|
||||
private readonly HealthCheckGroup _currentGroup;
|
||||
private readonly Dictionary<string, HealthCheckGroup> _groups;
|
||||
|
||||
public HealthCheckBuilder()
|
||||
{
|
||||
_checks = new Dictionary<string, IHealthCheck>(StringComparer.OrdinalIgnoreCase);
|
||||
_checksByName = new Dictionary<string, CachedHealthCheck>(StringComparer.OrdinalIgnoreCase);
|
||||
_currentGroup = new HealthCheckGroup(string.Empty, CheckStatus.Unhealthy);
|
||||
_groups = new Dictionary<string, HealthCheckGroup>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
[string.Empty] = _currentGroup
|
||||
};
|
||||
|
||||
DefaultCacheDuration = TimeSpan.FromMinutes(5);
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<string, IHealthCheck> Checks => _checks;
|
||||
/// <summary>
|
||||
/// This constructor should only be used when creating a grouped health check builder.
|
||||
/// </summary>
|
||||
public HealthCheckBuilder(HealthCheckBuilder rootBuilder, HealthCheckGroup currentGroup)
|
||||
{
|
||||
Guard.ArgumentNotNull(nameof(rootBuilder), rootBuilder);
|
||||
Guard.ArgumentNotNull(nameof(currentGroup), currentGroup);
|
||||
|
||||
_checksByName = rootBuilder._checksByName;
|
||||
_currentGroup = currentGroup;
|
||||
_groups = rootBuilder._groups;
|
||||
|
||||
DefaultCacheDuration = rootBuilder.DefaultCacheDuration;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the registered checks, indexed by check name.
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, CachedHealthCheck> ChecksByName => _checksByName;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current default cache duration used when registering checks.
|
||||
/// </summary>
|
||||
public TimeSpan DefaultCacheDuration { get; private set; }
|
||||
|
||||
public HealthCheckBuilder AddCheck(string name, IHealthCheck check)
|
||||
{
|
||||
Guard.ArgumentNotNullOrWhitespace(nameof(name), name);
|
||||
Guard.ArgumentNotNull(nameof(check), check);
|
||||
/// <summary>
|
||||
/// Gets the registered groups, indexed by group name. The root group's name is <see cref="string.Empty"/>.
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, HealthCheckGroup> Groups => _groups;
|
||||
|
||||
/// <summary>
|
||||
/// Registers a health check type that will later be resolved via dependency
|
||||
/// injection.
|
||||
/// </summary>
|
||||
public HealthCheckBuilder AddCheck<TCheck>(string checkName, TimeSpan cacheDuration) where TCheck : class, IHealthCheck
|
||||
{
|
||||
Guard.ArgumentNotNullOrEmpty(nameof(checkName), checkName);
|
||||
Guard.ArgumentValid(!_checksByName.ContainsKey(checkName), nameof(checkName), $"A check with name '{checkName}' has already been registered.");
|
||||
|
||||
var namedCheck = CachedHealthCheck.FromType(checkName, cacheDuration, typeof(TCheck));
|
||||
|
||||
_checksByName.Add(checkName, namedCheck);
|
||||
_currentGroup.ChecksInternal.Add(namedCheck);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a concrete health check to the builder.
|
||||
/// </summary>
|
||||
public HealthCheckBuilder AddCheck(string checkName, IHealthCheck check, TimeSpan cacheDuration)
|
||||
{
|
||||
Guard.ArgumentNotNullOrEmpty(nameof(checkName), checkName);
|
||||
Guard.ArgumentNotNull(nameof(check), check);
|
||||
Guard.ArgumentValid(!_checksByName.ContainsKey(checkName), nameof(checkName), $"A check with name '{checkName}' has already been registered.");
|
||||
|
||||
var namedCheck = CachedHealthCheck.FromHealthCheck(checkName, cacheDuration, check);
|
||||
|
||||
_checksByName.Add(checkName, namedCheck);
|
||||
_currentGroup.ChecksInternal.Add(namedCheck);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new health check group, to which you can add one or more health
|
||||
/// checks. Uses <see cref="CheckStatus.Unhealthy"/> when the group is
|
||||
/// partially successful.
|
||||
/// </summary>
|
||||
public HealthCheckBuilder AddHealthCheckGroup(string groupName, Action<HealthCheckBuilder> groupChecks)
|
||||
=> AddHealthCheckGroup(groupName, groupChecks, CheckStatus.Unhealthy);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new health check group, to which you can add one or more health
|
||||
/// checks.
|
||||
/// </summary>
|
||||
public HealthCheckBuilder AddHealthCheckGroup(string groupName, Action<HealthCheckBuilder> groupChecks, CheckStatus partialSuccessStatus)
|
||||
{
|
||||
Guard.ArgumentNotNullOrEmpty(nameof(groupName), groupName);
|
||||
Guard.ArgumentNotNull(nameof(groupChecks), groupChecks);
|
||||
Guard.ArgumentValid(partialSuccessStatus != CheckStatus.Unknown, nameof(partialSuccessStatus), "Check status 'Unknown' is not valid for partial success.");
|
||||
Guard.ArgumentValid(!_groups.ContainsKey(groupName), nameof(groupName), $"A group with name '{groupName}' has already been registered.");
|
||||
Guard.OperationValid(_currentGroup.GroupName == string.Empty, "Nested groups are not supported by HealthCheckBuilder.");
|
||||
|
||||
var group = new HealthCheckGroup(groupName, partialSuccessStatus);
|
||||
_groups.Add(groupName, group);
|
||||
|
||||
var innerBuilder = new HealthCheckBuilder(this, group);
|
||||
groupChecks(innerBuilder);
|
||||
|
||||
_checks.Add(name, check);
|
||||
return this;
|
||||
}
|
||||
|
||||
public HealthCheckBuilder WithDefaultCacheDuration(TimeSpan duration)
|
||||
{
|
||||
Guard.ArgumentValid(duration >= TimeSpan.Zero, nameof(duration), "Duration must be zero (disabled) or a positive duration");
|
||||
Guard.ArgumentValid(duration >= TimeSpan.Zero, nameof(duration), "Duration must be zero (disabled) or a positive duration.");
|
||||
|
||||
DefaultCacheDuration = duration;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HealthCheckBuilder WithPartialSuccessStatus(CheckStatus partiallyHealthyStatus)
|
||||
{
|
||||
_currentGroup.PartiallyHealthyStatus = partiallyHealthyStatus;
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +0,0 @@
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Extensions.HealthChecks
|
||||
{
|
||||
public static class HealthCheckExtensions
|
||||
{
|
||||
public static ValueTask<IHealthCheckResult> CheckAsync(this IHealthCheck healthCheck)
|
||||
{
|
||||
Guard.ArgumentNotNull(nameof(healthCheck), healthCheck);
|
||||
|
||||
return healthCheck.CheckAsync(CancellationToken.None);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.Extensions.HealthChecks
|
||||
{
|
||||
public class HealthCheckGroup
|
||||
{
|
||||
private CheckStatus _partialSuccessStatus;
|
||||
|
||||
public HealthCheckGroup(string groupName, CheckStatus partialSuccessStatus)
|
||||
{
|
||||
Guard.ArgumentNotNull(nameof(groupName), groupName);
|
||||
|
||||
GroupName = groupName;
|
||||
PartiallyHealthyStatus = partialSuccessStatus;
|
||||
}
|
||||
|
||||
public IReadOnlyList<CachedHealthCheck> Checks => ChecksInternal.AsReadOnly();
|
||||
|
||||
internal List<CachedHealthCheck> ChecksInternal { get; } = new List<CachedHealthCheck>();
|
||||
|
||||
public string GroupName { get; }
|
||||
|
||||
public CheckStatus PartiallyHealthyStatus
|
||||
{
|
||||
get => _partialSuccessStatus;
|
||||
internal set
|
||||
{
|
||||
Guard.ArgumentValid(value != CheckStatus.Unknown, nameof(value), "Check status 'Unknown' is not valid for partial success.");
|
||||
|
||||
_partialSuccessStatus = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
{
|
||||
|
@ -3,52 +3,117 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.Extensions.HealthChecks
|
||||
{
|
||||
public class HealthCheckService : IHealthCheckService
|
||||
{
|
||||
public IReadOnlyDictionary<string, IHealthCheck> _checks;
|
||||
private readonly HealthCheckBuilder _builder;
|
||||
private readonly IReadOnlyList<HealthCheckGroup> _groups;
|
||||
private readonly HealthCheckGroup _root;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||
|
||||
private ILogger<HealthCheckService> _logger;
|
||||
|
||||
public HealthCheckService(HealthCheckBuilder builder, ILogger<HealthCheckService> logger)
|
||||
public HealthCheckService(HealthCheckBuilder builder, IServiceProvider serviceProvider, IServiceScopeFactory serviceScopeFactory)
|
||||
{
|
||||
_checks = builder.Checks;
|
||||
_logger = logger;
|
||||
_builder = builder;
|
||||
_groups = GetGroups().Where(group => group.GroupName != string.Empty).ToList();
|
||||
_root = GetGroup(string.Empty);
|
||||
_serviceProvider = serviceProvider;
|
||||
_serviceScopeFactory = serviceScopeFactory;
|
||||
}
|
||||
|
||||
public async Task<CompositeHealthCheckResult> CheckHealthAsync(CheckStatus partiallyHealthyStatus, CancellationToken cancellationToken)
|
||||
public async Task<CompositeHealthCheckResult> CheckHealthAsync(CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
var logMessage = new StringBuilder();
|
||||
var result = new CompositeHealthCheckResult(partiallyHealthyStatus);
|
||||
|
||||
foreach (var check in _checks)
|
||||
using (var scope = GetServiceScope())
|
||||
{
|
||||
try
|
||||
var 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<CachedHealthCheck> GetAllChecks()
|
||||
=> _builder.ChecksByName.Values.ToList().AsReadOnly();
|
||||
|
||||
public CachedHealthCheck GetCheck(string checkName)
|
||||
=> _builder.ChecksByName[checkName];
|
||||
|
||||
public HealthCheckGroup GetGroup(string groupName)
|
||||
=> _builder.Groups[groupName];
|
||||
|
||||
public IReadOnlyList<HealthCheckGroup> GetGroups()
|
||||
=> _builder.Groups.Values.ToList().AsReadOnly();
|
||||
|
||||
private IServiceScope GetServiceScope()
|
||||
=> _serviceScopeFactory == null ? new UnscopedServiceProvider(_serviceProvider) : _serviceScopeFactory.CreateScope();
|
||||
|
||||
public async ValueTask<IHealthCheckResult> RunCheckAsync(CachedHealthCheck healthCheck, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
using (var scope = GetServiceScope())
|
||||
{
|
||||
return await RunCheckAsync(scope.ServiceProvider, healthCheck, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses the provided service provider and executes the provided check.
|
||||
/// </summary>
|
||||
public ValueTask<IHealthCheckResult> RunCheckAsync(IServiceProvider serviceProvider, CachedHealthCheck healthCheck, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
Guard.ArgumentNotNull(nameof(serviceProvider), serviceProvider);
|
||||
Guard.ArgumentNotNull(nameof(healthCheck), healthCheck);
|
||||
|
||||
return healthCheck.RunAsync(serviceProvider, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new resolution scope from the default service provider and executes the checks in the given group.
|
||||
/// </summary>
|
||||
public async Task<CompositeHealthCheckResult> RunGroupAsync(HealthCheckGroup group, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
using (var scope = GetServiceScope())
|
||||
return await RunGroupAsync(scope.ServiceProvider, group, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses the provided service provider and executes the checks in the given group.
|
||||
/// </summary>
|
||||
public async Task<CompositeHealthCheckResult> RunGroupAsync(IServiceProvider serviceProvider, HealthCheckGroup group, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
var result = new CompositeHealthCheckResult(group.PartiallyHealthyStatus);
|
||||
var checkTasks = group.Checks.Select(check => new { Check = check, Task = check.RunAsync(serviceProvider, cancellationToken).AsTask() }).ToList();
|
||||
await Task.WhenAll(checkTasks.Select(checkTask => checkTask.Task));
|
||||
|
||||
foreach (var checkTask in checkTasks)
|
||||
{
|
||||
result.Add(checkTask.Check.Name, checkTask.Task.Result);
|
||||
}
|
||||
|
||||
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() { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,20 +2,28 @@
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.HealthChecks;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
public static class HealthCheckServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddHealthChecks(this IServiceCollection services, Action<HealthCheckBuilder> checkupAction)
|
||||
private static readonly Type HealthCheckServiceInterface = typeof(IHealthCheckService);
|
||||
|
||||
public static IServiceCollection AddHealthChecks(this IServiceCollection services, Action<HealthCheckBuilder> checks)
|
||||
{
|
||||
var checkupBuilder = new HealthCheckBuilder();
|
||||
Guard.OperationValid(!services.Any(descriptor => descriptor.ServiceType == HealthCheckServiceInterface), "AddHealthChecks may only be called once.");
|
||||
|
||||
checkupAction.Invoke(checkupBuilder);
|
||||
var builder = new HealthCheckBuilder();
|
||||
|
||||
services.AddSingleton(checkupBuilder);
|
||||
services.AddSingleton<IHealthCheckService, HealthCheckService>();
|
||||
services.AddSingleton<IHealthCheckService, HealthCheckService>(serviceProvider =>
|
||||
{
|
||||
var serviceScopeFactory = serviceProvider.GetService<IServiceScopeFactory>();
|
||||
return new HealthCheckService(builder, serviceProvider, serviceScopeFactory);
|
||||
});
|
||||
|
||||
checks(builder);
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
@ -1,38 +0,0 @@
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Extensions.HealthChecks
|
||||
{
|
||||
public static class HealthCheckServiceExtensions
|
||||
{
|
||||
public static Task<CompositeHealthCheckResult> CheckHealthAsync(this IHealthCheckService service)
|
||||
{
|
||||
Guard.ArgumentNotNull(nameof(service), service);
|
||||
|
||||
return service.CheckHealthAsync(CheckStatus.Unhealthy, CancellationToken.None);
|
||||
}
|
||||
|
||||
public static Task<CompositeHealthCheckResult> CheckHealthAsync(this IHealthCheckService service, CheckStatus partiallyHealthyStatus)
|
||||
{
|
||||
Guard.ArgumentNotNull(nameof(service), service);
|
||||
|
||||
return service.CheckHealthAsync(partiallyHealthyStatus, CancellationToken.None);
|
||||
}
|
||||
|
||||
public static Task<CompositeHealthCheckResult> CheckHealthAsync(this IHealthCheckService service, CancellationToken cancellationToken)
|
||||
{
|
||||
Guard.ArgumentNotNull(nameof(service), service);
|
||||
|
||||
return service.CheckHealthAsync(CheckStatus.Unhealthy, cancellationToken);
|
||||
}
|
||||
public static Task<CompositeHealthCheckResult> CheckHealthAsync(this IHealthCheckService service, CheckStatus partiallyHealthyStatus, CancellationToken cancellationToken)
|
||||
{
|
||||
Guard.ArgumentNotNull(nameof(service), service);
|
||||
|
||||
return service.CheckHealthAsync(partiallyHealthyStatus, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@ -9,8 +8,6 @@ namespace Microsoft.Extensions.HealthChecks
|
||||
{
|
||||
public interface IHealthCheck
|
||||
{
|
||||
TimeSpan CacheDuration { get; }
|
||||
|
||||
ValueTask<IHealthCheckResult> CheckAsync(CancellationToken cancellationToken);
|
||||
ValueTask<IHealthCheckResult> CheckAsync(CancellationToken cancellationToken = default(CancellationToken));
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@ -8,6 +10,49 @@ namespace Microsoft.Extensions.HealthChecks
|
||||
{
|
||||
public interface IHealthCheckService
|
||||
{
|
||||
Task<CompositeHealthCheckResult> CheckHealthAsync(CheckStatus partiallyHealthyStatus, CancellationToken cancellationToken);
|
||||
/// <summary>
|
||||
/// Runs all registered health checks.
|
||||
/// </summary>
|
||||
Task<CompositeHealthCheckResult> CheckHealthAsync(CancellationToken cancellationToken = default(CancellationToken));
|
||||
|
||||
/// <summary>
|
||||
/// Gets all registered health checks as a flat list.
|
||||
/// </summary>
|
||||
IReadOnlyList<CachedHealthCheck> GetAllChecks();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a health check by name.
|
||||
/// </summary>
|
||||
CachedHealthCheck GetCheck(string checkName);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all health checks in a group.
|
||||
/// </summary>
|
||||
HealthCheckGroup GetGroup(string groupName);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all the health check groups.
|
||||
/// </summary>
|
||||
IReadOnlyList<HealthCheckGroup> GetGroups();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new resolution scope from the default service provider and executes the provided check.
|
||||
/// </summary>
|
||||
ValueTask<IHealthCheckResult> RunCheckAsync(CachedHealthCheck healthCheck, CancellationToken cancellationToken = default(CancellationToken));
|
||||
|
||||
/// <summary>
|
||||
/// Uses the provided service provider and executes the provided check.
|
||||
/// </summary>
|
||||
ValueTask<IHealthCheckResult> RunCheckAsync(IServiceProvider serviceProvider, CachedHealthCheck healthCheck, CancellationToken cancellationToken = default(CancellationToken));
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new resolution scope from the default service provider and executes the checks in the given group.
|
||||
/// </summary>
|
||||
Task<CompositeHealthCheckResult> RunGroupAsync(HealthCheckGroup group, CancellationToken cancellationToken = default(CancellationToken));
|
||||
|
||||
/// <summary>
|
||||
/// Uses the provided service provider and executes the checks in the given group.
|
||||
/// </summary>
|
||||
Task<CompositeHealthCheckResult> RunGroupAsync(IServiceProvider serviceProvider, HealthCheckGroup group, CancellationToken cancellationToken = default(CancellationToken));
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading.Tasks;
|
||||
@ -13,55 +12,33 @@ namespace Microsoft.Extensions.HealthChecks.Internal
|
||||
public class UrlChecker
|
||||
{
|
||||
private readonly Func<HttpResponseMessage, ValueTask<IHealthCheckResult>> _checkFunc;
|
||||
private readonly string[] _urls;
|
||||
private readonly string _url;
|
||||
|
||||
// REVIEW: Cache timeout here?
|
||||
public UrlChecker(Func<HttpResponseMessage, ValueTask<IHealthCheckResult>> checkFunc, params string[] urls)
|
||||
public UrlChecker(Func<HttpResponseMessage, ValueTask<IHealthCheckResult>> checkFunc, string url)
|
||||
{
|
||||
Guard.ArgumentNotNull(nameof(checkFunc), checkFunc);
|
||||
Guard.ArgumentNotNullOrEmpty(nameof(urls), urls);
|
||||
Guard.ArgumentNotNullOrEmpty(nameof(url), url);
|
||||
|
||||
_checkFunc = checkFunc;
|
||||
_urls = urls;
|
||||
_url = url;
|
||||
}
|
||||
|
||||
public CheckStatus PartiallyHealthyStatus { get; set; } = CheckStatus.Warning;
|
||||
|
||||
public Task<IHealthCheckResult> CheckAsync()
|
||||
=> _urls.Length == 1 ? CheckSingleAsync() : CheckMultiAsync();
|
||||
|
||||
public async Task<IHealthCheckResult> CheckSingleAsync()
|
||||
public async Task<IHealthCheckResult> CheckAsync()
|
||||
{
|
||||
var httpClient = CreateHttpClient();
|
||||
var result = default(IHealthCheckResult);
|
||||
await CheckUrlAsync(httpClient, _urls[0], (_, checkResult) => result = checkResult).ConfigureAwait(false);
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<IHealthCheckResult> 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<string, IHealthCheckResult> 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<string, object> { { "url", _url } };
|
||||
return HealthCheckResult.Unhealthy($"Exception during check: {ex.GetType().FullName}", data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,8 +51,7 @@ namespace Microsoft.Extensions.HealthChecks.Internal
|
||||
|
||||
public static async ValueTask<IHealthCheckResult> DefaultUrlCheck(HttpResponseMessage response)
|
||||
{
|
||||
// REVIEW: Should this be an explicit 200 check, or just an "is success" check?
|
||||
var status = response.StatusCode == HttpStatusCode.OK ? CheckStatus.Healthy : CheckStatus.Unhealthy;
|
||||
var status = response.IsSuccessStatusCode ? CheckStatus.Healthy : CheckStatus.Unhealthy;
|
||||
var data = new Dictionary<string, object>
|
||||
{
|
||||
{ "url", response.RequestMessage.RequestUri.ToString() },
|
||||
@ -83,7 +59,7 @@ namespace Microsoft.Extensions.HealthChecks.Internal
|
||||
{ "reason", response.ReasonPhrase },
|
||||
{ "body", await response.Content?.ReadAsStringAsync() }
|
||||
};
|
||||
return HealthCheckResult.FromStatus(status, $"UrlCheck({response.RequestMessage.RequestUri}): status code {response.StatusCode} ({(int)response.StatusCode})", data);
|
||||
return HealthCheckResult.FromStatus(status, $"status code {response.StatusCode} ({(int)response.StatusCode})", data);
|
||||
}
|
||||
|
||||
protected virtual HttpClient GetHttpClient()
|
||||
|
@ -9,37 +9,49 @@ static class Guard
|
||||
public static void ArgumentNotNull(string argumentName, object value)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(argumentName);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ArgumentNotNullOrEmpty<T>(string argumentName, string value)
|
||||
public static void ArgumentNotNullOrEmpty(string argumentName, string value)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(argumentName);
|
||||
}
|
||||
if (string.IsNullOrEmpty(value))
|
||||
throw new ArgumentException("Value cannot be an empty string", argumentName);
|
||||
{
|
||||
throw new ArgumentException("Value cannot be an empty string.", argumentName);
|
||||
}
|
||||
}
|
||||
|
||||
// Use IReadOnlyCollection<T> instead of IEnumerable<T> to discourage double enumeration
|
||||
public static void ArgumentNotNullOrEmpty<T>(string argumentName, IReadOnlyCollection<T> items)
|
||||
{
|
||||
if (items == null)
|
||||
{
|
||||
throw new ArgumentNullException(argumentName);
|
||||
}
|
||||
if (items.Count == 0)
|
||||
throw new ArgumentException("Collection must contain at least one item", argumentName);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +43,6 @@
|
||||
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBusRabbitMQ\EventBusRabbitMQ.csproj" />
|
||||
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBus\EventBus.csproj" />
|
||||
<ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.AspNetCore.HealthChecks\Microsoft.AspNetCore.HealthChecks.csproj" />
|
||||
<ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.Data\Microsoft.Extensions.HealthChecks.Data.csproj" />
|
||||
<ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks\Microsoft.Extensions.HealthChecks.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ using Basket.API.IntegrationEvents.EventHandling;
|
||||
using Basket.API.IntegrationEvents.Events;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus;
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ;
|
||||
using Microsoft.eShopOnContainers.Services.Basket.API.Auth.Server;
|
||||
@ -19,6 +20,7 @@ using StackExchange.Redis;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.Services.Basket.API
|
||||
{
|
||||
@ -68,20 +70,18 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
|
||||
});
|
||||
|
||||
|
||||
services.AddSingleton<IRabbitMQPersisterConnection>(sp =>
|
||||
services.AddSingleton<IRabbitMQPersistentConnection>(sp =>
|
||||
{
|
||||
var settings = sp.GetRequiredService<IOptions<BasketSettings>>().Value;
|
||||
var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersisterConnection>>();
|
||||
var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersistentConnection>>();
|
||||
var factory = new ConnectionFactory()
|
||||
{
|
||||
HostName = settings.EventBusConnection
|
||||
};
|
||||
|
||||
return new DefaultRabbitMQPersisterConnection(factory, logger);
|
||||
return new DefaultRabbitMQPersistentConnection(factory, logger);
|
||||
});
|
||||
|
||||
services.AddSingleton<IEventBus, EventBusRabbitMQ>();
|
||||
|
||||
services.AddSwaggerGen();
|
||||
|
||||
services.ConfigureSwaggerGen(options =>
|
||||
@ -108,9 +108,16 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
|
||||
});
|
||||
|
||||
services.AddTransient<IBasketRepository, RedisBasketRepository>();
|
||||
services.AddTransient<IIntegrationEventHandler<ProductPriceChangedIntegrationEvent>, ProductPriceChangedIntegrationEventHandler>();
|
||||
services.AddTransient<IIntegrationEventHandler<OrderStartedIntegrationEvent>, OrderStartedIntegrationEventHandler>();
|
||||
RegisterServiceBus(services);
|
||||
}
|
||||
|
||||
private void RegisterServiceBus(IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<IEventBus, EventBusRabbitMQ>();
|
||||
services.AddSingleton<IEventBusSubscriptionsManager, InMemoryEventBusSubscriptionsManager>();
|
||||
|
||||
services.AddTransient<ProductPriceChangedIntegrationEventHandler>();
|
||||
services.AddTransient<OrderStartedIntegrationEventHandler>();
|
||||
|
||||
}
|
||||
|
||||
@ -155,11 +162,13 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
|
||||
var orderStartedHandler = app.ApplicationServices
|
||||
.GetService<IIntegrationEventHandler<OrderStartedIntegrationEvent>>();
|
||||
|
||||
var eventBus = app.ApplicationServices
|
||||
.GetRequiredService<IEventBus>();
|
||||
var eventBus = app.ApplicationServices.GetRequiredService<IEventBus>();
|
||||
|
||||
eventBus.Subscribe<ProductPriceChangedIntegrationEvent>(catalogPriceHandler);
|
||||
eventBus.Subscribe<OrderStartedIntegrationEvent>(orderStartedHandler);
|
||||
eventBus.Subscribe<ProductPriceChangedIntegrationEvent, ProductPriceChangedIntegrationEventHandler>
|
||||
(() => app.ApplicationServices.GetRequiredService<ProductPriceChangedIntegrationEventHandler>());
|
||||
|
||||
eventBus.Subscribe<OrderStartedIntegrationEvent, OrderStartedIntegrationEventHandler>
|
||||
(() => app.ApplicationServices.GetRequiredService<OrderStartedIntegrationEventHandler>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -59,7 +59,7 @@
|
||||
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBusRabbitMQ\EventBusRabbitMQ.csproj" />
|
||||
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBus\EventBus.csproj" />
|
||||
<ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.AspNetCore.HealthChecks\Microsoft.AspNetCore.HealthChecks.csproj" />
|
||||
<ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.Data\Microsoft.Extensions.HealthChecks.Data.csproj" />
|
||||
<ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.SqlServer\Microsoft.Extensions.HealthChecks.SqlServer.csproj" />
|
||||
<ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks\Microsoft.Extensions.HealthChecks.csproj" />
|
||||
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\IntegrationEventLogEF\IntegrationEventLogEF.csproj" />
|
||||
</ItemGroup>
|
||||
|
@ -6,6 +6,7 @@
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus;
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ;
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF;
|
||||
@ -103,18 +104,19 @@
|
||||
|
||||
services.AddTransient<ICatalogIntegrationEventService, CatalogIntegrationEventService>();
|
||||
|
||||
services.AddSingleton<IRabbitMQPersisterConnection>(sp =>
|
||||
services.AddSingleton<IRabbitMQPersistentConnection>(sp =>
|
||||
{
|
||||
var settings = sp.GetRequiredService<IOptions<CatalogSettings>>().Value;
|
||||
var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersisterConnection>>();
|
||||
var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersistentConnection>>();
|
||||
var factory = new ConnectionFactory()
|
||||
{
|
||||
HostName = settings.EventBusConnection
|
||||
};
|
||||
|
||||
return new DefaultRabbitMQPersisterConnection(factory, logger);
|
||||
return new DefaultRabbitMQPersistentConnection(factory, logger);
|
||||
});
|
||||
|
||||
services.AddSingleton<IEventBusSubscriptionsManager, InMemoryEventBusSubscriptionsManager>();
|
||||
services.AddSingleton<IEventBus, EventBusRabbitMQ>();
|
||||
}
|
||||
|
||||
|
@ -63,7 +63,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.AspNetCore.HealthChecks\Microsoft.AspNetCore.HealthChecks.csproj" />
|
||||
<ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.Data\Microsoft.Extensions.HealthChecks.Data.csproj" />
|
||||
<ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.SqlServer\Microsoft.Extensions.HealthChecks.SqlServer.csproj" />
|
||||
<ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks\Microsoft.Extensions.HealthChecks.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -61,20 +61,16 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands
|
||||
[DataMember]
|
||||
public IEnumerable<OrderItemDTO> OrderItems => _orderItems;
|
||||
|
||||
public void AddOrderItem(OrderItemDTO item)
|
||||
{
|
||||
_orderItems.Add(item);
|
||||
}
|
||||
|
||||
public CreateOrderCommand()
|
||||
{
|
||||
_orderItems = new List<OrderItemDTO>();
|
||||
}
|
||||
|
||||
public CreateOrderCommand(string city, string street, string state, string country, string zipcode,
|
||||
public CreateOrderCommand(List<OrderItemDTO> orderItems, string city, string street, string state, string country, string zipcode,
|
||||
string cardNumber, string cardHolderName, DateTime cardExpiration,
|
||||
string cardSecurityNumber, int cardTypeId, int paymentId, int buyerId) : this()
|
||||
{
|
||||
_orderItems = orderItems;
|
||||
City = city;
|
||||
Street = street;
|
||||
State = state;
|
||||
|
@ -27,7 +27,7 @@
|
||||
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBus\EventBus.csproj" />
|
||||
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\IntegrationEventLogEF\IntegrationEventLogEF.csproj" />
|
||||
<ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.AspNetCore.HealthChecks\Microsoft.AspNetCore.HealthChecks.csproj" />
|
||||
<ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.Data\Microsoft.Extensions.HealthChecks.Data.csproj" />
|
||||
<ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.SqlServer\Microsoft.Extensions.HealthChecks.SqlServer.csproj" />
|
||||
<ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks\Microsoft.Extensions.HealthChecks.csproj" />
|
||||
<ProjectReference Include="..\Ordering.Domain\Ordering.Domain.csproj" />
|
||||
<ProjectReference Include="..\Ordering.Infrastructure\Ordering.Infrastructure.csproj" />
|
||||
|
@ -13,6 +13,7 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus;
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ;
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF;
|
||||
@ -107,18 +108,19 @@
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
services.AddTransient<IOrderingIntegrationEventService, OrderingIntegrationEventService>();
|
||||
|
||||
services.AddSingleton<IRabbitMQPersisterConnection>(sp =>
|
||||
services.AddSingleton<IRabbitMQPersistentConnection>(sp =>
|
||||
{
|
||||
var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersisterConnection>>();
|
||||
var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersistentConnection>>();
|
||||
|
||||
var factory = new ConnectionFactory()
|
||||
{
|
||||
HostName = Configuration["EventBusConnection"]
|
||||
};
|
||||
|
||||
return new DefaultRabbitMQPersisterConnection(factory, logger);
|
||||
return new DefaultRabbitMQPersistentConnection(factory, logger);
|
||||
});
|
||||
|
||||
services.AddSingleton<IEventBusSubscriptionsManager, InMemoryEventBusSubscriptionsManager>();
|
||||
services.AddSingleton<IEventBus, EventBusRabbitMQ>();
|
||||
|
||||
services.AddOptions();
|
||||
|
@ -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;
|
||||
|
@ -59,7 +59,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\BuildingBlocks\HealthChecks\src\Microsoft.AspNetCore.HealthChecks\Microsoft.AspNetCore.HealthChecks.csproj" />
|
||||
<ProjectReference Include="..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.Data\Microsoft.Extensions.HealthChecks.Data.csproj" />
|
||||
<ProjectReference Include="..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks\Microsoft.Extensions.HealthChecks.csproj" />
|
||||
<ProjectReference Include="..\..\BuildingBlocks\Resilience\Resilience.Http\Resilience.Http.csproj" />
|
||||
</ItemGroup>
|
||||
|
2
src/Web/WebMVC/wwwroot/css/site.min.css
vendored
58
src/Web/WebSPA/.angular-cli.json
Normal file
@ -0,0 +1,58 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"project": {
|
||||
"name": "WebSPA"
|
||||
},
|
||||
"apps": [
|
||||
{
|
||||
"root": "Client",
|
||||
"outDir": "wwwroot",
|
||||
"assets": [
|
||||
"assets",
|
||||
"favicon.ico"
|
||||
],
|
||||
"index": "index.html",
|
||||
"main": "main.ts",
|
||||
"polyfills": "polyfills.ts",
|
||||
"test": "test.ts",
|
||||
"tsconfig": "tsconfig.app.json",
|
||||
"testTsconfig": "tsconfig.spec.json",
|
||||
"prefix": "app",
|
||||
"styles": [
|
||||
"globals.scss",
|
||||
"../node_modules/bootstrap/scss/bootstrap.scss"
|
||||
],
|
||||
"scripts": [],
|
||||
"environmentSource": "environments/environment.ts",
|
||||
"environments": {
|
||||
"dev": "environments/environment.ts",
|
||||
"prod": "environments/environment.prod.ts"
|
||||
}
|
||||
}
|
||||
],
|
||||
"e2e": {
|
||||
"protractor": {
|
||||
"config": "./protractor.conf.js"
|
||||
}
|
||||
},
|
||||
"lint": [
|
||||
{
|
||||
"project": "Client/tsconfig.app.json"
|
||||
},
|
||||
{
|
||||
"project": "Client/tsconfig.spec.json"
|
||||
},
|
||||
{
|
||||
"project": "e2e/tsconfig.e2e.json"
|
||||
}
|
||||
],
|
||||
"test": {
|
||||
"karma": {
|
||||
"config": "./karma.conf.js"
|
||||
}
|
||||
},
|
||||
"defaults": {
|
||||
"styleExt": "scss",
|
||||
"component": {}
|
||||
}
|
||||
}
|
5
src/Web/WebSPA/.gitignore
vendored
@ -177,10 +177,7 @@ ClientBin/
|
||||
*.publishsettings
|
||||
node_modules/
|
||||
bower_components/
|
||||
**/wwwroot/tmp/
|
||||
**/wwwroot/*.bundle.map
|
||||
**/wwwroot/*.js
|
||||
/wwwroot/dist/
|
||||
wwwroot/
|
||||
|
||||
|
||||
orleans.codegen.cs
|
||||
|
0
src/Web/WebSPA/Client/assets/.gitkeep
Normal file
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 693 B After Width: | Height: | Size: 693 B |
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 429 B After Width: | Height: | Size: 429 B |
Before Width: | Height: | Size: 385 KiB After Width: | Height: | Size: 385 KiB |
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 8.6 KiB |
Before Width: | Height: | Size: 221 B After Width: | Height: | Size: 221 B |
2
src/Web/WebSPA/Client/custom-typings.d.ts
vendored
@ -1,2 +0,0 @@
|
||||
// Extra variables that live on Global that will be replaced by webpack DefinePlugin
|
||||
// declare var process: any;
|
3
src/Web/WebSPA/Client/environments/environment.prod.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export const environment = {
|
||||
production: true
|
||||
};
|
8
src/Web/WebSPA/Client/environments/environment.ts
Normal file
@ -0,0 +1,8 @@
|
||||
// The file contents for the current environment will overwrite these during build.
|
||||
// The build system defaults to the dev environment which uses `environment.ts`, but if you do
|
||||
// `ng build --env=prod` then `environment.prod.ts` will be used instead.
|
||||
// The list of which env maps to which file can be found in `.angular-cli.json`.
|
||||
|
||||
export const environment = {
|
||||
production: false
|
||||
};
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@ -1,3 +1,4 @@
|
||||
/* You can add global styles to this file, and also import other style files */
|
||||
@import './modules/variables';
|
||||
|
||||
$dist: './fonts/Montserrat-Regular';
|
||||
|
18
src/Web/WebSPA/Client/index.html
Normal file
@ -0,0 +1,18 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>eShopConContainers.WebSPA</title>
|
||||
<base href="/">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
</head>
|
||||
<body>
|
||||
<esh-app>
|
||||
<div class="preloading">
|
||||
<i class="fa fa-spinner fa-spin fa-5x" aria-hidden="true"></i>
|
||||
</div>
|
||||
</esh-app>
|
||||
</body>
|
||||
</html>
|
@ -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();
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
<section class="col-lg-7 col-md-6 col-xs-12">
|
||||
<a class="navbar-brand" routerLink="catalog">
|
||||
<img src="../images/brand.png" />
|
||||
<img src="assets/images/brand.png" />
|
||||
</a>
|
||||
</section>
|
||||
|
||||
@ -28,7 +28,7 @@
|
||||
<article class="row">
|
||||
|
||||
<section class="col-sm-6">
|
||||
<img class="esh-app-footer-brand" src="../images/brand_dark.png" />
|
||||
<img class="esh-app-footer-brand" src="assets/images/brand_dark.png" />
|
||||
</section>
|
||||
|
||||
<section class="col-sm-6">
|
||||
|
@ -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) {
|
||||
|
@ -3,7 +3,7 @@
|
||||
[routerLink]="['basket']">
|
||||
|
||||
<div class="esh-basketstatus-image">
|
||||
<img src="../../../images/cart.png" />
|
||||
<img src="assets/images/cart.png" />
|
||||
</div>
|
||||
<div class="esh-basketstatus-badge">
|
||||
{{badge}}
|
||||
|
@ -16,7 +16,7 @@
|
||||
<option *ngFor="let type of types" [value]="type.id">{{type.type}}</option>
|
||||
</select>
|
||||
</label>
|
||||
<img class="esh-catalog-send" (click)="onFilterApplied($event)" src="../../images/arrow-right.svg" />
|
||||
<img class="esh-catalog-send" (click)="onFilterApplied($event)" src="/assets/images/arrow-right.svg" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
@ -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;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { OrdersService } from '../orders.service';
|
||||
import { IOrder } from '../../shared/models/order.model';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { OrdersService } from '../orders.service';
|
||||
import { IOrderDetail } from '../../shared/models/order-detail.model';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
@ -9,7 +9,7 @@ import { ActivatedRoute } from '@angular/router';
|
||||
templateUrl: './orders-detail.component.html'
|
||||
})
|
||||
export class OrdersDetailComponent implements OnInit {
|
||||
order = {}; // new order
|
||||
public order: IOrderDetail = <IOrderDetail>{};
|
||||
|
||||
constructor(private service: OrdersService, private route: ActivatedRoute) { }
|
||||
|
||||
@ -27,5 +27,4 @@ export class OrdersDetailComponent implements OnInit {
|
||||
console.log(this.order);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
|
@ -4,6 +4,7 @@ import { Response } from '@angular/http';
|
||||
import { DataService } from '../shared/services/data.service';
|
||||
import { IOrder } from '../shared/models/order.model';
|
||||
import { IOrderItem } from '../shared/models/orderItem.model';
|
||||
import { IOrderDetail } from "../shared/models/order-detail.model";
|
||||
import { SecurityService } from '../shared/services/security.service';
|
||||
import { ConfigurationService } from '../shared/services/configuration.service';
|
||||
import { BasketWrapperService } from '../shared/services/basket.wrapper.service';
|
||||
@ -35,7 +36,7 @@ export class OrdersService {
|
||||
});
|
||||
}
|
||||
|
||||
getOrder(id: number): Observable<IOrder> {
|
||||
getOrder(id: number): Observable<IOrderDetail> {
|
||||
let url = this.ordersUrl + '/api/v1/orders/' + id;
|
||||
|
||||
return this.service.get(url).map((response: Response) => {
|
||||
|
@ -12,7 +12,7 @@
|
||||
*ngIf="authenticated">
|
||||
|
||||
<div class="esh-identity-name">{{userName}}</div>
|
||||
<img class="esh-identity-image" src="../../../../images/arrow-down.png">
|
||||
<img class="esh-identity-image" src="assets/images/arrow-down.png">
|
||||
</section>
|
||||
|
||||
<section class="esh-identity-drop"
|
||||
@ -22,14 +22,14 @@
|
||||
[routerLink]="['orders']">
|
||||
|
||||
<div class="esh-identity-name esh-identity-name--upper">My orders</div>
|
||||
<img class="esh-identity-image" src="../../../../images/my_orders.png">
|
||||
<img class="esh-identity-image" src="assets/images/my_orders.png">
|
||||
</div>
|
||||
|
||||
<div class="esh-identity-item"
|
||||
(click)="logoutClicked($event)">
|
||||
|
||||
<div class="esh-identity-name esh-identity-name--upper">Log Out</div>
|
||||
<img class="esh-identity-image" src="../../../../images/logout.png">
|
||||
<img class="esh-identity-image" src="assets/images/logout.png">
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
@ -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 = '';
|
||||
|
||||
|
@ -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[];
|
||||
}
|
@ -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 {
|
||||
|
@ -1,23 +1,68 @@
|
||||
// Added parts of es6 which are necessary for your project or your browser support requirements.
|
||||
import 'core-js/es6/symbol';
|
||||
import 'core-js/es6/object';
|
||||
import 'core-js/es6/function';
|
||||
import 'core-js/es6/parse-int';
|
||||
import 'core-js/es6/parse-float';
|
||||
import 'core-js/es6/number';
|
||||
import 'core-js/es6/math';
|
||||
import 'core-js/es6/string';
|
||||
import 'core-js/es6/date';
|
||||
import 'core-js/es6/array';
|
||||
import 'core-js/es6/regexp';
|
||||
import 'core-js/es6/map';
|
||||
import 'core-js/es6/set';
|
||||
import 'core-js/es6/weak-map';
|
||||
import 'core-js/es6/weak-set';
|
||||
import 'core-js/es6/typed';
|
||||
import 'core-js/es6/reflect';
|
||||
// see issue https://github.com/AngularClass/angular2-webpack-starter/issues/709
|
||||
// import 'core-js/es6/promise';
|
||||
/**
|
||||
* This file includes polyfills needed by Angular and is loaded before the app.
|
||||
* You can add your own extra polyfills to this file.
|
||||
*
|
||||
* This file is divided into 2 sections:
|
||||
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
|
||||
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
|
||||
* file.
|
||||
*
|
||||
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
|
||||
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
|
||||
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
|
||||
*
|
||||
* Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
|
||||
*/
|
||||
|
||||
/***************************************************************************************************
|
||||
* BROWSER POLYFILLS
|
||||
*/
|
||||
|
||||
/** IE9, IE10 and IE11 requires all of the following polyfills. **/
|
||||
// import 'core-js/es6/symbol';
|
||||
// import 'core-js/es6/object';
|
||||
// import 'core-js/es6/function';
|
||||
// import 'core-js/es6/parse-int';
|
||||
// import 'core-js/es6/parse-float';
|
||||
// import 'core-js/es6/number';
|
||||
// import 'core-js/es6/math';
|
||||
// import 'core-js/es6/string';
|
||||
// import 'core-js/es6/date';
|
||||
// import 'core-js/es6/array';
|
||||
// import 'core-js/es6/regexp';
|
||||
// import 'core-js/es6/map';
|
||||
// import 'core-js/es6/set';
|
||||
|
||||
/** IE10 and IE11 requires the following for NgClass support on SVG elements */
|
||||
// import 'classlist.js'; // Run `npm install --save classlist.js`.
|
||||
|
||||
/** IE10 and IE11 requires the following to support `@angular/animation`. */
|
||||
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
|
||||
|
||||
|
||||
/** Evergreen browsers require these. **/
|
||||
import 'core-js/es6/reflect';
|
||||
import 'core-js/es7/reflect';
|
||||
import 'zone.js/dist/zone';
|
||||
|
||||
|
||||
/** ALL Firefox browsers require the following to support `@angular/animation`. **/
|
||||
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
|
||||
|
||||
|
||||
|
||||
/***************************************************************************************************
|
||||
* Zone JS is required by Angular itself.
|
||||
*/
|
||||
import 'zone.js/dist/zone'; // Included with Angular CLI.
|
||||
|
||||
|
||||
|
||||
/***************************************************************************************************
|
||||
* APPLICATION IMPORTS
|
||||
*/
|
||||
|
||||
/**
|
||||
* Date, currency, decimal and percent pipes.
|
||||
* Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10
|
||||
*/
|
||||
// import 'intl'; // Run `npm install --save intl`.
|
||||
|
32
src/Web/WebSPA/Client/test.ts
Normal file
@ -0,0 +1,32 @@
|
||||
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
|
||||
|
||||
import 'zone.js/dist/long-stack-trace-zone';
|
||||
import 'zone.js/dist/proxy.js';
|
||||
import 'zone.js/dist/sync-test';
|
||||
import 'zone.js/dist/jasmine-patch';
|
||||
import 'zone.js/dist/async-test';
|
||||
import 'zone.js/dist/fake-async-test';
|
||||
import { getTestBed } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
// Unfortunately there's no typing for the `__karma__` variable. Just declare it as any.
|
||||
declare var __karma__: any;
|
||||
declare var require: any;
|
||||
|
||||
// Prevent Karma from running prematurely.
|
||||
__karma__.loaded = function () {};
|
||||
|
||||
// First, initialize the Angular testing environment.
|
||||
getTestBed().initTestEnvironment(
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting()
|
||||
);
|
||||
// Then we find all the tests.
|
||||
const context = require.context('./', true, /\.spec\.ts$/);
|
||||
// And load the modules.
|
||||
context.keys().map(context);
|
||||
// Finally, start Karma to run the tests.
|
||||
__karma__.start();
|
13
src/Web/WebSPA/Client/tsconfig.app.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../out-tsc/app",
|
||||
"module": "es2015",
|
||||
"baseUrl": "",
|
||||
"types": []
|
||||
},
|
||||
"exclude": [
|
||||
"test.ts",
|
||||
"**/*.spec.ts"
|
||||
]
|
||||
}
|
20
src/Web/WebSPA/Client/tsconfig.spec.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../out-tsc/spec",
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"baseUrl": "",
|
||||
"types": [
|
||||
"jasmine",
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"test.ts"
|
||||
],
|
||||
"include": [
|
||||
"**/*.spec.ts",
|
||||
"**/*.d.ts"
|
||||
]
|
||||
}
|
5
src/Web/WebSPA/Client/typings.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
/* SystemJS module definition */
|
||||
declare var module: NodeModule;
|
||||
interface NodeModule {
|
||||
id: string;
|
||||
}
|
@ -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';
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +0,0 @@
|
||||
<esh-app class="esh-app" asp-prerender-webpack-config="config/webpack.config.js">
|
||||
<div class="preloading">
|
||||
<i class="fa fa-spinner fa-spin fa-5x" aria-hidden="true"></i>
|
||||
</div>
|
||||
</esh-app>
|
||||
|
||||
<script>
|
||||
window.user = '@ViewBag.user';
|
||||
</script>
|
||||
|
||||
<script src="~/dist/vendor.js" asp-append-version="true"></script>
|
||||
<script src="~/dist/@ViewBag.HashedMain" asp-append-version="true"></script>
|
@ -1,16 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>eShopConContainers.WebSPA</title>
|
||||
<base href="/" />
|
||||
<link rel="stylesheet" href="~/dist/vendor.css" asp-append-version="true" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@RenderBody()
|
||||
</body>
|
||||
|
||||
</html>
|
@ -1,3 +0,0 @@
|
||||
|
||||
@addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers"
|
||||
@addTagHelper "*, Microsoft.AspNetCore.SpaServices"
|
@ -1,3 +0,0 @@
|
||||
@{
|
||||
Layout = "_Layout";
|
||||
}
|
@ -22,21 +22,9 @@
|
||||
<Content Update="appsettings.json;">
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
<Content Update="Client\**\*;">
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
<Content Update="Views\**\*;">
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
<Content Update="tsconfig.json;">
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
<Content Update="web.config;">
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
<Content Update="config\**\*;">
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
<Content Update="wwwroot\**\*;">
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
@ -51,7 +39,6 @@
|
||||
<PackageReference Include="Microsoft.AspNetCore.Antiforgery" Version="1.1.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="1.1.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
|
||||
<PackageReference Include="Webpack" Version="3.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.AngularServices" Version="1.0.0-beta-000014" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="1.1.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="1.1.0" />
|
||||
@ -87,7 +74,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\BuildingBlocks\HealthChecks\src\Microsoft.AspNetCore.HealthChecks\Microsoft.AspNetCore.HealthChecks.csproj" />
|
||||
<ProjectReference Include="..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.Data\Microsoft.Extensions.HealthChecks.Data.csproj" />
|
||||
<ProjectReference Include="..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks\Microsoft.Extensions.HealthChecks.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -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;
|
@ -1,3 +0,0 @@
|
||||
module.exports = {
|
||||
//devtool: 'cheap-module-source-map'
|
||||
};
|
@ -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);
|
@ -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
|
||||
})
|
||||
]
|
||||
};
|
@ -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
|
||||
})
|
||||
])
|
||||
};
|