Merge from eShopOnContainers Dev

This commit is contained in:
Ramón Tomás 2017-06-26 13:51:49 +02:00
commit a3e63c11aa
437 changed files with 13791 additions and 1448 deletions

1
.gitignore vendored
View File

@ -258,3 +258,4 @@ pub/
#Ignore marker-file used to know which docker files we have.
.eshopdocker_*
/src/Web/WebMVC/wwwroot/lib
/src/Web/WebMVC/wwwroot/css/site.min.css

View File

@ -43,7 +43,7 @@ You can download them and start reviewing these Guides/eBooks here:
| Architecting & Developing | Containers Lifecycle & CI/CD | App patterns with Xamarin.Forms |
| ------------ | ------------| ------------|
| <a href='https://aka.ms/microservicesebook'><img src="img/ebook_arch_dev_microservices_containers_cover.png"> </a> | <a href='https://aka.ms/dockerlifecycleebook'> <img src="img/ebook_containers_lifecycle.png"> </a> | <a href='https://aka.ms/xamarinpatternsebook'> <img src="img/xamarin-enterprise-patterns-ebook-cover-small.png"> </a> |
| <sup> <a href='https://aka.ms/microservicesebook'>**Download** (First Edition)</a> </sup> | <sup> <a href='https://aka.ms/dockerlifecycleebook'>**Download** (First Edition from late 2016) </a> </sup> | <sup> <a href='https://aka.ms/xamarinpatternsebook'>**Download** (Preview Edition) </a> </sup> |
| <sup> <a href='https://aka.ms/microservicesebook'>**Download** (First Edition)</a> </sup> | <sup> <a href='https://aka.ms/dockerlifecycleebook'>**Download** (First Edition from late 2016) </a> </sup> | <sup> <a href='https://aka.ms/xamarinpatternsebook'>**Download** (First Edition) </a> </sup> |
Send feedback to [dotnet-architecture-ebooks-feedback@service.microsoft.com](dotnet-architecture-ebooks-feedback@service.microsoft.com)
<p>
@ -76,7 +76,7 @@ Finally, those microservices are consumed by multiple client web and mobile apps
## Setting up your development environment for eShopOnContainers
### Visual Studio 2017 and Windows based
This is the more straightforward way to get started:
https://github.com/dotnet/eShopOnContainers/wiki/02.-Setting-eShopOnContainer-solution-up-in-a-Visual-Studio-2017-environment
https://github.com/dotnet-architecture/eShopOnContainers/wiki/02.-Setting-eShopOnContainers-in-a-Visual-Studio-2017-environment
### CLI and Windows based
For those who prefer the CLI on Windows, using dotnet CLI, docker CLI and VS Code for Windows:

View File

@ -1,12 +1,23 @@
#!/bin/bash
declare -x path=$1
if [ -z "$path" ]; then
$path="$(pwd)/../src";
fi
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'
"$path/Web/WebSPA"
"$path/Services/Catalog/Catalog.API"
"$path/Services/Basket/Basket.API"
"$path/Services/Ordering/Ordering.API"
"$path/Services/Identity/Identity.API"
"$path/Services/Location/Locations.API"
"$path/Services/Marketing/Marketing.API"
"$path/Services/Payment/Payment.API"
"$path/Services/GracePeriod/GracePeriodManager"
"$path/Web/WebMVC"
"$path/Web/WebStatus"
)
# Build SPA app
@ -15,9 +26,9 @@ declare -a projectList=(
for project in "${projectList[@]}"
do
echo -e "\e[33mWorking on $(pwd)/$project"
echo -e "\e[33mWorking on $path/$project"
echo -e "\e[33m\tRemoving old publish output"
pushd $(pwd)/$project
pushd $path/$project
rm -rf obj/Docker/publish
echo -e "\e[33m\tRestoring project"
dotnet restore
@ -26,14 +37,15 @@ do
popd
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
## 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
# 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.

View File

@ -21,6 +21,6 @@ try {
Write-Host "Rule found"
}
catch [Exception] {
New-NetFirewallRule -DisplayName eShopOnContainers-Inbound -Confirm -Description "eShopOnContainers Inbound Rule for port range 5100-5105" -LocalAddress Any -LocalPort 5100-5105 -Protocol tcp -RemoteAddress Any -RemotePort Any -Direction Inbound
New-NetFirewallRule -DisplayName eShopOnContainers-Outbound -Confirm -Description "eShopOnContainers Outbound Rule for port range 5100-5105" -LocalAddress Any -LocalPort 5100-5105 -Protocol tcp -RemoteAddress Any -RemotePort Any -Direction Outbound
New-NetFirewallRule -DisplayName eShopOnContainers-Inbound -Confirm -Description "eShopOnContainers Inbound Rule for port range 5100-5105" -LocalAddress Any -LocalPort 5100-5110 -Protocol tcp -RemoteAddress Any -RemotePort Any -Direction Inbound
New-NetFirewallRule -DisplayName eShopOnContainers-Outbound -Confirm -Description "eShopOnContainers Outbound Rule for port range 5100-5105" -LocalAddress Any -LocalPort 5100-5110 -Protocol tcp -RemoteAddress Any -RemotePort Any -Direction Outbound
}

View File

@ -0,0 +1,11 @@
Param([string] $imageTag)
$scriptPath = Split-Path $script:MyInvocation.MyCommand.Path
if ([string]::IsNullOrEmpty($imageTag)) {
$imageTag = $(git rev-parse --abbrev-ref HEAD)
}
Write-Host "Building images with tag $imageTag" -ForegroundColor Yellow
$env:TAG=$imageTag
docker-compose -f "$scriptPath\..\docker-compose.yml" build

View File

@ -81,3 +81,30 @@ services:
- ACCEPT_EULA=Y
ports:
- "5433:1433"
nosql.data:
ports:
- "27017:27017"
locations.api:
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=http://0.0.0.0:80
- ConnectionString=mongodb://nosql.data
- Database=LocationsDb
- identityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
- EventBusConnection=rabbitmq
ports:
- "5109:80"
marketing.api:
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=http://0.0.0.0:80
- ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.MarketingDb;User Id=sa;Password=Pass@word
- EventBusConnection=rabbitmq
- MongoConnectionString=mongodb://nosql.data
- MongoDatabase=MarketingDb
- identityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
ports:
- "5110:80"

View File

@ -76,6 +76,18 @@ services:
ports:
- "5100:80"
marketing.api:
environment:
- ASPNETCORE_ENVIRONMENT=Production
- ASPNETCORE_URLS=http://0.0.0.0:80
- ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.MarketingDb;User Id=sa;Password=Pass@word
- EventBusConnection=rabbitmq
- MongoConnectionString=mongodb://nosql.data
- MongoDatabase=MarketingDb
- identityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
ports:
- "5110:80"
sql.data:
environment:
- SA_PASSWORD=Pass@word

View File

@ -2,7 +2,7 @@ version: '2.1'
services:
basket.api:
image: eshop/basket.api-win
image: eshop/basket.api-win:${TAG:-latest}
build:
context: ./src/Services/Basket/Basket.API
dockerfile: Dockerfile.nanowin
@ -11,7 +11,7 @@ services:
- identity.api
catalog.api:
image: eshop/catalog.api-win
image: eshop/catalog.api-win:${TAG:-latest}
build:
context: ./src/Services/Catalog/Catalog.API
dockerfile: Dockerfile.nanowin
@ -19,7 +19,7 @@ services:
- sql.data
identity.api:
image: eshop/identity.api-win
image: eshop/identity.api-win:${TAG:-latest}
build:
context: ./src/Services/Identity/Identity.API
dockerfile: Dockerfile.nanowin
@ -27,7 +27,7 @@ services:
- sql.data
ordering.api:
image: eshop/ordering.api-win
image: eshop/ordering.api-win:${TAG:-latest}
build:
context: ./src/Services/Ordering/Ordering.API
dockerfile: Dockerfile.nanowin
@ -35,7 +35,7 @@ services:
- sql.data
webspa:
image: eshop/webspa-win
image: eshop/webspa-win:${TAG:-latest}
build:
context: ./src/Web/WebSPA
dockerfile: Dockerfile.nanowin
@ -44,7 +44,7 @@ services:
- basket.api
webmvc:
image: eshop/webmvc-win
image: eshop/webmvc-win:${TAG:-latest}
build:
context: ./src/Web/WebMVC
dockerfile: Dockerfile.nanowin
@ -54,9 +54,32 @@ services:
- identity.api
- basket.api
locations.api:
image: eshop/locations.api:${TAG:-latest}
build:
context: ./src/Services/Location/Locations.API
dockerfile: Dockerfile
depends_on:
- nosql.data
- rabbitmq
marketing.api:
image: eshop/marketing.api:${TAG:-latest}
build:
context: ./src/Services/Marketing/Marketing.API
dockerfile: Dockerfile
depends_on:
- sql.data
- nosql.data
- identity.api
- rabbitmq
sql.data:
image: microsoft/mssql-server-windows
nosql.data:
image: mongo:windowsservercore
basket.data:
image: redis:nanoserver
# build:

View File

@ -1,10 +1,14 @@
version: '2'
version: '3'
services:
ci-build:
image: microsoft/aspnetcore-build:1.0-1.1
image: microsoft/aspnetcore-build:1.1.2
volumes:
- .:/src
- ./cli-linux:/cli-linux
working_dir: /src
command: /bin/bash -c "pushd ./src/Web/WebSPA && npm rebuild node-sass && popd && dotnet restore ./eShopOnContainers-ServicesAndWebApps.sln && dotnet publish ./eShopOnContainers-ServicesAndWebApps.sln -c Release -o ./obj/Docker/publish"
# DO NOT USE the sln file to compile because msbuild issue (https://github.com/Microsoft/msbuild/issues/2153)
# command: /bin/bash -c "pushd ./src/Web/WebSPA && npm rebuild node-sass && popd && dotnet restore ./eShopOnContainers-ServicesAndWebApps.sln && dotnet publish ./eShopOnContainers-ServicesAndWebApps.sln -c Release -o ./obj/Docker/publish"
# NOTE: Using build-bits-linux.sh triggers the same MSBUILD error :( (but at least, less frequently)
command: /bin/bash -c "pushd ./src/Web/WebSPA && npm rebuild node-sass && popd && pushd /cli-linux && ./build-bits-linux.sh /src"

View File

@ -0,0 +1,56 @@
version: '2'
services:
basket.api:
image: eshop/basket.api
depends_on:
- basket.data
- identity.api
- rabbitmq
catalog.api:
image: eshop/catalog.api
depends_on:
- sql.data
- rabbitmq
identity.api:
image: eshop/identity.api
depends_on:
- sql.data
ordering.api:
image: eshop/ordering.api
depends_on:
- sql.data
webspa:
image: eshop/webspa
depends_on:
- identity.api
- basket.api
webmvc:
image: eshop/webmvc
depends_on:
- catalog.api
- ordering.api
- identity.api
- basket.api
sql.data:
image: microsoft/mssql-server-linux
basket.data:
image: redis
ports:
- "6379:6379"
rabbitmq:
image: rabbitmq
ports:
- "5672:5672"
webstatus:
image: eshop/webstatus

View File

@ -1,4 +1,4 @@
version: '2.1'
version: '3'
# The default docker-compose.override file can use the "localhost" as the external name for testing web apps within the same dev machine.
# The ESHOP_EXTERNAL_DNS_NAME_OR_IP environment variable is taken, by default, from the ".env" file defined like:
@ -7,6 +7,10 @@ version: '2.1'
# An external IP or DNS name has to be used (instead localhost and the 10.0.75.1 IP) when testing the Web apps and the Xamarin apps from remote machines/devices using the same WiFi, for instance.
services:
graceperiodmanager:
environment:
- ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word
- EventBusConnection=rabbitmq
basket.api:
environment:
@ -49,6 +53,19 @@ services:
ports:
- "5102:80"
marketing.api:
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=http://0.0.0.0:80
- ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.MarketingDb;User Id=sa;Password=Pass@word
- MongoConnectionString=mongodb://nosql.data
- MongoDatabase=MarketingDb
- EventBusConnection=rabbitmq
- ExternalCatalogBaseUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5110 #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
- identityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
ports:
- "5110:80"
webspa:
environment:
- ASPNETCORE_ENVIRONMENT=Development
@ -57,6 +74,7 @@ services:
- OrderingUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5102
- IdentityUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5105 #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
- BasketUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5103
- MarketingUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5110
- CatalogUrlHC=http://catalog.api/hc
- OrderingUrlHC=http://ordering.api/hc
- IdentityUrlHC=http://identity.api/hc #Local: Use ${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}, if using external IP or DNS name from browser.
@ -71,7 +89,8 @@ services:
- CatalogUrl=http://catalog.api
- OrderingUrl=http://ordering.api
- BasketUrl=http://basket.api
- IdentityUrl=http://10.0.75.1:5105 #Local: Use 10.0.75.1 in a "Docker for Windows" environment, if using "localhost" from browser.
- IdentityUrl=http://10.0.75.1:5105
- MarketingUrl=http://marketing.api #Local: Use 10.0.75.1 in a "Docker for Windows" environment, if using "localhost" from browser.
#Remote: Use ${ESHOP_EXTERNAL_DNS_NAME_OR_IP} if using external IP or DNS name from browser.
ports:
- "5100:80"
@ -83,6 +102,10 @@ services:
ports:
- "5433:1433"
nosql.data:
ports:
- "27017:27017"
webstatus:
environment:
- ASPNETCORE_ENVIRONMENT=Development
@ -95,3 +118,22 @@ services:
- spa=http://webspa/hc
ports:
- "5107:80"
payment.api:
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=http://0.0.0.0:5108
- EventBusConnection=rabbitmq
ports:
- "5108:80"
locations.api:
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=http://0.0.0.0:80
- ConnectionString=mongodb://nosql.data
- Database=LocationsDb
- identityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
- EventBusConnection=rabbitmq
ports:
- "5109:80"

View File

@ -1,4 +1,4 @@
version: '2.1'
version: '3'
# The Production docker-compose file has to have the external/real IPs or DNS names for the services
# The ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP environment variable is taken, by default, from the ".env" file defined like:
@ -54,6 +54,18 @@ services:
ports:
- "5102:80"
marketing.api:
environment:
- ASPNETCORE_ENVIRONMENT=Production
- ASPNETCORE_URLS=http://0.0.0.0:80
- ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.MarketingDb;User Id=sa;Password=Pass@word
- MongoConnectionString=mongodb://nosql.data
- MongoDatabase=MarketingDb
- EventBusConnection=rabbitmq
- identityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
ports:
- "5110:80"
webspa:
environment:
- ASPNETCORE_ENVIRONMENT=Production

View File

@ -1,4 +1,4 @@
version: '2.1'
version: '3'
services:
basket.api:
@ -61,6 +61,22 @@ services:
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=linux"
marketing.api:
image: eshop/marketing.api:dev
build:
args:
source: ${DOCKER_BUILD_SOURCE}
environment:
- DOTNET_USE_POLLING_FILE_WATCHER=1
volumes:
- ./src/Services/Marketing/Marketing.API:/app
- ~/.nuget/packages:/root/.nuget/packages:ro
- ~/clrdbg:/clrdbg:ro
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=linux"
webspa:
image: eshop/webspa:dev
build:
@ -105,3 +121,46 @@ services:
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=linux"
payment.api:
image: eshop/payment.api:dev
build:
args:
source: ${DOCKER_BUILD_SOURCE}
environment:
- DOTNET_USE_POLLING_FILE_WATCHER=1
volumes:
- ./src/Services/Payment/Payment.API:/app
- ~/.nuget/packages:/root/.nuget/packages:ro
- ~/clrdbg:/clrdbg:ro
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=linux"
graceperiodmanager:
image: eshop/graceperiodmanager:dev
build:
args:
source: ${DOCKER_BUILD_SOURCE}
volumes:
- ./src/Services/GracePeriod/GracePeriodManager:/app
- ~/.nuget/packages:/root/.nuget/packages:ro
- ~/clrdbg:/clrdbg:ro
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=linux"
locations.api:
image: eshop/locations.api:dev
build:
args:
source: ${DOCKER_BUILD_SOURCE}
environment:
- DOTNET_USE_POLLING_FILE_WATCHER=1
volumes:
- ./src/Services/Location/Locations.API:/app
- ~/.nuget/packages:/root/.nuget/packages:ro
- ~/clrdbg:/clrdbg:ro
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=linux"

View File

@ -1,4 +1,4 @@
version: '2.1'
version: '3'
services:
basket.api:
@ -41,6 +41,16 @@ services:
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=linux"
marketing.api:
build:
args:
source: ${DOCKER_BUILD_SOURCE}
volumes:
- ~/clrdbg:/clrdbg:ro
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=linux"
webspa:
build:
args:
@ -70,3 +80,33 @@ services:
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=linux"
payment.api:
build:
args:
source: ${DOCKER_BUILD_SOURCE}
volumes:
- ~/clrdbg:/clrdbg:ro
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=linux"
graceperiodmanager:
build:
args:
source: ${DOCKER_BUILD_SOURCE}
volumes:
- ~/clrdbg:/clrdbg:ro
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=linux"
locations.api:
build:
args:
source: ${DOCKER_BUILD_SOURCE}
volumes:
- ~/clrdbg:/clrdbg:ro
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=linux"

View File

@ -1,18 +1,26 @@
version: '2.1'
version: '3'
services:
graceperiodmanager:
image: eshop/graceperiodmanager:${TAG:-latest}
build:
context: ./src/Services/GracePeriod/GracePeriodManager
dockerfile: Dockerfile
depends_on:
- sql.data
- rabbitmq
basket.api:
image: eshop/basket.api
image: eshop/basket.api:${TAG:-latest}
build:
context: ./src/Services/Basket/Basket.API
dockerfile: Dockerfile
depends_on:
- basket.data
- identity.api
- rabbitmq
catalog.api:
image: eshop/catalog.api
image: eshop/catalog.api:${TAG:-latest}
build:
context: ./src/Services/Catalog/Catalog.API
dockerfile: Dockerfile
@ -21,7 +29,7 @@ services:
- rabbitmq
identity.api:
image: eshop/identity.api
image: eshop/identity.api:${TAG:-latest}
build:
context: ./src/Services/Identity/Identity.API
dockerfile: Dockerfile
@ -29,15 +37,27 @@ services:
- sql.data
ordering.api:
image: eshop/ordering.api
image: eshop/ordering.api:${TAG:-latest}
build:
context: ./src/Services/Ordering/Ordering.API
dockerfile: Dockerfile
depends_on:
- sql.data
- rabbitmq
marketing.api:
image: eshop/marketing.api:${TAG:-latest}
build:
context: ./src/Services/Marketing/Marketing.API
dockerfile: Dockerfile
depends_on:
- sql.data
- nosql.data
- identity.api
- rabbitmq
webspa:
image: eshop/webspa
image: eshop/webspa:${TAG:-latest}
build:
context: ./src/Web/WebSPA
dockerfile: Dockerfile
@ -46,7 +66,7 @@ services:
- basket.api
webmvc:
image: eshop/webmvc
image: eshop/webmvc:${TAG:-latest}
build:
context: ./src/Web/WebMVC
dockerfile: Dockerfile
@ -55,10 +75,14 @@ services:
- ordering.api
- identity.api
- basket.api
- marketing.api
sql.data:
image: microsoft/mssql-server-linux
nosql.data:
image: mongo
basket.data:
image: redis
ports:
@ -70,7 +94,24 @@ services:
- "5672:5672"
webstatus:
image: eshop/webstatus
image: eshop/webstatus:${TAG:-latest}
build:
context: ./src/Web/WebStatus
dockerfile: Dockerfile
payment.api:
image: eshop/payment.api:${TAG:-latest}
build:
context: ./src/Services/Payment/Payment.API
dockerfile: Dockerfile
depends_on:
- rabbitmq
locations.api:
image: eshop/locations.api:${TAG:-latest}
build:
context: ./src/Services/Location/Locations.API
dockerfile: Dockerfile
depends_on:
- nosql.data
- rabbitmq

View File

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26430.6
VisualStudioVersion = 15.0.26430.12
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{932D8224-11F6-4D07-B109-DA28AD288A63}"
EndProject
@ -70,11 +70,31 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Resilience", "Resilience",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Resilience.Http", "src\BuildingBlocks\Resilience\Resilience.Http\Resilience.Http.csproj", "{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Payment", "Payment", "{022E145D-1593-47EE-9608-8E323D3C63F5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Payment.API", "src\Services\Payment\Payment.API\Payment.API.csproj", "{1A01AF82-6FCB-464C-B39C-F127AEBD315D}"
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}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EventBus.Tests", "src\BuildingBlocks\EventBus\EventBus.Tests\EventBus.Tests.csproj", "{4A980AC4-7205-46BF-8CCB-09E44D700FD4}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GracePeriod", "GracePeriod", "{F38B4FF0-0B49-405A-B1B4-F7A5E3BC4C4E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GracePeriodManager", "src\Services\GracePeriod\GracePeriodManager\GracePeriodManager.csproj", "{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Location", "Location", "{41139F64-4046-4F16-96B7-D941D96FA9C6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Locations.API", "src\Services\Location\Locations.API\Locations.API.csproj", "{E7581357-FC34-474C-B8F5-307EE3CE05EF}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DataProtection", "DataProtection", "{88B22DBB-AA8F-4290-A454-2C109352C345}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataProtection", "src\BuildingBlocks\DataProtection\DataProtection\DataProtection.csproj", "{23A33F9B-7672-426D-ACF9-FF8436ADC81A}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Marketing", "Marketing", "{A5260DE0-1FDD-467E-9CC1-A028AB081CEE}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Marketing.API", "src\Services\Marketing\Marketing.API\Marketing.API.csproj", "{DF395F85-B010-465D-857A-7EBCC512C0C2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventBusServiceBus", "src\BuildingBlocks\EventBus\EventBusServiceBus\EventBusServiceBus.csproj", "{69AF10D3-AA76-4FF7-B187-EC7E8CC5F5B8}"
EndProject
@ -860,6 +880,54 @@ 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
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.AppStore|ARM.ActiveCfg = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.AppStore|ARM.Build.0 = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.AppStore|iPhone.Build.0 = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.AppStore|x64.ActiveCfg = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.AppStore|x64.Build.0 = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.AppStore|x86.ActiveCfg = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.AppStore|x86.Build.0 = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Debug|ARM.ActiveCfg = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Debug|ARM.Build.0 = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Debug|iPhone.Build.0 = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Debug|x64.ActiveCfg = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Debug|x64.Build.0 = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Debug|x86.ActiveCfg = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Debug|x86.Build.0 = Debug|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Release|Any CPU.Build.0 = Release|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Release|ARM.ActiveCfg = Release|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Release|ARM.Build.0 = Release|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Release|iPhone.ActiveCfg = Release|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Release|iPhone.Build.0 = Release|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Release|x64.ActiveCfg = Release|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Release|x64.Build.0 = Release|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Release|x86.ActiveCfg = Release|Any CPU
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.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
@ -956,6 +1024,246 @@ Global
{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
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.AppStore|ARM.ActiveCfg = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.AppStore|ARM.Build.0 = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.AppStore|iPhone.Build.0 = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.AppStore|x64.ActiveCfg = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.AppStore|x64.Build.0 = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.AppStore|x86.ActiveCfg = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.AppStore|x86.Build.0 = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Debug|ARM.ActiveCfg = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Debug|ARM.Build.0 = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Debug|iPhone.Build.0 = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Debug|x64.ActiveCfg = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Debug|x64.Build.0 = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Debug|x86.ActiveCfg = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Debug|x86.Build.0 = Debug|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Release|Any CPU.Build.0 = Release|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Release|ARM.ActiveCfg = Release|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Release|ARM.Build.0 = Release|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Release|iPhone.ActiveCfg = Release|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Release|iPhone.Build.0 = Release|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Release|x64.ActiveCfg = Release|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Release|x64.Build.0 = Release|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Release|x86.ActiveCfg = Release|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Release|x86.Build.0 = Release|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|ARM.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|ARM.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|iPhone.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|x64.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|x64.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|x86.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|x86.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|ARM.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|ARM.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|iPhone.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|x64.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|x64.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|x86.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|x86.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|Any CPU.Build.0 = Release|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|ARM.ActiveCfg = Release|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|ARM.Build.0 = Release|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|iPhone.ActiveCfg = Release|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|iPhone.Build.0 = Release|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|x64.ActiveCfg = Release|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|x64.Build.0 = Release|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|x86.ActiveCfg = Release|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|x86.Build.0 = Release|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.AppStore|ARM.ActiveCfg = Debug|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.AppStore|ARM.Build.0 = Debug|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.AppStore|iPhone.Build.0 = Debug|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.AppStore|x64.ActiveCfg = Debug|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.AppStore|x64.Build.0 = Debug|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.AppStore|x86.ActiveCfg = Debug|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.AppStore|x86.Build.0 = Debug|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Debug|ARM.ActiveCfg = Debug|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Debug|ARM.Build.0 = Debug|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Debug|iPhone.Build.0 = Debug|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Debug|x64.ActiveCfg = Debug|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Debug|x64.Build.0 = Debug|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Debug|x86.ActiveCfg = Debug|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Debug|x86.Build.0 = Debug|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Release|Any CPU.Build.0 = Release|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Release|ARM.ActiveCfg = Release|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Release|ARM.Build.0 = Release|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Release|iPhone.ActiveCfg = Release|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Release|iPhone.Build.0 = Release|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Release|x64.ActiveCfg = Release|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Release|x64.Build.0 = Release|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Release|x86.ActiveCfg = Release|Any CPU
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Release|x86.Build.0 = Release|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.AppStore|ARM.ActiveCfg = Debug|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.AppStore|ARM.Build.0 = Debug|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.AppStore|iPhone.Build.0 = Debug|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.AppStore|x64.ActiveCfg = Debug|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.AppStore|x64.Build.0 = Debug|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.AppStore|x86.ActiveCfg = Debug|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.AppStore|x86.Build.0 = Debug|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Debug|ARM.ActiveCfg = Debug|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Debug|ARM.Build.0 = Debug|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Debug|iPhone.Build.0 = Debug|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Debug|x64.ActiveCfg = Debug|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Debug|x64.Build.0 = Debug|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Debug|x86.ActiveCfg = Debug|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Debug|x86.Build.0 = Debug|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Release|Any CPU.Build.0 = Release|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Release|ARM.ActiveCfg = Release|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Release|ARM.Build.0 = Release|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Release|iPhone.ActiveCfg = Release|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Release|iPhone.Build.0 = Release|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Release|x64.ActiveCfg = Release|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Release|x64.Build.0 = Release|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Release|x86.ActiveCfg = Release|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Release|x86.Build.0 = Release|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.AppStore|ARM.ActiveCfg = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.AppStore|ARM.Build.0 = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.AppStore|iPhone.Build.0 = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.AppStore|x64.ActiveCfg = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.AppStore|x64.Build.0 = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.AppStore|x86.ActiveCfg = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.AppStore|x86.Build.0 = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Debug|ARM.ActiveCfg = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Debug|ARM.Build.0 = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Debug|iPhone.Build.0 = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Debug|x64.ActiveCfg = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Debug|x64.Build.0 = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Debug|x86.ActiveCfg = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Debug|x86.Build.0 = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Release|Any CPU.Build.0 = Release|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Release|ARM.ActiveCfg = Release|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Release|ARM.Build.0 = Release|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Release|iPhone.ActiveCfg = Release|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Release|iPhone.Build.0 = Release|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Release|x64.ActiveCfg = Release|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Release|x64.Build.0 = Release|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Release|x86.ActiveCfg = Release|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.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
@ -1085,9 +1393,18 @@ Global
{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}
{022E145D-1593-47EE-9608-8E323D3C63F5} = {91CF7717-08AB-4E65-B10E-0B426F01E2E8}
{1A01AF82-6FCB-464C-B39C-F127AEBD315D} = {022E145D-1593-47EE-9608-8E323D3C63F5}
{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}
{69AF10D3-AA76-4FF7-B187-EC7E8CC5F5B8} = {807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F}
{4A980AC4-7205-46BF-8CCB-09E44D700FD4} = {807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F}
{F38B4FF0-0B49-405A-B1B4-F7A5E3BC4C4E} = {91CF7717-08AB-4E65-B10E-0B426F01E2E8}
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47} = {F38B4FF0-0B49-405A-B1B4-F7A5E3BC4C4E}
{41139F64-4046-4F16-96B7-D941D96FA9C6} = {91CF7717-08AB-4E65-B10E-0B426F01E2E8}
{E7581357-FC34-474C-B8F5-307EE3CE05EF} = {41139F64-4046-4F16-96B7-D941D96FA9C6}
{88B22DBB-AA8F-4290-A454-2C109352C345} = {DB0EFB20-B024-4E5E-A75C-52143C131D25}
{23A33F9B-7672-426D-ACF9-FF8436ADC81A} = {88B22DBB-AA8F-4290-A454-2C109352C345}
{A5260DE0-1FDD-467E-9CC1-A028AB081CEE} = {91CF7717-08AB-4E65-B10E-0B426F01E2E8}
{DF395F85-B010-465D-857A-7EBCC512C0C2} = {A5260DE0-1FDD-467E-9CC1-A028AB081CEE}
EndGlobalSection
EndGlobal

View File

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26228.12
VisualStudioVersion = 15.0.26430.6
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{932D8224-11F6-4D07-B109-DA28AD288A63}"
EndProject
@ -97,14 +97,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Health
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.HealthChecks", "src\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks\Microsoft.Extensions.HealthChecks.csproj", "{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.HealthChecks.Data", "src\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.Data\Microsoft.Extensions.HealthChecks.Data.csproj", "{0E7AEB45-80F2-42B9-96BB-3414669E58AA}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Resilience", "Resilience", "{D13768ED-5AF1-4E09-96DD-FF6E7A2E5E06}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Resilience.Http", "src\BuildingBlocks\Resilience\Resilience.Http\Resilience.Http.csproj", "{D92EB452-7A72-4B26-A8ED-0204CD376BC4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebStatus", "src\Web\WebStatus\WebStatus.csproj", "{23FB706A-2701-41E9-8BF9-28936001CA41}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.HealthChecks.SqlServer", "src\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.SqlServer\Microsoft.Extensions.HealthChecks.SqlServer.csproj", "{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
@ -1322,54 +1322,6 @@ Global
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.Release|x64.Build.0 = Release|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.Release|x86.ActiveCfg = Release|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.Release|x86.Build.0 = Release|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.AppStore|ARM.ActiveCfg = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.AppStore|ARM.Build.0 = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.AppStore|iPhone.Build.0 = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.AppStore|x64.ActiveCfg = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.AppStore|x64.Build.0 = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.AppStore|x86.ActiveCfg = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.AppStore|x86.Build.0 = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Debug|ARM.ActiveCfg = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Debug|ARM.Build.0 = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Debug|iPhone.Build.0 = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Debug|x64.ActiveCfg = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Debug|x64.Build.0 = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Debug|x86.ActiveCfg = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Debug|x86.Build.0 = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Release|Any CPU.Build.0 = Release|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Release|ARM.ActiveCfg = Release|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Release|ARM.Build.0 = Release|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Release|iPhone.ActiveCfg = Release|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Release|iPhone.Build.0 = Release|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Release|x64.ActiveCfg = Release|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Release|x64.Build.0 = Release|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Release|x86.ActiveCfg = Release|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Release|x86.Build.0 = Release|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
@ -1466,6 +1418,54 @@ Global
{23FB706A-2701-41E9-8BF9-28936001CA41}.Release|x64.Build.0 = Release|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.Release|x86.ActiveCfg = Release|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.Release|x86.Build.0 = Release|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.AppStore|ARM.ActiveCfg = Debug|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.AppStore|ARM.Build.0 = Debug|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.AppStore|iPhone.Build.0 = Debug|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.AppStore|x64.ActiveCfg = Debug|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.AppStore|x64.Build.0 = Debug|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.AppStore|x86.ActiveCfg = Debug|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.AppStore|x86.Build.0 = Debug|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Debug|ARM.ActiveCfg = Debug|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Debug|ARM.Build.0 = Debug|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Debug|iPhone.Build.0 = Debug|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Debug|x64.ActiveCfg = Debug|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Debug|x64.Build.0 = Debug|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Debug|x86.ActiveCfg = Debug|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Debug|x86.Build.0 = Debug|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Release|Any CPU.Build.0 = Release|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Release|ARM.ActiveCfg = Release|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Release|ARM.Build.0 = Release|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Release|iPhone.ActiveCfg = Release|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Release|iPhone.Build.0 = Release|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Release|x64.ActiveCfg = Release|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Release|x64.Build.0 = Release|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Release|x86.ActiveCfg = Release|Any CPU
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -1510,9 +1510,9 @@ Global
{96CE8CE7-BC97-4A53-899F-5EB63D7BBF7B} = {1EF3AC0F-F27C-46DD-AC53-D762D2C11C45}
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3} = {96CE8CE7-BC97-4A53-899F-5EB63D7BBF7B}
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45} = {96CE8CE7-BC97-4A53-899F-5EB63D7BBF7B}
{0E7AEB45-80F2-42B9-96BB-3414669E58AA} = {96CE8CE7-BC97-4A53-899F-5EB63D7BBF7B}
{D13768ED-5AF1-4E09-96DD-FF6E7A2E5E06} = {1EF3AC0F-F27C-46DD-AC53-D762D2C11C45}
{D92EB452-7A72-4B26-A8ED-0204CD376BC4} = {D13768ED-5AF1-4E09-96DD-FF6E7A2E5E06}
{23FB706A-2701-41E9-8BF9-28936001CA41} = {E279BF0F-7F66-4F3A-A3AB-2CDA66C1CD04}
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612} = {96CE8CE7-BC97-4A53-899F-5EB63D7BBF7B}
EndGlobalSection
EndGlobal

5
global.json Normal file
View File

@ -0,0 +1,5 @@
{
"sdk": {
"version":"1.0.4"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 38 KiB

29
k8s/basket-data.yaml Normal file
View File

@ -0,0 +1,29 @@
apiVersion: v1
kind: Service
metadata:
labels:
app: eshop
component: basket-data
name: basket-data
spec:
ports:
- port: 6379
selector:
app: eshop
component: basket-data
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: basket-data
spec:
template:
metadata:
labels:
app: eshop
component: basket-data
spec:
containers:
- name: basket-data
image: redis:3.2-alpine

View File

@ -2,10 +2,14 @@ Param(
[parameter(Mandatory=$false)][string]$registry,
[parameter(Mandatory=$false)][string]$dockerUser,
[parameter(Mandatory=$false)][string]$dockerPassword,
[parameter(Mandatory=$false)][bool]$deployCI,
[parameter(Mandatory=$false)][bool]$useDockerHub,
[parameter(Mandatory=$false)][string]$execPath,
[parameter(Mandatory=$false)][string]$kubeconfigPath
[parameter(Mandatory=$false)][string]$kubeconfigPath,
[parameter(Mandatory=$true)][string]$configFile,
[parameter(Mandatory=$false)][string]$imageTag,
[parameter(Mandatory=$false)][string]$externalDns,
[parameter(Mandatory=$false)][bool]$deployCI=$false,
[parameter(Mandatory=$false)][bool]$buildImages=$false,
[parameter(Mandatory=$false)][bool]$deployInfrastructure=$true
)
function ExecKube($cmd) {
@ -20,7 +24,11 @@ function ExecKube($cmd) {
}
}
# Not used when deploying through CI VSTS
# Initialization
$debugMode = $PSCmdlet.MyInvocation.BoundParameters["Debug"].IsPresent
$useDockerHub = [string]::IsNullOrEmpty($registry)
# Check required commands (only if not in CI environment)
if(-not $deployCI) {
$requiredCommands = ("docker", "docker-compose", "kubectl")
foreach ($command in $requiredCommands) {
@ -30,6 +38,45 @@ if(-not $deployCI) {
}
}
}
else {
$buildImages = false; # Never build images through CI, as they previously built
}
# Get tag to use from current branch if no tag is passed
if ([string]::IsNullOrEmpty($imageTag)) {
$imageTag = $(git rev-parse --abbrev-ref HEAD)
}
Write-Host "Docker image Tag: $imageTag" -ForegroundColor Yellow
# Read config to use
$config = Get-Content -Raw -Path $configFile | ConvertFrom-Json
if ($debugMode) {
Write-Host "[DEBUG]: Using following JSON config: " -ForegroundColor Yellow
$json = ConvertTo-Json $config -Depth 5
Write-Host $json
if (-not $deployCI) {
Write-Host "[DEBUG]: Press a key " -ForegroundColor Yellow
[System.Console]::Read()
}
}
# building and publishing docker images if needed
if($buildImages) {
Write-Host "Building and publishing eShopOnContainers..." -ForegroundColor Yellow
dotnet restore ../eShopOnContainers-ServicesAndWebApps.sln
dotnet publish -c Release -o obj/Docker/publish ../eShopOnContainers-ServicesAndWebApps.sln
Write-Host "Building Docker images tagged with '$imageTag'" -ForegroundColor Yellow
$env:TAG=$imageTag
docker-compose -p .. -f ../docker-compose.yml build
Write-Host "Pushing images to $registry..." -ForegroundColor Yellow
$services = ("basket.api", "catalog.api", "identity.api", "ordering.api", "marketing.api","payment.api","locations.api", "webmvc", "webspa", "webstatus")
foreach ($service in $services) {
docker tag eshop/${service}:$imageTag $registry/eshop/${service}:$imageTag
docker push $registry/eshop/${service}:$imageTag
}
}
# Use ACR instead of DockerHub as image repository
if(-not $useDockerHub) {
@ -50,77 +97,115 @@ if(-not $useDockerHub) {
# Removing previous services & deployments
Write-Host "Removing existing services & deployments.." -ForegroundColor Yellow
ExecKube -cmd 'delete -f sql-data.yaml -f rabbitmq.yaml'
ExecKube -cmd 'delete -f services.yaml -f frontend.yaml -f deployments.yaml'
ExecKube -cmd 'delete deployments --all'
ExecKube -cmd 'delete services --all'
ExecKube -cmd 'delete configmap config-files'
ExecKube -cmd 'delete configmap urls'
ExecKube -cmd 'delete configmap externalcfg'
# start sql, rabbitmq, frontend deploymentsExecKube -cmd 'delete configmap config-files'
ExecKube -cmd 'create configmap config-files --from-file=nginx-conf=nginx.conf'
ExecKube -cmd 'label configmap config-files app=eshop'
ExecKube -cmd 'create -f sql-data.yaml -f rabbitmq.yaml -f services.yaml -f frontend.yaml'
# building and publishing docker images not necessary when deploying through CI VSTS
if(-not $deployCI) {
Write-Host "Building and publishing eShopOnContainers..." -ForegroundColor Yellow
dotnet restore ../eShopOnContainers-ServicesAndWebApps.sln
dotnet publish -c Release -o obj/Docker/publish ../eShopOnContainers-ServicesAndWebApps.sln
Write-Host "Building Docker images..." -ForegroundColor Yellow
docker-compose -p .. -f ../docker-compose.yml build
Write-Host "Pushing images to $registry..." -ForegroundColor Yellow
$services = ("basket.api", "catalog.api", "identity.api", "ordering.api", "webmvc", "webspa", "webstatus")
foreach ($service in $services) {
docker tag eshop/$service $registry/eshop/$service
docker push $registry/eshop/$service
}
if ($deployInfrastructure) {
Write-Host 'Deploying infrastructure deployments (databases, redis, ...)' -ForegroundColor Yellow
ExecKube -cmd 'create -f sql-data.yaml -f basket-data.yaml -f keystore-data.yaml -f rabbitmq.yaml -f nosql-data.yaml'
}
Write-Host "Waiting for frontend's external ip..." -ForegroundColor Yellow
while ($true) {
$frontendUrl = & ExecKube -cmd 'get svc frontend -o=jsonpath="{.status.loadBalancer.ingress[0].ip}"'
if ([bool]($frontendUrl -as [ipaddress])) {
break
}
Start-Sleep -s 15
Write-Host 'Deploying code deployments (databases, redis, ...)' -ForegroundColor Yellow
ExecKube -cmd 'create -f services.yaml -f frontend.yaml'
if ([string]::IsNullOrEmpty($externalDns)) {
Write-Host "Waiting for frontend's external ip..." -ForegroundColor Yellow
while ($true) {
$frontendUrl = & ExecKube -cmd 'get svc frontend -o=jsonpath="{.status.loadBalancer.ingress[0].ip}"'
if ([bool]($frontendUrl -as [ipaddress])) {
break
}
Start-Sleep -s 15
}
$externalDns = $frontendUrl
}
Write-Host "Using $externalDns as the external DNS/IP of the k8s cluster"
ExecKube -cmd 'create configmap urls `
--from-literal=BasketUrl=http://$($frontendUrl)/basket-api `
--from-literal=BasketHealthCheckUrl=http://$($frontendUrl)/basket-api/hc `
--from-literal=BasketUrl=http://basket `
--from-literal=BasketHealthCheckUrl=http://basket/hc `
--from-literal=CatalogUrl=http://$($frontendUrl)/catalog-api `
--from-literal=CatalogHealthCheckUrl=http://$($frontendUrl)/catalog-api/hc `
--from-literal=CatalogHealthCheckUrl=http://catalog/hc `
--from-literal=IdentityUrl=http://$($frontendUrl)/identity `
--from-literal=IdentityHealthCheckUrl=http://$($frontendUrl)/identity/hc `
--from-literal=OrderingUrl=http://$($frontendUrl)/ordering-api `
--from-literal=OrderingHealthCheckUrl=http://$($frontendUrl)/ordering-api/hc `
--from-literal=MvcClient=http://$($frontendUrl)/webmvc `
--from-literal=WebMvcHealthCheckUrl=http://$($frontendUrl)/webmvc/hc `
--from-literal=WebStatusClient=http://$($frontendUrl)/webstatus `
--from-literal=WebSpaHealthCheckUrl=http://$($frontendUrl)/hc `
--from-literal=SpaClient=http://$($frontendUrl)'
--from-literal=IdentityHealthCheckUrl=http://identity/hc `
--from-literal=OrderingUrl=http://ordering `
--from-literal=OrderingHealthCheckUrl=http://ordering/hc `
--from-literal=MvcClientExternalUrl=http://$($frontendUrl)/webmvc `
--from-literal=WebMvcHealthCheckUrl=http://webmvc/hc `
--from-literal=MvcClientOrderingUrl=http://ordering `
--from-literal=MvcClientCatalogUrl=http://catalog `
--from-literal=MvcClientBasketUrl=http://basket `
--from-literal=WebSpaHealthCheckUrl=http://webspa/hc `
--from-literal=SpaClientOrderingExternalUrl=http://$($externalDns)/ordering-api `
--from-literal=SpaClientCatalogExternalUrl=http://$($externalDns)/catalog-api `
--from-literal=SpaClientBasketExternalUrl=http://$($externalDns)/basket-api `
--from-literal=SpaClientIdentityExternalUrl=http://$($externalDns)/identity `
--from-literal=SpaClientExternalUrl=http://$($externalDns)'
ExecKube -cmd 'label configmap urls app=eshop'
Write-Host "Creating deployments..."
Write-Host "Applying external configuration from json" -ForegroundColor Yellow
ExecKube -cmd 'create configmap externalcfg `
--from-literal=CatalogSqlDb=$($config.sql.catalog) `
--from-literal=IdentitySqlDb=$($config.sql.identity) `
--from-literal=OrderingSqlDb=$($config.sql.ordering) `
--from-literal=MarketingSqlDb=$($config.sql.marketing) `
--from-literal=LocationsNoSqlDb=$($config.nosql.locations.constr) `
--from-literal=LocationsNoSqlDbName=$($config.nosql.locations.db) `
--from-literal=MarketingNoSqlDb=$($config.nosql.marketing.constr) `
--from-literal=MarketingNoSqlDbName=$($config.nosql.marketing.db) `
--from-literal=BasketRedisConStr=$($config.redis.basket) `
--from-literal=LocationsBus=$($config.servicebus.locations) `
--from-literal=MarketingBus=$($config.servicebus.marketing) `
--from-literal=BasketBus=$($config.servicebus.basket) `
--from-literal=OrderingBus=$($config.servicebus.ordering) `
--from-literal=CatalogBus=$($config.servicebus.catalog) `
--from-literal=PaymentBus=$($config.servicebus.payment) `
--from-literal=UseAzureServiceBus=$($config.servicebus.use_azure) `
--from-literal=keystore=$($config.redis.keystore) '
ExecKube -cmd 'label configmap externalcfg app=eshop'
Write-Host "Creating deployments..." -ForegroundColor Yellow
ExecKube -cmd 'create -f deployments.yaml'
# not using ACR for pulling images when deploying through CI VSTS
if(-not $deployCI) {
# update deployments with the private registry before k8s tries to pull images
# (deployment templating, or Helm, would obviate this)
ExecKube -cmd 'set image -f deployments.yaml `
basket=$registry/eshop/basket.api `
catalog=$registry/eshop/catalog.api `
identity=$registry/eshop/identity.api `
ordering=$registry/eshop/ordering.api `
webmvc=$registry/eshop/webmvc `
webstatus=$registry/eshop/webstatus `
webspa=$registry/eshop/webspa'
# update deployments with the correct image (with tag and/or registry)
Write-Host "Update Image containers to use prefix '$registry' and tag '$imageTag'" -ForegroundColor Yellow
$registryPath = ""
if (-not [string]::IsNullOrEmpty($registry)) {
$registryPath = "$registry/"
}
ExecKube -cmd 'rollout resume -f deployments.yaml'
ExecKube -cmd 'set image deployments/basket basket=${registryPath}eshop/basket.api:$imageTag'
ExecKube -cmd 'set image deployments/catalog catalog=${registryPath}eshop/catalog.api:$imageTag'
ExecKube -cmd 'set image deployments/identity identity=${registryPath}eshop/identity.api:$imageTag'
ExecKube -cmd 'set image deployments/ordering ordering=${registryPath}eshop/ordering.api:$imageTag'
ExecKube -cmd 'set image deployments/marketing marketing=${registryPath}eshop/marketing.api:$imageTag'
ExecKube -cmd 'set image deployments/locations locations=${registryPath}eshop/locations.api:$imageTag'
ExecKube -cmd 'set image deployments/payment payment=${registryPath}eshop/payment.api:$imageTag'
ExecKube -cmd 'set image deployments/webmvc webmvc=${registryPath}eshop/webmvc:$imageTag'
ExecKube -cmd 'set image deployments/webstatus webstatus=${registryPath}eshop/webstatus:$imageTag'
ExecKube -cmd 'set image deployments/webspa webspa=${registryPath}eshop/webspa:$imageTag'
Write-Host "Execute rollout..." -ForegroundColor Yellow
ExecKube -cmd 'rollout resume deployments/basket'
ExecKube -cmd 'rollout resume deployments/catalog'
ExecKube -cmd 'rollout resume deployments/identity'
ExecKube -cmd 'rollout resume deployments/ordering'
ExecKube -cmd 'rollout resume deployments/marketing'
ExecKube -cmd 'rollout resume deployments/locations'
ExecKube -cmd 'rollout resume deployments/payment'
ExecKube -cmd 'rollout resume deployments/webmvc'
ExecKube -cmd 'rollout resume deployments/webstatus'
ExecKube -cmd 'rollout resume deployments/webspa'
Write-Host "WebSPA is exposed at http://$frontendUrl, WebMVC at http://$frontendUrl/webmvc, WebStatus at http://$frontendUrl/webstatus" -ForegroundColor Yellow

View File

@ -18,9 +18,20 @@ spec:
- name: ASPNETCORE_URLS
value: http://0.0.0.0:80/basket-api
- name: ConnectionString
value: 127.0.0.1
valueFrom:
configMapKeyRef:
name: externalcfg
key: BasketRedisConStr
- name: EventBusConnection
value: rabbitmq
valueFrom:
configMapKeyRef:
name: externalcfg
key: BasketBus
- name: AzureServiceBusEnabled
valueFrom:
configMapKeyRef:
name: externalcfg
key: UseAzureServiceBus
- name: IdentityUrl
valueFrom:
configMapKeyRef:
@ -28,10 +39,6 @@ spec:
key: IdentityUrl
ports:
- containerPort: 80
- name: basket-data
image: redis:3.2-alpine
ports:
- containerPort: 6379
imagePullSecrets:
- name: registry-key
---
@ -55,14 +62,20 @@ spec:
- name: ASPNETCORE_URLS
value: http://0.0.0.0:80/catalog-api
- name: ConnectionString
value: "Server=sql-data;Initial Catalog=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word"
- name: EventBusConnection
value: rabbitmq
valueFrom:
configMapKeyRef:
name: externalcfg
key: CatalogSqlDb
- name: ExternalCatalogBaseUrl
valueFrom:
configMapKeyRef:
name: urls
key: CatalogUrl
- name: EventBusConnection
valueFrom:
configMapKeyRef:
name: externalcfg
key: CatalogBus
ports:
- containerPort: 80
imagePullSecrets:
@ -88,17 +101,27 @@ spec:
- name: ASPNETCORE_URLS
value: http://0.0.0.0:80/identity
- name: ConnectionStrings__DefaultConnection
value: "Server=sql-data;Initial Catalog=Microsoft.eShopOnContainers.Services.IdentityDb;User Id=sa;Password=Pass@word"
valueFrom:
configMapKeyRef:
name: externalcfg
key: IdentitySqlDb
- name: DPConnectionString
valueFrom:
configMapKeyRef:
name: externalcfg
key: keystore
- name: IsClusterEnv
value: 'True'
- name: MvcClient
valueFrom:
configMapKeyRef:
name: urls
key: MvcClient
key: MvcClientExternalUrl
- name: SpaClient
valueFrom:
configMapKeyRef:
name: urls
key: SpaClient
key: SpaClientExternalUrl
ports:
- containerPort: 80
imagePullSecrets:
@ -124,9 +147,20 @@ spec:
- name: ASPNETCORE_URLS
value: http://0.0.0.0:80/ordering-api
- name: ConnectionString
value: "Server=sql-data;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word;"
valueFrom:
configMapKeyRef:
name: externalcfg
key: OrderingSqlDb
- name: EventBusConnection
value: rabbitmq
valueFrom:
configMapKeyRef:
name: externalcfg
key: OrderingBus
- name: AzureServiceBusEnabled
valueFrom:
configMapKeyRef:
name: externalcfg
key: UseAzureServiceBus
- name: IdentityUrl
valueFrom:
configMapKeyRef:
@ -139,6 +173,143 @@ spec:
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: locations
spec:
paused: true
template:
metadata:
labels:
app: eshop
component: locations
spec:
containers:
- name: locations
image: eshop/locations.api
imagePullPolicy: Always
env:
- name: ASPNETCORE_URLS
value: http://0.0.0.0:80/locations-api
- name: ConnectionString
valueFrom:
configMapKeyRef:
name: externalcfg
key: LocationsNoSqlDb
- name: Database
valueFrom:
configMapKeyRef:
name: externalcfg
key: LocationsNoSqlDbName
- name: AzureServiceBusEnabled
valueFrom:
configMapKeyRef:
name: externalcfg
key: UseAzureServiceBus
- name: EventBusConnection
valueFrom:
configMapKeyRef:
name: externalcfg
key: LocationsBus
- name: IdentityUrl
valueFrom:
configMapKeyRef:
name: urls
key: IdentityUrl
ports:
- containerPort: 80
imagePullSecrets:
- name: registry-key
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: marketing
spec:
paused: true
template:
metadata:
labels:
app: eshop
component: marketing
spec:
containers:
- name: marketing
image: eshop/marketing.api
imagePullPolicy: Always
env:
- name: ASPNETCORE_URLS
value: http://0.0.0.0:80/marketing-api
- name: ConnectionString
valueFrom:
configMapKeyRef:
name: externalcfg
key: MarketingSqlDb
- name: MongoConnectionString
valueFrom:
configMapKeyRef:
name: externalcfg
key: MarketingNoSqlDb
- name: MongoDatabase
valueFrom:
configMapKeyRef:
name: externalcfg
key: MarketingNoSqlDbName
- name: AzureServiceBusEnabled
valueFrom:
configMapKeyRef:
name: externalcfg
key: UseAzureServiceBus
- name: EventBusConnection
valueFrom:
configMapKeyRef:
name: externalcfg
key: MarketingBus
- name: IdentityUrl
valueFrom:
configMapKeyRef:
name: urls
key: IdentityUrl
ports:
- containerPort: 80
imagePullSecrets:
- name: registry-key
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: payment
spec:
paused: true
template:
metadata:
labels:
app: eshop
component: payment
spec:
containers:
- name: payment
image: eshop/payment.api
imagePullPolicy: Always
env:
- name: ASPNETCORE_URLS
value: http://0.0.0.0:80/payment-api
- name: AzureServiceBusEnabled
valueFrom:
configMapKeyRef:
name: externalcfg
key: UseAzureServiceBus
- name: EventBusConnection
valueFrom:
configMapKeyRef:
name: externalcfg
key: PaymentBus
ports:
- containerPort: 80
imagePullSecrets:
- name: registry-key
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: webmvc
spec:
@ -156,21 +327,28 @@ spec:
env:
- name: ASPNETCORE_URLS
value: http://0.0.0.0:80/webmvc
- name: DPConnectionString
valueFrom:
configMapKeyRef:
name: externalcfg
key: keystore
- name: IsClusterEnv
value: 'True'
- name: BasketUrl
valueFrom:
configMapKeyRef:
name: urls
key: BasketUrl
key: MvcClientBasketUrl
- name: CallBackUrl
valueFrom:
configMapKeyRef:
name: urls
key: MvcClient
key: MvcClientExternalUrl
- name: CatalogUrl
valueFrom:
configMapKeyRef:
name: urls
key: CatalogUrl
key: MvcClientCatalogUrl
- name: IdentityUrl
valueFrom:
configMapKeyRef:
@ -180,13 +358,12 @@ spec:
valueFrom:
configMapKeyRef:
name: urls
key: OrderingUrl
key: MvcClientOrderingUrl
ports:
- containerPort: 80
imagePullSecrets:
- name: registry-key
---
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
@ -260,31 +437,58 @@ spec:
env:
- name: ASPNETCORE_URLS
value: http://0.0.0.0:80
- name: DPConnectionString
valueFrom:
configMapKeyRef:
name: externalcfg
key: keystore
- name: IsClusterEnv
value: 'True'
- name: BasketUrl
valueFrom:
configMapKeyRef:
name: urls
key: BasketUrl
key: SpaClientBasketExternalUrl
- name: CallBackUrl
valueFrom:
configMapKeyRef:
name: urls
key: SpaClient
key: SpaClientExternalUrl
- name: CatalogUrl
valueFrom:
configMapKeyRef:
name: urls
key: CatalogUrl
key: SpaClientCatalogExternalUrl
- name: IdentityUrl
valueFrom:
configMapKeyRef:
name: urls
key: IdentityUrl
key: SpaClientIdentityExternalUrl
- name: OrderingUrl
valueFrom:
configMapKeyRef:
name: urls
key: OrderingUrl
key: SpaClientOrderingExternalUrl
- name: BasketUrlHC
valueFrom:
configMapKeyRef:
name: urls
key: BasketHealthCheckUrl
- name: CatalogUrlHC
valueFrom:
configMapKeyRef:
name: urls
key: CatalogHealthCheckUrl
- name: IdentityUrlHC
valueFrom:
configMapKeyRef:
name: urls
key: IdentityHealthCheckUrl
- name: OrderingUrlHC
valueFrom:
configMapKeyRef:
name: urls
key: OrderingHealthCheckUrl
ports:
- containerPort: 80
imagePullSecrets:

View File

@ -3,16 +3,19 @@
[parameter(Mandatory=$true)][string]$location,
[parameter(Mandatory=$true)][string]$registryName,
[parameter(Mandatory=$true)][string]$orchestratorName,
[parameter(Mandatory=$true)][string]$dnsName
[parameter(Mandatory=$true)][string]$dnsName,
[parameter(Mandatory=$true)][string]$createAcr=$true
)
# Create resource group
Write-Host "Creating resource group..." -ForegroundColor Yellow
az group create --name=$resourceGroupName --location=$location
# Create Azure Container Registry
Write-Host "Creating Azure Container Registry..." -ForegroundColor Yellow
az acr create -n $registryName -g $resourceGroupName -l $location --admin-enabled true --sku Basic
if ($createAcr) {
# Create Azure Container Registry
Write-Host "Creating Azure Container Registry..." -ForegroundColor Yellow
az acr create -n $registryName -g $resourceGroupName -l $location --admin-enabled true --sku Basic
}
# Create kubernetes orchestrator
Write-Host "Creating kubernetes orchestrator..." -ForegroundColor Yellow

29
k8s/keystore-data.yaml Normal file
View File

@ -0,0 +1,29 @@
apiVersion: v1
kind: Service
metadata:
labels:
app: eshop
component: keystore-data
name: keystore-data
spec:
ports:
- port: 6379
selector:
app: eshop
component: keystore-data
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: keystore-data
spec:
template:
metadata:
labels:
app: eshop
component: keystore-data
spec:
containers:
- name: keystore-data
image: redis:3.2-alpine

31
k8s/local.json Normal file
View File

@ -0,0 +1,31 @@
{
"sql": {
"catalog": "Server=sql-data;Initial Catalog=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word;",
"identity":"Server=sql-data;Initial Catalog=Microsoft.eShopOnContainers.Services.IdentityDb;User Id=sa;Password=Pass@word;",
"ordering":"Server=sql-data;Initial Catalog=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word;",
"marketing":"Server=sql-data;Initial Catalog=Microsoft.eShopOnContainers.Services.MarketingDb;User Id=sa;Password=Pass@word;"
},
"nosql": {
"locations": {
"constr": "mongodb://nosql-data",
"db": "LocationsDb"
},
"marketing": {
"constr": "mongodb://nosql-data",
"db": "MarketingDb"
}
},
"redis": {
"basket" : "basket-data",
"keystore": "keystore-data"
},
"servicebus": {
"use_azure": false,
"ordering": "rabbitmq",
"marketing": "rabbitmq",
"locations": "rabbitmq",
"payment": "rabbitmq",
"basket": "rabbitmq",
"catalog": "rabbitmq"
}
}

View File

@ -71,6 +71,24 @@ http {
proxy_set_header Host $host;
}
location /marketing-api {
proxy_pass http://marketing;
proxy_redirect off;
proxy_set_header Host $host;
}
location /payment-api {
proxy_pass http://payment;
proxy_redirect off;
proxy_set_header Host $host;
}
location /locations-api {
proxy_pass http://locations;
proxy_redirect off;
proxy_set_header Host $host;
}
location / {
proxy_pass http://webspa;
proxy_redirect off;

30
k8s/nosql-data.yaml Normal file
View File

@ -0,0 +1,30 @@
apiVersion: v1
kind: Service
metadata:
labels:
app: eshop
component: nosql-data
name: nosql-data
spec:
ports:
- port: 27017
selector:
app: eshop
component: nosql-data
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nosql-data
spec:
template:
metadata:
labels:
app: eshop
component: nosql-data
spec:
containers:
- name: nosql-data
image: mongo
ports:
- containerPort: 27017

View File

@ -56,6 +56,48 @@ spec:
---
apiVersion: v1
kind: Service
metadata:
labels:
app: eshop
component: locations
name: locations
spec:
ports:
- port: 80
selector:
app: eshop
component: locations
---
apiVersion: v1
kind: Service
metadata:
labels:
app: eshop
component: payment
name: payment
spec:
ports:
- port: 80
selector:
app: eshop
component: payment
---
apiVersion: v1
kind: Service
metadata:
labels:
app: eshop
component: marketing
name: marketing
spec:
ports:
- port: 80
selector:
app: eshop
component: marketing
---
apiVersion: v1
kind: Service
metadata:
labels:
app: eshop

View File

@ -0,0 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.4</TargetFramework>
<RootNamespace>Microsoft.eShopOnContainers.BuildingBlocks.CommandBus</RootNamespace>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.BuildingBlocks.CommandBus
{
public interface ICommandBus
{
Task SendAsync<T>(T command) where T : IntegrationCommand;
}
}

View File

@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Microsoft.eShopOnContainers.BuildingBlocks.CommandBus
{
public abstract class IntegrationCommand
{
public Guid Id { get; private set; }
public DateTime Sent { get; private set; }
protected IntegrationCommand()
{
Id = Guid.NewGuid();
Sent = DateTime.UtcNow;
}
}
}

View File

@ -0,0 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.4</TargetFramework>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp1.1</TargetFramework>
<RootNamespace>Microsoft.eShopOnContainers.BuildingBlocks</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.DataProtection" Version="1.1.2" />
<PackageReference Include="StackExchange.Redis" Version="1.2.3" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,95 @@
namespace Microsoft.eShopOnContainers.BuildingBlocks
{
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.DataProtection.Repositories;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using System.Linq;
using System.Net;
/// <summary>
/// Extension methods for <see cref="IDataProtectionBuilder"/> for configuring
/// data protection options.
/// </summary>
public static class DataProtectionBuilderExtensions
{
/// <summary>
/// Sets up data protection to persist session keys in Redis.
/// </summary>
/// <param name="builder">The <see cref="IDataProtectionBuilder"/> used to set up data protection options.</param>
/// <param name="redisConnectionString">The connection string specifying the Redis instance and database for key storage.</param>
/// <returns>
/// The <paramref name="builder" /> for continued configuration.
/// </returns>
/// <exception cref="System.ArgumentNullException">
/// Thrown if <paramref name="builder" /> or <paramref name="redisConnectionString" /> is <see langword="null" />.
/// </exception>
/// <exception cref="System.ArgumentException">
/// Thrown if <paramref name="redisConnectionString" /> is empty.
/// </exception>
public static IDataProtectionBuilder PersistKeysToRedis(this IDataProtectionBuilder builder, string redisConnectionString)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
if (redisConnectionString == null)
{
throw new ArgumentNullException(nameof(redisConnectionString));
}
if (redisConnectionString.Length == 0)
{
throw new ArgumentException("Redis connection string may not be empty.", nameof(redisConnectionString));
}
var ips = Dns.GetHostAddressesAsync(redisConnectionString).Result;
return builder.Use(ServiceDescriptor.Singleton<IXmlRepository>(services =>
new RedisXmlRepository(ips.First().ToString(), services.GetRequiredService<ILogger<RedisXmlRepository>>())));
}
/// <summary>
/// Updates an <see cref="IDataProtectionBuilder"/> to use the service of
/// a specific type, removing all other services of that type.
/// </summary>
/// <param name="builder">The <see cref="IDataProtectionBuilder"/> that should use the specified service.</param>
/// <param name="descriptor">The <see cref="ServiceDescriptor"/> with the service the <paramref name="builder" /> should use.</param>
/// <returns>
/// The <paramref name="builder" /> for continued configuration.
/// </returns>
/// <exception cref="System.ArgumentNullException">
/// Thrown if <paramref name="builder" /> or <paramref name="descriptor" /> is <see langword="null" />.
/// </exception>
public static IDataProtectionBuilder Use(this IDataProtectionBuilder builder, ServiceDescriptor descriptor)
{
// This algorithm of removing all other services of a specific type
// before adding the new/replacement service is how the base ASP.NET
// DataProtection bits work. Due to some of the differences in how
// that base set of bits handles DI, it's better to follow suit
// and work in the same way than to try and debug weird issues.
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
if (descriptor == null)
{
throw new ArgumentNullException(nameof(descriptor));
}
for (int i = builder.Services.Count - 1; i >= 0; i--)
{
if (builder.Services[i]?.ServiceType == descriptor.ServiceType)
{
builder.Services.RemoveAt(i);
}
}
builder.Services.Add(descriptor);
return builder;
}
}
}

View File

@ -0,0 +1,210 @@
namespace Microsoft.eShopOnContainers.BuildingBlocks
{
using Microsoft.AspNetCore.DataProtection.Repositories;
using Microsoft.Extensions.Logging;
using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Xml.Linq;
/// <summary>
/// Key repository that stores XML encrypted keys in a Redis distributed cache.
/// </summary>
/// <remarks>
/// <para>
/// The values stored in Redis are XML documents that contain encrypted session
/// keys used for the protection of things like session state. The document contents
/// are double-encrypted - first with a changing session key; then by a master key.
/// As such, there's no risk in storing the keys in Redis - even if someone can crack
/// the master key, they still need to also crack the session key. (Other solutions
/// for sharing keys across a farm environment include writing them to files
/// on a file share.)
/// </para>
/// <para>
/// While the repository uses a hash to keep the set of encrypted keys separate, you
/// can further separate these items from other items in Redis by specifying a unique
/// database in the connection string.
/// </para>
/// <para>
/// Consumers of the repository are responsible for caching the XML items as needed.
/// Typically repositories are consumed by things like <see cref="Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingProvider"/>
/// which generates <see cref="Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.CacheableKeyRing"/>
/// values that get cached. The mechanism is already optimized for caching so there's
/// no need to create a redundant cache.
/// </para>
/// </remarks>
/// <seealso cref="Microsoft.AspNetCore.DataProtection.Repositories.IXmlRepository" />
/// <seealso cref="System.IDisposable" />
public class RedisXmlRepository : IXmlRepository, IDisposable
{
/// <summary>
/// The root cache key for XML items stored in Redis
/// </summary>
public static readonly string RedisHashKey = "DataProtectionXmlRepository";
/// <summary>
/// The connection to the Redis backing store.
/// </summary>
private IConnectionMultiplexer _connection;
/// <summary>
/// Flag indicating whether the object has been disposed.
/// </summary>
private bool _disposed = false;
/// <summary>
/// Initializes a new instance of the <see cref="RedisXmlRepository"/> class.
/// </summary>
/// <param name="connectionString">
/// The Redis connection string.
/// </param>
/// <param name="logger">
/// The <see cref="ILogger{T}"/> used to log diagnostic messages.
/// </param>
/// <exception cref="System.ArgumentNullException">
/// Thrown if <paramref name="connectionString" /> or <paramref name="logger" /> is <see langword="null" />.
/// </exception>
public RedisXmlRepository(string connectionString, ILogger<RedisXmlRepository> logger)
: this(ConnectionMultiplexer.Connect(connectionString), logger)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RedisXmlRepository"/> class.
/// </summary>
/// <param name="connection">
/// The Redis database connection.
/// </param>
/// <param name="logger">
/// The <see cref="ILogger{T}"/> used to log diagnostic messages.
/// </param>
/// <exception cref="System.ArgumentNullException">
/// Thrown if <paramref name="connection" /> or <paramref name="logger" /> is <see langword="null" />.
/// </exception>
public RedisXmlRepository(IConnectionMultiplexer connection, ILogger<RedisXmlRepository> logger)
{
if (connection == null)
{
throw new ArgumentNullException(nameof(connection));
}
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
this._connection = connection;
this.Logger = logger;
// Mask the password so it doesn't get logged.
var configuration = Regex.Replace(this._connection.Configuration, @"password\s*=\s*[^,]*", "password=****", RegexOptions.IgnoreCase);
this.Logger.LogDebug("Storing data protection keys in Redis: {RedisConfiguration}", configuration);
}
/// <summary>
/// Gets the logger.
/// </summary>
/// <value>
/// The <see cref="ILogger{T}"/> used to log diagnostic messages.
/// </value>
public ILogger<RedisXmlRepository> Logger { get; private set; }
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing,
/// or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
this.Dispose(true);
}
/// <summary>
/// Gets all top-level XML elements in the repository.
/// </summary>
/// <returns>
/// An <see cref="IReadOnlyCollection{T}"/> with the set of elements
/// stored in the repository.
/// </returns>
public IReadOnlyCollection<XElement> GetAllElements()
{
var database = this._connection.GetDatabase();
var hash = database.HashGetAll(RedisHashKey);
var elements = new List<XElement>();
if (hash == null || hash.Length == 0)
{
return elements.AsReadOnly();
}
foreach (var item in hash.ToStringDictionary())
{
elements.Add(XElement.Parse(item.Value));
}
this.Logger.LogDebug("Read {XmlElementCount} XML elements from Redis.", elements.Count);
return elements.AsReadOnly();
}
/// <summary>
/// Adds a top-level XML element to the repository.
/// </summary>
/// <param name="element">The element to add.</param>
/// <param name="friendlyName">
/// An optional name to be associated with the XML element.
/// For instance, if this repository stores XML files on disk, the friendly name may
/// be used as part of the file name. Repository implementations are not required to
/// observe this parameter even if it has been provided by the caller.
/// </param>
/// <remarks>
/// The <paramref name="friendlyName" /> parameter must be unique if specified.
/// For instance, it could be the ID of the key being stored.
/// </remarks>
/// <exception cref="System.ArgumentNullException">
/// Thrown if <paramref name="element" /> is <see langword="null" />.
/// </exception>
public void StoreElement(XElement element, string friendlyName)
{
if (element == null)
{
throw new ArgumentNullException(nameof(element));
}
if (string.IsNullOrEmpty(friendlyName))
{
// The framework always passes in a name, but
// the contract indicates this may be null or empty.
friendlyName = Guid.NewGuid().ToString();
}
this.Logger.LogDebug("Storing XML element with friendly name {XmlElementFriendlyName}.", friendlyName);
this._connection.GetDatabase().HashSet(RedisHashKey, friendlyName, element.ToString());
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="disposing">
/// <see langword="true" /> to release both managed and unmanaged resources;
/// <see langword="false" /> to release only unmanaged resources.
/// </param>
protected virtual void Dispose(bool disposing)
{
if (!this._disposed)
{
if (disposing)
{
if (this._connection != null)
{
this._connection.Close();
this._connection.Dispose();
}
}
this._connection = null;
this._disposed = true;
}
}
}
}

View File

@ -0,0 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.0</TargetFramework>
<RootNamespace>Microsoft.eShopOnContainers.BuildingBlocks.CommandBus</RootNamespace>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.BuildingBlocks.CommandBus
{
public interface ICommandBus
{
void Send<T>(string name, T data);
void Handle<TC>(string name, IIntegrationCommandHandler<TC> handler);
void Handle(string name, IIntegrationCommandHandler handler);
void Handle<TI, TC>(TI handler)
where TI : IIntegrationCommandHandler<TC>;
}
}

View File

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Microsoft.eShopOnContainers.BuildingBlocks.CommandBus
{
public interface IIntegrationCommandHandler
{
void Handle(IntegrationCommand command);
}
public interface IIntegrationCommandHandler<T> : IIntegrationCommandHandler
{
void Handle(IntegrationCommand<T> command);
}
}

View File

@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Microsoft.eShopOnContainers.BuildingBlocks.CommandBus
{
public abstract class IntegrationCommand
{
public Guid Id { get; }
public DateTime Sent { get; }
public abstract object GetDataAsObject();
protected IntegrationCommand()
{
Id = Guid.NewGuid();
Sent = DateTime.UtcNow;
}
}
public class IntegrationCommand<T> : IntegrationCommand
{
public T Data { get; }
public string Name { get; }
public override object GetDataAsObject() => Data;
public IntegrationCommand(string name, T data) : base()
{
Data = data;
Name = name;
}
}
}

View File

@ -18,7 +18,7 @@ namespace EventBus.Tests
public void After_One_Event_Subscription_Should_Contain_The_Event()
{
var manager = new InMemoryEventBusSubscriptionsManager();
manager.AddSubscription<TestIntegrationEvent,TestIntegrationEventHandler>(() => new TestIntegrationEventHandler());
manager.AddSubscription<TestIntegrationEvent,TestIntegrationEventHandler>();
Assert.True(manager.HasSubscriptionsForEvent<TestIntegrationEvent>());
}
@ -26,7 +26,7 @@ namespace EventBus.Tests
public void After_All_Subscriptions_Are_Deleted_Event_Should_No_Longer_Exists()
{
var manager = new InMemoryEventBusSubscriptionsManager();
manager.AddSubscription<TestIntegrationEvent, TestIntegrationEventHandler>(() => new TestIntegrationEventHandler());
manager.AddSubscription<TestIntegrationEvent, TestIntegrationEventHandler>();
manager.RemoveSubscription<TestIntegrationEvent, TestIntegrationEventHandler>();
Assert.False(manager.HasSubscriptionsForEvent<TestIntegrationEvent>());
}
@ -37,7 +37,7 @@ namespace EventBus.Tests
bool raised = false;
var manager = new InMemoryEventBusSubscriptionsManager();
manager.OnEventRemoved += (o, e) => raised = true;
manager.AddSubscription<TestIntegrationEvent, TestIntegrationEventHandler>(() => new TestIntegrationEventHandler());
manager.AddSubscription<TestIntegrationEvent, TestIntegrationEventHandler>();
manager.RemoveSubscription<TestIntegrationEvent, TestIntegrationEventHandler>();
Assert.True(raised);
}
@ -46,8 +46,8 @@ namespace EventBus.Tests
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());
manager.AddSubscription<TestIntegrationEvent, TestIntegrationEventHandler>();
manager.AddSubscription<TestIntegrationEvent, TestIntegrationOtherEventHandler>();
var handlers = manager.GetHandlersForEvent<TestIntegrationEvent>();
Assert.Equal(2, handlers.Count());
}

View File

@ -0,0 +1,13 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions
{
public interface IDynamicIntegrationEventHandler
{
Task Handle(dynamic eventData);
}
}

View File

@ -5,9 +5,15 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions
{
public interface IEventBus
{
void Subscribe<T, TH>(Func<TH> handler)
void Subscribe<T, TH>()
where T : IntegrationEvent
where TH : IIntegrationEventHandler<T>;
void SubscribeDynamic<TH>(string eventName)
where TH : IDynamicIntegrationEventHandler;
void UnsubscribeDynamic<TH>(string eventName)
where TH : IDynamicIntegrationEventHandler;
void Unsubscribe<T, TH>()
where TH : IIntegrationEventHandler<T>
where T : IntegrationEvent;

View File

@ -2,7 +2,6 @@
<PropertyGroup>
<TargetFramework>netcoreapp1.1</TargetFramework>
<RuntimeFrameworkVersion>1.1.0</RuntimeFrameworkVersion>
<RootNamespace>Microsoft.eShopOnContainers.BuildingBlocks.EventBus</RootNamespace>
</PropertyGroup>
@ -11,7 +10,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.2" />
</ItemGroup>
</Project>

View File

@ -2,6 +2,7 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using System;
using System.Collections.Generic;
using static Microsoft.eShopOnContainers.BuildingBlocks.EventBus.InMemoryEventBusSubscriptionsManager;
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus
{
@ -9,18 +10,25 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus
{
bool IsEmpty { get; }
event EventHandler<string> OnEventRemoved;
void AddSubscription<T, TH>(Func<TH> handler)
void AddDynamicSubscription<TH>(string eventName)
where TH : IDynamicIntegrationEventHandler;
void AddSubscription<T, TH>()
where T : IntegrationEvent
where TH : IIntegrationEventHandler<T>;
void RemoveSubscription<T, TH>()
where TH : IIntegrationEventHandler<T>
where T : IntegrationEvent;
void RemoveSubscription<T, TH>()
where TH : IIntegrationEventHandler<T>
where T : IntegrationEvent;
void RemoveDynamicSubscription<TH>(string eventName)
where TH : IDynamicIntegrationEventHandler;
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);
IEnumerable<SubscriptionInfo> GetHandlersForEvent<T>() where T : IntegrationEvent;
IEnumerable<SubscriptionInfo> GetHandlersForEvent(string eventName);
string GetEventKey<T>();
}
}

View File

@ -8,64 +8,106 @@ using System.Text;
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus
{
public class InMemoryEventBusSubscriptionsManager : IEventBusSubscriptionsManager
public partial class InMemoryEventBusSubscriptionsManager : IEventBusSubscriptionsManager
{
private readonly Dictionary<string, List<Delegate>> _handlers;
private readonly Dictionary<string, List<SubscriptionInfo>> _handlers;
private readonly List<Type> _eventTypes;
public event EventHandler<string> OnEventRemoved;
public InMemoryEventBusSubscriptionsManager()
{
_handlers = new Dictionary<string, List<Delegate>>();
_handlers = new Dictionary<string, List<SubscriptionInfo>>();
_eventTypes = new List<Type>();
}
public bool IsEmpty => !_handlers.Keys.Any();
public void Clear() => _handlers.Clear();
public void AddSubscription<T, TH>(Func<TH> handler)
public void AddDynamicSubscription<TH>(string eventName)
where TH : IDynamicIntegrationEventHandler
{
DoAddSubscription(typeof(TH), eventName, isDynamic: true);
}
public void AddSubscription<T, TH>()
where T : IntegrationEvent
where TH : IIntegrationEventHandler<T>
{
var key = GetEventKey<T>();
if (!HasSubscriptionsForEvent<T>())
{
_handlers.Add(key, new List<Delegate>());
}
_handlers[key].Add(handler);
var eventName = GetEventKey<T>();
DoAddSubscription(typeof(TH), eventName, isDynamic: false);
_eventTypes.Add(typeof(T));
}
private void DoAddSubscription(Type handlerType, string eventName, bool isDynamic)
{
if (!HasSubscriptionsForEvent(eventName))
{
_handlers.Add(eventName, new List<SubscriptionInfo>());
}
if (_handlers[eventName].Any(s => s.HandlerType == handlerType))
{
throw new ArgumentException(
$"Handler Type {handlerType.Name} already registered for '{eventName}'", nameof(handlerType));
}
if (isDynamic)
{
_handlers[eventName].Add(SubscriptionInfo.Dynamic(handlerType));
}
else
{
_handlers[eventName].Add(SubscriptionInfo.Typed(handlerType));
}
}
public void RemoveDynamicSubscription<TH>(string eventName)
where TH : IDynamicIntegrationEventHandler
{
var handlerToRemove = FindDynamicSubscriptionToRemove<TH>(eventName);
DoRemoveHandler(eventName, handlerToRemove);
}
public void RemoveSubscription<T, TH>()
where TH : IIntegrationEventHandler<T>
where T : IntegrationEvent
{
var handlerToRemove = FindHandlerToRemove<T, TH>();
if (handlerToRemove != null)
var handlerToRemove = FindSubscriptionToRemove<T, TH>();
var eventName = GetEventKey<T>();
DoRemoveHandler(eventName, handlerToRemove);
}
private void DoRemoveHandler(string eventName, SubscriptionInfo subsToRemove)
{
if (subsToRemove != null)
{
var key = GetEventKey<T>();
_handlers[key].Remove(handlerToRemove);
if (!_handlers[key].Any())
_handlers[eventName].Remove(subsToRemove);
if (!_handlers[eventName].Any())
{
_handlers.Remove(key);
var eventType = _eventTypes.SingleOrDefault(e => e.Name == key);
_handlers.Remove(eventName);
var eventType = _eventTypes.SingleOrDefault(e => e.Name == eventName);
if (eventType != null)
{
_eventTypes.Remove(eventType);
RaiseOnEventRemoved(eventType.Name);
}
RaiseOnEventRemoved(eventName);
}
}
}
public IEnumerable<Delegate> GetHandlersForEvent<T>() where T : IntegrationEvent
public IEnumerable<SubscriptionInfo> GetHandlersForEvent<T>() where T : IntegrationEvent
{
var key = GetEventKey<T>();
return GetHandlersForEvent(key);
}
public IEnumerable<Delegate> GetHandlersForEvent(string eventName) => _handlers[eventName];
public IEnumerable<SubscriptionInfo> GetHandlersForEvent(string eventName) => _handlers[eventName];
private void RaiseOnEventRemoved(string eventName)
{
@ -76,26 +118,31 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus
}
}
private Delegate FindHandlerToRemove<T, TH>()
where T : IntegrationEvent
where TH : IIntegrationEventHandler<T>
private SubscriptionInfo FindDynamicSubscriptionToRemove<TH>(string eventName)
where TH : IDynamicIntegrationEventHandler
{
if (!HasSubscriptionsForEvent<T>())
return DoFindSubscriptionToRemove(eventName, typeof(TH));
}
private SubscriptionInfo FindSubscriptionToRemove<T, TH>()
where T : IntegrationEvent
where TH : IIntegrationEventHandler<T>
{
var eventName = GetEventKey<T>();
return DoFindSubscriptionToRemove(eventName, typeof(TH));
}
private SubscriptionInfo DoFindSubscriptionToRemove(string eventName, Type handlerType)
{
if (!HasSubscriptionsForEvent(eventName))
{
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 _handlers[eventName].SingleOrDefault(s => s.HandlerType == handlerType);
return null;
}
public bool HasSubscriptionsForEvent<T>() where T : IntegrationEvent
@ -105,9 +152,9 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus
}
public bool HasSubscriptionsForEvent(string eventName) => _handlers.ContainsKey(eventName);
public Type GetEventTypeByName(string eventName) => _eventTypes.Single(t => t.Name == eventName);
public Type GetEventTypeByName(string eventName) => _eventTypes.SingleOrDefault(t => t.Name == eventName);
private string GetEventKey<T>()
public string GetEventKey<T>()
{
return typeof(T).Name;
}

View File

@ -0,0 +1,28 @@
using System;
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus
{
public partial class InMemoryEventBusSubscriptionsManager : IEventBusSubscriptionsManager
{
public class SubscriptionInfo
{
public bool IsDynamic { get; }
public Type HandlerType{ get; }
private SubscriptionInfo(bool isDynamic, Type handlerType)
{
IsDynamic = isDynamic;
HandlerType = handlerType;
}
public static SubscriptionInfo Dynamic(Type handlerType)
{
return new SubscriptionInfo(true, handlerType);
}
public static SubscriptionInfo Typed(Type handlerType)
{
return new SubscriptionInfo(false, handlerType);
}
}
}
}

View File

@ -0,0 +1,145 @@
//using Microsoft.eShopOnContainers.BuildingBlocks.CommandBus;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Polly;
using Polly.Retry;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using RabbitMQ.Client.Exceptions;
using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
/*
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
{
public class CommandBusRabbitMQ : ICommandBus, IDisposable
{
const string BROKER_NAME = "eshop_command_bus";
private readonly IRabbitMQPersistentConnection _persistentConnection;
private readonly ILogger<CommandBusRabbitMQ> _logger;
private IModel _consumerChannel;
private string _queueName;
private readonly Dictionary<string, IIntegrationCommandHandler> _handlers;
private readonly Dictionary<string, Type> _typeMappings;
public CommandBusRabbitMQ(IRabbitMQPersistentConnection persistentConnection,
ILogger<CommandBusRabbitMQ> logger)
{
_logger = logger;
_persistentConnection = persistentConnection;
_handlers = new Dictionary<string, IIntegrationCommandHandler>();
_typeMappings = new Dictionary<string, Type>();
}
public void Send<T>(string name, T data)
{
Send(new IntegrationCommand<T>(name, data));
}
public void Handle<TC>(string name, IIntegrationCommandHandler<TC> handler)
{
_handlers.Add(name, handler);
_typeMappings.Add(name, typeof(TC));
}
public void Handle(string name, IIntegrationCommandHandler handler)
{
_handlers.Add(name, handler);
}
public void Handle<TI, TC>(TI handler) where TI : IIntegrationCommandHandler<TC>
{
var name = typeof(TI).Name;
_handlers.Add(name, handler);
_typeMappings.Add(name, typeof(TC));
}
private void Send<T>(IntegrationCommand<T> command)
{
if (!_persistentConnection.IsConnected)
{
_persistentConnection.TryConnect();
}
var policy = RetryPolicy.Handle<BrokerUnreachableException>()
.Or<SocketException>()
.WaitAndRetry(5, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (ex, time) =>
{
_logger.LogWarning(ex.ToString());
});
using (var channel = _persistentConnection.CreateModel())
{
var commandName = command.Name;
channel.ExchangeDeclare(exchange: BROKER_NAME, type: "direct");
var message = JsonConvert.SerializeObject(command);
var body = Encoding.UTF8.GetBytes(message);
policy.Execute(() =>
{
channel.BasicPublish(exchange: BROKER_NAME,
routingKey: commandName,
basicProperties: null,
body: body);
});
}
}
private IModel CreateConsumerChannel()
{
if (!_persistentConnection.IsConnected)
{
_persistentConnection.TryConnect();
}
var channel = _persistentConnection.CreateModel();
channel.ExchangeDeclare(exchange: BROKER_NAME, type: "direct");
_queueName = channel.QueueDeclare().QueueName;
var consumer = new EventingBasicConsumer(channel);
consumer.Received += async (model, ea) =>
{
var commandName = ea.RoutingKey;
var message = Encoding.UTF8.GetString(ea.Body);
await InvokeHandler(commandName, message);
};
channel.BasicConsume(queue: _queueName,
noAck: true,
consumer: consumer);
channel.CallbackException += (sender, ea) =>
{
_consumerChannel.Dispose();
_consumerChannel = CreateConsumerChannel();
};
return channel;
}
private Task InvokeHandler(string commandName, string message)
{
if (_handlers.ContainsKey(commandName))
{
}
}
public void Dispose()
{
if (_consumerChannel != null)
{
_consumerChannel.Dispose();
}
}
}
}
*/

View File

@ -1,8 +1,10 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus;
using Autofac;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Polly;
using Polly.Retry;
using RabbitMQ.Client;
@ -25,17 +27,20 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
private readonly IRabbitMQPersistentConnection _persistentConnection;
private readonly ILogger<EventBusRabbitMQ> _logger;
private readonly IEventBusSubscriptionsManager _subsManager;
private readonly ILifetimeScope _autofac;
private readonly string AUTOFAC_SCOPE_NAME = "eshop_event_bus";
private IModel _consumerChannel;
private string _queueName;
public EventBusRabbitMQ(IRabbitMQPersistentConnection persistentConnection, ILogger<EventBusRabbitMQ> logger, IEventBusSubscriptionsManager subsManager)
public EventBusRabbitMQ(IRabbitMQPersistentConnection persistentConnection, ILogger<EventBusRabbitMQ> logger,
ILifetimeScope autofac, IEventBusSubscriptionsManager subsManager)
{
_persistentConnection = persistentConnection ?? throw new ArgumentNullException(nameof(persistentConnection));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_subsManager = subsManager ?? new InMemoryEventBusSubscriptionsManager();
_consumerChannel = CreateConsumerChannel();
_autofac = autofac;
_subsManager.OnEventRemoved += SubsManager_OnEventRemoved;
}
@ -96,12 +101,25 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
}
}
public void Subscribe<T, TH>(Func<TH> handler)
public void SubscribeDynamic<TH>(string eventName)
where TH : IDynamicIntegrationEventHandler
{
DoInternalSubscription(eventName);
_subsManager.AddDynamicSubscription<TH>(eventName);
}
public void Subscribe<T, TH>()
where T : IntegrationEvent
where TH : IIntegrationEventHandler<T>
{
var eventName = typeof(T).Name;
var containsKey = _subsManager.HasSubscriptionsForEvent<T>();
var eventName = _subsManager.GetEventKey<T>();
DoInternalSubscription(eventName);
_subsManager.AddSubscription<T, TH>();
}
private void DoInternalSubscription(string eventName)
{
var containsKey = _subsManager.HasSubscriptionsForEvent(eventName);
if (!containsKey)
{
if (!_persistentConnection.IsConnected)
@ -116,9 +134,6 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
routingKey: eventName);
}
}
_subsManager.AddSubscription<T, TH>(handler);
}
public void Unsubscribe<T, TH>()
@ -128,19 +143,13 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
_subsManager.RemoveSubscription<T, TH>();
}
private static Func<IIntegrationEventHandler> FindHandlerByType(Type handlerType, IEnumerable<Func<IIntegrationEventHandler>> handlers)
public void UnsubscribeDynamic<TH>(string eventName)
where TH : IDynamicIntegrationEventHandler
{
foreach (var func in handlers)
{
if (func.GetMethodInfo().ReturnType == handlerType)
{
return func;
}
}
return null;
_subsManager.RemoveDynamicSubscription<TH>(eventName);
}
public void Dispose()
{
if (_consumerChannel != null)
@ -190,17 +199,29 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
private async Task ProcessEvent(string eventName, string message)
{
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)
using (var scope = _autofac.BeginLifetimeScope(AUTOFAC_SCOPE_NAME))
{
var handler = handlerfactory.DynamicInvoke();
var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType);
await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent });
var subscriptions = _subsManager.GetHandlersForEvent(eventName);
foreach (var subscription in subscriptions)
{
if (subscription.IsDynamic)
{
var handler = scope.ResolveOptional(subscription.HandlerType) as IDynamicIntegrationEventHandler;
dynamic eventData = JObject.Parse(message);
await handler.Handle(eventData);
}
else
{
var eventType = _subsManager.GetEventTypeByName(eventName);
var integrationEvent = JsonConvert.DeserializeObject(message, eventType);
var handler = scope.ResolveOptional(subscription.HandlerType);
var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType);
await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent });
}
}
}
}
}

View File

@ -2,16 +2,16 @@
<PropertyGroup>
<TargetFramework>netcoreapp1.1</TargetFramework>
<RuntimeFrameworkVersion>1.1.0</RuntimeFrameworkVersion>
<RootNamespace>Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.1" />
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
<PackageReference Include="Polly" Version="5.0.6" />
<PackageReference Include="RabbitMQ.Client" Version="4.1.1" />
<PackageReference Include="System.ValueTuple" Version="4.3.0" />
<PackageReference Include="Autofac" Version="4.5.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.2" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.2" />
<PackageReference Include="Polly" Version="5.1.0" />
<PackageReference Include="RabbitMQ.Client" Version="4.1.3" />
<PackageReference Include="System.ValueTuple" Version="4.3.1" />
</ItemGroup>
<ItemGroup>

View File

@ -2,18 +2,17 @@
<PropertyGroup>
<TargetFramework>netcoreapp1.1</TargetFramework>
<RuntimeFrameworkVersion>1.1.0</RuntimeFrameworkVersion>
<RootNamespace>Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="1.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="1.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="1.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.Design" Version="1.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="1.1.0" />
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="1.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="1.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="1.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.Design" Version="1.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="1.1.1" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.2" />
</ItemGroup>
<ItemGroup>

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp1.0</TargetFramework>
<TargetFramework>netstandard1.3</TargetFramework>
</PropertyGroup>
<ItemGroup>
@ -9,7 +9,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="1.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="1.1.2" />
<ProjectReference Include="..\Microsoft.Extensions.HealthChecks\Microsoft.Extensions.HealthChecks.csproj" />
</ItemGroup>

View File

@ -9,7 +9,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Data.SqlClient" Version="4.3.0" />
<PackageReference Include="System.Data.SqlClient" Version="4.3.1" />
</ItemGroup>
<ItemGroup>

View File

@ -9,8 +9,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.0.2" />
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.2" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.2" />
<PackageReference Include="System.Diagnostics.Process" Version="4.3.0" />
<PackageReference Include="System.Threading.Tasks.Parallel" Version="4.3.0" />
<PackageReference Include="System.Threading.Thread" Version="4.3.0" />

View File

@ -6,9 +6,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="1.1.0" />
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
<PackageReference Include="Polly" Version="5.0.6" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="1.1.2" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.2" />
<PackageReference Include="Polly" Version="5.1.0" />
</ItemGroup>
</Project>

View File

@ -96,7 +96,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http
// as it is disposed after each call
var origin = GetOriginFromUri(uri);
return HttpInvoker(origin, () =>
return HttpInvoker(origin, async () =>
{
var requestMessage = new HttpRequestMessage(method, uri);
@ -112,7 +112,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http
requestMessage.Headers.Add("x-requestid", requestId);
}
var response = _client.SendAsync(requestMessage).Result;
var response = await _client.SendAsync(requestMessage);
// raise exception if HttpResponseCode 500
// needed for circuit breaker to track fails
@ -122,7 +122,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http
throw new HttpRequestException();
}
return Task.FromResult(response);
return response;
});
}
@ -132,13 +132,13 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http
if (!_policyWrappers.TryGetValue(normalizedOrigin, out PolicyWrap policyWrap))
{
policyWrap = Policy.Wrap(_policyCreator(normalizedOrigin).ToArray());
policyWrap = Policy.WrapAsync(_policyCreator(normalizedOrigin).ToArray());
_policyWrappers.TryAdd(normalizedOrigin, policyWrap);
}
// Executes the action applying all
// the policies defined in the wrapper
return await policyWrap.Execute(action, new Context(normalizedOrigin));
return await policyWrap.ExecuteAsync(action, new Context(normalizedOrigin));
}

View File

@ -1,12 +1,12 @@
#eShopOnContainers
# eShopOnContainers
eShopOnContainers is a reference app whose imagined purpose is to serve the mobile workforce of a fictitious company that sells products. The app allow to manage the catalog, view products, manage the basket and the orders.
<img src="Images/eShopOnContainers_Architecture_Diagram.png" alt="eShopOnContainers" Width="800" />
###Supported platforms: iOS, Android and Windows
### Supported platforms: iOS, Android and Windows
###The app architecture consists of two parts:
### The app architecture consists of two parts:
1. A Xamarin.Forms mobile app for iOS, Android and Windows.
2. Several .NET Web API microservices deployed as Docker containers.
@ -34,7 +34,7 @@ This project exercises the following platforms, frameworks or features:
* Entity Framework
* Identity Server 4
##Three platforms
## Three platforms
The app targets **three** platforms:
* iOS
@ -45,7 +45,7 @@ The app targets **three** platforms:
As of 07/03/2017, eShopOnContainers features **89.2% code share** (7.2% iOS / 16.7% Android / 8.7% Windows).
##Licenses
## Licenses
This project uses some third-party assets with a license that requires attribution:

View File

@ -1,7 +1,11 @@
using eShopOnContainers.Core.Helpers;
using System;
using eShopOnContainers.Core.Helpers;
using eShopOnContainers.Services;
using eShopOnContainers.Core.ViewModels.Base;
using System.Threading.Tasks;
using eShopOnContainers.Core.Models.Location;
using eShopOnContainers.Core.Services.Location;
using Plugin.Geolocator;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
@ -36,6 +40,7 @@ namespace eShopOnContainers
return navigationService.InitializeAsync();
}
protected override async void OnStart()
{
base.OnStart();
@ -44,6 +49,18 @@ namespace eShopOnContainers
{
await InitNavigation();
}
if (!Settings.UseFakeLocation)
{
await GetRealLocation();
}
if (!Settings.UseMocks && !string.IsNullOrEmpty(Settings.UserId))
{
await SendCurrentLocation();
}
base.OnResume();
}
protected override void OnSleep()
@ -51,9 +68,29 @@ namespace eShopOnContainers
// Handle when your app sleeps
}
protected override void OnResume()
private async Task GetRealLocation()
{
// Handle when your app resumes
var locator = CrossGeolocator.Current;
locator.AllowsBackgroundUpdates = true;
locator.DesiredAccuracy = 50;
var position = await locator.GetPositionAsync(20000);
Settings.Latitude = position.Latitude;
Settings.Longitude = position.Longitude;
}
private async Task SendCurrentLocation()
{
var location = new Location
{
Latitude = Settings.Latitude,
Longitude = Settings.Longitude
};
var locationService = ViewModelLocator.Resolve<ILocationService>();
await locationService.UpdateUserLocation(location,
Settings.AuthAccessToken);
}
}
}

View File

@ -6,7 +6,6 @@
public const string MockTag = "Mock";
public const string DefaultEndpoint = "http://13.88.8.119";
private string _baseEndpoint;
private static readonly GlobalSetting _instance = new GlobalSetting();
@ -31,6 +30,10 @@
}
}
public string ClientId { get { return "xamarin"; }}
public string ClientSecret { get { return "secret"; }}
public string AuthToken { get; set; }
public string RegisterWebsite { get; set; }
@ -43,8 +46,14 @@
public string IdentityEndpoint { get; set; }
public string LocationEndpoint { get; set; }
public string MarketingEndpoint { get; set; }
public string UserInfoEndpoint { get; set; }
public string TokenEndpoint { get; set; }
public string LogoutEndpoint { get; set; }
public string IdentityCallback { get; set; }
@ -53,15 +62,18 @@
private void UpdateEndpoint(string baseEndpoint)
{
RegisterWebsite = string.Format("{0}:5105/Account/Register", baseEndpoint);
CatalogEndpoint = string.Format("{0}:5101", baseEndpoint);
OrdersEndpoint = string.Format("{0}:5102", baseEndpoint);
BasketEndpoint = string.Format("{0}:5103", baseEndpoint);
IdentityEndpoint = string.Format("{0}:5105/connect/authorize", baseEndpoint);
UserInfoEndpoint = string.Format("{0}:5105/connect/userinfo", baseEndpoint);
LogoutEndpoint = string.Format("{0}:5105/connect/endsession", baseEndpoint);
IdentityCallback = string.Format("{0}:5105/xamarincallback", baseEndpoint);
LogoutCallback = string.Format("{0}:5105/Account/Redirecting", baseEndpoint);
RegisterWebsite = $"{baseEndpoint}:5105/Account/Register";
CatalogEndpoint = $"{baseEndpoint}:5101";
OrdersEndpoint = $"{baseEndpoint}:5102";
BasketEndpoint = $"{baseEndpoint}:5103";
IdentityEndpoint = $"{baseEndpoint}:5105/connect/authorize";
UserInfoEndpoint = $"{baseEndpoint}:5105/connect/userinfo";
TokenEndpoint = $"{baseEndpoint}:5105/connect/token";
LogoutEndpoint = $"{baseEndpoint}:5105/connect/endsession";
IdentityCallback = $"{baseEndpoint}:5105/xamarincallback";
LogoutCallback = $"{baseEndpoint}:5105/Account/Redirecting";
LocationEndpoint = $"{baseEndpoint}:5109";
MarketingEndpoint = $"{baseEndpoint}:5110";
}
}
}

View File

@ -1,5 +1,6 @@
using eShopOnContainers.Core.Models.Basket;
using eShopOnContainers.Core.Models.Catalog;
using eShopOnContainers.Core.Models.Marketing;
using eShopOnContainers.Core.ViewModels.Base;
using System;
using System.Collections.Generic;
@ -76,5 +77,38 @@ namespace eShopOnContainers.Core.Helpers
Debug.WriteLine(ex.Message);
}
}
public static void FixCatalogItemPictureUri(IEnumerable<CampaignItem> campaignItems)
{
if (campaignItems == null)
{
return;
}
try
{
if (!ViewModelLocator.UseMockService
&& Settings.UrlBase != GlobalSetting.DefaultEndpoint)
{
foreach (var catalogItem in campaignItems)
{
MatchCollection serverResult = IpRegex.Matches(catalogItem.PictureUri);
MatchCollection localResult = IpRegex.Matches(Settings.UrlBase);
if (serverResult.Count != -1 && localResult.Count != -1)
{
var serviceIp = serverResult[0].Value;
var localIp = localResult[0].Value;
catalogItem.PictureUri = catalogItem.PictureUri.Replace(serviceIp, localIp);
}
}
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
}
}

View File

@ -20,63 +20,69 @@ namespace eShopOnContainers.Core.Helpers
#region Setting Constants
private const string IdUserId = "user_id";
private const string AccessToken = "access_token";
private const string IdToken = "id_token";
private const string IdUseMocks = "use_mocks";
private const string IdUrlBase = "url_base";
private const string IdUseFakeLocation = "use_fake_location";
private const string IdLatitude = "latitude";
private const string IdLongitude = "flongitude";
private static readonly string AccessTokenDefault = string.Empty;
private static readonly string IdTokenDefault = string.Empty;
private static readonly bool UseMocksDefault = true;
private static readonly bool UseFakeLocationDefault = false;
private static readonly double FakeLatitudeValue = 47.604610d;
private static readonly double FakeLongitudeValue = -122.315752d;
private static readonly string UrlBaseDefault = GlobalSetting.Instance.BaseEndpoint;
#endregion
public static string UserId
{
get => AppSettings.GetValueOrDefault<string>(IdUserId);
set => AppSettings.AddOrUpdateValue<string>(IdUserId, value);
}
public static string AuthAccessToken
{
get
{
return AppSettings.GetValueOrDefault<string>(AccessToken, AccessTokenDefault);
}
set
{
AppSettings.AddOrUpdateValue<string>(AccessToken, value);
}
get => AppSettings.GetValueOrDefault<string>(AccessToken, AccessTokenDefault);
set => AppSettings.AddOrUpdateValue<string>(AccessToken, value);
}
public static string AuthIdToken
{
get
{
return AppSettings.GetValueOrDefault<string>(IdToken, IdTokenDefault);
}
set
{
AppSettings.AddOrUpdateValue<string>(IdToken, value);
}
get => AppSettings.GetValueOrDefault<string>(IdToken, IdTokenDefault);
set => AppSettings.AddOrUpdateValue<string>(IdToken, value);
}
public static bool UseMocks
{
get
{
return AppSettings.GetValueOrDefault<bool>(IdUseMocks, UseMocksDefault);
}
set
{
AppSettings.AddOrUpdateValue<bool>(IdUseMocks, value);
}
get => AppSettings.GetValueOrDefault<bool>(IdUseMocks, UseMocksDefault);
set => AppSettings.AddOrUpdateValue<bool>(IdUseMocks, value);
}
public static string UrlBase
{
get
{
return AppSettings.GetValueOrDefault<string>(IdUrlBase, UrlBaseDefault);
}
set
{
AppSettings.AddOrUpdateValue<string>(IdUrlBase, value);
}
get => AppSettings.GetValueOrDefault<string>(IdUrlBase, UrlBaseDefault);
set => AppSettings.AddOrUpdateValue<string>(IdUrlBase, value);
}
public static bool UseFakeLocation
{
get => AppSettings.GetValueOrDefault<bool>(IdUseFakeLocation, UseFakeLocationDefault);
set => AppSettings.AddOrUpdateValue<bool>(IdUseFakeLocation, value);
}
public static double Latitude
{
get => AppSettings.GetValueOrDefault<double>(IdLatitude, FakeLatitudeValue);
set => AppSettings.AddOrUpdateValue<double>(IdLatitude, value);
}
public static double Longitude
{
get => AppSettings.GetValueOrDefault<double>(IdLongitude, FakeLongitudeValue);
set => AppSettings.AddOrUpdateValue<double>(IdLongitude, value);
}
}
}

View File

@ -0,0 +1,36 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace eShopOnContainers.Core.Models.Basket
{
public class BasketCheckout
{
[Required]
public string City { get; set; }
[Required]
public string Street { get; set; }
[Required]
public string State { get; set; }
[Required]
public string Country { get; set; }
public string ZipCode { get; set; }
[Required]
public string CardNumber { get; set; }
[Required]
public string CardHolderName { get; set; }
[Required]
public DateTime CardExpiration { get; set; }
[Required]
public string CardSecurityNumber { get; set; }
public int CardTypeId { get; set; }
public string Buyer { get; set; }
[Required]
public Guid RequestId { get; set; }
}
}

View File

@ -0,0 +1,8 @@
namespace eShopOnContainers.Core.Models.Location
{
public class Location
{
public double Longitude { get; set; }
public double Latitude { get; set; }
}
}

View File

@ -0,0 +1,19 @@
namespace eShopOnContainers.Core.Models.Marketing
{
using System;
public class Campaign
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public DateTime From { get; set; }
public DateTime To { get; set; }
public string PictureUri { get; set; }
}
}

View File

@ -0,0 +1,19 @@
namespace eShopOnContainers.Core.Models.Marketing
{
using System;
public class CampaignItem
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public DateTime From { get; set; }
public DateTime To { get; set; }
public string PictureUri { get; set; }
}
}

View File

@ -0,0 +1,12 @@
namespace eShopOnContainers.Core.Models.Marketing
{
using System.Collections.Generic;
public class CampaignRoot
{
public int PageIndex { get; set; }
public int PageSize { get; set; }
public int Count { get; set; }
public List<CampaignItem> Data { get; set; }
}
}

View File

@ -0,0 +1,22 @@
using Newtonsoft.Json;
namespace eShopOnContainers.Core.Models.Token
{
public class UserToken
{
[JsonProperty("id_token")]
public string IdToken { get; set; }
[JsonProperty("access_token")]
public string AccessToken { get; set; }
[JsonProperty("expires_in")]
public int ExpiresIn { get; set; }
[JsonProperty("token_type")]
public string TokenType { get; set; }
[JsonProperty("refresh_token")]
public string RefreshToken { get; set; }
}
}

View File

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Xamarin.Forms;
using System;
namespace eShopOnContainers.Core.Services.Basket
{
@ -57,5 +58,10 @@ namespace eShopOnContainers.Core.Services.Basket
MockCustomBasket.Items.Clear();
}
}
public Task CheckoutAsync(BasketCheckout basketCheckout, string token)
{
throw new NotImplementedException();
}
}
}

View File

@ -42,6 +42,15 @@ namespace eShopOnContainers.Core.Services.Basket
return result;
}
public async Task CheckoutAsync(BasketCheckout basketCheckout, string token)
{
UriBuilder builder = new UriBuilder(GlobalSetting.Instance.BasketEndpoint + "/checkout");
string uri = builder.ToString();
await _requestProvider.PostAsync(uri, basketCheckout, token);
}
public async Task ClearBasketAsync(string guidUser, string token)
{
UriBuilder builder = new UriBuilder(GlobalSetting.Instance.BasketEndpoint);

View File

@ -7,6 +7,7 @@ namespace eShopOnContainers.Core.Services.Basket
{
Task<CustomerBasket> GetBasketAsync(string guidUser, string token);
Task<CustomerBasket> UpdateBasketAsync(CustomerBasket customerBasket, string token);
Task CheckoutAsync(BasketCheckout basketCheckout, string token);
Task ClearBasketAsync(string guidUser, string token);
}
}

View File

@ -7,5 +7,8 @@
public static string MockCatalogItemId03 = "3";
public static string MockCatalogItemId04 = "4";
public static string MockCatalogItemId05 = "5";
public static int MockCampaignd01 = 1;
public static int MockCampaignd02 = 2;
}
}

View File

@ -1,8 +1,12 @@
namespace eShopOnContainers.Core.Services.Identity
using eShopOnContainers.Core.Models.Token;
using System.Threading.Tasks;
namespace eShopOnContainers.Core.Services.Identity
{
public interface IIdentityService
{
string CreateAuthorizationRequest();
string CreateLogoutRequest(string token);
Task<UserToken> GetTokenAsync(string code);
}
}

View File

@ -1,11 +1,22 @@
using IdentityModel.Client;
using System;
using System.Collections.Generic;
using System.Net;
using System.Threading.Tasks;
using eShopOnContainers.Core.Services.RequestProvider;
using eShopOnContainers.Core.Models.Token;
namespace eShopOnContainers.Core.Services.Identity
{
public class IdentityService : IIdentityService
{
private readonly IRequestProvider _requestProvider;
public IdentityService(IRequestProvider requestProvider)
{
_requestProvider = requestProvider;
}
public string CreateAuthorizationRequest()
{
// Create URI to authorization endpoint
@ -13,10 +24,10 @@ namespace eShopOnContainers.Core.Services.Identity
// Dictionary with values for the authorize request
var dic = new Dictionary<string, string>();
dic.Add("client_id", "xamarin");
dic.Add("response_type", "id_token token");
dic.Add("scope", "openid profile basket orders");
dic.Add("client_id", GlobalSetting.Instance.ClientId);
dic.Add("client_secret", GlobalSetting.Instance.ClientSecret);
dic.Add("response_type", "code id_token");
dic.Add("scope", "openid profile basket orders locations marketing offline_access");
dic.Add("redirect_uri", GlobalSetting.Instance.IdentityCallback);
dic.Add("nonce", Guid.NewGuid().ToString("N"));
@ -30,7 +41,7 @@ namespace eShopOnContainers.Core.Services.Identity
public string CreateLogoutRequest(string token)
{
if(string.IsNullOrEmpty(token))
if (string.IsNullOrEmpty(token))
{
return string.Empty;
}
@ -40,5 +51,12 @@ namespace eShopOnContainers.Core.Services.Identity
token,
GlobalSetting.Instance.LogoutCallback);
}
public async Task<UserToken> GetTokenAsync(string code)
{
string data = string.Format("grant_type=authorization_code&code={0}&redirect_uri={1}", code, WebUtility.UrlEncode(GlobalSetting.Instance.IdentityCallback));
var token = await _requestProvider.PostAsync<UserToken>(GlobalSetting.Instance.TokenEndpoint, data, GlobalSetting.Instance.ClientId, GlobalSetting.Instance.ClientSecret);
return token;
}
}
}

View File

@ -0,0 +1,10 @@
namespace eShopOnContainers.Core.Services.Location
{
using System.Threading.Tasks;
using eShopOnContainers.Core.Models.Location;
public interface ILocationService
{
Task UpdateUserLocation(Location newLocReq, string token);
}
}

View File

@ -0,0 +1,28 @@
namespace eShopOnContainers.Core.Services.Location
{
using eShopOnContainers.Core.Models.Location;
using eShopOnContainers.Core.Services.RequestProvider;
using System;
using System.Threading.Tasks;
public class LocationService : ILocationService
{
private readonly IRequestProvider _requestProvider;
public LocationService(IRequestProvider requestProvider)
{
_requestProvider = requestProvider;
}
public async Task UpdateUserLocation(Location newLocReq, string token)
{
UriBuilder builder = new UriBuilder(GlobalSetting.Instance.LocationEndpoint);
builder.Path = "api/v1/locations";
string uri = builder.ToString();
await _requestProvider.PostAsync(uri, newLocReq, token);
}
}
}

View File

@ -0,0 +1,53 @@
using System.Collections.Generic;
using System.Linq;
namespace eShopOnContainers.Core.Services.Marketing
{
using System;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using Models.Marketing;
using Xamarin.Forms;
public class CampaignMockService : ICampaignService
{
private readonly ObservableCollection<CampaignItem> _mockCampaign = new ObservableCollection<CampaignItem>
{
new CampaignItem
{
Id = Common.Common.MockCampaignd01,
PictureUri = Device.RuntimePlatform != Device.Windows
? "fake_campaign_01.png"
: "Assets/fake_campaign_01.png",
Name = ".NET Bot Black Hoodie 50% OFF",
Description = "Campaign Description 1",
From = DateTime.Now,
To = DateTime.Now.AddDays(7)
},
new CampaignItem
{
Id = Common.Common.MockCampaignd02,
PictureUri = Device.RuntimePlatform != Device.Windows
? "fake_campaign_02.png"
: "Assets/fake_campaign_02.png",
Name = "Roslyn Red T-Shirt 3x2",
Description = "Campaign Description 2",
From = DateTime.Now.AddDays(-7),
To = DateTime.Now.AddDays(14)
}
};
public async Task<ObservableCollection<CampaignItem>> GetAllCampaignsAsync(string userId, string token)
{
await Task.Delay(500);
return _mockCampaign;
}
public async Task<CampaignItem> GetCampaignByIdAsync(int campaignId, string token)
{
return _mockCampaign.SingleOrDefault(c => c.Id == campaignId);
}
}
}

View File

@ -0,0 +1,53 @@
using eShopOnContainers.Core.Extensions;
using eShopOnContainers.Core.Helpers;
namespace eShopOnContainers.Core.Services.Marketing
{
using System;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using Models.Marketing;
using RequestProvider;
public class CampaignService : ICampaignService
{
private readonly IRequestProvider _requestProvider;
public CampaignService(IRequestProvider requestProvider)
{
_requestProvider = requestProvider;
}
public async Task<ObservableCollection<CampaignItem>> GetAllCampaignsAsync(string userId, string token)
{
UriBuilder builder = new UriBuilder(GlobalSetting.Instance.MarketingEndpoint);
builder.Path = $"api/v1/campaigns/user/{userId}";
string uri = builder.ToString();
CampaignRoot campaign =
await _requestProvider.GetAsync<CampaignRoot>(uri, token);
if (campaign?.Data != null)
{
ServicesHelper.FixCatalogItemPictureUri(campaign?.Data);
return campaign?.Data.ToObservableCollection();
}
return new ObservableCollection<CampaignItem>();
}
public async Task<CampaignItem> GetCampaignByIdAsync(int campaignId, string token)
{
UriBuilder builder = new UriBuilder(GlobalSetting.Instance.MarketingEndpoint);
builder.Path = $"api/v1/campaigns/{campaignId}";
string uri = builder.ToString();
return await _requestProvider.GetAsync<CampaignItem>(uri, token);
}
}
}

View File

@ -0,0 +1,14 @@

namespace eShopOnContainers.Core.Services.Marketing
{
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using Models.Marketing;
public interface ICampaignService
{
Task<ObservableCollection<CampaignItem>> GetAllCampaignsAsync(string userId, string token);
Task<CampaignItem> GetCampaignByIdAsync(int id, string token);
}
}

View File

@ -1,13 +1,15 @@
using System.Collections.ObjectModel;
using eShopOnContainers.Core.Models.Basket;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
namespace eShopOnContainers.Core.Services.Order
{
public interface IOrderService
{
Task CreateOrderAsync(Models.Orders.Order newOrder, string token);
//Task CreateOrderAsync(Models.Orders.Order newOrder, string token);
Task<ObservableCollection<Models.Orders.Order>> GetOrdersAsync(string token);
Task<Models.Orders.Order> GetOrderAsync(int orderId, string token);
Task<ObservableCollection<Models.Orders.CardType>> GetCardTypesAsync(string token);
BasketCheckout MapOrderToBasket(Models.Orders.Order order);
}
}

View File

@ -1,4 +1,5 @@
using eShopOnContainers.Core.Extensions;
using eShopOnContainers.Core.Models.Basket;
using eShopOnContainers.Core.Models.Orders;
using eShopOnContainers.Core.Models.User;
using System;
@ -64,17 +65,18 @@ namespace eShopOnContainers.Core.Services.Order
new CardType { Id = 3, Name = "MasterCard" },
};
public async Task CreateOrderAsync(Models.Orders.Order newOrder, string token)
private static BasketCheckout MockBasketCheckout = new BasketCheckout()
{
await Task.Delay(500);
if (!string.IsNullOrEmpty(token))
{
newOrder.OrderNumber = string.Format("{0}", MockOrders.Count + 1);
MockOrders.Insert(0, newOrder);
}
}
CardExpiration = DateTime.UtcNow,
CardHolderName = "FakeCardHolderName",
CardNumber = "122333423224",
CardSecurityNumber = "1234",
CardTypeId = 1,
City = "FakeCity",
Country = "FakeCountry",
ZipCode = "FakeZipCode",
Street = "FakeStreet"
};
public async Task<ObservableCollection<Models.Orders.Order>> GetOrdersAsync(string token)
{
@ -111,5 +113,10 @@ namespace eShopOnContainers.Core.Services.Order
else
return new ObservableCollection<CardType>();
}
public BasketCheckout MapOrderToBasket(Models.Orders.Order order)
{
return MockBasketCheckout;
}
}
}

View File

@ -1,4 +1,5 @@
using eShopOnContainers.Core.Services.RequestProvider;
using eShopOnContainers.Core.Models.Basket;
using eShopOnContainers.Core.Services.RequestProvider;
using System;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
@ -14,17 +15,6 @@ namespace eShopOnContainers.Core.Services.Order
_requestProvider = requestProvider;
}
public async Task CreateOrderAsync(Models.Orders.Order newOrder, string token)
{
UriBuilder builder = new UriBuilder(GlobalSetting.Instance.OrdersEndpoint);
builder.Path = "api/v1/orders/new";
string uri = builder.ToString();
await _requestProvider.PostAsync(uri, newOrder, token, "x-requestid");
}
public async Task<ObservableCollection<Models.Orders.Order>> GetOrdersAsync(string token)
{
@ -82,5 +72,21 @@ namespace eShopOnContainers.Core.Services.Order
return new ObservableCollection<Models.Orders.CardType>();
}
}
public BasketCheckout MapOrderToBasket(Models.Orders.Order order)
{
return new BasketCheckout()
{
CardExpiration = order.CardExpiration,
CardHolderName = order.CardHolderName,
CardNumber = order.CardNumber,
CardSecurityNumber = order.CardSecurityNumber,
CardTypeId = order.CardTypeId,
City = order.ShippingCity,
Country = order.ShippingCountry,
ZipCode = order.ShippingZipCode,
Street = order.ShippingStreet
};
}
}
}

View File

@ -8,6 +8,8 @@ namespace eShopOnContainers.Core.Services.RequestProvider
Task<TResult> PostAsync<TResult>(string uri, TResult data, string token = "", string header = "");
Task<TResult> PostAsync<TResult>(string uri, string data, string clientId, string clientSecret);
Task DeleteAsync(string uri, string token = "");
}
}

View File

@ -61,6 +61,28 @@ namespace eShopOnContainers.Core.Services.RequestProvider
return result;
}
public async Task<TResult> PostAsync<TResult>(string uri, string data, string clientId, string clientSecret)
{
HttpClient httpClient = CreateHttpClient(string.Empty);
if (!string.IsNullOrWhiteSpace(clientId) && !string.IsNullOrWhiteSpace(clientSecret))
{
AddBasicAuthenticationHeader(httpClient, clientId, clientSecret);
}
var content = new StringContent(data);
content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
HttpResponseMessage response = await httpClient.PostAsync(uri, content);
await HandleResponse(response);
string serialized = await response.Content.ReadAsStringAsync();
TResult result = await Task.Run(() =>
JsonConvert.DeserializeObject<TResult>(serialized, _serializerSettings));
return result;
}
public async Task DeleteAsync(string uri, string token = "")
{
HttpClient httpClient = CreateHttpClient(token);
@ -90,6 +112,17 @@ namespace eShopOnContainers.Core.Services.RequestProvider
httpClient.DefaultRequestHeaders.Add(parameter, Guid.NewGuid().ToString());
}
private void AddBasicAuthenticationHeader(HttpClient httpClient, string clientId, string clientSecret)
{
if (httpClient == null)
return;
if (string.IsNullOrWhiteSpace(clientId) || string.IsNullOrWhiteSpace(clientSecret))
return;
httpClient.DefaultRequestHeaders.Authorization = new BasicAuthenticationHeaderValue(clientId, clientSecret);
}
private async Task HandleResponse(HttpResponseMessage response)
{
if (!response.IsSuccessStatusCode)

View File

@ -11,6 +11,8 @@ using eShopOnContainers.Core.Services.Identity;
using eShopOnContainers.Core.Services.Order;
using eShopOnContainers.Core.Services.User;
using Xamarin.Forms;
using eShopOnContainers.Core.Services.Location;
using eShopOnContainers.Core.Services.Marketing;
namespace eShopOnContainers.Core.ViewModels.Base
{
@ -46,22 +48,26 @@ namespace eShopOnContainers.Core.ViewModels.Base
builder.RegisterType<OrderDetailViewModel>();
builder.RegisterType<ProfileViewModel>();
builder.RegisterType<SettingsViewModel>();
builder.RegisterType<CampaignViewModel>();
builder.RegisterType<CampaignDetailsViewModel>();
// Services
builder.RegisterType<NavigationService>().As<INavigationService>().SingleInstance();
// Services
builder.RegisterType<NavigationService>().As<INavigationService>().SingleInstance();
builder.RegisterType<DialogService>().As<IDialogService>();
builder.RegisterType<OpenUrlService>().As<IOpenUrlService>();
builder.RegisterType<IdentityService>().As<IIdentityService>();
builder.RegisterType<RequestProvider>().As<IRequestProvider>();
builder.RegisterType<LocationService>().As<ILocationService>().SingleInstance();
if (useMockServices)
if (useMockServices)
{
builder.RegisterInstance(new CatalogMockService()).As<ICatalogService>();
builder.RegisterInstance(new BasketMockService()).As<IBasketService>();
builder.RegisterInstance(new OrderMockService()).As<IOrderService>();
builder.RegisterInstance(new UserMockService()).As<IUserService>();
builder.RegisterInstance(new CampaignMockService()).As<ICampaignService>();
UseMockService = true;
UseMockService = true;
}
else
{
@ -69,8 +75,9 @@ namespace eShopOnContainers.Core.ViewModels.Base
builder.RegisterType<BasketService>().As<IBasketService>().SingleInstance();
builder.RegisterType<OrderService>().As<IOrderService>().SingleInstance();
builder.RegisterType<UserService>().As<IUserService>().SingleInstance();
builder.RegisterType<CampaignService>().As<ICampaignService>().SingleInstance();
UseMockService = false;
UseMockService = false;
}
if (_container != null)

View File

@ -0,0 +1,42 @@
namespace eShopOnContainers.Core.ViewModels
{
using System.Threading.Tasks;
using Helpers;
using Models.Marketing;
using Services.Marketing;
using Base;
public class CampaignDetailsViewModel : ViewModelBase
{
private CampaignItem _campaign;
private readonly ICampaignService _campaignService;
public CampaignDetailsViewModel(ICampaignService campaignService)
{
_campaignService = campaignService;
}
public CampaignItem Campaign
{
get => _campaign;
set
{
_campaign = value;
RaisePropertyChanged(() => Campaign);
}
}
public override async Task InitializeAsync(object navigationData)
{
if (navigationData is int)
{
IsBusy = true;
// Get campaign by id
Campaign = await _campaignService.GetCampaignByIdAsync((int) navigationData, Settings.AuthAccessToken);
IsBusy = false;
}
}
}
}

View File

@ -0,0 +1,49 @@
namespace eShopOnContainers.Core.ViewModels
{
using System.Threading.Tasks;
using System.Windows.Input;
using Xamarin.Forms;
using System.Collections.ObjectModel;
using Models.Marketing;
using Services.Marketing;
using Base;
using Helpers;
public class CampaignViewModel : ViewModelBase
{
private ObservableCollection<CampaignItem> _campaigns;
private readonly ICampaignService _campaignService;
public CampaignViewModel(ICampaignService campaignService)
{
_campaignService = campaignService;
}
public ObservableCollection<CampaignItem> Campaigns
{
get => _campaigns;
set
{
_campaigns = value;
RaisePropertyChanged(() => Campaigns);
}
}
public ICommand GetCampaignDetailsCommand => new Command<CampaignItem>(async (item) => await GetCampaignDetails(item));
public override async Task InitializeAsync(object navigationData)
{
IsBusy = true;
// Get campaigns by user
Campaigns = await _campaignService.GetAllCampaignsAsync(Settings.UserId, Settings.AuthAccessToken);
IsBusy = false;
}
private async Task GetCampaignDetails(CampaignItem campaign)
{
await NavigationService.NavigateToAsync<CampaignDetailsViewModel>(campaign.Id);
}
}
}

View File

@ -132,8 +132,10 @@ namespace eShopOnContainers.Core.ViewModels
{
var authToken = Settings.AuthAccessToken;
// Create new order
await _orderService.CreateOrderAsync(Order, authToken);
var basket = _orderService.MapOrderToBasket(Order);
// Create basket checkout
await _basketService.CheckoutAsync(basket, authToken);
// Clean Basket
await _basketService.ClearBasketAsync(_shippingAddress.Id.ToString(), authToken);

View File

@ -203,20 +203,22 @@ namespace eShopOnContainers.Core.ViewModels
private void Logout()
{
var authIdToken = Settings.AuthIdToken;
var logoutRequest = _identityService.CreateLogoutRequest(authIdToken);
if(!string.IsNullOrEmpty(logoutRequest))
if (!string.IsNullOrEmpty(logoutRequest))
{
// Logout
LoginUrl = logoutRequest;
}
if(Settings.UseMocks)
if (Settings.UseMocks)
{
Settings.AuthAccessToken = string.Empty;
Settings.AuthIdToken = string.Empty;
}
Settings.UserId = string.Empty;
Settings.UseFakeLocation = false;
}
private async Task NavigateAsync(string url)
@ -233,14 +235,16 @@ namespace eShopOnContainers.Core.ViewModels
else if (unescapedUrl.Contains(GlobalSetting.Instance.IdentityCallback))
{
var authResponse = new AuthorizeResponse(url);
if (!string.IsNullOrWhiteSpace(authResponse.AccessToken))
if (!string.IsNullOrWhiteSpace(authResponse.Code))
{
if (authResponse.AccessToken != null)
{
Settings.AuthAccessToken = authResponse.AccessToken;
Settings.AuthIdToken = authResponse.IdentityToken;
var userToken = await _identityService.GetTokenAsync(authResponse.Code);
string accessToken = userToken.AccessToken;
if (!string.IsNullOrWhiteSpace(accessToken))
{
Settings.AuthAccessToken = accessToken;
Settings.AuthIdToken = authResponse.IdentityToken;
Settings.UserId = authResponse.Values["sub"];
await NavigationService.NavigateToAsync<MainViewModel>();
await NavigationService.RemoveLastFromBackStackAsync();
}

View File

@ -4,38 +4,51 @@ using Xamarin.Forms;
using System.Threading.Tasks;
using eShopOnContainers.Core.Helpers;
using eShopOnContainers.Core.Models.User;
using System;
using eShopOnContainers.Core.Models.Location;
using eShopOnContainers.Core.Services.Location;
namespace eShopOnContainers.Core.ViewModels
{
public class SettingsViewModel : ViewModelBase
{
private string _title;
private string _description;
private string _titleUseAzureServices;
private string _descriptionUseAzureServices;
private bool _useAzureServices;
private string _titleUseFakeLocation;
private string _descriptionUseFakeLocation;
private bool _useFakeLocation;
private string _endpoint;
private double _latitude;
private double _longitude;
private ILocationService _locationService;
public SettingsViewModel()
public SettingsViewModel(ILocationService locationService)
{
UseAzureServices = !Settings.UseMocks;
_useFakeLocation = Settings.UseFakeLocation;
_latitude = Settings.Latitude;
_longitude = Settings.Longitude;
_locationService = locationService;
}
public string Title
public string TitleUseAzureServices
{
get { return _title; }
get { return _titleUseAzureServices; }
set
{
_title = value;
RaisePropertyChanged(() => Title);
_titleUseAzureServices = value;
RaisePropertyChanged(() => TitleUseAzureServices);
}
}
public string Description
public string DescriptionUseAzureServices
{
get { return _description; }
get { return _descriptionUseAzureServices; }
set
{
_description = value;
RaisePropertyChanged(() => Description);
_descriptionUseAzureServices = value;
RaisePropertyChanged(() => DescriptionUseAzureServices);
}
}
@ -52,6 +65,39 @@ namespace eShopOnContainers.Core.ViewModels
}
}
public string TitleUseFakeLocation
{
get { return _titleUseFakeLocation; }
set
{
_titleUseFakeLocation = value;
RaisePropertyChanged(() => TitleUseFakeLocation);
}
}
public string DescriptionUseFakeLocation
{
get { return _descriptionUseFakeLocation; }
set
{
_descriptionUseFakeLocation = value;
RaisePropertyChanged(() => DescriptionUseFakeLocation);
}
}
public bool UseFakeLocation
{
get { return _useFakeLocation; }
set
{
_useFakeLocation = value;
// Save use fake location services to local storage
Settings.UseFakeLocation = _useFakeLocation;
RaisePropertyChanged(() => UseFakeLocation);
}
}
public string Endpoint
{
get { return _endpoint; }
@ -68,12 +114,49 @@ namespace eShopOnContainers.Core.ViewModels
}
}
public double Latitude
{
get { return _latitude; }
set
{
_latitude = value;
UpdateLatitude(_latitude);
RaisePropertyChanged(() => Latitude);
}
}
public double Longitude
{
get { return _longitude; }
set
{
_longitude = value;
UpdateLongitude(_longitude);
RaisePropertyChanged(() => Longitude);
}
}
public bool UserIsLogged => !string.IsNullOrEmpty(Settings.AuthAccessToken);
public ICommand ToggleMockServicesCommand => new Command(async () => await ToggleMockServicesAsync());
public ICommand ToggleFakeLocationCommand => new Command(() => ToggleFakeLocationAsync());
public ICommand ToggleSendLocationCommand => new Command(async () => await ToggleSendLocationAsync());
public override Task InitializeAsync(object navigationData)
{
UpdateInfo();
UpdateInfoFakeLocation();
Endpoint = Settings.UrlBase;
_latitude = Settings.Latitude;
_longitude = Settings.Longitude;
_useFakeLocation = Settings.UseFakeLocation;
return base.InitializeAsync(navigationData);
}
@ -100,17 +183,52 @@ namespace eShopOnContainers.Core.ViewModels
}
}
private void UpdateInfo()
private void ToggleFakeLocationAsync()
{
ViewModelLocator.RegisterDependencies(!UseAzureServices);
UpdateInfoFakeLocation();
}
private async Task ToggleSendLocationAsync()
{
if (!Settings.UseMocks)
{
var locationRequest = new Location
{
Latitude = _latitude,
Longitude = _longitude
};
var authToken = Settings.AuthAccessToken;
await _locationService.UpdateUserLocation(locationRequest, authToken);
}
}
private void UpdateInfo()
{
if (!UseAzureServices)
{
Title = "Use Mock Services";
Description = "Mock Services are simulated objects that mimic the behavior of real services using a controlled approach.";
TitleUseAzureServices = "Use Mock Services";
DescriptionUseAzureServices = "Mock Services are simulated objects that mimic the behavior of real services using a controlled approach.";
}
else
{
Title = "Use Microservices/Containers from eShopOnContainers";
Description = "When enabling the use of microservices/containers, the app will attempt to use real services deployed as Docker containers at the specified base endpoint, which will must be reachable through the network.";
TitleUseAzureServices = "Use Microservices/Containers from eShopOnContainers";
DescriptionUseAzureServices = "When enabling the use of microservices/containers, the app will attempt to use real services deployed as Docker containers at the specified base endpoint, which will must be reachable through the network.";
}
}
private void UpdateInfoFakeLocation()
{
if (!UseFakeLocation)
{
TitleUseFakeLocation = "Use Fake Location";
DescriptionUseFakeLocation = "Fake Location are added for marketing campaign testing.";
}
else
{
TitleUseFakeLocation = "Use Real Location";
DescriptionUseFakeLocation = "When enabling the use of real location, the app will attempt to use real location from the device.";
}
}
@ -119,5 +237,17 @@ namespace eShopOnContainers.Core.ViewModels
// Update remote endpoint (save to local storage)
Settings.UrlBase = endpoint;
}
private void UpdateLatitude(double latitude)
{
// Update fake latitude (save to local storage)
Settings.Latitude = latitude;
}
private void UpdateLongitude(double longitude)
{
// Update fake longitude (save to local storage)
Settings.Longitude = longitude;
}
}
}

View File

@ -0,0 +1,113 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="eShopOnContainers.Core.Views.CampaignDetailsView"
xmlns:viewModelBase="clr-namespace:eShopOnContainers.Core.ViewModels.Base;assembly=eShopOnContainers.Core"
viewModelBase:ViewModelLocator.AutoWireViewModel="true"
Title="Campaign Details">
<ContentPage.Resources>
<ResourceDictionary>
<Style x:Key="CampaignStyle"
TargetType="{x:Type StackLayout}">
<Setter Property="VerticalOptions"
Value="Center" />
<Setter Property="Margin"
Value="0" />
</Style>
<Style x:Key="CampaignTitleStyle"
TargetType="{x:Type Label}">
<Setter Property="FontFamily"
Value="{StaticResource MontserratRegular}" />
<Setter Property="FontSize"
Value="{StaticResource MediumSize}" />
<Setter Property="HorizontalOptions"
Value="Start" />
<Setter Property="VerticalOptions"
Value="Center" />
<Setter Property="Margin"
Value="12, 0" />
</Style>
<Style x:Key="CampaignDescriptionStyle"
TargetType="{x:Type Label}"
BasedOn="{StaticResource CampaignTitleStyle}">
<Setter Property="FontSize"
Value="{StaticResource LittleSize}" />
</Style>
</ResourceDictionary>
</ContentPage.Resources>
<Grid
ColumnSpacing="0"
RowSpacing="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- CAMPAIGN DETAILS -->
<ScrollView>
<StackLayout
x:Name="Campaign">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="1" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="1" />
</Grid.RowDefinitions>
<Grid
Grid.Row="0"
Grid.Column="0"
Grid.ColumnSpan="2"
BackgroundColor="Gray"/>
<StackLayout
Style ="{StaticResource CampaignStyle}"
Grid.Column="0"
Grid.Row="1">
<Image
Source="{Binding Campaign.PictureUri, Converter={StaticResource ImageConverter}}"
Aspect="AspectFit"
VerticalOptions="Start"
Margin="12,0,0,0" />
<Label
Text="{Binding Campaign.Name}"
TextColor="{StaticResource GreenColor}"
Style="{StaticResource CampaignTitleStyle}"/>
<Label
Text="{Binding Campaign.Description}"
Style="{StaticResource CampaignDescriptionStyle}"/>
<StackLayout
HorizontalOptions="Center"
Margin="12,0,0,0" >
<Label
Text="{Binding Campaign.From, StringFormat='From {0:MMMM dd, yyyy}'}"
Style="{StaticResource CampaignDescriptionStyle}"/>
<Label
Text="{Binding Campaign.To, StringFormat='until {0:MMMM dd, yyyy}'}"
Style="{StaticResource CampaignDescriptionStyle}"/>
</StackLayout>
</StackLayout>
</Grid>
</StackLayout>
</ScrollView>
<!-- INDICATOR -->
<ActivityIndicator
Grid.Row="0"
Grid.RowSpan="2"
Color="{StaticResource LightGreenColor}"
IsRunning="{Binding IsBusy}"
IsVisible="{Binding IsBusy}"
VerticalOptions="Center"
HorizontalOptions="Center">
<ActivityIndicator.WidthRequest>
<OnPlatform
x:TypeArguments="x:Double"
iOS="100"
Android="100"
WinPhone="400" />
</ActivityIndicator.WidthRequest>
</ActivityIndicator>
</Grid>
</ContentPage>

View File

@ -0,0 +1,12 @@
using Xamarin.Forms;
namespace eShopOnContainers.Core.Views
{
public partial class CampaignDetailsView : ContentPage
{
public CampaignDetailsView()
{
InitializeComponent();
}
}
}

View File

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="eShopOnContainers.Core.Views.CampaignView"
xmlns:templates="clr-namespace:eShopOnContainers.Core.Views.Templates;assembly=eShopOnContainers.Core"
xmlns:views="clr-namespace:eShopOnContainers.Core.Views;assembly=eShopOnContainers.Core"
xmlns:viewModelBase="clr-namespace:eShopOnContainers.Core.ViewModels.Base;assembly=eShopOnContainers.Core"
xmlns:animations="clr-namespace:eShopOnContainers.Core.Animations;assembly=eShopOnContainers.Core"
xmlns:triggers="clr-namespace:eShopOnContainers.Core.Triggers;assembly=eShopOnContainers.Core"
xmlns:behaviors="clr-namespace:eShopOnContainers.Core.Behaviors;assembly=eShopOnContainers.Core"
viewModelBase:ViewModelLocator.AutoWireViewModel="true"
Title="Catalog">
<ContentPage.Resources>
<ResourceDictionary>
<Style x:Key="CampaignsListStyle"
TargetType="{x:Type ListView}">
<Setter Property="RowHeight"
Value="400" />
<Setter Property="VerticalOptions"
Value="Center" />
<Setter Property="Margin"
Value="0" />
</Style>
<animations:StoryBoard
x:Key="CampaignsAnimation"
Target="{x:Reference Campaigns}">
<animations:FadeInAnimation
Direction="Up"
Duration="1500"
Delay="250"/>
</animations:StoryBoard>
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.Triggers>
<EventTrigger
Event="Appearing">
<triggers:BeginAnimation
Animation="{StaticResource CampaignsAnimation}" />
</EventTrigger>
</ContentPage.Triggers>
<Grid
ColumnSpacing="0"
RowSpacing="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- CAMPAIGNS -->
<Grid
Grid.Row="1">
<Grid
IsVisible="{Binding IsBusy, Converter={StaticResource InverseBoolConverter}}">
<Label
Text="NO CAMPAIGNS FOUND"
IsVisible="{Binding Campaigns.Count, Converter={StaticResource InverseCountToBoolConverter}}"
HorizontalOptions="Center"
VerticalOptions="Center"/>
</Grid>
<ListView
x:Name="Campaigns"
IsVisible="{Binding Campaigns.Count, Converter={StaticResource CountToBoolConverter}}"
ItemsSource="{Binding Campaigns}"
HasUnevenRows="True"
SeparatorVisibility="None"
CachingStrategy="RecycleElement"
Style="{StaticResource CampaignsListStyle}">
<ListView.Behaviors>
<behaviors:EventToCommandBehavior
EventName="ItemTapped"
Command="{Binding GetCampaignDetailsCommand}"
EventArgsConverter="{StaticResource ItemTappedEventArgsConverter}" />
</ListView.Behaviors>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<templates:CampaignTemplate />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
<!-- INDICATOR -->
<ActivityIndicator
Grid.Row="0"
Grid.RowSpan="2"
Color="{StaticResource LightGreenColor}"
IsRunning="{Binding IsBusy}"
IsVisible="{Binding IsBusy}"
VerticalOptions="Center"
HorizontalOptions="Center">
<ActivityIndicator.WidthRequest>
<OnPlatform
x:TypeArguments="x:Double"
iOS="100"
Android="100"
WinPhone="400" />
</ActivityIndicator.WidthRequest>
</ActivityIndicator>
</Grid>
</ContentPage>

View File

@ -0,0 +1,13 @@
namespace eShopOnContainers.Core.Views
{
using Xamarin.Forms;
public partial class CampaignView: ContentPage
{
public CampaignView()
{
InitializeComponent();
}
}
}

View File

@ -63,4 +63,15 @@
WinPhone="Assets\menu_cart.png"/>
</views:BasketView.Icon>
</views:BasketView>
<!-- CAMPAIGNS -->
<views:CampaignView
x:Name="CampaignView">
<views:CampaignView.Icon>
<OnPlatform
x:TypeArguments="FileImageSource"
Android="menu_filter"
iOS="menu_filter"
WinPhone="Assets\menu_filter.png"/>
</views:CampaignView.Icon>
</views:CampaignView>
</TabbedPage>

View File

@ -28,12 +28,16 @@ namespace eShopOnContainers.Core.Views
case 2:
CurrentPage = BasketView;
break;
case 3:
CurrentPage = CampaignView;
break;
}
});
await ((CatalogViewModel)HomeView.BindingContext).InitializeAsync(null);
await ((BasketViewModel)BasketView.BindingContext).InitializeAsync(null);
await ((ProfileViewModel)ProfileView.BindingContext).InitializeAsync(null);
await ((CampaignViewModel)CampaignView.BindingContext).InitializeAsync(null);
}
protected override async void OnCurrentPageChanged()
@ -44,6 +48,7 @@ namespace eShopOnContainers.Core.Views
{
// Force basket view refresh every time we access it
await (BasketView.BindingContext as ViewModelBase).InitializeAsync(null);
await (CampaignView.BindingContext as ViewModelBase).InitializeAsync(null);
}
}
}

View File

@ -98,6 +98,8 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="1" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid
Grid.Row="0"
@ -108,11 +110,11 @@
Grid.Column="0"
Grid.Row="1">
<Label
Text="{Binding Title}"
Text="{Binding TitleUseAzureServices}"
TextColor="{StaticResource GreenColor}"
Style="{StaticResource SettingsTitleStyle}"/>
<Label
Text="{Binding Description}"
Text="{Binding DescriptionUseAzureServices}"
Style="{StaticResource SettingsDescriptionStyle}"/>
</StackLayout>
<!-- ON / OFF -->
@ -160,8 +162,81 @@
<Grid
Grid.Row="3"
Grid.Column="0"
Grid.ColumnSpan="2"/>
<StackLayout
Grid.Column="0"
Grid.Row="4"
IsVisible="{Binding UserIsLogged}">
<Label
Text="{Binding TitleUseFakeLocation}"
TextColor="{StaticResource GreenColor}"
Style="{StaticResource SettingsTitleStyle}"/>
<Label
Text="{Binding DescriptionUseFakeLocation}"
Style="{StaticResource SettingsDescriptionStyle}"/>
</StackLayout>
<!-- ON / OFF -->
<controls:ToggleButton
Grid.Column="1"
Grid.Row="4"
Animate="True"
Checked="{Binding UseFakeLocation, Mode=TwoWay}"
Command="{Binding ToggleFakeLocationCommand}"
Style="{StaticResource SettingsToggleButtonStyle}"
IsVisible="{Binding UserIsLogged}">
<controls:ToggleButton.CheckedImage>
<OnPlatform x:TypeArguments="ImageSource"
Android="switch_on.png"
iOS="switchOn.png"
WinPhone="Assets/switchOn.png"/>
</controls:ToggleButton.CheckedImage>
<controls:ToggleButton.UnCheckedImage>
<OnPlatform x:TypeArguments="ImageSource"
Android="switch_off.png"
iOS="switchOff.png"
WinPhone="Assets/switchOff.png"/>
</controls:ToggleButton.UnCheckedImage>
</controls:ToggleButton>
<!-- FAKE LOCATIONS -->
<StackLayout
Grid.Row="5"
Grid.Column="0"
Grid.ColumnSpan="2"
BackgroundColor="Gray"/>
Margin="12, 0, 12, 12"
IsVisible="{Binding UseFakeLocation}">
<Label
Text="Latitude"
Style="{StaticResource HeaderLabelStyle}"/>
<Entry
Text="{Binding Latitude, Mode=TwoWay}"
Keyboard="Text">
<Entry.Style>
<OnPlatform
x:TypeArguments="Style"
iOS="{StaticResource EntryStyle}"
Android="{StaticResource EntryStyle}"
WinPhone="{StaticResource UwpEntryStyle}"/>
</Entry.Style>
</Entry>
<Label
Text="Longitude"
Style="{StaticResource HeaderLabelStyle}"/>
<Entry
Text="{Binding Longitude, Mode=TwoWay}"
Keyboard="Text">
<Entry.Style>
<OnPlatform
x:TypeArguments="Style"
iOS="{StaticResource EntryStyle}"
Android="{StaticResource EntryStyle}"
WinPhone="{StaticResource UwpEntryStyle}"/>
</Entry.Style>
</Entry>
<Button
Command="{Binding ToggleSendLocationCommand}"
Text="Send Location"/>
</StackLayout>
</Grid>
</StackLayout>
</ScrollView>

View File

@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<ContentView
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:ffimageloading="clr-namespace:FFImageLoading.Forms;assembly=FFImageLoading.Forms"
xmlns:controls="clr-namespace:eShopOnContainers.Core.Controls;assembly=eShopOnContainers.Core"
x:Class="eShopOnContainers.Core.Views.Templates.CampaignTemplate">
<ContentView.Resources>
<ResourceDictionary>
<Style x:Key="CampaignNameStyle"
TargetType="{x:Type Label}">
<Setter Property="FontFamily"
Value="{StaticResource MontserratRegular}" />
<Setter Property="FontSize"
Value="{StaticResource LargeSize}" />
<Setter Property="HorizontalOptions"
Value="Center" />
<Setter Property="Margin"
Value="0, 12, 0, 6" />
</Style>
<Style x:Key="MoreDetailsButtonStyle"
TargetType="{x:Type Grid}">
<Setter Property="HeightRequest"
Value="42" />
<Setter Property="WidthRequest"
Value="42" />
<Setter Property="HorizontalOptions"
Value="Center" />
<Setter Property="VerticalOptions"
Value="End" />
<Setter Property="Margin"
Value="0,0,0,24" />
</Style>
<Style x:Key="AddImageStyle"
TargetType="{x:Type Image}">
<Setter Property="HeightRequest"
Value="24" />
<Setter Property="WidthRequest"
Value="24" />
</Style>
</ResourceDictionary>
</ContentView.Resources>
<ContentView.Content>
<Grid
BackgroundColor="{StaticResource BackgroundColor}"
Padding="0"
Margin="0">
<Grid.RowDefinitions>
<RowDefinition Height="250" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- IMAGE -->
<ffimageloading:CachedImage
Grid.Row="0"
Source="{Binding PictureUri}"
Aspect="AspectFill">
<ffimageloading:CachedImage.LoadingPlaceholder>
<OnPlatform
x:TypeArguments="ImageSource"
iOS="default_campaign"
Android="default_campaign"
WinPhone="Assets/default_campaign.png"/>
</ffimageloading:CachedImage.LoadingPlaceholder>
<ffimageloading:CachedImage.ErrorPlaceholder>
<OnPlatform
x:TypeArguments="ImageSource"
iOS="noimage"
Android="noimage"
WinPhone="Assets/noimage.png"/>
</ffimageloading:CachedImage.ErrorPlaceholder>
</ffimageloading:CachedImage>
<!-- NAME -->
<Label
Grid.Row="1"
Text="{Binding Name, Converter={StaticResource ToUpperConverter}}"
Style="{StaticResource CampaignNameStyle}"/>
</Grid>
</ContentView.Content>
</ContentView>

View File

@ -0,0 +1,12 @@
using Xamarin.Forms;
namespace eShopOnContainers.Core.Views.Templates
{
public partial class CampaignTemplate : ContentView
{
public CampaignTemplate()
{
InitializeComponent();
}
}
}

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
@ -64,12 +64,16 @@
<Compile Include="Helpers\EasingHelper.cs" />
<Compile Include="Helpers\ServicesHelper.cs" />
<Compile Include="Helpers\Settings.cs" />
<Compile Include="Models\Basket\BasketCheckout.cs" />
<Compile Include="Models\Basket\BasketItem.cs" />
<Compile Include="Models\Basket\CustomerBasket.cs" />
<Compile Include="Models\Catalog\CatalogBrand.cs" />
<Compile Include="Models\Catalog\CatalogItem.cs" />
<Compile Include="Models\Catalog\CatalogRoot.cs" />
<Compile Include="Models\Catalog\CatalogType.cs" />
<Compile Include="Models\Location\Location.cs" />
<Compile Include="Models\Marketing\CampaignItem.cs" />
<Compile Include="Models\Marketing\CampaignRoot.cs" />
<Compile Include="Models\Navigation\TabParameter.cs" />
<Compile Include="Models\Orders\CardType.CS" />
<Compile Include="Models\Orders\Order.cs" />
@ -91,6 +95,11 @@
<Compile Include="Services\Dialog\IDialogService.cs" />
<Compile Include="Services\Identity\IdentityService.cs" />
<Compile Include="Services\Identity\IIdentityService.cs" />
<Compile Include="Services\Location\ILocationService.cs" />
<Compile Include="Services\Location\LocationService.cs" />
<Compile Include="Services\Marketing\ICampaignService.cs" />
<Compile Include="Services\Marketing\CampaignMockService.cs" />
<Compile Include="Services\Marketing\CampaignService.cs" />
<Compile Include="Services\Navigation\INavigationService.cs" />
<Compile Include="Services\Navigation\NavigationService.cs" />
<Compile Include="Services\OpenUrl\IOpenUrlService.cs" />
@ -113,6 +122,8 @@
<Compile Include="ViewModels\Base\ViewModelBase.cs" />
<Compile Include="ViewModels\Base\ViewModelLocator.cs" />
<Compile Include="ViewModels\BasketViewModel.cs" />
<Compile Include="ViewModels\CampaignDetailsViewModel.cs" />
<Compile Include="ViewModels\CampaignViewModel.cs" />
<Compile Include="ViewModels\CatalogViewModel.cs" />
<Compile Include="ViewModels\CheckoutViewModel.cs" />
<Compile Include="ViewModels\LoginViewModel.cs" />
@ -123,9 +134,15 @@
<Compile Include="Views\BasketView.xaml.cs">
<DependentUpon>BasketView.xaml</DependentUpon>
</Compile>
<Compile Include="Views\CampaignView.xaml.cs">
<DependentUpon>CampaignView.xaml</DependentUpon>
</Compile>
<Compile Include="Views\CatalogView.xaml.cs">
<DependentUpon>CatalogView.xaml</DependentUpon>
</Compile>
<Compile Include="Views\CampaignDetailsView.xaml.cs">
<DependentUpon>CampaignDetailsView.xaml</DependentUpon>
</Compile>
<Compile Include="Views\CheckoutView.xaml.cs">
<DependentUpon>CheckoutView.xaml</DependentUpon>
</Compile>
@ -159,6 +176,9 @@
<Compile Include="Views\Templates\OrderTemplate.xaml.cs">
<DependentUpon>OrderTemplate.xaml</DependentUpon>
</Compile>
<Compile Include="Views\Templates\CampaignTemplate.xaml.cs">
<DependentUpon>CampaignTemplate.xaml</DependentUpon>
</Compile>
<Compile Include="Views\Templates\ProductTemplate.xaml.cs">
<DependentUpon>ProductTemplate.xaml</DependentUpon>
</Compile>
@ -166,6 +186,7 @@
<Compile Include="Converters\FirstValidationErrorConverter.cs" />
<Compile Include="Effects\EntryLineColorEffect.cs" />
<Compile Include="Behaviors\LineColorBehavior.cs" />
<Compile Include="Models\Token\UserToken.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
@ -259,6 +280,29 @@
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Views\CampaignView.xaml">
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Reference Include="System.ComponentModel.Annotations">
<HintPath>..\..\..\..\..\..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETPortable\v4.6\Profile\Profile44\System.ComponentModel.Annotations.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Views\CampaignDetailsView.xaml">
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Views\Templates\CampaignTemplate.xaml">
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>

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