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. #Ignore marker-file used to know which docker files we have.
.eshopdocker_* .eshopdocker_*
/src/Web/WebMVC/wwwroot/lib /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 | | 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> | | <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) Send feedback to [dotnet-architecture-ebooks-feedback@service.microsoft.com](dotnet-architecture-ebooks-feedback@service.microsoft.com)
<p> <p>
@ -76,7 +76,7 @@ Finally, those microservices are consumed by multiple client web and mobile apps
## Setting up your development environment for eShopOnContainers ## Setting up your development environment for eShopOnContainers
### Visual Studio 2017 and Windows based ### Visual Studio 2017 and Windows based
This is the more straightforward way to get started: 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 ### CLI and Windows based
For those who prefer the CLI on Windows, using dotnet CLI, docker CLI and VS Code for Windows: 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 #!/bin/bash
declare -x path=$1
if [ -z "$path" ]; then
$path="$(pwd)/../src";
fi
declare -a projectList=( declare -a projectList=(
'../src/Services/Catalog/Catalog.API' "$path/Web/WebSPA"
'../src/Services/Basket/Basket.API' "$path/Services/Catalog/Catalog.API"
'../src/Services/Ordering/Ordering.API' "$path/Services/Basket/Basket.API"
'../src/Services/Identity/Identity.API' "$path/Services/Ordering/Ordering.API"
'../src/Web/WebMVC' "$path/Services/Identity/Identity.API"
'../src/Web/WebSPA' "$path/Services/Location/Locations.API"
'../src/Web/WebStatus' "$path/Services/Marketing/Marketing.API"
"$path/Services/Payment/Payment.API"
"$path/Services/GracePeriod/GracePeriodManager"
"$path/Web/WebMVC"
"$path/Web/WebStatus"
) )
# Build SPA app # Build SPA app
@ -15,9 +26,9 @@ declare -a projectList=(
for project in "${projectList[@]}" for project in "${projectList[@]}"
do do
echo -e "\e[33mWorking on $(pwd)/$project" echo -e "\e[33mWorking on $path/$project"
echo -e "\e[33m\tRemoving old publish output" echo -e "\e[33m\tRemoving old publish output"
pushd $(pwd)/$project pushd $path/$project
rm -rf obj/Docker/publish rm -rf obj/Docker/publish
echo -e "\e[33m\tRestoring project" echo -e "\e[33m\tRestoring project"
dotnet restore dotnet restore
@ -26,14 +37,15 @@ do
popd popd
done done
# remove old docker images: ## remove old docker images:
images=$(docker images --filter=reference="eshop/*" -q) #images=$(docker images --filter=reference="eshop/*" -q)
if [ -n "$images" ]; then #if [ -n "$images" ]; then
docker rm $(docker ps -a -q) -f # docker rm $(docker ps -a -q) -f
echo "Deleting eShop images in local Docker repo" # echo "Deleting eShop images in local Docker repo"
echo $images # echo $images
docker rmi $(docker images --filter=reference="eshop/*" -q) -f # docker rmi $(docker images --filter=reference="eshop/*" -q) -f
fi #fi
# No need to build the images, docker build or docker compose will # 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. # 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" Write-Host "Rule found"
} }
catch [Exception] { 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-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-5105 -Protocol tcp -RemoteAddress Any -RemotePort Any -Direction Outbound 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 - ACCEPT_EULA=Y
ports: ports:
- "5433:1433" - "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: ports:
- "5100:80" - "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: sql.data:
environment: environment:
- SA_PASSWORD=Pass@word - SA_PASSWORD=Pass@word

View File

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

View File

@ -1,10 +1,14 @@
version: '2' version: '3'
services: services:
ci-build: ci-build:
image: microsoft/aspnetcore-build:1.0-1.1 image: microsoft/aspnetcore-build:1.1.2
volumes: volumes:
- .:/src - .:/src
- ./cli-linux:/cli-linux
working_dir: /src 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 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: # 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. # 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: services:
graceperiodmanager:
environment:
- ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word
- EventBusConnection=rabbitmq
basket.api: basket.api:
environment: environment:
@ -49,6 +53,19 @@ services:
ports: ports:
- "5102:80" - "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: webspa:
environment: environment:
- ASPNETCORE_ENVIRONMENT=Development - ASPNETCORE_ENVIRONMENT=Development
@ -57,6 +74,7 @@ services:
- OrderingUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5102 - 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. - 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 - BasketUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5103
- MarketingUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5110
- CatalogUrlHC=http://catalog.api/hc - CatalogUrlHC=http://catalog.api/hc
- OrderingUrlHC=http://ordering.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. - 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 - CatalogUrl=http://catalog.api
- OrderingUrl=http://ordering.api - OrderingUrl=http://ordering.api
- BasketUrl=http://basket.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. #Remote: Use ${ESHOP_EXTERNAL_DNS_NAME_OR_IP} if using external IP or DNS name from browser.
ports: ports:
- "5100:80" - "5100:80"
@ -83,6 +102,10 @@ services:
ports: ports:
- "5433:1433" - "5433:1433"
nosql.data:
ports:
- "27017:27017"
webstatus: webstatus:
environment: environment:
- ASPNETCORE_ENVIRONMENT=Development - ASPNETCORE_ENVIRONMENT=Development
@ -95,3 +118,22 @@ services:
- spa=http://webspa/hc - spa=http://webspa/hc
ports: ports:
- "5107:80" - "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 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: # 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: ports:
- "5102:80" - "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: webspa:
environment: environment:
- ASPNETCORE_ENVIRONMENT=Production - ASPNETCORE_ENVIRONMENT=Production

View File

@ -1,4 +1,4 @@
version: '2.1' version: '3'
services: services:
basket.api: basket.api:
@ -61,6 +61,22 @@ services:
labels: labels:
- "com.microsoft.visualstudio.targetoperatingsystem=linux" - "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: webspa:
image: eshop/webspa:dev image: eshop/webspa:dev
build: build:
@ -105,3 +121,46 @@ services:
entrypoint: tail -f /dev/null entrypoint: tail -f /dev/null
labels: labels:
- "com.microsoft.visualstudio.targetoperatingsystem=linux" - "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: services:
basket.api: basket.api:
@ -41,6 +41,16 @@ services:
labels: labels:
- "com.microsoft.visualstudio.targetoperatingsystem=linux" - "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: webspa:
build: build:
args: args:
@ -70,3 +80,33 @@ services:
entrypoint: tail -f /dev/null entrypoint: tail -f /dev/null
labels: labels:
- "com.microsoft.visualstudio.targetoperatingsystem=linux" - "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: services:
graceperiodmanager:
image: eshop/graceperiodmanager:${TAG:-latest}
build:
context: ./src/Services/GracePeriod/GracePeriodManager
dockerfile: Dockerfile
depends_on:
- sql.data
- rabbitmq
basket.api: basket.api:
image: eshop/basket.api image: eshop/basket.api:${TAG:-latest}
build: build:
context: ./src/Services/Basket/Basket.API context: ./src/Services/Basket/Basket.API
dockerfile: Dockerfile dockerfile: Dockerfile
depends_on: depends_on:
- basket.data - basket.data
- identity.api - identity.api
- rabbitmq
catalog.api: catalog.api:
image: eshop/catalog.api image: eshop/catalog.api:${TAG:-latest}
build: build:
context: ./src/Services/Catalog/Catalog.API context: ./src/Services/Catalog/Catalog.API
dockerfile: Dockerfile dockerfile: Dockerfile
@ -21,7 +29,7 @@ services:
- rabbitmq - rabbitmq
identity.api: identity.api:
image: eshop/identity.api image: eshop/identity.api:${TAG:-latest}
build: build:
context: ./src/Services/Identity/Identity.API context: ./src/Services/Identity/Identity.API
dockerfile: Dockerfile dockerfile: Dockerfile
@ -29,15 +37,27 @@ services:
- sql.data - sql.data
ordering.api: ordering.api:
image: eshop/ordering.api image: eshop/ordering.api:${TAG:-latest}
build: build:
context: ./src/Services/Ordering/Ordering.API context: ./src/Services/Ordering/Ordering.API
dockerfile: Dockerfile dockerfile: Dockerfile
depends_on: depends_on:
- sql.data - 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: webspa:
image: eshop/webspa image: eshop/webspa:${TAG:-latest}
build: build:
context: ./src/Web/WebSPA context: ./src/Web/WebSPA
dockerfile: Dockerfile dockerfile: Dockerfile
@ -46,7 +66,7 @@ services:
- basket.api - basket.api
webmvc: webmvc:
image: eshop/webmvc image: eshop/webmvc:${TAG:-latest}
build: build:
context: ./src/Web/WebMVC context: ./src/Web/WebMVC
dockerfile: Dockerfile dockerfile: Dockerfile
@ -55,10 +75,14 @@ services:
- ordering.api - ordering.api
- identity.api - identity.api
- basket.api - basket.api
- marketing.api
sql.data: sql.data:
image: microsoft/mssql-server-linux image: microsoft/mssql-server-linux
nosql.data:
image: mongo
basket.data: basket.data:
image: redis image: redis
ports: ports:
@ -70,7 +94,24 @@ services:
- "5672:5672" - "5672:5672"
webstatus: webstatus:
image: eshop/webstatus image: eshop/webstatus:${TAG:-latest}
build: build:
context: ./src/Web/WebStatus context: ./src/Web/WebStatus
dockerfile: Dockerfile 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 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15 # Visual Studio 15
VisualStudioVersion = 15.0.26430.6 VisualStudioVersion = 15.0.26430.12
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{932D8224-11F6-4D07-B109-DA28AD288A63}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{932D8224-11F6-4D07-B109-DA28AD288A63}"
EndProject EndProject
@ -70,11 +70,31 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Resilience", "Resilience",
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Resilience.Http", "src\BuildingBlocks\Resilience\Resilience.Http\Resilience.Http.csproj", "{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Resilience.Http", "src\BuildingBlocks\Resilience\Resilience.Http\Resilience.Http.csproj", "{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}"
EndProject 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}" 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 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}" 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 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 EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventBusServiceBus", "src\BuildingBlocks\EventBus\EventBusServiceBus\EventBusServiceBus.csproj", "{69AF10D3-AA76-4FF7-B187-EC7E8CC5F5B8}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventBusServiceBus", "src\BuildingBlocks\EventBus\EventBusServiceBus\EventBusServiceBus.csproj", "{69AF10D3-AA76-4FF7-B187-EC7E8CC5F5B8}"
EndProject EndProject
@ -860,6 +880,54 @@ Global
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Release|x64.Build.0 = Release|Any CPU {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.ActiveCfg = Release|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Release|x86.Build.0 = 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.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|Any CPU.Build.0 = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|ARM.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|x64.Build.0 = Release|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|x86.ActiveCfg = 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 {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.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|Any CPU.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
@ -1085,9 +1393,18 @@ Global
{C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F} = {E279BF0F-7F66-4F3A-A3AB-2CDA66C1CD04} {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F} = {E279BF0F-7F66-4F3A-A3AB-2CDA66C1CD04}
{FBF43D93-F2E7-4FF8-B4AB-186895949B88} = {DB0EFB20-B024-4E5E-A75C-52143C131D25} {FBF43D93-F2E7-4FF8-B4AB-186895949B88} = {DB0EFB20-B024-4E5E-A75C-52143C131D25}
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502} = {FBF43D93-F2E7-4FF8-B4AB-186895949B88} {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} {22A0F9C1-2D4A-4107-95B7-8459E6688BC5} = {A81ECBC2-6B00-4DCD-8388-469174033379}
{4BD76717-3102-4969-8C2C-BAAA3F0263B6} = {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} {4A980AC4-7205-46BF-8CCB-09E44D700FD4} = {807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F}
{69AF10D3-AA76-4FF7-B187-EC7E8CC5F5B8} = {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 EndGlobalSection
EndGlobal EndGlobal

View File

@ -1,7 +1,7 @@
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15 # Visual Studio 15
VisualStudioVersion = 15.0.26228.12 VisualStudioVersion = 15.0.26430.6
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{932D8224-11F6-4D07-B109-DA28AD288A63}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{932D8224-11F6-4D07-B109-DA28AD288A63}"
EndProject EndProject
@ -97,14 +97,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Health
EndProject 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}" 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 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}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Resilience", "Resilience", "{D13768ED-5AF1-4E09-96DD-FF6E7A2E5E06}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Resilience.Http", "src\BuildingBlocks\Resilience\Resilience.Http\Resilience.Http.csproj", "{D92EB452-7A72-4B26-A8ED-0204CD376BC4}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Resilience.Http", "src\BuildingBlocks\Resilience\Resilience.Http\Resilience.Http.csproj", "{D92EB452-7A72-4B26-A8ED-0204CD376BC4}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebStatus", "src\Web\WebStatus\WebStatus.csproj", "{23FB706A-2701-41E9-8BF9-28936001CA41}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebStatus", "src\Web\WebStatus\WebStatus.csproj", "{23FB706A-2701-41E9-8BF9-28936001CA41}"
EndProject 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 Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU 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|x64.Build.0 = Release|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.Release|x86.ActiveCfg = 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 {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.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|Any CPU.Build.0 = Debug|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.Ad-Hoc|ARM.ActiveCfg = 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|x64.Build.0 = Release|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.Release|x86.ActiveCfg = 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 {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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -1510,9 +1510,9 @@ Global
{96CE8CE7-BC97-4A53-899F-5EB63D7BBF7B} = {1EF3AC0F-F27C-46DD-AC53-D762D2C11C45} {96CE8CE7-BC97-4A53-899F-5EB63D7BBF7B} = {1EF3AC0F-F27C-46DD-AC53-D762D2C11C45}
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3} = {96CE8CE7-BC97-4A53-899F-5EB63D7BBF7B} {FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3} = {96CE8CE7-BC97-4A53-899F-5EB63D7BBF7B}
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45} = {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} {D13768ED-5AF1-4E09-96DD-FF6E7A2E5E06} = {1EF3AC0F-F27C-46DD-AC53-D762D2C11C45}
{D92EB452-7A72-4B26-A8ED-0204CD376BC4} = {D13768ED-5AF1-4E09-96DD-FF6E7A2E5E06} {D92EB452-7A72-4B26-A8ED-0204CD376BC4} = {D13768ED-5AF1-4E09-96DD-FF6E7A2E5E06}
{23FB706A-2701-41E9-8BF9-28936001CA41} = {E279BF0F-7F66-4F3A-A3AB-2CDA66C1CD04} {23FB706A-2701-41E9-8BF9-28936001CA41} = {E279BF0F-7F66-4F3A-A3AB-2CDA66C1CD04}
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612} = {96CE8CE7-BC97-4A53-899F-5EB63D7BBF7B}
EndGlobalSection EndGlobalSection
EndGlobal 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]$registry,
[parameter(Mandatory=$false)][string]$dockerUser, [parameter(Mandatory=$false)][string]$dockerUser,
[parameter(Mandatory=$false)][string]$dockerPassword, [parameter(Mandatory=$false)][string]$dockerPassword,
[parameter(Mandatory=$false)][bool]$deployCI,
[parameter(Mandatory=$false)][bool]$useDockerHub,
[parameter(Mandatory=$false)][string]$execPath, [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) { 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) { if(-not $deployCI) {
$requiredCommands = ("docker", "docker-compose", "kubectl") $requiredCommands = ("docker", "docker-compose", "kubectl")
foreach ($command in $requiredCommands) { 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 # Use ACR instead of DockerHub as image repository
if(-not $useDockerHub) { if(-not $useDockerHub) {
@ -50,77 +97,115 @@ if(-not $useDockerHub) {
# Removing previous services & deployments # Removing previous services & deployments
Write-Host "Removing existing services & deployments.." -ForegroundColor Yellow Write-Host "Removing existing services & deployments.." -ForegroundColor Yellow
ExecKube -cmd 'delete -f sql-data.yaml -f rabbitmq.yaml' ExecKube -cmd 'delete deployments --all'
ExecKube -cmd 'delete -f services.yaml -f frontend.yaml -f deployments.yaml' ExecKube -cmd 'delete services --all'
ExecKube -cmd 'delete configmap config-files' ExecKube -cmd 'delete configmap config-files'
ExecKube -cmd 'delete configmap urls' ExecKube -cmd 'delete configmap urls'
ExecKube -cmd 'delete configmap externalcfg'
# start sql, rabbitmq, frontend deploymentsExecKube -cmd 'delete configmap config-files' # start sql, rabbitmq, frontend deploymentsExecKube -cmd 'delete configmap config-files'
ExecKube -cmd 'create configmap config-files --from-file=nginx-conf=nginx.conf' ExecKube -cmd 'create configmap config-files --from-file=nginx-conf=nginx.conf'
ExecKube -cmd 'label configmap config-files app=eshop' 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 ($deployInfrastructure) {
if(-not $deployCI) { Write-Host 'Deploying infrastructure deployments (databases, redis, ...)' -ForegroundColor Yellow
Write-Host "Building and publishing eShopOnContainers..." -ForegroundColor Yellow ExecKube -cmd 'create -f sql-data.yaml -f basket-data.yaml -f keystore-data.yaml -f rabbitmq.yaml -f nosql-data.yaml'
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
}
} }
Write-Host "Waiting for frontend's external ip..." -ForegroundColor Yellow Write-Host 'Deploying code deployments (databases, redis, ...)' -ForegroundColor Yellow
while ($true) { 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}"' $frontendUrl = & ExecKube -cmd 'get svc frontend -o=jsonpath="{.status.loadBalancer.ingress[0].ip}"'
if ([bool]($frontendUrl -as [ipaddress])) { if ([bool]($frontendUrl -as [ipaddress])) {
break break
} }
Start-Sleep -s 15 Start-Sleep -s 15
}
$externalDns = $frontendUrl
} }
Write-Host "Using $externalDns as the external DNS/IP of the k8s cluster"
ExecKube -cmd 'create configmap urls ` ExecKube -cmd 'create configmap urls `
--from-literal=BasketUrl=http://$($frontendUrl)/basket-api ` --from-literal=BasketUrl=http://basket `
--from-literal=BasketHealthCheckUrl=http://$($frontendUrl)/basket-api/hc ` --from-literal=BasketHealthCheckUrl=http://basket/hc `
--from-literal=CatalogUrl=http://$($frontendUrl)/catalog-api ` --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=IdentityUrl=http://$($frontendUrl)/identity `
--from-literal=IdentityHealthCheckUrl=http://$($frontendUrl)/identity/hc ` --from-literal=IdentityHealthCheckUrl=http://identity/hc `
--from-literal=OrderingUrl=http://$($frontendUrl)/ordering-api ` --from-literal=OrderingUrl=http://ordering `
--from-literal=OrderingHealthCheckUrl=http://$($frontendUrl)/ordering-api/hc ` --from-literal=OrderingHealthCheckUrl=http://ordering/hc `
--from-literal=MvcClient=http://$($frontendUrl)/webmvc ` --from-literal=MvcClientExternalUrl=http://$($frontendUrl)/webmvc `
--from-literal=WebMvcHealthCheckUrl=http://$($frontendUrl)/webmvc/hc ` --from-literal=WebMvcHealthCheckUrl=http://webmvc/hc `
--from-literal=WebStatusClient=http://$($frontendUrl)/webstatus ` --from-literal=MvcClientOrderingUrl=http://ordering `
--from-literal=WebSpaHealthCheckUrl=http://$($frontendUrl)/hc ` --from-literal=MvcClientCatalogUrl=http://catalog `
--from-literal=SpaClient=http://$($frontendUrl)' --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' 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' ExecKube -cmd 'create -f deployments.yaml'
# not using ACR for pulling images when deploying through CI VSTS # update deployments with the correct image (with tag and/or registry)
if(-not $deployCI) { Write-Host "Update Image containers to use prefix '$registry' and tag '$imageTag'" -ForegroundColor Yellow
# update deployments with the private registry before k8s tries to pull images $registryPath = ""
# (deployment templating, or Helm, would obviate this) if (-not [string]::IsNullOrEmpty($registry)) {
ExecKube -cmd 'set image -f deployments.yaml ` $registryPath = "$registry/"
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'
} }
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 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 - name: ASPNETCORE_URLS
value: http://0.0.0.0:80/basket-api value: http://0.0.0.0:80/basket-api
- name: ConnectionString - name: ConnectionString
value: 127.0.0.1 valueFrom:
configMapKeyRef:
name: externalcfg
key: BasketRedisConStr
- name: EventBusConnection - name: EventBusConnection
value: rabbitmq valueFrom:
configMapKeyRef:
name: externalcfg
key: BasketBus
- name: AzureServiceBusEnabled
valueFrom:
configMapKeyRef:
name: externalcfg
key: UseAzureServiceBus
- name: IdentityUrl - name: IdentityUrl
valueFrom: valueFrom:
configMapKeyRef: configMapKeyRef:
@ -28,10 +39,6 @@ spec:
key: IdentityUrl key: IdentityUrl
ports: ports:
- containerPort: 80 - containerPort: 80
- name: basket-data
image: redis:3.2-alpine
ports:
- containerPort: 6379
imagePullSecrets: imagePullSecrets:
- name: registry-key - name: registry-key
--- ---
@ -55,14 +62,20 @@ spec:
- name: ASPNETCORE_URLS - name: ASPNETCORE_URLS
value: http://0.0.0.0:80/catalog-api value: http://0.0.0.0:80/catalog-api
- name: ConnectionString - name: ConnectionString
value: "Server=sql-data;Initial Catalog=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word" valueFrom:
- name: EventBusConnection configMapKeyRef:
value: rabbitmq name: externalcfg
key: CatalogSqlDb
- name: ExternalCatalogBaseUrl - name: ExternalCatalogBaseUrl
valueFrom: valueFrom:
configMapKeyRef: configMapKeyRef:
name: urls name: urls
key: CatalogUrl key: CatalogUrl
- name: EventBusConnection
valueFrom:
configMapKeyRef:
name: externalcfg
key: CatalogBus
ports: ports:
- containerPort: 80 - containerPort: 80
imagePullSecrets: imagePullSecrets:
@ -88,17 +101,27 @@ spec:
- name: ASPNETCORE_URLS - name: ASPNETCORE_URLS
value: http://0.0.0.0:80/identity value: http://0.0.0.0:80/identity
- name: ConnectionStrings__DefaultConnection - 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 - name: MvcClient
valueFrom: valueFrom:
configMapKeyRef: configMapKeyRef:
name: urls name: urls
key: MvcClient key: MvcClientExternalUrl
- name: SpaClient - name: SpaClient
valueFrom: valueFrom:
configMapKeyRef: configMapKeyRef:
name: urls name: urls
key: SpaClient key: SpaClientExternalUrl
ports: ports:
- containerPort: 80 - containerPort: 80
imagePullSecrets: imagePullSecrets:
@ -124,9 +147,20 @@ spec:
- name: ASPNETCORE_URLS - name: ASPNETCORE_URLS
value: http://0.0.0.0:80/ordering-api value: http://0.0.0.0:80/ordering-api
- name: ConnectionString - 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 - name: EventBusConnection
value: rabbitmq valueFrom:
configMapKeyRef:
name: externalcfg
key: OrderingBus
- name: AzureServiceBusEnabled
valueFrom:
configMapKeyRef:
name: externalcfg
key: UseAzureServiceBus
- name: IdentityUrl - name: IdentityUrl
valueFrom: valueFrom:
configMapKeyRef: configMapKeyRef:
@ -139,6 +173,143 @@ spec:
--- ---
apiVersion: extensions/v1beta1 apiVersion: extensions/v1beta1
kind: Deployment 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: metadata:
name: webmvc name: webmvc
spec: spec:
@ -156,21 +327,28 @@ spec:
env: env:
- name: ASPNETCORE_URLS - name: ASPNETCORE_URLS
value: http://0.0.0.0:80/webmvc value: http://0.0.0.0:80/webmvc
- name: DPConnectionString
valueFrom:
configMapKeyRef:
name: externalcfg
key: keystore
- name: IsClusterEnv
value: 'True'
- name: BasketUrl - name: BasketUrl
valueFrom: valueFrom:
configMapKeyRef: configMapKeyRef:
name: urls name: urls
key: BasketUrl key: MvcClientBasketUrl
- name: CallBackUrl - name: CallBackUrl
valueFrom: valueFrom:
configMapKeyRef: configMapKeyRef:
name: urls name: urls
key: MvcClient key: MvcClientExternalUrl
- name: CatalogUrl - name: CatalogUrl
valueFrom: valueFrom:
configMapKeyRef: configMapKeyRef:
name: urls name: urls
key: CatalogUrl key: MvcClientCatalogUrl
- name: IdentityUrl - name: IdentityUrl
valueFrom: valueFrom:
configMapKeyRef: configMapKeyRef:
@ -180,13 +358,12 @@ spec:
valueFrom: valueFrom:
configMapKeyRef: configMapKeyRef:
name: urls name: urls
key: OrderingUrl key: MvcClientOrderingUrl
ports: ports:
- containerPort: 80 - containerPort: 80
imagePullSecrets: imagePullSecrets:
- name: registry-key - name: registry-key
--- ---
---
apiVersion: extensions/v1beta1 apiVersion: extensions/v1beta1
kind: Deployment kind: Deployment
metadata: metadata:
@ -260,31 +437,58 @@ spec:
env: env:
- name: ASPNETCORE_URLS - name: ASPNETCORE_URLS
value: http://0.0.0.0:80 value: http://0.0.0.0:80
- name: DPConnectionString
valueFrom:
configMapKeyRef:
name: externalcfg
key: keystore
- name: IsClusterEnv
value: 'True'
- name: BasketUrl - name: BasketUrl
valueFrom: valueFrom:
configMapKeyRef: configMapKeyRef:
name: urls name: urls
key: BasketUrl key: SpaClientBasketExternalUrl
- name: CallBackUrl - name: CallBackUrl
valueFrom: valueFrom:
configMapKeyRef: configMapKeyRef:
name: urls name: urls
key: SpaClient key: SpaClientExternalUrl
- name: CatalogUrl - name: CatalogUrl
valueFrom: valueFrom:
configMapKeyRef: configMapKeyRef:
name: urls name: urls
key: CatalogUrl key: SpaClientCatalogExternalUrl
- name: IdentityUrl - name: IdentityUrl
valueFrom: valueFrom:
configMapKeyRef: configMapKeyRef:
name: urls name: urls
key: IdentityUrl key: SpaClientIdentityExternalUrl
- name: OrderingUrl - name: OrderingUrl
valueFrom: valueFrom:
configMapKeyRef: configMapKeyRef:
name: urls 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: ports:
- containerPort: 80 - containerPort: 80
imagePullSecrets: imagePullSecrets:

View File

@ -3,16 +3,19 @@
[parameter(Mandatory=$true)][string]$location, [parameter(Mandatory=$true)][string]$location,
[parameter(Mandatory=$true)][string]$registryName, [parameter(Mandatory=$true)][string]$registryName,
[parameter(Mandatory=$true)][string]$orchestratorName, [parameter(Mandatory=$true)][string]$orchestratorName,
[parameter(Mandatory=$true)][string]$dnsName [parameter(Mandatory=$true)][string]$dnsName,
[parameter(Mandatory=$true)][string]$createAcr=$true
) )
# Create resource group # Create resource group
Write-Host "Creating resource group..." -ForegroundColor Yellow Write-Host "Creating resource group..." -ForegroundColor Yellow
az group create --name=$resourceGroupName --location=$location az group create --name=$resourceGroupName --location=$location
# Create Azure Container Registry if ($createAcr) {
Write-Host "Creating Azure Container Registry..." -ForegroundColor Yellow # Create Azure Container Registry
az acr create -n $registryName -g $resourceGroupName -l $location --admin-enabled true --sku Basic 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 # Create kubernetes orchestrator
Write-Host "Creating kubernetes orchestrator..." -ForegroundColor Yellow 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; 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 / { location / {
proxy_pass http://webspa; proxy_pass http://webspa;
proxy_redirect off; 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 apiVersion: v1
kind: Service 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: metadata:
labels: labels:
app: eshop 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() public void After_One_Event_Subscription_Should_Contain_The_Event()
{ {
var manager = new InMemoryEventBusSubscriptionsManager(); var manager = new InMemoryEventBusSubscriptionsManager();
manager.AddSubscription<TestIntegrationEvent,TestIntegrationEventHandler>(() => new TestIntegrationEventHandler()); manager.AddSubscription<TestIntegrationEvent,TestIntegrationEventHandler>();
Assert.True(manager.HasSubscriptionsForEvent<TestIntegrationEvent>()); Assert.True(manager.HasSubscriptionsForEvent<TestIntegrationEvent>());
} }
@ -26,7 +26,7 @@ namespace EventBus.Tests
public void After_All_Subscriptions_Are_Deleted_Event_Should_No_Longer_Exists() public void After_All_Subscriptions_Are_Deleted_Event_Should_No_Longer_Exists()
{ {
var manager = new InMemoryEventBusSubscriptionsManager(); var manager = new InMemoryEventBusSubscriptionsManager();
manager.AddSubscription<TestIntegrationEvent, TestIntegrationEventHandler>(() => new TestIntegrationEventHandler()); manager.AddSubscription<TestIntegrationEvent, TestIntegrationEventHandler>();
manager.RemoveSubscription<TestIntegrationEvent, TestIntegrationEventHandler>(); manager.RemoveSubscription<TestIntegrationEvent, TestIntegrationEventHandler>();
Assert.False(manager.HasSubscriptionsForEvent<TestIntegrationEvent>()); Assert.False(manager.HasSubscriptionsForEvent<TestIntegrationEvent>());
} }
@ -37,7 +37,7 @@ namespace EventBus.Tests
bool raised = false; bool raised = false;
var manager = new InMemoryEventBusSubscriptionsManager(); var manager = new InMemoryEventBusSubscriptionsManager();
manager.OnEventRemoved += (o, e) => raised = true; manager.OnEventRemoved += (o, e) => raised = true;
manager.AddSubscription<TestIntegrationEvent, TestIntegrationEventHandler>(() => new TestIntegrationEventHandler()); manager.AddSubscription<TestIntegrationEvent, TestIntegrationEventHandler>();
manager.RemoveSubscription<TestIntegrationEvent, TestIntegrationEventHandler>(); manager.RemoveSubscription<TestIntegrationEvent, TestIntegrationEventHandler>();
Assert.True(raised); Assert.True(raised);
} }
@ -46,8 +46,8 @@ namespace EventBus.Tests
public void Get_Handlers_For_Event_Should_Return_All_Handlers() public void Get_Handlers_For_Event_Should_Return_All_Handlers()
{ {
var manager = new InMemoryEventBusSubscriptionsManager(); var manager = new InMemoryEventBusSubscriptionsManager();
manager.AddSubscription<TestIntegrationEvent, TestIntegrationEventHandler>(() => new TestIntegrationEventHandler()); manager.AddSubscription<TestIntegrationEvent, TestIntegrationEventHandler>();
manager.AddSubscription<TestIntegrationEvent, TestIntegrationOtherEventHandler>(() => new TestIntegrationOtherEventHandler()); manager.AddSubscription<TestIntegrationEvent, TestIntegrationOtherEventHandler>();
var handlers = manager.GetHandlersForEvent<TestIntegrationEvent>(); var handlers = manager.GetHandlersForEvent<TestIntegrationEvent>();
Assert.Equal(2, handlers.Count()); 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 public interface IEventBus
{ {
void Subscribe<T, TH>(Func<TH> handler) void Subscribe<T, TH>()
where T : IntegrationEvent where T : IntegrationEvent
where TH : IIntegrationEventHandler<T>; where TH : IIntegrationEventHandler<T>;
void SubscribeDynamic<TH>(string eventName)
where TH : IDynamicIntegrationEventHandler;
void UnsubscribeDynamic<TH>(string eventName)
where TH : IDynamicIntegrationEventHandler;
void Unsubscribe<T, TH>() void Unsubscribe<T, TH>()
where TH : IIntegrationEventHandler<T> where TH : IIntegrationEventHandler<T>
where T : IntegrationEvent; where T : IntegrationEvent;

View File

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

View File

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

View File

@ -8,64 +8,106 @@ using System.Text;
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus 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; private readonly List<Type> _eventTypes;
public event EventHandler<string> OnEventRemoved; public event EventHandler<string> OnEventRemoved;
public InMemoryEventBusSubscriptionsManager() public InMemoryEventBusSubscriptionsManager()
{ {
_handlers = new Dictionary<string, List<Delegate>>(); _handlers = new Dictionary<string, List<SubscriptionInfo>>();
_eventTypes = new List<Type>(); _eventTypes = new List<Type>();
} }
public bool IsEmpty => !_handlers.Keys.Any(); public bool IsEmpty => !_handlers.Keys.Any();
public void Clear() => _handlers.Clear(); 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 T : IntegrationEvent
where TH : IIntegrationEventHandler<T> where TH : IIntegrationEventHandler<T>
{ {
var key = GetEventKey<T>(); var eventName = GetEventKey<T>();
if (!HasSubscriptionsForEvent<T>()) DoAddSubscription(typeof(TH), eventName, isDynamic: false);
{
_handlers.Add(key, new List<Delegate>());
}
_handlers[key].Add(handler);
_eventTypes.Add(typeof(T)); _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>() public void RemoveSubscription<T, TH>()
where TH : IIntegrationEventHandler<T> where TH : IIntegrationEventHandler<T>
where T : IntegrationEvent where T : IntegrationEvent
{ {
var handlerToRemove = FindHandlerToRemove<T, TH>(); var handlerToRemove = FindSubscriptionToRemove<T, TH>();
if (handlerToRemove != null) var eventName = GetEventKey<T>();
DoRemoveHandler(eventName, handlerToRemove);
}
private void DoRemoveHandler(string eventName, SubscriptionInfo subsToRemove)
{ {
var key = GetEventKey<T>(); if (subsToRemove != null)
_handlers[key].Remove(handlerToRemove);
if (!_handlers[key].Any())
{ {
_handlers.Remove(key); _handlers[eventName].Remove(subsToRemove);
var eventType = _eventTypes.SingleOrDefault(e => e.Name == key); if (!_handlers[eventName].Any())
{
_handlers.Remove(eventName);
var eventType = _eventTypes.SingleOrDefault(e => e.Name == eventName);
if (eventType != null) if (eventType != null)
{ {
_eventTypes.Remove(eventType); _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>(); var key = GetEventKey<T>();
return GetHandlersForEvent(key); return GetHandlersForEvent(key);
} }
public IEnumerable<Delegate> GetHandlersForEvent(string eventName) => _handlers[eventName]; public IEnumerable<SubscriptionInfo> GetHandlersForEvent(string eventName) => _handlers[eventName];
private void RaiseOnEventRemoved(string eventName) private void RaiseOnEventRemoved(string eventName)
{ {
@ -76,26 +118,31 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus
} }
} }
private Delegate FindHandlerToRemove<T, TH>()
private SubscriptionInfo FindDynamicSubscriptionToRemove<TH>(string eventName)
where TH : IDynamicIntegrationEventHandler
{
return DoFindSubscriptionToRemove(eventName, typeof(TH));
}
private SubscriptionInfo FindSubscriptionToRemove<T, TH>()
where T : IntegrationEvent where T : IntegrationEvent
where TH : IIntegrationEventHandler<T> where TH : IIntegrationEventHandler<T>
{ {
if (!HasSubscriptionsForEvent<T>()) var eventName = GetEventKey<T>();
return DoFindSubscriptionToRemove(eventName, typeof(TH));
}
private SubscriptionInfo DoFindSubscriptionToRemove(string eventName, Type handlerType)
{
if (!HasSubscriptionsForEvent(eventName))
{ {
return null; return null;
} }
var key = GetEventKey<T>(); return _handlers[eventName].SingleOrDefault(s => s.HandlerType == handlerType);
foreach (var func in _handlers[key])
{
var genericArgs = func.GetType().GetGenericArguments();
if (genericArgs.SingleOrDefault() == typeof(TH))
{
return func;
}
}
return null;
} }
public bool HasSubscriptionsForEvent<T>() where T : IntegrationEvent 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 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; 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.Abstractions;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Polly; using Polly;
using Polly.Retry; using Polly.Retry;
using RabbitMQ.Client; using RabbitMQ.Client;
@ -25,17 +27,20 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
private readonly IRabbitMQPersistentConnection _persistentConnection; private readonly IRabbitMQPersistentConnection _persistentConnection;
private readonly ILogger<EventBusRabbitMQ> _logger; private readonly ILogger<EventBusRabbitMQ> _logger;
private readonly IEventBusSubscriptionsManager _subsManager; private readonly IEventBusSubscriptionsManager _subsManager;
private readonly ILifetimeScope _autofac;
private readonly string AUTOFAC_SCOPE_NAME = "eshop_event_bus";
private IModel _consumerChannel; private IModel _consumerChannel;
private string _queueName; 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)); _persistentConnection = persistentConnection ?? throw new ArgumentNullException(nameof(persistentConnection));
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); _logger = logger ?? throw new ArgumentNullException(nameof(logger));
_subsManager = subsManager ?? new InMemoryEventBusSubscriptionsManager(); _subsManager = subsManager ?? new InMemoryEventBusSubscriptionsManager();
_consumerChannel = CreateConsumerChannel(); _consumerChannel = CreateConsumerChannel();
_autofac = autofac;
_subsManager.OnEventRemoved += SubsManager_OnEventRemoved; _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 T : IntegrationEvent
where TH : IIntegrationEventHandler<T> where TH : IIntegrationEventHandler<T>
{ {
var eventName = typeof(T).Name; var eventName = _subsManager.GetEventKey<T>();
var containsKey = _subsManager.HasSubscriptionsForEvent<T>(); DoInternalSubscription(eventName);
_subsManager.AddSubscription<T, TH>();
}
private void DoInternalSubscription(string eventName)
{
var containsKey = _subsManager.HasSubscriptionsForEvent(eventName);
if (!containsKey) if (!containsKey)
{ {
if (!_persistentConnection.IsConnected) if (!_persistentConnection.IsConnected)
@ -116,9 +134,6 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
routingKey: eventName); routingKey: eventName);
} }
} }
_subsManager.AddSubscription<T, TH>(handler);
} }
public void Unsubscribe<T, TH>() public void Unsubscribe<T, TH>()
@ -128,18 +143,12 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
_subsManager.RemoveSubscription<T, TH>(); _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) _subsManager.RemoveDynamicSubscription<TH>(eventName);
{
if (func.GetMethodInfo().ReturnType == handlerType)
{
return func;
}
} }
return null;
}
public void Dispose() public void Dispose()
{ {
@ -190,19 +199,31 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
private async Task ProcessEvent(string eventName, string message) private async Task ProcessEvent(string eventName, string message)
{ {
if (_subsManager.HasSubscriptionsForEvent(eventName)) if (_subsManager.HasSubscriptionsForEvent(eventName))
{
using (var scope = _autofac.BeginLifetimeScope(AUTOFAC_SCOPE_NAME))
{
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 eventType = _subsManager.GetEventTypeByName(eventName);
var integrationEvent = JsonConvert.DeserializeObject(message, eventType); var integrationEvent = JsonConvert.DeserializeObject(message, eventType);
var handlers = _subsManager.GetHandlersForEvent(eventName); var handler = scope.ResolveOptional(subscription.HandlerType);
foreach (var handlerfactory in handlers)
{
var handler = handlerfactory.DynamicInvoke();
var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType); var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType);
await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent }); await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent });
} }
} }
} }
} }
}
}
} }

View File

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

View File

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

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp1.0</TargetFramework> <TargetFramework>netstandard1.3</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@ -9,7 +9,7 @@
</ItemGroup> </ItemGroup>
<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" /> <ProjectReference Include="..\Microsoft.Extensions.HealthChecks\Microsoft.Extensions.HealthChecks.csproj" />
</ItemGroup> </ItemGroup>

View File

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

View File

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

View File

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

View File

@ -96,7 +96,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http
// as it is disposed after each call // as it is disposed after each call
var origin = GetOriginFromUri(uri); var origin = GetOriginFromUri(uri);
return HttpInvoker(origin, () => return HttpInvoker(origin, async () =>
{ {
var requestMessage = new HttpRequestMessage(method, uri); var requestMessage = new HttpRequestMessage(method, uri);
@ -112,7 +112,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http
requestMessage.Headers.Add("x-requestid", requestId); requestMessage.Headers.Add("x-requestid", requestId);
} }
var response = _client.SendAsync(requestMessage).Result; var response = await _client.SendAsync(requestMessage);
// raise exception if HttpResponseCode 500 // raise exception if HttpResponseCode 500
// needed for circuit breaker to track fails // needed for circuit breaker to track fails
@ -122,7 +122,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http
throw new HttpRequestException(); 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)) if (!_policyWrappers.TryGetValue(normalizedOrigin, out PolicyWrap policyWrap))
{ {
policyWrap = Policy.Wrap(_policyCreator(normalizedOrigin).ToArray()); policyWrap = Policy.WrapAsync(_policyCreator(normalizedOrigin).ToArray());
_policyWrappers.TryAdd(normalizedOrigin, policyWrap); _policyWrappers.TryAdd(normalizedOrigin, policyWrap);
} }
// Executes the action applying all // Executes the action applying all
// the policies defined in the wrapper // 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. 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" /> <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. 1. A Xamarin.Forms mobile app for iOS, Android and Windows.
2. Several .NET Web API microservices deployed as Docker containers. 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 * Entity Framework
* Identity Server 4 * Identity Server 4
##Three platforms ## Three platforms
The app targets **three** platforms: The app targets **three** platforms:
* iOS * 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). 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: 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.Services;
using eShopOnContainers.Core.ViewModels.Base; using eShopOnContainers.Core.ViewModels.Base;
using System.Threading.Tasks; using System.Threading.Tasks;
using eShopOnContainers.Core.Models.Location;
using eShopOnContainers.Core.Services.Location;
using Plugin.Geolocator;
using Xamarin.Forms; using Xamarin.Forms;
using Xamarin.Forms.Xaml; using Xamarin.Forms.Xaml;
@ -36,6 +40,7 @@ namespace eShopOnContainers
return navigationService.InitializeAsync(); return navigationService.InitializeAsync();
} }
protected override async void OnStart() protected override async void OnStart()
{ {
base.OnStart(); base.OnStart();
@ -44,6 +49,18 @@ namespace eShopOnContainers
{ {
await InitNavigation(); await InitNavigation();
} }
if (!Settings.UseFakeLocation)
{
await GetRealLocation();
}
if (!Settings.UseMocks && !string.IsNullOrEmpty(Settings.UserId))
{
await SendCurrentLocation();
}
base.OnResume();
} }
protected override void OnSleep() protected override void OnSleep()
@ -51,9 +68,29 @@ namespace eShopOnContainers
// Handle when your app sleeps // 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 MockTag = "Mock";
public const string DefaultEndpoint = "http://13.88.8.119"; public const string DefaultEndpoint = "http://13.88.8.119";
private string _baseEndpoint; private string _baseEndpoint;
private static readonly GlobalSetting _instance = new GlobalSetting(); 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 AuthToken { get; set; }
public string RegisterWebsite { get; set; } public string RegisterWebsite { get; set; }
@ -43,8 +46,14 @@
public string IdentityEndpoint { get; set; } public string IdentityEndpoint { get; set; }
public string LocationEndpoint { get; set; }
public string MarketingEndpoint { get; set; }
public string UserInfoEndpoint { get; set; } public string UserInfoEndpoint { get; set; }
public string TokenEndpoint { get; set; }
public string LogoutEndpoint { get; set; } public string LogoutEndpoint { get; set; }
public string IdentityCallback { get; set; } public string IdentityCallback { get; set; }
@ -53,15 +62,18 @@
private void UpdateEndpoint(string baseEndpoint) private void UpdateEndpoint(string baseEndpoint)
{ {
RegisterWebsite = string.Format("{0}:5105/Account/Register", baseEndpoint); RegisterWebsite = $"{baseEndpoint}:5105/Account/Register";
CatalogEndpoint = string.Format("{0}:5101", baseEndpoint); CatalogEndpoint = $"{baseEndpoint}:5101";
OrdersEndpoint = string.Format("{0}:5102", baseEndpoint); OrdersEndpoint = $"{baseEndpoint}:5102";
BasketEndpoint = string.Format("{0}:5103", baseEndpoint); BasketEndpoint = $"{baseEndpoint}:5103";
IdentityEndpoint = string.Format("{0}:5105/connect/authorize", baseEndpoint); IdentityEndpoint = $"{baseEndpoint}:5105/connect/authorize";
UserInfoEndpoint = string.Format("{0}:5105/connect/userinfo", baseEndpoint); UserInfoEndpoint = $"{baseEndpoint}:5105/connect/userinfo";
LogoutEndpoint = string.Format("{0}:5105/connect/endsession", baseEndpoint); TokenEndpoint = $"{baseEndpoint}:5105/connect/token";
IdentityCallback = string.Format("{0}:5105/xamarincallback", baseEndpoint); LogoutEndpoint = $"{baseEndpoint}:5105/connect/endsession";
LogoutCallback = string.Format("{0}:5105/Account/Redirecting", baseEndpoint); 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.Basket;
using eShopOnContainers.Core.Models.Catalog; using eShopOnContainers.Core.Models.Catalog;
using eShopOnContainers.Core.Models.Marketing;
using eShopOnContainers.Core.ViewModels.Base; using eShopOnContainers.Core.ViewModels.Base;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -76,5 +77,38 @@ namespace eShopOnContainers.Core.Helpers
Debug.WriteLine(ex.Message); 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 #region Setting Constants
private const string IdUserId = "user_id";
private const string AccessToken = "access_token"; private const string AccessToken = "access_token";
private const string IdToken = "id_token"; private const string IdToken = "id_token";
private const string IdUseMocks = "use_mocks"; private const string IdUseMocks = "use_mocks";
private const string IdUrlBase = "url_base"; 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 AccessTokenDefault = string.Empty;
private static readonly string IdTokenDefault = string.Empty; private static readonly string IdTokenDefault = string.Empty;
private static readonly bool UseMocksDefault = true; 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; private static readonly string UrlBaseDefault = GlobalSetting.Instance.BaseEndpoint;
#endregion #endregion
public static string UserId
{
get => AppSettings.GetValueOrDefault<string>(IdUserId);
set => AppSettings.AddOrUpdateValue<string>(IdUserId, value);
}
public static string AuthAccessToken public static string AuthAccessToken
{ {
get get => AppSettings.GetValueOrDefault<string>(AccessToken, AccessTokenDefault);
{ set => AppSettings.AddOrUpdateValue<string>(AccessToken, value);
return AppSettings.GetValueOrDefault<string>(AccessToken, AccessTokenDefault);
}
set
{
AppSettings.AddOrUpdateValue<string>(AccessToken, value);
}
} }
public static string AuthIdToken public static string AuthIdToken
{ {
get get => AppSettings.GetValueOrDefault<string>(IdToken, IdTokenDefault);
{ set => AppSettings.AddOrUpdateValue<string>(IdToken, value);
return AppSettings.GetValueOrDefault<string>(IdToken, IdTokenDefault);
}
set
{
AppSettings.AddOrUpdateValue<string>(IdToken, value);
}
} }
public static bool UseMocks public static bool UseMocks
{ {
get get => AppSettings.GetValueOrDefault<bool>(IdUseMocks, UseMocksDefault);
{ set => AppSettings.AddOrUpdateValue<bool>(IdUseMocks, value);
return AppSettings.GetValueOrDefault<bool>(IdUseMocks, UseMocksDefault);
}
set
{
AppSettings.AddOrUpdateValue<bool>(IdUseMocks, value);
}
} }
public static string UrlBase public static string UrlBase
{ {
get get => AppSettings.GetValueOrDefault<string>(IdUrlBase, UrlBaseDefault);
{ set => AppSettings.AddOrUpdateValue<string>(IdUrlBase, value);
return AppSettings.GetValueOrDefault<string>(IdUrlBase, UrlBaseDefault);
} }
set
public static bool UseFakeLocation
{ {
AppSettings.AddOrUpdateValue<string>(IdUrlBase, value); 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.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Xamarin.Forms; using Xamarin.Forms;
using System;
namespace eShopOnContainers.Core.Services.Basket namespace eShopOnContainers.Core.Services.Basket
{ {
@ -57,5 +58,10 @@ namespace eShopOnContainers.Core.Services.Basket
MockCustomBasket.Items.Clear(); 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; 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) public async Task ClearBasketAsync(string guidUser, string token)
{ {
UriBuilder builder = new UriBuilder(GlobalSetting.Instance.BasketEndpoint); 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> GetBasketAsync(string guidUser, string token);
Task<CustomerBasket> UpdateBasketAsync(CustomerBasket customerBasket, string token); Task<CustomerBasket> UpdateBasketAsync(CustomerBasket customerBasket, string token);
Task CheckoutAsync(BasketCheckout basketCheckout, string token);
Task ClearBasketAsync(string guidUser, string token); Task ClearBasketAsync(string guidUser, string token);
} }
} }

View File

@ -7,5 +7,8 @@
public static string MockCatalogItemId03 = "3"; public static string MockCatalogItemId03 = "3";
public static string MockCatalogItemId04 = "4"; public static string MockCatalogItemId04 = "4";
public static string MockCatalogItemId05 = "5"; 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 public interface IIdentityService
{ {
string CreateAuthorizationRequest(); string CreateAuthorizationRequest();
string CreateLogoutRequest(string token); string CreateLogoutRequest(string token);
Task<UserToken> GetTokenAsync(string code);
} }
} }

View File

@ -1,11 +1,22 @@
using IdentityModel.Client; using IdentityModel.Client;
using System; using System;
using System.Collections.Generic; 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 namespace eShopOnContainers.Core.Services.Identity
{ {
public class IdentityService : IIdentityService public class IdentityService : IIdentityService
{ {
private readonly IRequestProvider _requestProvider;
public IdentityService(IRequestProvider requestProvider)
{
_requestProvider = requestProvider;
}
public string CreateAuthorizationRequest() public string CreateAuthorizationRequest()
{ {
// Create URI to authorization endpoint // Create URI to authorization endpoint
@ -13,10 +24,10 @@ namespace eShopOnContainers.Core.Services.Identity
// Dictionary with values for the authorize request // Dictionary with values for the authorize request
var dic = new Dictionary<string, string>(); var dic = new Dictionary<string, string>();
dic.Add("client_id", "xamarin"); dic.Add("client_id", GlobalSetting.Instance.ClientId);
dic.Add("response_type", "id_token token"); dic.Add("client_secret", GlobalSetting.Instance.ClientSecret);
dic.Add("scope", "openid profile basket orders"); 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("redirect_uri", GlobalSetting.Instance.IdentityCallback);
dic.Add("nonce", Guid.NewGuid().ToString("N")); dic.Add("nonce", Guid.NewGuid().ToString("N"));
@ -30,7 +41,7 @@ namespace eShopOnContainers.Core.Services.Identity
public string CreateLogoutRequest(string token) public string CreateLogoutRequest(string token)
{ {
if(string.IsNullOrEmpty(token)) if (string.IsNullOrEmpty(token))
{ {
return string.Empty; return string.Empty;
} }
@ -40,5 +51,12 @@ namespace eShopOnContainers.Core.Services.Identity
token, token,
GlobalSetting.Instance.LogoutCallback); 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; using System.Threading.Tasks;
namespace eShopOnContainers.Core.Services.Order namespace eShopOnContainers.Core.Services.Order
{ {
public interface IOrderService 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<ObservableCollection<Models.Orders.Order>> GetOrdersAsync(string token);
Task<Models.Orders.Order> GetOrderAsync(int orderId, string token); Task<Models.Orders.Order> GetOrderAsync(int orderId, string token);
Task<ObservableCollection<Models.Orders.CardType>> GetCardTypesAsync(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.Extensions;
using eShopOnContainers.Core.Models.Basket;
using eShopOnContainers.Core.Models.Orders; using eShopOnContainers.Core.Models.Orders;
using eShopOnContainers.Core.Models.User; using eShopOnContainers.Core.Models.User;
using System; using System;
@ -64,17 +65,18 @@ namespace eShopOnContainers.Core.Services.Order
new CardType { Id = 3, Name = "MasterCard" }, 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); CardExpiration = DateTime.UtcNow,
CardHolderName = "FakeCardHolderName",
if (!string.IsNullOrEmpty(token)) CardNumber = "122333423224",
{ CardSecurityNumber = "1234",
newOrder.OrderNumber = string.Format("{0}", MockOrders.Count + 1); CardTypeId = 1,
City = "FakeCity",
MockOrders.Insert(0, newOrder); Country = "FakeCountry",
} ZipCode = "FakeZipCode",
} Street = "FakeStreet"
};
public async Task<ObservableCollection<Models.Orders.Order>> GetOrdersAsync(string token) public async Task<ObservableCollection<Models.Orders.Order>> GetOrdersAsync(string token)
{ {
@ -111,5 +113,10 @@ namespace eShopOnContainers.Core.Services.Order
else else
return new ObservableCollection<CardType>(); 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;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -14,17 +15,6 @@ namespace eShopOnContainers.Core.Services.Order
_requestProvider = requestProvider; _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) 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>(); 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, TResult data, string token = "", string header = "");
Task<TResult> PostAsync<TResult>(string uri, string data, string clientId, string clientSecret);
Task DeleteAsync(string uri, string token = ""); Task DeleteAsync(string uri, string token = "");
} }
} }

View File

@ -61,6 +61,28 @@ namespace eShopOnContainers.Core.Services.RequestProvider
return result; 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 = "") public async Task DeleteAsync(string uri, string token = "")
{ {
HttpClient httpClient = CreateHttpClient(token); HttpClient httpClient = CreateHttpClient(token);
@ -90,6 +112,17 @@ namespace eShopOnContainers.Core.Services.RequestProvider
httpClient.DefaultRequestHeaders.Add(parameter, Guid.NewGuid().ToString()); 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) private async Task HandleResponse(HttpResponseMessage response)
{ {
if (!response.IsSuccessStatusCode) if (!response.IsSuccessStatusCode)

View File

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

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; var authToken = Settings.AuthAccessToken;
// Create new order var basket = _orderService.MapOrderToBasket(Order);
await _orderService.CreateOrderAsync(Order, authToken);
// Create basket checkout
await _basketService.CheckoutAsync(basket, authToken);
// Clean Basket // Clean Basket
await _basketService.ClearBasketAsync(_shippingAddress.Id.ToString(), authToken); await _basketService.ClearBasketAsync(_shippingAddress.Id.ToString(), authToken);

View File

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

View File

@ -4,38 +4,51 @@ using Xamarin.Forms;
using System.Threading.Tasks; using System.Threading.Tasks;
using eShopOnContainers.Core.Helpers; using eShopOnContainers.Core.Helpers;
using eShopOnContainers.Core.Models.User; using eShopOnContainers.Core.Models.User;
using System;
using eShopOnContainers.Core.Models.Location;
using eShopOnContainers.Core.Services.Location;
namespace eShopOnContainers.Core.ViewModels namespace eShopOnContainers.Core.ViewModels
{ {
public class SettingsViewModel : ViewModelBase public class SettingsViewModel : ViewModelBase
{ {
private string _title; private string _titleUseAzureServices;
private string _description; private string _descriptionUseAzureServices;
private bool _useAzureServices; private bool _useAzureServices;
private string _titleUseFakeLocation;
private string _descriptionUseFakeLocation;
private bool _useFakeLocation;
private string _endpoint; private string _endpoint;
private double _latitude;
private double _longitude;
private ILocationService _locationService;
public SettingsViewModel() public SettingsViewModel(ILocationService locationService)
{ {
UseAzureServices = !Settings.UseMocks; 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 set
{ {
_title = value; _titleUseAzureServices = value;
RaisePropertyChanged(() => Title); RaisePropertyChanged(() => TitleUseAzureServices);
} }
} }
public string Description public string DescriptionUseAzureServices
{ {
get { return _description; } get { return _descriptionUseAzureServices; }
set set
{ {
_description = value; _descriptionUseAzureServices = value;
RaisePropertyChanged(() => Description); 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 public string Endpoint
{ {
get { return _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 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) public override Task InitializeAsync(object navigationData)
{ {
UpdateInfo(); UpdateInfo();
UpdateInfoFakeLocation();
Endpoint = Settings.UrlBase; Endpoint = Settings.UrlBase;
_latitude = Settings.Latitude;
_longitude = Settings.Longitude;
_useFakeLocation = Settings.UseFakeLocation;
return base.InitializeAsync(navigationData); return base.InitializeAsync(navigationData);
} }
@ -100,17 +183,52 @@ namespace eShopOnContainers.Core.ViewModels
} }
} }
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() private void UpdateInfo()
{ {
if (!UseAzureServices) if (!UseAzureServices)
{ {
Title = "Use Mock Services"; TitleUseAzureServices = "Use Mock Services";
Description = "Mock Services are simulated objects that mimic the behavior of real services using a controlled approach."; DescriptionUseAzureServices = "Mock Services are simulated objects that mimic the behavior of real services using a controlled approach.";
} }
else else
{ {
Title = "Use Microservices/Containers from eShopOnContainers"; TitleUseAzureServices = "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."; 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) // Update remote endpoint (save to local storage)
Settings.UrlBase = endpoint; 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"/> WinPhone="Assets\menu_cart.png"/>
</views:BasketView.Icon> </views:BasketView.Icon>
</views:BasketView> </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> </TabbedPage>

View File

@ -28,12 +28,16 @@ namespace eShopOnContainers.Core.Views
case 2: case 2:
CurrentPage = BasketView; CurrentPage = BasketView;
break; break;
case 3:
CurrentPage = CampaignView;
break;
} }
}); });
await ((CatalogViewModel)HomeView.BindingContext).InitializeAsync(null); await ((CatalogViewModel)HomeView.BindingContext).InitializeAsync(null);
await ((BasketViewModel)BasketView.BindingContext).InitializeAsync(null); await ((BasketViewModel)BasketView.BindingContext).InitializeAsync(null);
await ((ProfileViewModel)ProfileView.BindingContext).InitializeAsync(null); await ((ProfileViewModel)ProfileView.BindingContext).InitializeAsync(null);
await ((CampaignViewModel)CampaignView.BindingContext).InitializeAsync(null);
} }
protected override async void OnCurrentPageChanged() protected override async void OnCurrentPageChanged()
@ -44,6 +48,7 @@ namespace eShopOnContainers.Core.Views
{ {
// Force basket view refresh every time we access it // Force basket view refresh every time we access it
await (BasketView.BindingContext as ViewModelBase).InitializeAsync(null); 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="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="1" /> <RowDefinition Height="1" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid <Grid
Grid.Row="0" Grid.Row="0"
@ -108,11 +110,11 @@
Grid.Column="0" Grid.Column="0"
Grid.Row="1"> Grid.Row="1">
<Label <Label
Text="{Binding Title}" Text="{Binding TitleUseAzureServices}"
TextColor="{StaticResource GreenColor}" TextColor="{StaticResource GreenColor}"
Style="{StaticResource SettingsTitleStyle}"/> Style="{StaticResource SettingsTitleStyle}"/>
<Label <Label
Text="{Binding Description}" Text="{Binding DescriptionUseAzureServices}"
Style="{StaticResource SettingsDescriptionStyle}"/> Style="{StaticResource SettingsDescriptionStyle}"/>
</StackLayout> </StackLayout>
<!-- ON / OFF --> <!-- ON / OFF -->
@ -160,8 +162,81 @@
<Grid <Grid
Grid.Row="3" Grid.Row="3"
Grid.Column="0" 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" 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> </Grid>
</StackLayout> </StackLayout>
</ScrollView> </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"> <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')" /> <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup> <PropertyGroup>
@ -64,12 +64,16 @@
<Compile Include="Helpers\EasingHelper.cs" /> <Compile Include="Helpers\EasingHelper.cs" />
<Compile Include="Helpers\ServicesHelper.cs" /> <Compile Include="Helpers\ServicesHelper.cs" />
<Compile Include="Helpers\Settings.cs" /> <Compile Include="Helpers\Settings.cs" />
<Compile Include="Models\Basket\BasketCheckout.cs" />
<Compile Include="Models\Basket\BasketItem.cs" /> <Compile Include="Models\Basket\BasketItem.cs" />
<Compile Include="Models\Basket\CustomerBasket.cs" /> <Compile Include="Models\Basket\CustomerBasket.cs" />
<Compile Include="Models\Catalog\CatalogBrand.cs" /> <Compile Include="Models\Catalog\CatalogBrand.cs" />
<Compile Include="Models\Catalog\CatalogItem.cs" /> <Compile Include="Models\Catalog\CatalogItem.cs" />
<Compile Include="Models\Catalog\CatalogRoot.cs" /> <Compile Include="Models\Catalog\CatalogRoot.cs" />
<Compile Include="Models\Catalog\CatalogType.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\Navigation\TabParameter.cs" />
<Compile Include="Models\Orders\CardType.CS" /> <Compile Include="Models\Orders\CardType.CS" />
<Compile Include="Models\Orders\Order.cs" /> <Compile Include="Models\Orders\Order.cs" />
@ -91,6 +95,11 @@
<Compile Include="Services\Dialog\IDialogService.cs" /> <Compile Include="Services\Dialog\IDialogService.cs" />
<Compile Include="Services\Identity\IdentityService.cs" /> <Compile Include="Services\Identity\IdentityService.cs" />
<Compile Include="Services\Identity\IIdentityService.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\INavigationService.cs" />
<Compile Include="Services\Navigation\NavigationService.cs" /> <Compile Include="Services\Navigation\NavigationService.cs" />
<Compile Include="Services\OpenUrl\IOpenUrlService.cs" /> <Compile Include="Services\OpenUrl\IOpenUrlService.cs" />
@ -113,6 +122,8 @@
<Compile Include="ViewModels\Base\ViewModelBase.cs" /> <Compile Include="ViewModels\Base\ViewModelBase.cs" />
<Compile Include="ViewModels\Base\ViewModelLocator.cs" /> <Compile Include="ViewModels\Base\ViewModelLocator.cs" />
<Compile Include="ViewModels\BasketViewModel.cs" /> <Compile Include="ViewModels\BasketViewModel.cs" />
<Compile Include="ViewModels\CampaignDetailsViewModel.cs" />
<Compile Include="ViewModels\CampaignViewModel.cs" />
<Compile Include="ViewModels\CatalogViewModel.cs" /> <Compile Include="ViewModels\CatalogViewModel.cs" />
<Compile Include="ViewModels\CheckoutViewModel.cs" /> <Compile Include="ViewModels\CheckoutViewModel.cs" />
<Compile Include="ViewModels\LoginViewModel.cs" /> <Compile Include="ViewModels\LoginViewModel.cs" />
@ -123,9 +134,15 @@
<Compile Include="Views\BasketView.xaml.cs"> <Compile Include="Views\BasketView.xaml.cs">
<DependentUpon>BasketView.xaml</DependentUpon> <DependentUpon>BasketView.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="Views\CampaignView.xaml.cs">
<DependentUpon>CampaignView.xaml</DependentUpon>
</Compile>
<Compile Include="Views\CatalogView.xaml.cs"> <Compile Include="Views\CatalogView.xaml.cs">
<DependentUpon>CatalogView.xaml</DependentUpon> <DependentUpon>CatalogView.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="Views\CampaignDetailsView.xaml.cs">
<DependentUpon>CampaignDetailsView.xaml</DependentUpon>
</Compile>
<Compile Include="Views\CheckoutView.xaml.cs"> <Compile Include="Views\CheckoutView.xaml.cs">
<DependentUpon>CheckoutView.xaml</DependentUpon> <DependentUpon>CheckoutView.xaml</DependentUpon>
</Compile> </Compile>
@ -159,6 +176,9 @@
<Compile Include="Views\Templates\OrderTemplate.xaml.cs"> <Compile Include="Views\Templates\OrderTemplate.xaml.cs">
<DependentUpon>OrderTemplate.xaml</DependentUpon> <DependentUpon>OrderTemplate.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="Views\Templates\CampaignTemplate.xaml.cs">
<DependentUpon>CampaignTemplate.xaml</DependentUpon>
</Compile>
<Compile Include="Views\Templates\ProductTemplate.xaml.cs"> <Compile Include="Views\Templates\ProductTemplate.xaml.cs">
<DependentUpon>ProductTemplate.xaml</DependentUpon> <DependentUpon>ProductTemplate.xaml</DependentUpon>
</Compile> </Compile>
@ -166,6 +186,7 @@
<Compile Include="Converters\FirstValidationErrorConverter.cs" /> <Compile Include="Converters\FirstValidationErrorConverter.cs" />
<Compile Include="Effects\EntryLineColorEffect.cs" /> <Compile Include="Effects\EntryLineColorEffect.cs" />
<Compile Include="Behaviors\LineColorBehavior.cs" /> <Compile Include="Behaviors\LineColorBehavior.cs" />
<Compile Include="Models\Token\UserToken.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="app.config" /> <None Include="app.config" />
@ -259,6 +280,29 @@
<Generator>MSBuild:UpdateDesignTimeXaml</Generator> <Generator>MSBuild:UpdateDesignTimeXaml</Generator>
</EmbeddedResource> </EmbeddedResource>
</ItemGroup> </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" /> <Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup> <PropertyGroup>

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