Merge from eShopOnContainers Dev
This commit is contained in:
commit
a3e63c11aa
1
.gitignore
vendored
1
.gitignore
vendored
@ -258,3 +258,4 @@ pub/
|
||||
#Ignore marker-file used to know which docker files we have.
|
||||
.eshopdocker_*
|
||||
/src/Web/WebMVC/wwwroot/lib
|
||||
/src/Web/WebMVC/wwwroot/css/site.min.css
|
||||
|
26
README.md
26
README.md
@ -3,20 +3,20 @@ Sample .NET Core reference application, powered by Microsoft, based on a simplif
|
||||
**Note for Pull Requests**: We accept pull request from the community. When doing it, please do it onto the DEV branch which is the consolidated work-in-progress branch. Do not request it onto Master, if possible.
|
||||
|
||||
> ### DISCLAIMER
|
||||
> **IMPORTANT:** The current state of this sample application is **BETA**, consider it version a 0.1 foundational version, therefore, many areas could be improved and change significantly while refactoring current code and implementing new features. **Feedback with improvements and pull requests from the community will be highly appreciated and accepted.**
|
||||
> **IMPORTANT:** The current state of this sample application is **BETA**, consider it version a 0.1 foundational version, therefore, many areas could be improved and change significantly while refactoring current code and implementing new features. **Feedback with improvements and pull requests from the community will be highly appreciated and accepted.**
|
||||
>
|
||||
> This reference application proposes a simplified microservice oriented architecture implementation to introduce technologies like .NET Core with Docker containers through a comprehensive application. The chosen domain is an eShop/eCommerce but simply because it is a well-know domain by most people/developers.
|
||||
However, this sample application should not be considered as an "eCommerce reference model", at all. The implemented business domain might not be ideal from an eCommerce business point of view. It is neither trying to solve all the problems in a large, scalable and mission-critical distributed system. It is just a bootstrap for developers to easily get started in the world of Docker containers and microservices with .NET Core.
|
||||
> <p>For example, the next step (still not covered in eShopOnContainers) after understanding Docker containers and microservices development with .NET Core, is to select a microservice cluster/orchestrator like Docker Swarm, Kubernetes or DC/OS (in Azure Container Service) or Azure Service Fabric which in most of the cases will require additional partial changes to your application's configuration (although the present architecture should work on most orchestrators with small changes).
|
||||
However, this sample application should not be considered as an "eCommerce reference model", at all. The implemented business domain might not be ideal from an eCommerce business point of view. It is neither trying to solve all the problems in a large, scalable and mission-critical distributed system. It is just a bootstrap for developers to easily get started in the world of Docker containers and microservices with .NET Core.
|
||||
> <p>For example, the next step (still not covered in eShopOnContainers) after understanding Docker containers and microservices development with .NET Core, is to select a microservice cluster/orchestrator like Docker Swarm, Kubernetes or DC/OS (in Azure Container Service) or Azure Service Fabric which in most of the cases will require additional partial changes to your application's configuration (although the present architecture should work on most orchestrators with small changes).
|
||||
> Additional steps would be to move your databases to HA cloud services, or to implement your EventBus with Azure Service Bus or any other production ready Service Bus in the market.
|
||||
> <p> In the future we might fork this project and make multiple versions targeting specific microservice cluster/orchestrators plus using additional cloud infrastructure. <p>
|
||||
> <img src="img/exploring-to-production-ready.png">
|
||||
> Read the planned <a href='https://github.com/dotnet/eShopOnContainers/wiki/01.-Roadmap-and-Milestones-for-future-releases'>Roadmap and Milestones for future releases of eShopOnContainers</a> within the Wiki for further info about possible new implementations and provide feedback at the <a href='https://github.com/dotnet/eShopOnContainers/issues'>ISSUES section</a> if you'd like to see any specific scenario implemented or improved. Also, feel free to discuss on any current issue.
|
||||
|
||||
**Architecture overview**: This reference application is cross-platform either at the server and client side, thanks to .NET Core services capable of running on Linux or Windows containers depending on your Docker host, and to Xamarin for mobile apps running on Android, iOS or Windows/UWP plus any browser for the client web apps.
|
||||
The architecture proposes a simplified microservice oriented architecture implementation with multiple autonomous microservices (each one owning its own data/db) and implementing different approaches within each microservice (simple CRUD vs. DDD/CQRS patterns) using Http as the current communication protocol.
|
||||
**Architecture overview**: This reference application is cross-platform either at the server and client side, thanks to .NET Core services capable of running on Linux or Windows containers depending on your Docker host, and to Xamarin for mobile apps running on Android, iOS or Windows/UWP plus any browser for the client web apps.
|
||||
The architecture proposes a simplified microservice oriented architecture implementation with multiple autonomous microservices (each one owning its own data/db) and implementing different approaches within each microservice (simple CRUD vs. DDD/CQRS patterns) using Http as the current communication protocol.
|
||||
<p>
|
||||
It also supports asynchronous communication for data updates propagation across multiple services based on Integration Events and an Event Bus plus other features defined at the <a href='https://github.com/dotnet/eShopOnContainers/wiki/01.-Roadmap-and-Milestones-for-future-releases'>roadmap</a>.
|
||||
It also supports asynchronous communication for data updates propagation across multiple services based on Integration Events and an Event Bus plus other features defined at the <a href='https://github.com/dotnet/eShopOnContainers/wiki/01.-Roadmap-and-Milestones-for-future-releases'>roadmap</a>.
|
||||
<p>
|
||||
<img src="img/eshop_logo.png">
|
||||
<img src="img/eShopOnContainers_Architecture_Diagram.png">
|
||||
@ -43,7 +43,7 @@ You can download them and start reviewing these Guides/eBooks here:
|
||||
| Architecting & Developing | Containers Lifecycle & CI/CD | App patterns with Xamarin.Forms |
|
||||
| ------------ | ------------| ------------|
|
||||
| <a href='https://aka.ms/microservicesebook'><img src="img/ebook_arch_dev_microservices_containers_cover.png"> </a> | <a href='https://aka.ms/dockerlifecycleebook'> <img src="img/ebook_containers_lifecycle.png"> </a> | <a href='https://aka.ms/xamarinpatternsebook'> <img src="img/xamarin-enterprise-patterns-ebook-cover-small.png"> </a> |
|
||||
| <sup> <a href='https://aka.ms/microservicesebook'>**Download** (First Edition)</a> </sup> | <sup> <a href='https://aka.ms/dockerlifecycleebook'>**Download** (First Edition from late 2016) </a> </sup> | <sup> <a href='https://aka.ms/xamarinpatternsebook'>**Download** (Preview Edition) </a> </sup> |
|
||||
| <sup> <a href='https://aka.ms/microservicesebook'>**Download** (First Edition)</a> </sup> | <sup> <a href='https://aka.ms/dockerlifecycleebook'>**Download** (First Edition from late 2016) </a> </sup> | <sup> <a href='https://aka.ms/xamarinpatternsebook'>**Download** (First Edition) </a> </sup> |
|
||||
|
||||
Send feedback to [dotnet-architecture-ebooks-feedback@service.microsoft.com](dotnet-architecture-ebooks-feedback@service.microsoft.com)
|
||||
<p>
|
||||
@ -66,7 +66,7 @@ Finally, those microservices are consumed by multiple client web and mobile apps
|
||||
<b>*MVC Application (ASP.NET Core)*</b>: Its an MVC application where you can find interesting scenarios on how to consume HTTP-based microservices from C# running in the server side, as it is a typical ASP.NET Core MVC application. Since it is a server-side application, access to other containers/microservices is done within the internal Docker Host network with its internal name resolution.
|
||||
<img src="img/eshop-webmvc-app-screenshot.png">
|
||||
<br>
|
||||
<b>*SPA (Single Page Application)*</b>: Providing similar "eShop business functionality" but developed with Angular 2, Typescript and slightly using ASP.NET Core MVC. This is another approach for client web applications to be used when you want to have a more modern client behavior which is not behaving with the typical browser round-trip on every action but behaving like a Single-Page-Application which is more similar to a desktop app usage experience. The consumption of the HTTP-based microservices is done from TypeScript/JavaScript in the client browser, so the client calls to the microservices come from out of the Docker Host internal network (Like from your network or even from the Internet).
|
||||
<b>*SPA (Single Page Application)*</b>: Providing similar "eShop business functionality" but developed with Angular 2, Typescript and slightly using ASP.NET Core MVC. This is another approach for client web applications to be used when you want to have a more modern client behavior which is not behaving with the typical browser round-trip on every action but behaving like a Single-Page-Application which is more similar to a desktop app usage experience. The consumption of the HTTP-based microservices is done from TypeScript/JavaScript in the client browser, so the client calls to the microservices come from out of the Docker Host internal network (Like from your network or even from the Internet).
|
||||
<img src="img/eshop-webspa-app-screenshot.png">
|
||||
<br>
|
||||
<b>*Xamarin Mobile App (For iOS, Android and Windows/UWP)*</b>: It is a client mobile app supporting the most common mobile OS platforms (iOS, Android and Windows/UWP). In this case, the consumption of the microservices is done from C# but running on the client devices, so out of the Docker Host internal network (Like from your network or even the Internet).
|
||||
@ -76,19 +76,19 @@ Finally, those microservices are consumed by multiple client web and mobile apps
|
||||
## Setting up your development environment for eShopOnContainers
|
||||
### Visual Studio 2017 and Windows based
|
||||
This is the more straightforward way to get started:
|
||||
https://github.com/dotnet/eShopOnContainers/wiki/02.-Setting-eShopOnContainer-solution-up-in-a-Visual-Studio-2017-environment
|
||||
https://github.com/dotnet-architecture/eShopOnContainers/wiki/02.-Setting-eShopOnContainers-in-a-Visual-Studio-2017-environment
|
||||
|
||||
### CLI and Windows based
|
||||
For those who prefer the CLI on Windows, using dotnet CLI, docker CLI and VS Code for Windows:
|
||||
For those who prefer the CLI on Windows, using dotnet CLI, docker CLI and VS Code for Windows:
|
||||
https://github.com/dotnet/eShopOnContainers/wiki/03.-Setting-the-eShopOnContainers-solution-up-in-a-Windows-CLI-environment-(dotnet-CLI,-Docker-CLI-and-VS-Code)
|
||||
|
||||
### CLI and Mac based
|
||||
For those who prefer the CLI on a Mac, using dotnet CLI, docker CLI and VS Code for Mac
|
||||
For those who prefer the CLI on a Mac, using dotnet CLI, docker CLI and VS Code for Mac
|
||||
(Instructions still TBD, but similar to Windows CLI):
|
||||
https://github.com/dotnet/eShopOnContainers/wiki/04.-Setting-eShopOnContainer-solution-up-in-a-Mac,-VS-Code-and-CLI-environment--(dotnet-CLI,-Docker-CLI-and-VS-Code)
|
||||
|
||||
> ### Note on tested Docker Containers/Images
|
||||
> Most of the development and testing of this project was (as of early March 2017) done <b> on Docker Linux containers</b> running in development machines with "Docker for Windows" and the default Hyper-V Linux VM (MobiLinuxVM) installed by "Docker for Windows".
|
||||
> Most of the development and testing of this project was (as of early March 2017) done <b> on Docker Linux containers</b> running in development machines with "Docker for Windows" and the default Hyper-V Linux VM (MobiLinuxVM) installed by "Docker for Windows".
|
||||
The <b>Windows Containers scenario is currently being implemented/tested yet</b>. The application should be able to run on Windows Nano Containers based on different Docker base images, as well, as the .NET Core services have also been tested running on plain Windows (with no Docker).
|
||||
The app was also partially tested on "Docker for Mac" using a development MacOS machine with .NET Core and VS Code installed, which is still a scenario using Linux containers running on the VM setup in the Mac by the "Docker for Windows" setup. But further testing and feedback on Mac environments and Windows Containers, from the community, will be appreciated.
|
||||
|
||||
@ -118,4 +118,4 @@ You can create new issues at the issues section, do pull requests and/or send em
|
||||
|
||||
## Questions
|
||||
[QUESTION] Answer +1 if the solution is working for you (Through VS2017 or CLI environment):
|
||||
https://github.com/dotnet/eShopOnContainers/issues/107
|
||||
https://github.com/dotnet/eShopOnContainers/issues/107
|
||||
|
@ -1,12 +1,23 @@
|
||||
#!/bin/bash
|
||||
|
||||
declare -x path=$1
|
||||
|
||||
if [ -z "$path" ]; then
|
||||
$path="$(pwd)/../src";
|
||||
fi
|
||||
|
||||
declare -a projectList=(
|
||||
'../src/Services/Catalog/Catalog.API'
|
||||
'../src/Services/Basket/Basket.API'
|
||||
'../src/Services/Ordering/Ordering.API'
|
||||
'../src/Services/Identity/Identity.API'
|
||||
'../src/Web/WebMVC'
|
||||
'../src/Web/WebSPA'
|
||||
'../src/Web/WebStatus'
|
||||
"$path/Web/WebSPA"
|
||||
"$path/Services/Catalog/Catalog.API"
|
||||
"$path/Services/Basket/Basket.API"
|
||||
"$path/Services/Ordering/Ordering.API"
|
||||
"$path/Services/Identity/Identity.API"
|
||||
"$path/Services/Location/Locations.API"
|
||||
"$path/Services/Marketing/Marketing.API"
|
||||
"$path/Services/Payment/Payment.API"
|
||||
"$path/Services/GracePeriod/GracePeriodManager"
|
||||
"$path/Web/WebMVC"
|
||||
"$path/Web/WebStatus"
|
||||
)
|
||||
|
||||
# Build SPA app
|
||||
@ -15,9 +26,9 @@ declare -a projectList=(
|
||||
|
||||
for project in "${projectList[@]}"
|
||||
do
|
||||
echo -e "\e[33mWorking on $(pwd)/$project"
|
||||
echo -e "\e[33mWorking on $path/$project"
|
||||
echo -e "\e[33m\tRemoving old publish output"
|
||||
pushd $(pwd)/$project
|
||||
pushd $path/$project
|
||||
rm -rf obj/Docker/publish
|
||||
echo -e "\e[33m\tRestoring project"
|
||||
dotnet restore
|
||||
@ -26,14 +37,15 @@ do
|
||||
popd
|
||||
done
|
||||
|
||||
# remove old docker images:
|
||||
images=$(docker images --filter=reference="eshop/*" -q)
|
||||
if [ -n "$images" ]; then
|
||||
docker rm $(docker ps -a -q) -f
|
||||
echo "Deleting eShop images in local Docker repo"
|
||||
echo $images
|
||||
docker rmi $(docker images --filter=reference="eshop/*" -q) -f
|
||||
fi
|
||||
## remove old docker images:
|
||||
#images=$(docker images --filter=reference="eshop/*" -q)
|
||||
#if [ -n "$images" ]; then
|
||||
# docker rm $(docker ps -a -q) -f
|
||||
# echo "Deleting eShop images in local Docker repo"
|
||||
# echo $images
|
||||
# docker rmi $(docker images --filter=reference="eshop/*" -q) -f
|
||||
#fi
|
||||
|
||||
|
||||
# No need to build the images, docker build or docker compose will
|
||||
# do that using the images and containers defined in the docker-compose.yml file.
|
||||
|
@ -21,6 +21,6 @@ try {
|
||||
Write-Host "Rule found"
|
||||
}
|
||||
catch [Exception] {
|
||||
New-NetFirewallRule -DisplayName eShopOnContainers-Inbound -Confirm -Description "eShopOnContainers Inbound Rule for port range 5100-5105" -LocalAddress Any -LocalPort 5100-5105 -Protocol tcp -RemoteAddress Any -RemotePort Any -Direction Inbound
|
||||
New-NetFirewallRule -DisplayName eShopOnContainers-Outbound -Confirm -Description "eShopOnContainers Outbound Rule for port range 5100-5105" -LocalAddress Any -LocalPort 5100-5105 -Protocol tcp -RemoteAddress Any -RemotePort Any -Direction Outbound
|
||||
New-NetFirewallRule -DisplayName eShopOnContainers-Inbound -Confirm -Description "eShopOnContainers Inbound Rule for port range 5100-5105" -LocalAddress Any -LocalPort 5100-5110 -Protocol tcp -RemoteAddress Any -RemotePort Any -Direction Inbound
|
||||
New-NetFirewallRule -DisplayName eShopOnContainers-Outbound -Confirm -Description "eShopOnContainers Outbound Rule for port range 5100-5105" -LocalAddress Any -LocalPort 5100-5110 -Protocol tcp -RemoteAddress Any -RemotePort Any -Direction Outbound
|
||||
}
|
11
cli-windows/build-images.ps1
Normal file
11
cli-windows/build-images.ps1
Normal 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
|
@ -80,4 +80,31 @@ services:
|
||||
- SA_PASSWORD=Pass@word
|
||||
- ACCEPT_EULA=Y
|
||||
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"
|
@ -75,7 +75,19 @@ services:
|
||||
- BasketUrl=http://basket.api
|
||||
ports:
|
||||
- "5100:80"
|
||||
|
||||
|
||||
marketing.api:
|
||||
environment:
|
||||
- ASPNETCORE_ENVIRONMENT=Production
|
||||
- ASPNETCORE_URLS=http://0.0.0.0:80
|
||||
- ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.MarketingDb;User Id=sa;Password=Pass@word
|
||||
- EventBusConnection=rabbitmq
|
||||
- MongoConnectionString=mongodb://nosql.data
|
||||
- MongoDatabase=MarketingDb
|
||||
- identityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
|
||||
ports:
|
||||
- "5110:80"
|
||||
|
||||
sql.data:
|
||||
environment:
|
||||
- SA_PASSWORD=Pass@word
|
||||
|
@ -2,7 +2,7 @@ version: '2.1'
|
||||
|
||||
services:
|
||||
basket.api:
|
||||
image: eshop/basket.api-win
|
||||
image: eshop/basket.api-win:${TAG:-latest}
|
||||
build:
|
||||
context: ./src/Services/Basket/Basket.API
|
||||
dockerfile: Dockerfile.nanowin
|
||||
@ -11,7 +11,7 @@ services:
|
||||
- identity.api
|
||||
|
||||
catalog.api:
|
||||
image: eshop/catalog.api-win
|
||||
image: eshop/catalog.api-win:${TAG:-latest}
|
||||
build:
|
||||
context: ./src/Services/Catalog/Catalog.API
|
||||
dockerfile: Dockerfile.nanowin
|
||||
@ -19,7 +19,7 @@ services:
|
||||
- sql.data
|
||||
|
||||
identity.api:
|
||||
image: eshop/identity.api-win
|
||||
image: eshop/identity.api-win:${TAG:-latest}
|
||||
build:
|
||||
context: ./src/Services/Identity/Identity.API
|
||||
dockerfile: Dockerfile.nanowin
|
||||
@ -27,7 +27,7 @@ services:
|
||||
- sql.data
|
||||
|
||||
ordering.api:
|
||||
image: eshop/ordering.api-win
|
||||
image: eshop/ordering.api-win:${TAG:-latest}
|
||||
build:
|
||||
context: ./src/Services/Ordering/Ordering.API
|
||||
dockerfile: Dockerfile.nanowin
|
||||
@ -35,7 +35,7 @@ services:
|
||||
- sql.data
|
||||
|
||||
webspa:
|
||||
image: eshop/webspa-win
|
||||
image: eshop/webspa-win:${TAG:-latest}
|
||||
build:
|
||||
context: ./src/Web/WebSPA
|
||||
dockerfile: Dockerfile.nanowin
|
||||
@ -44,7 +44,7 @@ services:
|
||||
- basket.api
|
||||
|
||||
webmvc:
|
||||
image: eshop/webmvc-win
|
||||
image: eshop/webmvc-win:${TAG:-latest}
|
||||
build:
|
||||
context: ./src/Web/WebMVC
|
||||
dockerfile: Dockerfile.nanowin
|
||||
@ -53,9 +53,32 @@ services:
|
||||
- ordering.api
|
||||
- identity.api
|
||||
- basket.api
|
||||
|
||||
locations.api:
|
||||
image: eshop/locations.api:${TAG:-latest}
|
||||
build:
|
||||
context: ./src/Services/Location/Locations.API
|
||||
dockerfile: Dockerfile
|
||||
depends_on:
|
||||
- nosql.data
|
||||
- rabbitmq
|
||||
|
||||
marketing.api:
|
||||
image: eshop/marketing.api:${TAG:-latest}
|
||||
build:
|
||||
context: ./src/Services/Marketing/Marketing.API
|
||||
dockerfile: Dockerfile
|
||||
depends_on:
|
||||
- sql.data
|
||||
- nosql.data
|
||||
- identity.api
|
||||
- rabbitmq
|
||||
|
||||
sql.data:
|
||||
image: microsoft/mssql-server-windows
|
||||
|
||||
nosql.data:
|
||||
image: mongo:windowsservercore
|
||||
|
||||
basket.data:
|
||||
image: redis:nanoserver
|
||||
|
@ -1,10 +1,14 @@
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
ci-build:
|
||||
image: microsoft/aspnetcore-build:1.0-1.1
|
||||
image: microsoft/aspnetcore-build:1.1.2
|
||||
volumes:
|
||||
- .:/src
|
||||
- ./cli-linux:/cli-linux
|
||||
working_dir: /src
|
||||
command: /bin/bash -c "pushd ./src/Web/WebSPA && npm rebuild node-sass && popd && dotnet restore ./eShopOnContainers-ServicesAndWebApps.sln && dotnet publish ./eShopOnContainers-ServicesAndWebApps.sln -c Release -o ./obj/Docker/publish"
|
||||
# DO NOT USE the sln file to compile because msbuild issue (https://github.com/Microsoft/msbuild/issues/2153)
|
||||
# command: /bin/bash -c "pushd ./src/Web/WebSPA && npm rebuild node-sass && popd && dotnet restore ./eShopOnContainers-ServicesAndWebApps.sln && dotnet publish ./eShopOnContainers-ServicesAndWebApps.sln -c Release -o ./obj/Docker/publish"
|
||||
# NOTE: Using build-bits-linux.sh triggers the same MSBUILD error :( (but at least, less frequently)
|
||||
command: /bin/bash -c "pushd ./src/Web/WebSPA && npm rebuild node-sass && popd && pushd /cli-linux && ./build-bits-linux.sh /src"
|
||||
|
56
docker-compose.nobuild.yml
Normal file
56
docker-compose.nobuild.yml
Normal 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
|
||||
|
@ -1,4 +1,4 @@
|
||||
version: '2.1'
|
||||
version: '3'
|
||||
|
||||
# The default docker-compose.override file can use the "localhost" as the external name for testing web apps within the same dev machine.
|
||||
# The ESHOP_EXTERNAL_DNS_NAME_OR_IP environment variable is taken, by default, from the ".env" file defined like:
|
||||
@ -7,7 +7,11 @@ version: '2.1'
|
||||
# An external IP or DNS name has to be used (instead localhost and the 10.0.75.1 IP) when testing the Web apps and the Xamarin apps from remote machines/devices using the same WiFi, for instance.
|
||||
|
||||
services:
|
||||
|
||||
graceperiodmanager:
|
||||
environment:
|
||||
- ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word
|
||||
- EventBusConnection=rabbitmq
|
||||
|
||||
basket.api:
|
||||
environment:
|
||||
- ASPNETCORE_ENVIRONMENT=Development
|
||||
@ -49,6 +53,19 @@ services:
|
||||
ports:
|
||||
- "5102:80"
|
||||
|
||||
marketing.api:
|
||||
environment:
|
||||
- ASPNETCORE_ENVIRONMENT=Development
|
||||
- ASPNETCORE_URLS=http://0.0.0.0:80
|
||||
- ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.MarketingDb;User Id=sa;Password=Pass@word
|
||||
- MongoConnectionString=mongodb://nosql.data
|
||||
- MongoDatabase=MarketingDb
|
||||
- EventBusConnection=rabbitmq
|
||||
- ExternalCatalogBaseUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5110 #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
|
||||
- identityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
|
||||
ports:
|
||||
- "5110:80"
|
||||
|
||||
webspa:
|
||||
environment:
|
||||
- ASPNETCORE_ENVIRONMENT=Development
|
||||
@ -57,6 +74,7 @@ services:
|
||||
- OrderingUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5102
|
||||
- IdentityUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5105 #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
|
||||
- BasketUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5103
|
||||
- MarketingUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5110
|
||||
- CatalogUrlHC=http://catalog.api/hc
|
||||
- OrderingUrlHC=http://ordering.api/hc
|
||||
- IdentityUrlHC=http://identity.api/hc #Local: Use ${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}, if using external IP or DNS name from browser.
|
||||
@ -71,7 +89,8 @@ services:
|
||||
- CatalogUrl=http://catalog.api
|
||||
- OrderingUrl=http://ordering.api
|
||||
- BasketUrl=http://basket.api
|
||||
- IdentityUrl=http://10.0.75.1:5105 #Local: Use 10.0.75.1 in a "Docker for Windows" environment, if using "localhost" from browser.
|
||||
- IdentityUrl=http://10.0.75.1:5105
|
||||
- MarketingUrl=http://marketing.api #Local: Use 10.0.75.1 in a "Docker for Windows" environment, if using "localhost" from browser.
|
||||
#Remote: Use ${ESHOP_EXTERNAL_DNS_NAME_OR_IP} if using external IP or DNS name from browser.
|
||||
ports:
|
||||
- "5100:80"
|
||||
@ -83,6 +102,10 @@ services:
|
||||
ports:
|
||||
- "5433:1433"
|
||||
|
||||
nosql.data:
|
||||
ports:
|
||||
- "27017:27017"
|
||||
|
||||
webstatus:
|
||||
environment:
|
||||
- ASPNETCORE_ENVIRONMENT=Development
|
||||
@ -95,3 +118,22 @@ services:
|
||||
- spa=http://webspa/hc
|
||||
ports:
|
||||
- "5107:80"
|
||||
|
||||
payment.api:
|
||||
environment:
|
||||
- ASPNETCORE_ENVIRONMENT=Development
|
||||
- ASPNETCORE_URLS=http://0.0.0.0:5108
|
||||
- EventBusConnection=rabbitmq
|
||||
ports:
|
||||
- "5108:80"
|
||||
|
||||
locations.api:
|
||||
environment:
|
||||
- ASPNETCORE_ENVIRONMENT=Development
|
||||
- ASPNETCORE_URLS=http://0.0.0.0:80
|
||||
- ConnectionString=mongodb://nosql.data
|
||||
- Database=LocationsDb
|
||||
- identityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
|
||||
- EventBusConnection=rabbitmq
|
||||
ports:
|
||||
- "5109:80"
|
||||
|
@ -1,4 +1,4 @@
|
||||
version: '2.1'
|
||||
version: '3'
|
||||
|
||||
# The Production docker-compose file has to have the external/real IPs or DNS names for the services
|
||||
# The ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP environment variable is taken, by default, from the ".env" file defined like:
|
||||
@ -54,6 +54,18 @@ services:
|
||||
ports:
|
||||
- "5102:80"
|
||||
|
||||
marketing.api:
|
||||
environment:
|
||||
- ASPNETCORE_ENVIRONMENT=Production
|
||||
- ASPNETCORE_URLS=http://0.0.0.0:80
|
||||
- ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.MarketingDb;User Id=sa;Password=Pass@word
|
||||
- MongoConnectionString=mongodb://nosql.data
|
||||
- MongoDatabase=MarketingDb
|
||||
- EventBusConnection=rabbitmq
|
||||
- identityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
|
||||
ports:
|
||||
- "5110:80"
|
||||
|
||||
webspa:
|
||||
environment:
|
||||
- ASPNETCORE_ENVIRONMENT=Production
|
||||
|
@ -1,4 +1,4 @@
|
||||
version: '2.1'
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
basket.api:
|
||||
@ -61,6 +61,22 @@ services:
|
||||
labels:
|
||||
- "com.microsoft.visualstudio.targetoperatingsystem=linux"
|
||||
|
||||
marketing.api:
|
||||
image: eshop/marketing.api:dev
|
||||
build:
|
||||
args:
|
||||
source: ${DOCKER_BUILD_SOURCE}
|
||||
environment:
|
||||
- DOTNET_USE_POLLING_FILE_WATCHER=1
|
||||
volumes:
|
||||
- ./src/Services/Marketing/Marketing.API:/app
|
||||
- ~/.nuget/packages:/root/.nuget/packages:ro
|
||||
- ~/clrdbg:/clrdbg:ro
|
||||
entrypoint: tail -f /dev/null
|
||||
labels:
|
||||
- "com.microsoft.visualstudio.targetoperatingsystem=linux"
|
||||
|
||||
|
||||
webspa:
|
||||
image: eshop/webspa:dev
|
||||
build:
|
||||
@ -105,3 +121,46 @@ services:
|
||||
entrypoint: tail -f /dev/null
|
||||
labels:
|
||||
- "com.microsoft.visualstudio.targetoperatingsystem=linux"
|
||||
|
||||
payment.api:
|
||||
image: eshop/payment.api:dev
|
||||
build:
|
||||
args:
|
||||
source: ${DOCKER_BUILD_SOURCE}
|
||||
environment:
|
||||
- DOTNET_USE_POLLING_FILE_WATCHER=1
|
||||
volumes:
|
||||
- ./src/Services/Payment/Payment.API:/app
|
||||
- ~/.nuget/packages:/root/.nuget/packages:ro
|
||||
- ~/clrdbg:/clrdbg:ro
|
||||
entrypoint: tail -f /dev/null
|
||||
labels:
|
||||
- "com.microsoft.visualstudio.targetoperatingsystem=linux"
|
||||
|
||||
graceperiodmanager:
|
||||
image: eshop/graceperiodmanager:dev
|
||||
build:
|
||||
args:
|
||||
source: ${DOCKER_BUILD_SOURCE}
|
||||
volumes:
|
||||
- ./src/Services/GracePeriod/GracePeriodManager:/app
|
||||
- ~/.nuget/packages:/root/.nuget/packages:ro
|
||||
- ~/clrdbg:/clrdbg:ro
|
||||
entrypoint: tail -f /dev/null
|
||||
labels:
|
||||
- "com.microsoft.visualstudio.targetoperatingsystem=linux"
|
||||
|
||||
locations.api:
|
||||
image: eshop/locations.api:dev
|
||||
build:
|
||||
args:
|
||||
source: ${DOCKER_BUILD_SOURCE}
|
||||
environment:
|
||||
- DOTNET_USE_POLLING_FILE_WATCHER=1
|
||||
volumes:
|
||||
- ./src/Services/Location/Locations.API:/app
|
||||
- ~/.nuget/packages:/root/.nuget/packages:ro
|
||||
- ~/clrdbg:/clrdbg:ro
|
||||
entrypoint: tail -f /dev/null
|
||||
labels:
|
||||
- "com.microsoft.visualstudio.targetoperatingsystem=linux"
|
||||
|
@ -1,4 +1,4 @@
|
||||
version: '2.1'
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
basket.api:
|
||||
@ -41,6 +41,16 @@ services:
|
||||
labels:
|
||||
- "com.microsoft.visualstudio.targetoperatingsystem=linux"
|
||||
|
||||
marketing.api:
|
||||
build:
|
||||
args:
|
||||
source: ${DOCKER_BUILD_SOURCE}
|
||||
volumes:
|
||||
- ~/clrdbg:/clrdbg:ro
|
||||
entrypoint: tail -f /dev/null
|
||||
labels:
|
||||
- "com.microsoft.visualstudio.targetoperatingsystem=linux"
|
||||
|
||||
webspa:
|
||||
build:
|
||||
args:
|
||||
@ -70,3 +80,33 @@ services:
|
||||
entrypoint: tail -f /dev/null
|
||||
labels:
|
||||
- "com.microsoft.visualstudio.targetoperatingsystem=linux"
|
||||
|
||||
payment.api:
|
||||
build:
|
||||
args:
|
||||
source: ${DOCKER_BUILD_SOURCE}
|
||||
volumes:
|
||||
- ~/clrdbg:/clrdbg:ro
|
||||
entrypoint: tail -f /dev/null
|
||||
labels:
|
||||
- "com.microsoft.visualstudio.targetoperatingsystem=linux"
|
||||
|
||||
graceperiodmanager:
|
||||
build:
|
||||
args:
|
||||
source: ${DOCKER_BUILD_SOURCE}
|
||||
volumes:
|
||||
- ~/clrdbg:/clrdbg:ro
|
||||
entrypoint: tail -f /dev/null
|
||||
labels:
|
||||
- "com.microsoft.visualstudio.targetoperatingsystem=linux"
|
||||
|
||||
locations.api:
|
||||
build:
|
||||
args:
|
||||
source: ${DOCKER_BUILD_SOURCE}
|
||||
volumes:
|
||||
- ~/clrdbg:/clrdbg:ro
|
||||
entrypoint: tail -f /dev/null
|
||||
labels:
|
||||
- "com.microsoft.visualstudio.targetoperatingsystem=linux"
|
||||
|
@ -1,64 +1,88 @@
|
||||
version: '2.1'
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
graceperiodmanager:
|
||||
image: eshop/graceperiodmanager:${TAG:-latest}
|
||||
build:
|
||||
context: ./src/Services/GracePeriod/GracePeriodManager
|
||||
dockerfile: Dockerfile
|
||||
depends_on:
|
||||
- sql.data
|
||||
- rabbitmq
|
||||
|
||||
basket.api:
|
||||
image: eshop/basket.api
|
||||
image: eshop/basket.api:${TAG:-latest}
|
||||
build:
|
||||
context: ./src/Services/Basket/Basket.API
|
||||
dockerfile: Dockerfile
|
||||
dockerfile: Dockerfile
|
||||
depends_on:
|
||||
- basket.data
|
||||
- identity.api
|
||||
- rabbitmq
|
||||
|
||||
catalog.api:
|
||||
image: eshop/catalog.api
|
||||
image: eshop/catalog.api:${TAG:-latest}
|
||||
build:
|
||||
context: ./src/Services/Catalog/Catalog.API
|
||||
dockerfile: Dockerfile
|
||||
dockerfile: Dockerfile
|
||||
depends_on:
|
||||
- sql.data
|
||||
- rabbitmq
|
||||
|
||||
identity.api:
|
||||
image: eshop/identity.api
|
||||
image: eshop/identity.api:${TAG:-latest}
|
||||
build:
|
||||
context: ./src/Services/Identity/Identity.API
|
||||
dockerfile: Dockerfile
|
||||
dockerfile: Dockerfile
|
||||
depends_on:
|
||||
- sql.data
|
||||
|
||||
ordering.api:
|
||||
image: eshop/ordering.api
|
||||
image: eshop/ordering.api:${TAG:-latest}
|
||||
build:
|
||||
context: ./src/Services/Ordering/Ordering.API
|
||||
dockerfile: Dockerfile
|
||||
dockerfile: Dockerfile
|
||||
depends_on:
|
||||
- sql.data
|
||||
- rabbitmq
|
||||
|
||||
marketing.api:
|
||||
image: eshop/marketing.api:${TAG:-latest}
|
||||
build:
|
||||
context: ./src/Services/Marketing/Marketing.API
|
||||
dockerfile: Dockerfile
|
||||
depends_on:
|
||||
- sql.data
|
||||
- nosql.data
|
||||
- identity.api
|
||||
- rabbitmq
|
||||
|
||||
webspa:
|
||||
image: eshop/webspa
|
||||
image: eshop/webspa:${TAG:-latest}
|
||||
build:
|
||||
context: ./src/Web/WebSPA
|
||||
dockerfile: Dockerfile
|
||||
dockerfile: Dockerfile
|
||||
depends_on:
|
||||
- identity.api
|
||||
- basket.api
|
||||
|
||||
webmvc:
|
||||
image: eshop/webmvc
|
||||
image: eshop/webmvc:${TAG:-latest}
|
||||
build:
|
||||
context: ./src/Web/WebMVC
|
||||
dockerfile: Dockerfile
|
||||
dockerfile: Dockerfile
|
||||
depends_on:
|
||||
- catalog.api
|
||||
- ordering.api
|
||||
- identity.api
|
||||
- basket.api
|
||||
- marketing.api
|
||||
|
||||
sql.data:
|
||||
image: microsoft/mssql-server-linux
|
||||
|
||||
nosql.data:
|
||||
image: mongo
|
||||
|
||||
basket.data:
|
||||
image: redis
|
||||
ports:
|
||||
@ -70,7 +94,24 @@ services:
|
||||
- "5672:5672"
|
||||
|
||||
webstatus:
|
||||
image: eshop/webstatus
|
||||
image: eshop/webstatus:${TAG:-latest}
|
||||
build:
|
||||
context: ./src/Web/WebStatus
|
||||
dockerfile: Dockerfile
|
||||
|
||||
payment.api:
|
||||
image: eshop/payment.api:${TAG:-latest}
|
||||
build:
|
||||
context: ./src/Services/Payment/Payment.API
|
||||
dockerfile: Dockerfile
|
||||
depends_on:
|
||||
- rabbitmq
|
||||
|
||||
locations.api:
|
||||
image: eshop/locations.api:${TAG:-latest}
|
||||
build:
|
||||
context: ./src/Services/Location/Locations.API
|
||||
dockerfile: Dockerfile
|
||||
depends_on:
|
||||
- nosql.data
|
||||
- rabbitmq
|
||||
|
Binary file not shown.
Binary file not shown.
@ -1,7 +1,7 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.26430.6
|
||||
VisualStudioVersion = 15.0.26430.12
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{932D8224-11F6-4D07-B109-DA28AD288A63}"
|
||||
EndProject
|
||||
@ -70,11 +70,31 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Resilience", "Resilience",
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Resilience.Http", "src\BuildingBlocks\Resilience\Resilience.Http\Resilience.Http.csproj", "{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Payment", "Payment", "{022E145D-1593-47EE-9608-8E323D3C63F5}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Payment.API", "src\Services\Payment\Payment.API\Payment.API.csproj", "{1A01AF82-6FCB-464C-B39C-F127AEBD315D}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.HealthChecks", "src\BuildingBlocks\HealthChecks\src\Microsoft.AspNetCore.HealthChecks\Microsoft.AspNetCore.HealthChecks.csproj", "{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.HealthChecks.SqlServer", "src\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.SqlServer\Microsoft.Extensions.HealthChecks.SqlServer.csproj", "{4BD76717-3102-4969-8C2C-BAAA3F0263B6}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventBus.Tests", "src\BuildingBlocks\EventBus\EventBus.Tests\EventBus.Tests.csproj", "{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EventBus.Tests", "src\BuildingBlocks\EventBus\EventBus.Tests\EventBus.Tests.csproj", "{4A980AC4-7205-46BF-8CCB-09E44D700FD4}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GracePeriod", "GracePeriod", "{F38B4FF0-0B49-405A-B1B4-F7A5E3BC4C4E}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GracePeriodManager", "src\Services\GracePeriod\GracePeriodManager\GracePeriodManager.csproj", "{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Location", "Location", "{41139F64-4046-4F16-96B7-D941D96FA9C6}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Locations.API", "src\Services\Location\Locations.API\Locations.API.csproj", "{E7581357-FC34-474C-B8F5-307EE3CE05EF}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DataProtection", "DataProtection", "{88B22DBB-AA8F-4290-A454-2C109352C345}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataProtection", "src\BuildingBlocks\DataProtection\DataProtection\DataProtection.csproj", "{23A33F9B-7672-426D-ACF9-FF8436ADC81A}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Marketing", "Marketing", "{A5260DE0-1FDD-467E-9CC1-A028AB081CEE}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Marketing.API", "src\Services\Marketing\Marketing.API\Marketing.API.csproj", "{DF395F85-B010-465D-857A-7EBCC512C0C2}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventBusServiceBus", "src\BuildingBlocks\EventBus\EventBusServiceBus\EventBusServiceBus.csproj", "{69AF10D3-AA76-4FF7-B187-EC7E8CC5F5B8}"
|
||||
EndProject
|
||||
@ -860,6 +880,54 @@ Global
|
||||
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Release|x64.Build.0 = Release|Any CPU
|
||||
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Release|x86.Build.0 = Release|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.AppStore|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.AppStore|ARM.ActiveCfg = Debug|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.AppStore|ARM.Build.0 = Debug|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.AppStore|iPhone.Build.0 = Debug|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.AppStore|x64.ActiveCfg = Debug|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.AppStore|x64.Build.0 = Debug|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.AppStore|x86.ActiveCfg = Debug|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.AppStore|x86.Build.0 = Debug|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Debug|iPhone.Build.0 = Debug|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Release|iPhone.Build.0 = Release|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Release|x64.Build.0 = Release|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Release|x86.Build.0 = Release|Any CPU
|
||||
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
|
||||
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
|
||||
@ -956,6 +1024,246 @@ Global
|
||||
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|x64.Build.0 = Release|Any CPU
|
||||
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|x86.Build.0 = Release|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.AppStore|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.AppStore|ARM.ActiveCfg = Debug|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.AppStore|ARM.Build.0 = Debug|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.AppStore|iPhone.Build.0 = Debug|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.AppStore|x64.ActiveCfg = Debug|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.AppStore|x64.Build.0 = Debug|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.AppStore|x86.ActiveCfg = Debug|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.AppStore|x86.Build.0 = Debug|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Debug|iPhone.Build.0 = Debug|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Release|iPhone.Build.0 = Release|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Release|x64.Build.0 = Release|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Release|x86.Build.0 = Release|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|ARM.ActiveCfg = Debug|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|ARM.Build.0 = Debug|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|iPhone.Build.0 = Debug|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|x64.ActiveCfg = Debug|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|x64.Build.0 = Debug|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|x86.ActiveCfg = Debug|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|x86.Build.0 = Debug|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|iPhone.Build.0 = Debug|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|iPhone.Build.0 = Release|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|x64.Build.0 = Release|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|x86.Build.0 = Release|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.AppStore|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.AppStore|ARM.ActiveCfg = Debug|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.AppStore|ARM.Build.0 = Debug|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.AppStore|iPhone.Build.0 = Debug|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.AppStore|x64.ActiveCfg = Debug|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.AppStore|x64.Build.0 = Debug|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.AppStore|x86.ActiveCfg = Debug|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.AppStore|x86.Build.0 = Debug|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Debug|iPhone.Build.0 = Debug|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Release|iPhone.Build.0 = Release|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Release|x64.Build.0 = Release|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF}.Release|x86.Build.0 = Release|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.AppStore|Any CPU.Build.0 = Debug|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.AppStore|ARM.ActiveCfg = Debug|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.AppStore|ARM.Build.0 = Debug|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.AppStore|iPhone.Build.0 = Debug|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.AppStore|x64.ActiveCfg = Debug|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.AppStore|x64.Build.0 = Debug|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.AppStore|x86.ActiveCfg = Debug|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.AppStore|x86.Build.0 = Debug|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Debug|iPhone.Build.0 = Debug|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Release|iPhone.Build.0 = Release|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Release|x64.Build.0 = Release|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Release|x86.Build.0 = Release|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.AppStore|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.AppStore|ARM.ActiveCfg = Debug|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.AppStore|ARM.Build.0 = Debug|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.AppStore|iPhone.Build.0 = Debug|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.AppStore|x64.ActiveCfg = Debug|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.AppStore|x64.Build.0 = Debug|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.AppStore|x86.ActiveCfg = Debug|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.AppStore|x86.Build.0 = Debug|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Debug|iPhone.Build.0 = Debug|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Release|iPhone.Build.0 = Release|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Release|x64.Build.0 = Release|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Release|x86.Build.0 = Release|Any CPU
|
||||
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
|
||||
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
|
||||
@ -1085,9 +1393,18 @@ Global
|
||||
{C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F} = {E279BF0F-7F66-4F3A-A3AB-2CDA66C1CD04}
|
||||
{FBF43D93-F2E7-4FF8-B4AB-186895949B88} = {DB0EFB20-B024-4E5E-A75C-52143C131D25}
|
||||
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502} = {FBF43D93-F2E7-4FF8-B4AB-186895949B88}
|
||||
{022E145D-1593-47EE-9608-8E323D3C63F5} = {91CF7717-08AB-4E65-B10E-0B426F01E2E8}
|
||||
{1A01AF82-6FCB-464C-B39C-F127AEBD315D} = {022E145D-1593-47EE-9608-8E323D3C63F5}
|
||||
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5} = {A81ECBC2-6B00-4DCD-8388-469174033379}
|
||||
{4BD76717-3102-4969-8C2C-BAAA3F0263B6} = {A81ECBC2-6B00-4DCD-8388-469174033379}
|
||||
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D} = {807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F}
|
||||
{69AF10D3-AA76-4FF7-B187-EC7E8CC5F5B8} = {807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F}
|
||||
{4A980AC4-7205-46BF-8CCB-09E44D700FD4} = {807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F}
|
||||
{F38B4FF0-0B49-405A-B1B4-F7A5E3BC4C4E} = {91CF7717-08AB-4E65-B10E-0B426F01E2E8}
|
||||
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47} = {F38B4FF0-0B49-405A-B1B4-F7A5E3BC4C4E}
|
||||
{41139F64-4046-4F16-96B7-D941D96FA9C6} = {91CF7717-08AB-4E65-B10E-0B426F01E2E8}
|
||||
{E7581357-FC34-474C-B8F5-307EE3CE05EF} = {41139F64-4046-4F16-96B7-D941D96FA9C6}
|
||||
{88B22DBB-AA8F-4290-A454-2C109352C345} = {DB0EFB20-B024-4E5E-A75C-52143C131D25}
|
||||
{23A33F9B-7672-426D-ACF9-FF8436ADC81A} = {88B22DBB-AA8F-4290-A454-2C109352C345}
|
||||
{A5260DE0-1FDD-467E-9CC1-A028AB081CEE} = {91CF7717-08AB-4E65-B10E-0B426F01E2E8}
|
||||
{DF395F85-B010-465D-857A-7EBCC512C0C2} = {A5260DE0-1FDD-467E-9CC1-A028AB081CEE}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
@ -1,7 +1,7 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.26228.12
|
||||
VisualStudioVersion = 15.0.26430.6
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{932D8224-11F6-4D07-B109-DA28AD288A63}"
|
||||
EndProject
|
||||
@ -97,14 +97,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Health
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.HealthChecks", "src\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks\Microsoft.Extensions.HealthChecks.csproj", "{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.HealthChecks.Data", "src\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.Data\Microsoft.Extensions.HealthChecks.Data.csproj", "{0E7AEB45-80F2-42B9-96BB-3414669E58AA}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Resilience", "Resilience", "{D13768ED-5AF1-4E09-96DD-FF6E7A2E5E06}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Resilience.Http", "src\BuildingBlocks\Resilience\Resilience.Http\Resilience.Http.csproj", "{D92EB452-7A72-4B26-A8ED-0204CD376BC4}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebStatus", "src\Web\WebStatus\WebStatus.csproj", "{23FB706A-2701-41E9-8BF9-28936001CA41}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.HealthChecks.SqlServer", "src\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.SqlServer\Microsoft.Extensions.HealthChecks.SqlServer.csproj", "{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
|
||||
@ -1322,54 +1322,6 @@ Global
|
||||
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.Release|x64.Build.0 = Release|Any CPU
|
||||
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.Release|x86.Build.0 = Release|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.AppStore|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.AppStore|ARM.ActiveCfg = Debug|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.AppStore|ARM.Build.0 = Debug|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.AppStore|iPhone.Build.0 = Debug|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.AppStore|x64.ActiveCfg = Debug|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.AppStore|x64.Build.0 = Debug|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.AppStore|x86.ActiveCfg = Debug|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.AppStore|x86.Build.0 = Debug|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Debug|iPhone.Build.0 = Debug|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Release|iPhone.Build.0 = Release|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Release|x64.Build.0 = Release|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Release|x86.Build.0 = Release|Any CPU
|
||||
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
|
||||
@ -1466,6 +1418,54 @@ Global
|
||||
{23FB706A-2701-41E9-8BF9-28936001CA41}.Release|x64.Build.0 = Release|Any CPU
|
||||
{23FB706A-2701-41E9-8BF9-28936001CA41}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{23FB706A-2701-41E9-8BF9-28936001CA41}.Release|x86.Build.0 = Release|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.AppStore|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.AppStore|ARM.ActiveCfg = Debug|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.AppStore|ARM.Build.0 = Debug|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.AppStore|iPhone.Build.0 = Debug|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.AppStore|x64.ActiveCfg = Debug|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.AppStore|x64.Build.0 = Debug|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.AppStore|x86.ActiveCfg = Debug|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.AppStore|x86.Build.0 = Debug|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Debug|iPhone.Build.0 = Debug|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Release|iPhone.Build.0 = Release|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Release|x64.Build.0 = Release|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@ -1510,9 +1510,9 @@ Global
|
||||
{96CE8CE7-BC97-4A53-899F-5EB63D7BBF7B} = {1EF3AC0F-F27C-46DD-AC53-D762D2C11C45}
|
||||
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3} = {96CE8CE7-BC97-4A53-899F-5EB63D7BBF7B}
|
||||
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45} = {96CE8CE7-BC97-4A53-899F-5EB63D7BBF7B}
|
||||
{0E7AEB45-80F2-42B9-96BB-3414669E58AA} = {96CE8CE7-BC97-4A53-899F-5EB63D7BBF7B}
|
||||
{D13768ED-5AF1-4E09-96DD-FF6E7A2E5E06} = {1EF3AC0F-F27C-46DD-AC53-D762D2C11C45}
|
||||
{D92EB452-7A72-4B26-A8ED-0204CD376BC4} = {D13768ED-5AF1-4E09-96DD-FF6E7A2E5E06}
|
||||
{23FB706A-2701-41E9-8BF9-28936001CA41} = {E279BF0F-7F66-4F3A-A3AB-2CDA66C1CD04}
|
||||
{6CCC4F1B-602D-4FAD-91A7-002CC86C7612} = {96CE8CE7-BC97-4A53-899F-5EB63D7BBF7B}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
5
global.json
Normal file
5
global.json
Normal 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
29
k8s/basket-data.yaml
Normal 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
|
||||
|
195
k8s/deploy.ps1
195
k8s/deploy.ps1
@ -2,10 +2,14 @@ Param(
|
||||
[parameter(Mandatory=$false)][string]$registry,
|
||||
[parameter(Mandatory=$false)][string]$dockerUser,
|
||||
[parameter(Mandatory=$false)][string]$dockerPassword,
|
||||
[parameter(Mandatory=$false)][bool]$deployCI,
|
||||
[parameter(Mandatory=$false)][bool]$useDockerHub,
|
||||
[parameter(Mandatory=$false)][string]$execPath,
|
||||
[parameter(Mandatory=$false)][string]$kubeconfigPath
|
||||
[parameter(Mandatory=$false)][string]$kubeconfigPath,
|
||||
[parameter(Mandatory=$true)][string]$configFile,
|
||||
[parameter(Mandatory=$false)][string]$imageTag,
|
||||
[parameter(Mandatory=$false)][string]$externalDns,
|
||||
[parameter(Mandatory=$false)][bool]$deployCI=$false,
|
||||
[parameter(Mandatory=$false)][bool]$buildImages=$false,
|
||||
[parameter(Mandatory=$false)][bool]$deployInfrastructure=$true
|
||||
)
|
||||
|
||||
function ExecKube($cmd) {
|
||||
@ -20,7 +24,11 @@ function ExecKube($cmd) {
|
||||
}
|
||||
}
|
||||
|
||||
# Not used when deploying through CI VSTS
|
||||
# Initialization
|
||||
$debugMode = $PSCmdlet.MyInvocation.BoundParameters["Debug"].IsPresent
|
||||
$useDockerHub = [string]::IsNullOrEmpty($registry)
|
||||
|
||||
# Check required commands (only if not in CI environment)
|
||||
if(-not $deployCI) {
|
||||
$requiredCommands = ("docker", "docker-compose", "kubectl")
|
||||
foreach ($command in $requiredCommands) {
|
||||
@ -30,6 +38,45 @@ if(-not $deployCI) {
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$buildImages = false; # Never build images through CI, as they previously built
|
||||
}
|
||||
|
||||
# Get tag to use from current branch if no tag is passed
|
||||
if ([string]::IsNullOrEmpty($imageTag)) {
|
||||
$imageTag = $(git rev-parse --abbrev-ref HEAD)
|
||||
}
|
||||
Write-Host "Docker image Tag: $imageTag" -ForegroundColor Yellow
|
||||
|
||||
# Read config to use
|
||||
$config = Get-Content -Raw -Path $configFile | ConvertFrom-Json
|
||||
if ($debugMode) {
|
||||
Write-Host "[DEBUG]: Using following JSON config: " -ForegroundColor Yellow
|
||||
$json = ConvertTo-Json $config -Depth 5
|
||||
Write-Host $json
|
||||
if (-not $deployCI) {
|
||||
Write-Host "[DEBUG]: Press a key " -ForegroundColor Yellow
|
||||
[System.Console]::Read()
|
||||
}
|
||||
}
|
||||
|
||||
# building and publishing docker images if needed
|
||||
if($buildImages) {
|
||||
Write-Host "Building and publishing eShopOnContainers..." -ForegroundColor Yellow
|
||||
dotnet restore ../eShopOnContainers-ServicesAndWebApps.sln
|
||||
dotnet publish -c Release -o obj/Docker/publish ../eShopOnContainers-ServicesAndWebApps.sln
|
||||
|
||||
Write-Host "Building Docker images tagged with '$imageTag'" -ForegroundColor Yellow
|
||||
$env:TAG=$imageTag
|
||||
docker-compose -p .. -f ../docker-compose.yml build
|
||||
|
||||
Write-Host "Pushing images to $registry..." -ForegroundColor Yellow
|
||||
$services = ("basket.api", "catalog.api", "identity.api", "ordering.api", "marketing.api","payment.api","locations.api", "webmvc", "webspa", "webstatus")
|
||||
foreach ($service in $services) {
|
||||
docker tag eshop/${service}:$imageTag $registry/eshop/${service}:$imageTag
|
||||
docker push $registry/eshop/${service}:$imageTag
|
||||
}
|
||||
}
|
||||
|
||||
# Use ACR instead of DockerHub as image repository
|
||||
if(-not $useDockerHub) {
|
||||
@ -50,77 +97,115 @@ if(-not $useDockerHub) {
|
||||
|
||||
# Removing previous services & deployments
|
||||
Write-Host "Removing existing services & deployments.." -ForegroundColor Yellow
|
||||
ExecKube -cmd 'delete -f sql-data.yaml -f rabbitmq.yaml'
|
||||
ExecKube -cmd 'delete -f services.yaml -f frontend.yaml -f deployments.yaml'
|
||||
ExecKube -cmd 'delete deployments --all'
|
||||
ExecKube -cmd 'delete services --all'
|
||||
ExecKube -cmd 'delete configmap config-files'
|
||||
ExecKube -cmd 'delete configmap urls'
|
||||
ExecKube -cmd 'delete configmap externalcfg'
|
||||
|
||||
# start sql, rabbitmq, frontend deploymentsExecKube -cmd 'delete configmap config-files'
|
||||
ExecKube -cmd 'create configmap config-files --from-file=nginx-conf=nginx.conf'
|
||||
ExecKube -cmd 'label configmap config-files app=eshop'
|
||||
ExecKube -cmd 'create -f sql-data.yaml -f rabbitmq.yaml -f services.yaml -f frontend.yaml'
|
||||
|
||||
# building and publishing docker images not necessary when deploying through CI VSTS
|
||||
if(-not $deployCI) {
|
||||
Write-Host "Building and publishing eShopOnContainers..." -ForegroundColor Yellow
|
||||
dotnet restore ../eShopOnContainers-ServicesAndWebApps.sln
|
||||
dotnet publish -c Release -o obj/Docker/publish ../eShopOnContainers-ServicesAndWebApps.sln
|
||||
|
||||
Write-Host "Building Docker images..." -ForegroundColor Yellow
|
||||
docker-compose -p .. -f ../docker-compose.yml build
|
||||
|
||||
Write-Host "Pushing images to $registry..." -ForegroundColor Yellow
|
||||
$services = ("basket.api", "catalog.api", "identity.api", "ordering.api", "webmvc", "webspa", "webstatus")
|
||||
foreach ($service in $services) {
|
||||
docker tag eshop/$service $registry/eshop/$service
|
||||
docker push $registry/eshop/$service
|
||||
}
|
||||
if ($deployInfrastructure) {
|
||||
Write-Host 'Deploying infrastructure deployments (databases, redis, ...)' -ForegroundColor Yellow
|
||||
ExecKube -cmd 'create -f sql-data.yaml -f basket-data.yaml -f keystore-data.yaml -f rabbitmq.yaml -f nosql-data.yaml'
|
||||
}
|
||||
|
||||
Write-Host "Waiting for frontend's external ip..." -ForegroundColor Yellow
|
||||
while ($true) {
|
||||
$frontendUrl = & ExecKube -cmd 'get svc frontend -o=jsonpath="{.status.loadBalancer.ingress[0].ip}"'
|
||||
if ([bool]($frontendUrl -as [ipaddress])) {
|
||||
break
|
||||
}
|
||||
Start-Sleep -s 15
|
||||
Write-Host 'Deploying code deployments (databases, redis, ...)' -ForegroundColor Yellow
|
||||
ExecKube -cmd 'create -f services.yaml -f frontend.yaml'
|
||||
|
||||
if ([string]::IsNullOrEmpty($externalDns)) {
|
||||
Write-Host "Waiting for frontend's external ip..." -ForegroundColor Yellow
|
||||
while ($true) {
|
||||
$frontendUrl = & ExecKube -cmd 'get svc frontend -o=jsonpath="{.status.loadBalancer.ingress[0].ip}"'
|
||||
if ([bool]($frontendUrl -as [ipaddress])) {
|
||||
break
|
||||
}
|
||||
Start-Sleep -s 15
|
||||
}
|
||||
$externalDns = $frontendUrl
|
||||
}
|
||||
|
||||
Write-Host "Using $externalDns as the external DNS/IP of the k8s cluster"
|
||||
|
||||
ExecKube -cmd 'create configmap urls `
|
||||
--from-literal=BasketUrl=http://$($frontendUrl)/basket-api `
|
||||
--from-literal=BasketHealthCheckUrl=http://$($frontendUrl)/basket-api/hc `
|
||||
--from-literal=BasketUrl=http://basket `
|
||||
--from-literal=BasketHealthCheckUrl=http://basket/hc `
|
||||
--from-literal=CatalogUrl=http://$($frontendUrl)/catalog-api `
|
||||
--from-literal=CatalogHealthCheckUrl=http://$($frontendUrl)/catalog-api/hc `
|
||||
--from-literal=CatalogHealthCheckUrl=http://catalog/hc `
|
||||
--from-literal=IdentityUrl=http://$($frontendUrl)/identity `
|
||||
--from-literal=IdentityHealthCheckUrl=http://$($frontendUrl)/identity/hc `
|
||||
--from-literal=OrderingUrl=http://$($frontendUrl)/ordering-api `
|
||||
--from-literal=OrderingHealthCheckUrl=http://$($frontendUrl)/ordering-api/hc `
|
||||
--from-literal=MvcClient=http://$($frontendUrl)/webmvc `
|
||||
--from-literal=WebMvcHealthCheckUrl=http://$($frontendUrl)/webmvc/hc `
|
||||
--from-literal=WebStatusClient=http://$($frontendUrl)/webstatus `
|
||||
--from-literal=WebSpaHealthCheckUrl=http://$($frontendUrl)/hc `
|
||||
--from-literal=SpaClient=http://$($frontendUrl)'
|
||||
|
||||
--from-literal=IdentityHealthCheckUrl=http://identity/hc `
|
||||
--from-literal=OrderingUrl=http://ordering `
|
||||
--from-literal=OrderingHealthCheckUrl=http://ordering/hc `
|
||||
--from-literal=MvcClientExternalUrl=http://$($frontendUrl)/webmvc `
|
||||
--from-literal=WebMvcHealthCheckUrl=http://webmvc/hc `
|
||||
--from-literal=MvcClientOrderingUrl=http://ordering `
|
||||
--from-literal=MvcClientCatalogUrl=http://catalog `
|
||||
--from-literal=MvcClientBasketUrl=http://basket `
|
||||
--from-literal=WebSpaHealthCheckUrl=http://webspa/hc `
|
||||
--from-literal=SpaClientOrderingExternalUrl=http://$($externalDns)/ordering-api `
|
||||
--from-literal=SpaClientCatalogExternalUrl=http://$($externalDns)/catalog-api `
|
||||
--from-literal=SpaClientBasketExternalUrl=http://$($externalDns)/basket-api `
|
||||
--from-literal=SpaClientIdentityExternalUrl=http://$($externalDns)/identity `
|
||||
--from-literal=SpaClientExternalUrl=http://$($externalDns)'
|
||||
|
||||
ExecKube -cmd 'label configmap urls app=eshop'
|
||||
|
||||
Write-Host "Creating deployments..."
|
||||
Write-Host "Applying external configuration from json" -ForegroundColor Yellow
|
||||
|
||||
ExecKube -cmd 'create configmap externalcfg `
|
||||
--from-literal=CatalogSqlDb=$($config.sql.catalog) `
|
||||
--from-literal=IdentitySqlDb=$($config.sql.identity) `
|
||||
--from-literal=OrderingSqlDb=$($config.sql.ordering) `
|
||||
--from-literal=MarketingSqlDb=$($config.sql.marketing) `
|
||||
--from-literal=LocationsNoSqlDb=$($config.nosql.locations.constr) `
|
||||
--from-literal=LocationsNoSqlDbName=$($config.nosql.locations.db) `
|
||||
--from-literal=MarketingNoSqlDb=$($config.nosql.marketing.constr) `
|
||||
--from-literal=MarketingNoSqlDbName=$($config.nosql.marketing.db) `
|
||||
--from-literal=BasketRedisConStr=$($config.redis.basket) `
|
||||
--from-literal=LocationsBus=$($config.servicebus.locations) `
|
||||
--from-literal=MarketingBus=$($config.servicebus.marketing) `
|
||||
--from-literal=BasketBus=$($config.servicebus.basket) `
|
||||
--from-literal=OrderingBus=$($config.servicebus.ordering) `
|
||||
--from-literal=CatalogBus=$($config.servicebus.catalog) `
|
||||
--from-literal=PaymentBus=$($config.servicebus.payment) `
|
||||
--from-literal=UseAzureServiceBus=$($config.servicebus.use_azure) `
|
||||
--from-literal=keystore=$($config.redis.keystore) '
|
||||
ExecKube -cmd 'label configmap externalcfg app=eshop'
|
||||
|
||||
Write-Host "Creating deployments..." -ForegroundColor Yellow
|
||||
ExecKube -cmd 'create -f deployments.yaml'
|
||||
|
||||
# not using ACR for pulling images when deploying through CI VSTS
|
||||
if(-not $deployCI) {
|
||||
# update deployments with the private registry before k8s tries to pull images
|
||||
# (deployment templating, or Helm, would obviate this)
|
||||
ExecKube -cmd 'set image -f deployments.yaml `
|
||||
basket=$registry/eshop/basket.api `
|
||||
catalog=$registry/eshop/catalog.api `
|
||||
identity=$registry/eshop/identity.api `
|
||||
ordering=$registry/eshop/ordering.api `
|
||||
webmvc=$registry/eshop/webmvc `
|
||||
webstatus=$registry/eshop/webstatus `
|
||||
webspa=$registry/eshop/webspa'
|
||||
# update deployments with the correct image (with tag and/or registry)
|
||||
Write-Host "Update Image containers to use prefix '$registry' and tag '$imageTag'" -ForegroundColor Yellow
|
||||
$registryPath = ""
|
||||
if (-not [string]::IsNullOrEmpty($registry)) {
|
||||
$registryPath = "$registry/"
|
||||
}
|
||||
|
||||
ExecKube -cmd 'rollout resume -f deployments.yaml'
|
||||
ExecKube -cmd 'set image deployments/basket basket=${registryPath}eshop/basket.api:$imageTag'
|
||||
ExecKube -cmd 'set image deployments/catalog catalog=${registryPath}eshop/catalog.api:$imageTag'
|
||||
ExecKube -cmd 'set image deployments/identity identity=${registryPath}eshop/identity.api:$imageTag'
|
||||
ExecKube -cmd 'set image deployments/ordering ordering=${registryPath}eshop/ordering.api:$imageTag'
|
||||
ExecKube -cmd 'set image deployments/marketing marketing=${registryPath}eshop/marketing.api:$imageTag'
|
||||
ExecKube -cmd 'set image deployments/locations locations=${registryPath}eshop/locations.api:$imageTag'
|
||||
ExecKube -cmd 'set image deployments/payment payment=${registryPath}eshop/payment.api:$imageTag'
|
||||
ExecKube -cmd 'set image deployments/webmvc webmvc=${registryPath}eshop/webmvc:$imageTag'
|
||||
ExecKube -cmd 'set image deployments/webstatus webstatus=${registryPath}eshop/webstatus:$imageTag'
|
||||
ExecKube -cmd 'set image deployments/webspa webspa=${registryPath}eshop/webspa:$imageTag'
|
||||
|
||||
Write-Host "Execute rollout..." -ForegroundColor Yellow
|
||||
ExecKube -cmd 'rollout resume deployments/basket'
|
||||
ExecKube -cmd 'rollout resume deployments/catalog'
|
||||
ExecKube -cmd 'rollout resume deployments/identity'
|
||||
ExecKube -cmd 'rollout resume deployments/ordering'
|
||||
ExecKube -cmd 'rollout resume deployments/marketing'
|
||||
ExecKube -cmd 'rollout resume deployments/locations'
|
||||
ExecKube -cmd 'rollout resume deployments/payment'
|
||||
ExecKube -cmd 'rollout resume deployments/webmvc'
|
||||
ExecKube -cmd 'rollout resume deployments/webstatus'
|
||||
ExecKube -cmd 'rollout resume deployments/webspa'
|
||||
|
||||
Write-Host "WebSPA is exposed at http://$frontendUrl, WebMVC at http://$frontendUrl/webmvc, WebStatus at http://$frontendUrl/webstatus" -ForegroundColor Yellow
|
||||
|
||||
|
@ -18,20 +18,27 @@ spec:
|
||||
- name: ASPNETCORE_URLS
|
||||
value: http://0.0.0.0:80/basket-api
|
||||
- name: ConnectionString
|
||||
value: 127.0.0.1
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: externalcfg
|
||||
key: BasketRedisConStr
|
||||
- name: EventBusConnection
|
||||
value: rabbitmq
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: externalcfg
|
||||
key: BasketBus
|
||||
- name: AzureServiceBusEnabled
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: externalcfg
|
||||
key: UseAzureServiceBus
|
||||
- name: IdentityUrl
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: urls
|
||||
key: IdentityUrl
|
||||
ports:
|
||||
- containerPort: 80
|
||||
- name: basket-data
|
||||
image: redis:3.2-alpine
|
||||
ports:
|
||||
- containerPort: 6379
|
||||
- containerPort: 80
|
||||
imagePullSecrets:
|
||||
- name: registry-key
|
||||
---
|
||||
@ -55,14 +62,20 @@ spec:
|
||||
- name: ASPNETCORE_URLS
|
||||
value: http://0.0.0.0:80/catalog-api
|
||||
- name: ConnectionString
|
||||
value: "Server=sql-data;Initial Catalog=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word"
|
||||
- name: EventBusConnection
|
||||
value: rabbitmq
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: externalcfg
|
||||
key: CatalogSqlDb
|
||||
- name: ExternalCatalogBaseUrl
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: urls
|
||||
key: CatalogUrl
|
||||
- name: EventBusConnection
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: externalcfg
|
||||
key: CatalogBus
|
||||
ports:
|
||||
- containerPort: 80
|
||||
imagePullSecrets:
|
||||
@ -88,17 +101,27 @@ spec:
|
||||
- name: ASPNETCORE_URLS
|
||||
value: http://0.0.0.0:80/identity
|
||||
- name: ConnectionStrings__DefaultConnection
|
||||
value: "Server=sql-data;Initial Catalog=Microsoft.eShopOnContainers.Services.IdentityDb;User Id=sa;Password=Pass@word"
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: externalcfg
|
||||
key: IdentitySqlDb
|
||||
- name: DPConnectionString
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: externalcfg
|
||||
key: keystore
|
||||
- name: IsClusterEnv
|
||||
value: 'True'
|
||||
- name: MvcClient
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: urls
|
||||
key: MvcClient
|
||||
key: MvcClientExternalUrl
|
||||
- name: SpaClient
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: urls
|
||||
key: SpaClient
|
||||
key: SpaClientExternalUrl
|
||||
ports:
|
||||
- containerPort: 80
|
||||
imagePullSecrets:
|
||||
@ -124,9 +147,20 @@ spec:
|
||||
- name: ASPNETCORE_URLS
|
||||
value: http://0.0.0.0:80/ordering-api
|
||||
- name: ConnectionString
|
||||
value: "Server=sql-data;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word;"
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: externalcfg
|
||||
key: OrderingSqlDb
|
||||
- name: EventBusConnection
|
||||
value: rabbitmq
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: externalcfg
|
||||
key: OrderingBus
|
||||
- name: AzureServiceBusEnabled
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: externalcfg
|
||||
key: UseAzureServiceBus
|
||||
- name: IdentityUrl
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
@ -139,6 +173,143 @@ spec:
|
||||
---
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: locations
|
||||
spec:
|
||||
paused: true
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: eshop
|
||||
component: locations
|
||||
spec:
|
||||
containers:
|
||||
- name: locations
|
||||
image: eshop/locations.api
|
||||
imagePullPolicy: Always
|
||||
env:
|
||||
- name: ASPNETCORE_URLS
|
||||
value: http://0.0.0.0:80/locations-api
|
||||
- name: ConnectionString
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: externalcfg
|
||||
key: LocationsNoSqlDb
|
||||
- name: Database
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: externalcfg
|
||||
key: LocationsNoSqlDbName
|
||||
- name: AzureServiceBusEnabled
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: externalcfg
|
||||
key: UseAzureServiceBus
|
||||
- name: EventBusConnection
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: externalcfg
|
||||
key: LocationsBus
|
||||
- name: IdentityUrl
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: urls
|
||||
key: IdentityUrl
|
||||
ports:
|
||||
- containerPort: 80
|
||||
imagePullSecrets:
|
||||
- name: registry-key
|
||||
---
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: marketing
|
||||
spec:
|
||||
paused: true
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: eshop
|
||||
component: marketing
|
||||
spec:
|
||||
containers:
|
||||
- name: marketing
|
||||
image: eshop/marketing.api
|
||||
imagePullPolicy: Always
|
||||
env:
|
||||
- name: ASPNETCORE_URLS
|
||||
value: http://0.0.0.0:80/marketing-api
|
||||
- name: ConnectionString
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: externalcfg
|
||||
key: MarketingSqlDb
|
||||
- name: MongoConnectionString
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: externalcfg
|
||||
key: MarketingNoSqlDb
|
||||
- name: MongoDatabase
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: externalcfg
|
||||
key: MarketingNoSqlDbName
|
||||
- name: AzureServiceBusEnabled
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: externalcfg
|
||||
key: UseAzureServiceBus
|
||||
- name: EventBusConnection
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: externalcfg
|
||||
key: MarketingBus
|
||||
- name: IdentityUrl
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: urls
|
||||
key: IdentityUrl
|
||||
ports:
|
||||
- containerPort: 80
|
||||
imagePullSecrets:
|
||||
- name: registry-key
|
||||
---
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: payment
|
||||
spec:
|
||||
paused: true
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: eshop
|
||||
component: payment
|
||||
spec:
|
||||
containers:
|
||||
- name: payment
|
||||
image: eshop/payment.api
|
||||
imagePullPolicy: Always
|
||||
env:
|
||||
- name: ASPNETCORE_URLS
|
||||
value: http://0.0.0.0:80/payment-api
|
||||
- name: AzureServiceBusEnabled
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: externalcfg
|
||||
key: UseAzureServiceBus
|
||||
- name: EventBusConnection
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: externalcfg
|
||||
key: PaymentBus
|
||||
ports:
|
||||
- containerPort: 80
|
||||
imagePullSecrets:
|
||||
- name: registry-key
|
||||
---
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: webmvc
|
||||
spec:
|
||||
@ -156,21 +327,28 @@ spec:
|
||||
env:
|
||||
- name: ASPNETCORE_URLS
|
||||
value: http://0.0.0.0:80/webmvc
|
||||
- name: DPConnectionString
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: externalcfg
|
||||
key: keystore
|
||||
- name: IsClusterEnv
|
||||
value: 'True'
|
||||
- name: BasketUrl
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: urls
|
||||
key: BasketUrl
|
||||
key: MvcClientBasketUrl
|
||||
- name: CallBackUrl
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: urls
|
||||
key: MvcClient
|
||||
key: MvcClientExternalUrl
|
||||
- name: CatalogUrl
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: urls
|
||||
key: CatalogUrl
|
||||
key: MvcClientCatalogUrl
|
||||
- name: IdentityUrl
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
@ -180,13 +358,12 @@ spec:
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: urls
|
||||
key: OrderingUrl
|
||||
key: MvcClientOrderingUrl
|
||||
ports:
|
||||
- containerPort: 80
|
||||
imagePullSecrets:
|
||||
- name: registry-key
|
||||
---
|
||||
---
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
@ -260,31 +437,58 @@ spec:
|
||||
env:
|
||||
- name: ASPNETCORE_URLS
|
||||
value: http://0.0.0.0:80
|
||||
- name: DPConnectionString
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: externalcfg
|
||||
key: keystore
|
||||
- name: IsClusterEnv
|
||||
value: 'True'
|
||||
- name: BasketUrl
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: urls
|
||||
key: BasketUrl
|
||||
key: SpaClientBasketExternalUrl
|
||||
- name: CallBackUrl
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: urls
|
||||
key: SpaClient
|
||||
key: SpaClientExternalUrl
|
||||
- name: CatalogUrl
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: urls
|
||||
key: CatalogUrl
|
||||
key: SpaClientCatalogExternalUrl
|
||||
- name: IdentityUrl
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: urls
|
||||
key: IdentityUrl
|
||||
key: SpaClientIdentityExternalUrl
|
||||
- name: OrderingUrl
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: urls
|
||||
key: OrderingUrl
|
||||
key: SpaClientOrderingExternalUrl
|
||||
- name: BasketUrlHC
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: urls
|
||||
key: BasketHealthCheckUrl
|
||||
- name: CatalogUrlHC
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: urls
|
||||
key: CatalogHealthCheckUrl
|
||||
- name: IdentityUrlHC
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: urls
|
||||
key: IdentityHealthCheckUrl
|
||||
- name: OrderingUrlHC
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: urls
|
||||
key: OrderingHealthCheckUrl
|
||||
ports:
|
||||
- containerPort: 80
|
||||
imagePullSecrets:
|
||||
|
@ -3,16 +3,19 @@
|
||||
[parameter(Mandatory=$true)][string]$location,
|
||||
[parameter(Mandatory=$true)][string]$registryName,
|
||||
[parameter(Mandatory=$true)][string]$orchestratorName,
|
||||
[parameter(Mandatory=$true)][string]$dnsName
|
||||
[parameter(Mandatory=$true)][string]$dnsName,
|
||||
[parameter(Mandatory=$true)][string]$createAcr=$true
|
||||
)
|
||||
|
||||
# Create resource group
|
||||
Write-Host "Creating resource group..." -ForegroundColor Yellow
|
||||
az group create --name=$resourceGroupName --location=$location
|
||||
|
||||
# Create Azure Container Registry
|
||||
Write-Host "Creating Azure Container Registry..." -ForegroundColor Yellow
|
||||
az acr create -n $registryName -g $resourceGroupName -l $location --admin-enabled true --sku Basic
|
||||
if ($createAcr) {
|
||||
# Create Azure Container Registry
|
||||
Write-Host "Creating Azure Container Registry..." -ForegroundColor Yellow
|
||||
az acr create -n $registryName -g $resourceGroupName -l $location --admin-enabled true --sku Basic
|
||||
}
|
||||
|
||||
# Create kubernetes orchestrator
|
||||
Write-Host "Creating kubernetes orchestrator..." -ForegroundColor Yellow
|
||||
|
29
k8s/keystore-data.yaml
Normal file
29
k8s/keystore-data.yaml
Normal 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
31
k8s/local.json
Normal 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"
|
||||
}
|
||||
}
|
@ -71,6 +71,24 @@ http {
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
|
||||
location /marketing-api {
|
||||
proxy_pass http://marketing;
|
||||
proxy_redirect off;
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
|
||||
location /payment-api {
|
||||
proxy_pass http://payment;
|
||||
proxy_redirect off;
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
|
||||
location /locations-api {
|
||||
proxy_pass http://locations;
|
||||
proxy_redirect off;
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://webspa;
|
||||
proxy_redirect off;
|
||||
|
30
k8s/nosql-data.yaml
Normal file
30
k8s/nosql-data.yaml
Normal 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
|
@ -56,6 +56,48 @@ spec:
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
app: eshop
|
||||
component: locations
|
||||
name: locations
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
selector:
|
||||
app: eshop
|
||||
component: locations
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
app: eshop
|
||||
component: payment
|
||||
name: payment
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
selector:
|
||||
app: eshop
|
||||
component: payment
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
app: eshop
|
||||
component: marketing
|
||||
name: marketing
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
selector:
|
||||
app: eshop
|
||||
component: marketing
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
app: eshop
|
||||
@ -94,4 +136,4 @@ spec:
|
||||
- port: 80
|
||||
selector:
|
||||
app: eshop
|
||||
component: webspa
|
||||
component: webspa
|
||||
|
@ -0,0 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard1.4</TargetFramework>
|
||||
<RootNamespace>Microsoft.eShopOnContainers.BuildingBlocks.CommandBus</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
13
src/BuildingBlocks/CommandBus/CommandBus/ICommandBus.cs
Normal file
13
src/BuildingBlocks/CommandBus/CommandBus/ICommandBus.cs
Normal 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;
|
||||
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard1.4</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
@ -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>
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
8
src/BuildingBlocks/EventBus/CommandBus/CommandBus.csproj
Normal file
8
src/BuildingBlocks/EventBus/CommandBus/CommandBus.csproj
Normal file
@ -0,0 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard1.0</TargetFramework>
|
||||
<RootNamespace>Microsoft.eShopOnContainers.BuildingBlocks.CommandBus</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
16
src/BuildingBlocks/EventBus/CommandBus/ICommandBus.cs
Normal file
16
src/BuildingBlocks/EventBus/CommandBus/ICommandBus.cs
Normal 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>;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
35
src/BuildingBlocks/EventBus/CommandBus/IntegrationCommand.cs
Normal file
35
src/BuildingBlocks/EventBus/CommandBus/IntegrationCommand.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -18,7 +18,7 @@ namespace EventBus.Tests
|
||||
public void After_One_Event_Subscription_Should_Contain_The_Event()
|
||||
{
|
||||
var manager = new InMemoryEventBusSubscriptionsManager();
|
||||
manager.AddSubscription<TestIntegrationEvent,TestIntegrationEventHandler>(() => new TestIntegrationEventHandler());
|
||||
manager.AddSubscription<TestIntegrationEvent,TestIntegrationEventHandler>();
|
||||
Assert.True(manager.HasSubscriptionsForEvent<TestIntegrationEvent>());
|
||||
}
|
||||
|
||||
@ -26,7 +26,7 @@ namespace EventBus.Tests
|
||||
public void After_All_Subscriptions_Are_Deleted_Event_Should_No_Longer_Exists()
|
||||
{
|
||||
var manager = new InMemoryEventBusSubscriptionsManager();
|
||||
manager.AddSubscription<TestIntegrationEvent, TestIntegrationEventHandler>(() => new TestIntegrationEventHandler());
|
||||
manager.AddSubscription<TestIntegrationEvent, TestIntegrationEventHandler>();
|
||||
manager.RemoveSubscription<TestIntegrationEvent, TestIntegrationEventHandler>();
|
||||
Assert.False(manager.HasSubscriptionsForEvent<TestIntegrationEvent>());
|
||||
}
|
||||
@ -37,7 +37,7 @@ namespace EventBus.Tests
|
||||
bool raised = false;
|
||||
var manager = new InMemoryEventBusSubscriptionsManager();
|
||||
manager.OnEventRemoved += (o, e) => raised = true;
|
||||
manager.AddSubscription<TestIntegrationEvent, TestIntegrationEventHandler>(() => new TestIntegrationEventHandler());
|
||||
manager.AddSubscription<TestIntegrationEvent, TestIntegrationEventHandler>();
|
||||
manager.RemoveSubscription<TestIntegrationEvent, TestIntegrationEventHandler>();
|
||||
Assert.True(raised);
|
||||
}
|
||||
@ -46,8 +46,8 @@ namespace EventBus.Tests
|
||||
public void Get_Handlers_For_Event_Should_Return_All_Handlers()
|
||||
{
|
||||
var manager = new InMemoryEventBusSubscriptionsManager();
|
||||
manager.AddSubscription<TestIntegrationEvent, TestIntegrationEventHandler>(() => new TestIntegrationEventHandler());
|
||||
manager.AddSubscription<TestIntegrationEvent, TestIntegrationOtherEventHandler>(() => new TestIntegrationOtherEventHandler());
|
||||
manager.AddSubscription<TestIntegrationEvent, TestIntegrationEventHandler>();
|
||||
manager.AddSubscription<TestIntegrationEvent, TestIntegrationOtherEventHandler>();
|
||||
var handlers = manager.GetHandlersForEvent<TestIntegrationEvent>();
|
||||
Assert.Equal(2, handlers.Count());
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -5,9 +5,15 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions
|
||||
{
|
||||
public interface IEventBus
|
||||
{
|
||||
void Subscribe<T, TH>(Func<TH> handler)
|
||||
void Subscribe<T, TH>()
|
||||
where T : IntegrationEvent
|
||||
where TH : IIntegrationEventHandler<T>;
|
||||
void SubscribeDynamic<TH>(string eventName)
|
||||
where TH : IDynamicIntegrationEventHandler;
|
||||
|
||||
void UnsubscribeDynamic<TH>(string eventName)
|
||||
where TH : IDynamicIntegrationEventHandler;
|
||||
|
||||
void Unsubscribe<T, TH>()
|
||||
where TH : IIntegrationEventHandler<T>
|
||||
where T : IntegrationEvent;
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp1.1</TargetFramework>
|
||||
<RuntimeFrameworkVersion>1.1.0</RuntimeFrameworkVersion>
|
||||
<RootNamespace>Microsoft.eShopOnContainers.BuildingBlocks.EventBus</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
@ -11,7 +10,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="10.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
@ -2,6 +2,7 @@
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using static Microsoft.eShopOnContainers.BuildingBlocks.EventBus.InMemoryEventBusSubscriptionsManager;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus
|
||||
{
|
||||
@ -9,18 +10,25 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus
|
||||
{
|
||||
bool IsEmpty { get; }
|
||||
event EventHandler<string> OnEventRemoved;
|
||||
void AddSubscription<T, TH>(Func<TH> handler)
|
||||
void AddDynamicSubscription<TH>(string eventName)
|
||||
where TH : IDynamicIntegrationEventHandler;
|
||||
|
||||
void AddSubscription<T, TH>()
|
||||
where T : IntegrationEvent
|
||||
where TH : IIntegrationEventHandler<T>;
|
||||
|
||||
void RemoveSubscription<T, TH>()
|
||||
where TH : IIntegrationEventHandler<T>
|
||||
where T : IntegrationEvent;
|
||||
void RemoveSubscription<T, TH>()
|
||||
where TH : IIntegrationEventHandler<T>
|
||||
where T : IntegrationEvent;
|
||||
void RemoveDynamicSubscription<TH>(string eventName)
|
||||
where TH : IDynamicIntegrationEventHandler;
|
||||
|
||||
bool HasSubscriptionsForEvent<T>() where T : IntegrationEvent;
|
||||
bool HasSubscriptionsForEvent(string eventName);
|
||||
Type GetEventTypeByName(string eventName);
|
||||
void Clear();
|
||||
IEnumerable<Delegate> GetHandlersForEvent<T>() where T : IntegrationEvent;
|
||||
IEnumerable<Delegate> GetHandlersForEvent(string eventName);
|
||||
IEnumerable<SubscriptionInfo> GetHandlersForEvent<T>() where T : IntegrationEvent;
|
||||
IEnumerable<SubscriptionInfo> GetHandlersForEvent(string eventName);
|
||||
string GetEventKey<T>();
|
||||
}
|
||||
}
|
@ -8,64 +8,106 @@ using System.Text;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus
|
||||
{
|
||||
public class InMemoryEventBusSubscriptionsManager : IEventBusSubscriptionsManager
|
||||
public partial class InMemoryEventBusSubscriptionsManager : IEventBusSubscriptionsManager
|
||||
{
|
||||
private readonly Dictionary<string, List<Delegate>> _handlers;
|
||||
|
||||
|
||||
private readonly Dictionary<string, List<SubscriptionInfo>> _handlers;
|
||||
private readonly List<Type> _eventTypes;
|
||||
|
||||
public event EventHandler<string> OnEventRemoved;
|
||||
|
||||
public InMemoryEventBusSubscriptionsManager()
|
||||
{
|
||||
_handlers = new Dictionary<string, List<Delegate>>();
|
||||
_handlers = new Dictionary<string, List<SubscriptionInfo>>();
|
||||
_eventTypes = new List<Type>();
|
||||
}
|
||||
|
||||
public bool IsEmpty => !_handlers.Keys.Any();
|
||||
public void Clear() => _handlers.Clear();
|
||||
|
||||
public void AddSubscription<T, TH>(Func<TH> handler)
|
||||
public void AddDynamicSubscription<TH>(string eventName)
|
||||
where TH : IDynamicIntegrationEventHandler
|
||||
{
|
||||
DoAddSubscription(typeof(TH), eventName, isDynamic: true);
|
||||
}
|
||||
|
||||
public void AddSubscription<T, TH>()
|
||||
where T : IntegrationEvent
|
||||
where TH : IIntegrationEventHandler<T>
|
||||
{
|
||||
var key = GetEventKey<T>();
|
||||
if (!HasSubscriptionsForEvent<T>())
|
||||
{
|
||||
_handlers.Add(key, new List<Delegate>());
|
||||
}
|
||||
_handlers[key].Add(handler);
|
||||
var eventName = GetEventKey<T>();
|
||||
DoAddSubscription(typeof(TH), eventName, isDynamic: false);
|
||||
_eventTypes.Add(typeof(T));
|
||||
}
|
||||
|
||||
private void DoAddSubscription(Type handlerType, string eventName, bool isDynamic)
|
||||
{
|
||||
if (!HasSubscriptionsForEvent(eventName))
|
||||
{
|
||||
_handlers.Add(eventName, new List<SubscriptionInfo>());
|
||||
}
|
||||
|
||||
if (_handlers[eventName].Any(s => s.HandlerType == handlerType))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"Handler Type {handlerType.Name} already registered for '{eventName}'", nameof(handlerType));
|
||||
}
|
||||
|
||||
if (isDynamic)
|
||||
{
|
||||
_handlers[eventName].Add(SubscriptionInfo.Dynamic(handlerType));
|
||||
}
|
||||
else
|
||||
{
|
||||
_handlers[eventName].Add(SubscriptionInfo.Typed(handlerType));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void RemoveDynamicSubscription<TH>(string eventName)
|
||||
where TH : IDynamicIntegrationEventHandler
|
||||
{
|
||||
var handlerToRemove = FindDynamicSubscriptionToRemove<TH>(eventName);
|
||||
DoRemoveHandler(eventName, handlerToRemove);
|
||||
}
|
||||
|
||||
|
||||
public void RemoveSubscription<T, TH>()
|
||||
where TH : IIntegrationEventHandler<T>
|
||||
where T : IntegrationEvent
|
||||
{
|
||||
var handlerToRemove = FindHandlerToRemove<T, TH>();
|
||||
if (handlerToRemove != null)
|
||||
var handlerToRemove = FindSubscriptionToRemove<T, TH>();
|
||||
var eventName = GetEventKey<T>();
|
||||
DoRemoveHandler(eventName, handlerToRemove);
|
||||
}
|
||||
|
||||
|
||||
private void DoRemoveHandler(string eventName, SubscriptionInfo subsToRemove)
|
||||
{
|
||||
if (subsToRemove != null)
|
||||
{
|
||||
var key = GetEventKey<T>();
|
||||
_handlers[key].Remove(handlerToRemove);
|
||||
if (!_handlers[key].Any())
|
||||
_handlers[eventName].Remove(subsToRemove);
|
||||
if (!_handlers[eventName].Any())
|
||||
{
|
||||
_handlers.Remove(key);
|
||||
var eventType = _eventTypes.SingleOrDefault(e => e.Name == key);
|
||||
_handlers.Remove(eventName);
|
||||
var eventType = _eventTypes.SingleOrDefault(e => e.Name == eventName);
|
||||
if (eventType != null)
|
||||
{
|
||||
_eventTypes.Remove(eventType);
|
||||
RaiseOnEventRemoved(eventType.Name);
|
||||
}
|
||||
RaiseOnEventRemoved(eventName);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<Delegate> GetHandlersForEvent<T>() where T : IntegrationEvent
|
||||
public IEnumerable<SubscriptionInfo> GetHandlersForEvent<T>() where T : IntegrationEvent
|
||||
{
|
||||
var key = GetEventKey<T>();
|
||||
return GetHandlersForEvent(key);
|
||||
}
|
||||
public IEnumerable<Delegate> GetHandlersForEvent(string eventName) => _handlers[eventName];
|
||||
public IEnumerable<SubscriptionInfo> GetHandlersForEvent(string eventName) => _handlers[eventName];
|
||||
|
||||
private void RaiseOnEventRemoved(string eventName)
|
||||
{
|
||||
@ -76,26 +118,31 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus
|
||||
}
|
||||
}
|
||||
|
||||
private Delegate FindHandlerToRemove<T, TH>()
|
||||
where T : IntegrationEvent
|
||||
where TH : IIntegrationEventHandler<T>
|
||||
|
||||
private SubscriptionInfo FindDynamicSubscriptionToRemove<TH>(string eventName)
|
||||
where TH : IDynamicIntegrationEventHandler
|
||||
{
|
||||
if (!HasSubscriptionsForEvent<T>())
|
||||
return DoFindSubscriptionToRemove(eventName, typeof(TH));
|
||||
}
|
||||
|
||||
|
||||
private SubscriptionInfo FindSubscriptionToRemove<T, TH>()
|
||||
where T : IntegrationEvent
|
||||
where TH : IIntegrationEventHandler<T>
|
||||
{
|
||||
var eventName = GetEventKey<T>();
|
||||
return DoFindSubscriptionToRemove(eventName, typeof(TH));
|
||||
}
|
||||
|
||||
private SubscriptionInfo DoFindSubscriptionToRemove(string eventName, Type handlerType)
|
||||
{
|
||||
if (!HasSubscriptionsForEvent(eventName))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var key = GetEventKey<T>();
|
||||
foreach (var func in _handlers[key])
|
||||
{
|
||||
var genericArgs = func.GetType().GetGenericArguments();
|
||||
if (genericArgs.SingleOrDefault() == typeof(TH))
|
||||
{
|
||||
return func;
|
||||
}
|
||||
}
|
||||
return _handlers[eventName].SingleOrDefault(s => s.HandlerType == handlerType);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool HasSubscriptionsForEvent<T>() where T : IntegrationEvent
|
||||
@ -104,10 +151,10 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus
|
||||
return HasSubscriptionsForEvent(key);
|
||||
}
|
||||
public bool HasSubscriptionsForEvent(string eventName) => _handlers.ContainsKey(eventName);
|
||||
|
||||
public Type GetEventTypeByName(string eventName) => _eventTypes.Single(t => t.Name == eventName);
|
||||
|
||||
private string GetEventKey<T>()
|
||||
public Type GetEventTypeByName(string eventName) => _eventTypes.SingleOrDefault(t => t.Name == eventName);
|
||||
|
||||
public string GetEventKey<T>()
|
||||
{
|
||||
return typeof(T).Name;
|
||||
}
|
||||
|
28
src/BuildingBlocks/EventBus/EventBus/SubscriptionInfo.cs
Normal file
28
src/BuildingBlocks/EventBus/EventBus/SubscriptionInfo.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
*/
|
@ -1,8 +1,10 @@
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus;
|
||||
using Autofac;
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus;
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Polly;
|
||||
using Polly.Retry;
|
||||
using RabbitMQ.Client;
|
||||
@ -25,17 +27,20 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
|
||||
private readonly IRabbitMQPersistentConnection _persistentConnection;
|
||||
private readonly ILogger<EventBusRabbitMQ> _logger;
|
||||
private readonly IEventBusSubscriptionsManager _subsManager;
|
||||
|
||||
private readonly ILifetimeScope _autofac;
|
||||
private readonly string AUTOFAC_SCOPE_NAME = "eshop_event_bus";
|
||||
|
||||
private IModel _consumerChannel;
|
||||
private string _queueName;
|
||||
|
||||
public EventBusRabbitMQ(IRabbitMQPersistentConnection persistentConnection, ILogger<EventBusRabbitMQ> logger, IEventBusSubscriptionsManager subsManager)
|
||||
public EventBusRabbitMQ(IRabbitMQPersistentConnection persistentConnection, ILogger<EventBusRabbitMQ> logger,
|
||||
ILifetimeScope autofac, IEventBusSubscriptionsManager subsManager)
|
||||
{
|
||||
_persistentConnection = persistentConnection ?? throw new ArgumentNullException(nameof(persistentConnection));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_subsManager = subsManager ?? new InMemoryEventBusSubscriptionsManager();
|
||||
_consumerChannel = CreateConsumerChannel();
|
||||
_autofac = autofac;
|
||||
|
||||
_subsManager.OnEventRemoved += SubsManager_OnEventRemoved;
|
||||
}
|
||||
@ -96,12 +101,25 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
|
||||
}
|
||||
}
|
||||
|
||||
public void Subscribe<T, TH>(Func<TH> handler)
|
||||
public void SubscribeDynamic<TH>(string eventName)
|
||||
where TH : IDynamicIntegrationEventHandler
|
||||
{
|
||||
DoInternalSubscription(eventName);
|
||||
_subsManager.AddDynamicSubscription<TH>(eventName);
|
||||
}
|
||||
|
||||
public void Subscribe<T, TH>()
|
||||
where T : IntegrationEvent
|
||||
where TH : IIntegrationEventHandler<T>
|
||||
{
|
||||
var eventName = typeof(T).Name;
|
||||
var containsKey = _subsManager.HasSubscriptionsForEvent<T>();
|
||||
var eventName = _subsManager.GetEventKey<T>();
|
||||
DoInternalSubscription(eventName);
|
||||
_subsManager.AddSubscription<T, TH>();
|
||||
}
|
||||
|
||||
private void DoInternalSubscription(string eventName)
|
||||
{
|
||||
var containsKey = _subsManager.HasSubscriptionsForEvent(eventName);
|
||||
if (!containsKey)
|
||||
{
|
||||
if (!_persistentConnection.IsConnected)
|
||||
@ -116,9 +134,6 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
|
||||
routingKey: eventName);
|
||||
}
|
||||
}
|
||||
|
||||
_subsManager.AddSubscription<T, TH>(handler);
|
||||
|
||||
}
|
||||
|
||||
public void Unsubscribe<T, TH>()
|
||||
@ -128,19 +143,13 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
|
||||
_subsManager.RemoveSubscription<T, TH>();
|
||||
}
|
||||
|
||||
private static Func<IIntegrationEventHandler> FindHandlerByType(Type handlerType, IEnumerable<Func<IIntegrationEventHandler>> handlers)
|
||||
public void UnsubscribeDynamic<TH>(string eventName)
|
||||
where TH : IDynamicIntegrationEventHandler
|
||||
{
|
||||
foreach (var func in handlers)
|
||||
{
|
||||
if (func.GetMethodInfo().ReturnType == handlerType)
|
||||
{
|
||||
return func;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
_subsManager.RemoveDynamicSubscription<TH>(eventName);
|
||||
}
|
||||
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_consumerChannel != null)
|
||||
@ -190,17 +199,29 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
|
||||
private async Task ProcessEvent(string eventName, string message)
|
||||
{
|
||||
|
||||
if (_subsManager.HasSubscriptionsForEvent(eventName))
|
||||
{
|
||||
var eventType = _subsManager.GetEventTypeByName(eventName);
|
||||
var integrationEvent = JsonConvert.DeserializeObject(message, eventType);
|
||||
var handlers = _subsManager.GetHandlersForEvent(eventName);
|
||||
|
||||
foreach (var handlerfactory in handlers)
|
||||
if (_subsManager.HasSubscriptionsForEvent(eventName))
|
||||
{
|
||||
using (var scope = _autofac.BeginLifetimeScope(AUTOFAC_SCOPE_NAME))
|
||||
{
|
||||
var handler = handlerfactory.DynamicInvoke();
|
||||
var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType);
|
||||
await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent });
|
||||
var subscriptions = _subsManager.GetHandlersForEvent(eventName);
|
||||
foreach (var subscription in subscriptions)
|
||||
{
|
||||
if (subscription.IsDynamic)
|
||||
{
|
||||
var handler = scope.ResolveOptional(subscription.HandlerType) as IDynamicIntegrationEventHandler;
|
||||
dynamic eventData = JObject.Parse(message);
|
||||
await handler.Handle(eventData);
|
||||
}
|
||||
else
|
||||
{
|
||||
var eventType = _subsManager.GetEventTypeByName(eventName);
|
||||
var integrationEvent = JsonConvert.DeserializeObject(message, eventType);
|
||||
var handler = scope.ResolveOptional(subscription.HandlerType);
|
||||
var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType);
|
||||
await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,16 +2,16 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp1.1</TargetFramework>
|
||||
<RuntimeFrameworkVersion>1.1.0</RuntimeFrameworkVersion>
|
||||
<RootNamespace>Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
|
||||
<PackageReference Include="Polly" Version="5.0.6" />
|
||||
<PackageReference Include="RabbitMQ.Client" Version="4.1.1" />
|
||||
<PackageReference Include="System.ValueTuple" Version="4.3.0" />
|
||||
<PackageReference Include="Autofac" Version="4.5.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.2" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="10.0.2" />
|
||||
<PackageReference Include="Polly" Version="5.1.0" />
|
||||
<PackageReference Include="RabbitMQ.Client" Version="4.1.3" />
|
||||
<PackageReference Include="System.ValueTuple" Version="4.3.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -2,18 +2,17 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp1.1</TargetFramework>
|
||||
<RuntimeFrameworkVersion>1.1.0</RuntimeFrameworkVersion>
|
||||
<RootNamespace>Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="1.1.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="1.1.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="1.1.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.Design" Version="1.1.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="1.1.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="1.1.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="1.1.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="1.1.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.Design" Version="1.1.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="1.1.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="10.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp1.0</TargetFramework>
|
||||
<TargetFramework>netstandard1.3</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -9,7 +9,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="1.0.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="1.1.2" />
|
||||
<ProjectReference Include="..\Microsoft.Extensions.HealthChecks\Microsoft.Extensions.HealthChecks.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -9,7 +9,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Data.SqlClient" Version="4.3.0" />
|
||||
<PackageReference Include="System.Data.SqlClient" Version="4.3.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -9,8 +9,8 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.0.2" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.2" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="10.0.2" />
|
||||
<PackageReference Include="System.Diagnostics.Process" Version="4.3.0" />
|
||||
<PackageReference Include="System.Threading.Tasks.Parallel" Version="4.3.0" />
|
||||
<PackageReference Include="System.Threading.Thread" Version="4.3.0" />
|
||||
|
@ -6,9 +6,9 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="1.1.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
|
||||
<PackageReference Include="Polly" Version="5.0.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="1.1.2" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="10.0.2" />
|
||||
<PackageReference Include="Polly" Version="5.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
@ -96,7 +96,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http
|
||||
// as it is disposed after each call
|
||||
var origin = GetOriginFromUri(uri);
|
||||
|
||||
return HttpInvoker(origin, () =>
|
||||
return HttpInvoker(origin, async () =>
|
||||
{
|
||||
var requestMessage = new HttpRequestMessage(method, uri);
|
||||
|
||||
@ -112,7 +112,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http
|
||||
requestMessage.Headers.Add("x-requestid", requestId);
|
||||
}
|
||||
|
||||
var response = _client.SendAsync(requestMessage).Result;
|
||||
var response = await _client.SendAsync(requestMessage);
|
||||
|
||||
// raise exception if HttpResponseCode 500
|
||||
// needed for circuit breaker to track fails
|
||||
@ -122,7 +122,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http
|
||||
throw new HttpRequestException();
|
||||
}
|
||||
|
||||
return Task.FromResult(response);
|
||||
return response;
|
||||
});
|
||||
}
|
||||
|
||||
@ -132,13 +132,13 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http
|
||||
|
||||
if (!_policyWrappers.TryGetValue(normalizedOrigin, out PolicyWrap policyWrap))
|
||||
{
|
||||
policyWrap = Policy.Wrap(_policyCreator(normalizedOrigin).ToArray());
|
||||
policyWrap = Policy.WrapAsync(_policyCreator(normalizedOrigin).ToArray());
|
||||
_policyWrappers.TryAdd(normalizedOrigin, policyWrap);
|
||||
}
|
||||
|
||||
// Executes the action applying all
|
||||
// the policies defined in the wrapper
|
||||
return await policyWrap.Execute(action, new Context(normalizedOrigin));
|
||||
return await policyWrap.ExecuteAsync(action, new Context(normalizedOrigin));
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
#eShopOnContainers
|
||||
# eShopOnContainers
|
||||
|
||||
eShopOnContainers is a reference app whose imagined purpose is to serve the mobile workforce of a fictitious company that sells products. The app allow to manage the catalog, view products, manage the basket and the orders.
|
||||
|
||||
<img src="Images/eShopOnContainers_Architecture_Diagram.png" alt="eShopOnContainers" Width="800" />
|
||||
|
||||
###Supported platforms: iOS, Android and Windows
|
||||
### Supported platforms: iOS, Android and Windows
|
||||
|
||||
###The app architecture consists of two parts:
|
||||
### The app architecture consists of two parts:
|
||||
1. A Xamarin.Forms mobile app for iOS, Android and Windows.
|
||||
2. Several .NET Web API microservices deployed as Docker containers.
|
||||
|
||||
@ -34,7 +34,7 @@ This project exercises the following platforms, frameworks or features:
|
||||
* Entity Framework
|
||||
* Identity Server 4
|
||||
|
||||
##Three platforms
|
||||
## Three platforms
|
||||
The app targets **three** platforms:
|
||||
|
||||
* iOS
|
||||
@ -45,7 +45,7 @@ The app targets **three** platforms:
|
||||
|
||||
As of 07/03/2017, eShopOnContainers features **89.2% code share** (7.2% iOS / 16.7% Android / 8.7% Windows).
|
||||
|
||||
##Licenses
|
||||
## Licenses
|
||||
|
||||
This project uses some third-party assets with a license that requires attribution:
|
||||
|
||||
@ -155,4 +155,4 @@ In the configuration window of the machine, go to the Compatibility section and
|
||||
<img src="Images/set-compatibility-vs-sml.png" alt="Migrate to a physical computer with a different processor version" Width="600" />
|
||||
|
||||
## Copyright and license
|
||||
* Code and documentation copyright 2017 Microsoft Corp. Code released under the [MIT license](https://opensource.org/licenses/MIT).
|
||||
* Code and documentation copyright 2017 Microsoft Corp. Code released under the [MIT license](https://opensource.org/licenses/MIT).
|
||||
|
@ -1,7 +1,11 @@
|
||||
using eShopOnContainers.Core.Helpers;
|
||||
using System;
|
||||
using eShopOnContainers.Core.Helpers;
|
||||
using eShopOnContainers.Services;
|
||||
using eShopOnContainers.Core.ViewModels.Base;
|
||||
using System.Threading.Tasks;
|
||||
using eShopOnContainers.Core.Models.Location;
|
||||
using eShopOnContainers.Core.Services.Location;
|
||||
using Plugin.Geolocator;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Xaml;
|
||||
|
||||
@ -36,6 +40,7 @@ namespace eShopOnContainers
|
||||
return navigationService.InitializeAsync();
|
||||
}
|
||||
|
||||
|
||||
protected override async void OnStart()
|
||||
{
|
||||
base.OnStart();
|
||||
@ -44,6 +49,18 @@ namespace eShopOnContainers
|
||||
{
|
||||
await InitNavigation();
|
||||
}
|
||||
|
||||
if (!Settings.UseFakeLocation)
|
||||
{
|
||||
await GetRealLocation();
|
||||
}
|
||||
|
||||
if (!Settings.UseMocks && !string.IsNullOrEmpty(Settings.UserId))
|
||||
{
|
||||
await SendCurrentLocation();
|
||||
}
|
||||
|
||||
base.OnResume();
|
||||
}
|
||||
|
||||
protected override void OnSleep()
|
||||
@ -51,9 +68,29 @@ namespace eShopOnContainers
|
||||
// Handle when your app sleeps
|
||||
}
|
||||
|
||||
protected override void OnResume()
|
||||
private async Task GetRealLocation()
|
||||
{
|
||||
// Handle when your app resumes
|
||||
var locator = CrossGeolocator.Current;
|
||||
locator.AllowsBackgroundUpdates = true;
|
||||
locator.DesiredAccuracy = 50;
|
||||
|
||||
var position = await locator.GetPositionAsync(20000);
|
||||
|
||||
Settings.Latitude = position.Latitude;
|
||||
Settings.Longitude = position.Longitude;
|
||||
}
|
||||
|
||||
private async Task SendCurrentLocation()
|
||||
{
|
||||
var location = new Location
|
||||
{
|
||||
Latitude = Settings.Latitude,
|
||||
Longitude = Settings.Longitude
|
||||
};
|
||||
|
||||
var locationService = ViewModelLocator.Resolve<ILocationService>();
|
||||
await locationService.UpdateUserLocation(location,
|
||||
Settings.AuthAccessToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -6,7 +6,6 @@
|
||||
public const string MockTag = "Mock";
|
||||
public const string DefaultEndpoint = "http://13.88.8.119";
|
||||
|
||||
|
||||
private string _baseEndpoint;
|
||||
private static readonly GlobalSetting _instance = new GlobalSetting();
|
||||
|
||||
@ -31,6 +30,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
public string ClientId { get { return "xamarin"; }}
|
||||
|
||||
public string ClientSecret { get { return "secret"; }}
|
||||
|
||||
public string AuthToken { get; set; }
|
||||
|
||||
public string RegisterWebsite { get; set; }
|
||||
@ -43,8 +46,14 @@
|
||||
|
||||
public string IdentityEndpoint { get; set; }
|
||||
|
||||
public string LocationEndpoint { get; set; }
|
||||
|
||||
public string MarketingEndpoint { get; set; }
|
||||
|
||||
public string UserInfoEndpoint { get; set; }
|
||||
|
||||
public string TokenEndpoint { get; set; }
|
||||
|
||||
public string LogoutEndpoint { get; set; }
|
||||
|
||||
public string IdentityCallback { get; set; }
|
||||
@ -53,15 +62,18 @@
|
||||
|
||||
private void UpdateEndpoint(string baseEndpoint)
|
||||
{
|
||||
RegisterWebsite = string.Format("{0}:5105/Account/Register", baseEndpoint);
|
||||
CatalogEndpoint = string.Format("{0}:5101", baseEndpoint);
|
||||
OrdersEndpoint = string.Format("{0}:5102", baseEndpoint);
|
||||
BasketEndpoint = string.Format("{0}:5103", baseEndpoint);
|
||||
IdentityEndpoint = string.Format("{0}:5105/connect/authorize", baseEndpoint);
|
||||
UserInfoEndpoint = string.Format("{0}:5105/connect/userinfo", baseEndpoint);
|
||||
LogoutEndpoint = string.Format("{0}:5105/connect/endsession", baseEndpoint);
|
||||
IdentityCallback = string.Format("{0}:5105/xamarincallback", baseEndpoint);
|
||||
LogoutCallback = string.Format("{0}:5105/Account/Redirecting", baseEndpoint);
|
||||
RegisterWebsite = $"{baseEndpoint}:5105/Account/Register";
|
||||
CatalogEndpoint = $"{baseEndpoint}:5101";
|
||||
OrdersEndpoint = $"{baseEndpoint}:5102";
|
||||
BasketEndpoint = $"{baseEndpoint}:5103";
|
||||
IdentityEndpoint = $"{baseEndpoint}:5105/connect/authorize";
|
||||
UserInfoEndpoint = $"{baseEndpoint}:5105/connect/userinfo";
|
||||
TokenEndpoint = $"{baseEndpoint}:5105/connect/token";
|
||||
LogoutEndpoint = $"{baseEndpoint}:5105/connect/endsession";
|
||||
IdentityCallback = $"{baseEndpoint}:5105/xamarincallback";
|
||||
LogoutCallback = $"{baseEndpoint}:5105/Account/Redirecting";
|
||||
LocationEndpoint = $"{baseEndpoint}:5109";
|
||||
MarketingEndpoint = $"{baseEndpoint}:5110";
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
using eShopOnContainers.Core.Models.Basket;
|
||||
using eShopOnContainers.Core.Models.Catalog;
|
||||
using eShopOnContainers.Core.Models.Marketing;
|
||||
using eShopOnContainers.Core.ViewModels.Base;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@ -76,5 +77,38 @@ namespace eShopOnContainers.Core.Helpers
|
||||
Debug.WriteLine(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public static void FixCatalogItemPictureUri(IEnumerable<CampaignItem> campaignItems)
|
||||
{
|
||||
if (campaignItems == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (!ViewModelLocator.UseMockService
|
||||
&& Settings.UrlBase != GlobalSetting.DefaultEndpoint)
|
||||
{
|
||||
foreach (var catalogItem in campaignItems)
|
||||
{
|
||||
MatchCollection serverResult = IpRegex.Matches(catalogItem.PictureUri);
|
||||
MatchCollection localResult = IpRegex.Matches(Settings.UrlBase);
|
||||
|
||||
if (serverResult.Count != -1 && localResult.Count != -1)
|
||||
{
|
||||
var serviceIp = serverResult[0].Value;
|
||||
var localIp = localResult[0].Value;
|
||||
|
||||
catalogItem.PictureUri = catalogItem.PictureUri.Replace(serviceIp, localIp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,63 +20,69 @@ namespace eShopOnContainers.Core.Helpers
|
||||
|
||||
#region Setting Constants
|
||||
|
||||
private const string IdUserId = "user_id";
|
||||
private const string AccessToken = "access_token";
|
||||
private const string IdToken = "id_token";
|
||||
private const string IdUseMocks = "use_mocks";
|
||||
private const string IdUrlBase = "url_base";
|
||||
private const string IdUseFakeLocation = "use_fake_location";
|
||||
private const string IdLatitude = "latitude";
|
||||
private const string IdLongitude = "flongitude";
|
||||
private static readonly string AccessTokenDefault = string.Empty;
|
||||
private static readonly string IdTokenDefault = string.Empty;
|
||||
private static readonly bool UseMocksDefault = true;
|
||||
private static readonly bool UseFakeLocationDefault = false;
|
||||
private static readonly double FakeLatitudeValue = 47.604610d;
|
||||
private static readonly double FakeLongitudeValue = -122.315752d;
|
||||
private static readonly string UrlBaseDefault = GlobalSetting.Instance.BaseEndpoint;
|
||||
|
||||
#endregion
|
||||
|
||||
public static string UserId
|
||||
{
|
||||
get => AppSettings.GetValueOrDefault<string>(IdUserId);
|
||||
set => AppSettings.AddOrUpdateValue<string>(IdUserId, value);
|
||||
}
|
||||
|
||||
public static string AuthAccessToken
|
||||
{
|
||||
get
|
||||
{
|
||||
return AppSettings.GetValueOrDefault<string>(AccessToken, AccessTokenDefault);
|
||||
}
|
||||
set
|
||||
{
|
||||
AppSettings.AddOrUpdateValue<string>(AccessToken, value);
|
||||
}
|
||||
get => AppSettings.GetValueOrDefault<string>(AccessToken, AccessTokenDefault);
|
||||
set => AppSettings.AddOrUpdateValue<string>(AccessToken, value);
|
||||
}
|
||||
|
||||
public static string AuthIdToken
|
||||
{
|
||||
get
|
||||
{
|
||||
return AppSettings.GetValueOrDefault<string>(IdToken, IdTokenDefault);
|
||||
}
|
||||
set
|
||||
{
|
||||
AppSettings.AddOrUpdateValue<string>(IdToken, value);
|
||||
}
|
||||
get => AppSettings.GetValueOrDefault<string>(IdToken, IdTokenDefault);
|
||||
set => AppSettings.AddOrUpdateValue<string>(IdToken, value);
|
||||
}
|
||||
|
||||
public static bool UseMocks
|
||||
{
|
||||
get
|
||||
{
|
||||
return AppSettings.GetValueOrDefault<bool>(IdUseMocks, UseMocksDefault);
|
||||
}
|
||||
set
|
||||
{
|
||||
AppSettings.AddOrUpdateValue<bool>(IdUseMocks, value);
|
||||
}
|
||||
get => AppSettings.GetValueOrDefault<bool>(IdUseMocks, UseMocksDefault);
|
||||
set => AppSettings.AddOrUpdateValue<bool>(IdUseMocks, value);
|
||||
}
|
||||
|
||||
public static string UrlBase
|
||||
{
|
||||
get
|
||||
{
|
||||
return AppSettings.GetValueOrDefault<string>(IdUrlBase, UrlBaseDefault);
|
||||
}
|
||||
set
|
||||
{
|
||||
AppSettings.AddOrUpdateValue<string>(IdUrlBase, value);
|
||||
}
|
||||
get => AppSettings.GetValueOrDefault<string>(IdUrlBase, UrlBaseDefault);
|
||||
set => AppSettings.AddOrUpdateValue<string>(IdUrlBase, value);
|
||||
}
|
||||
|
||||
public static bool UseFakeLocation
|
||||
{
|
||||
get => AppSettings.GetValueOrDefault<bool>(IdUseFakeLocation, UseFakeLocationDefault);
|
||||
set => AppSettings.AddOrUpdateValue<bool>(IdUseFakeLocation, value);
|
||||
}
|
||||
|
||||
public static double Latitude
|
||||
{
|
||||
get => AppSettings.GetValueOrDefault<double>(IdLatitude, FakeLatitudeValue);
|
||||
set => AppSettings.AddOrUpdateValue<double>(IdLatitude, value);
|
||||
}
|
||||
public static double Longitude
|
||||
{
|
||||
get => AppSettings.GetValueOrDefault<double>(IdLongitude, FakeLongitudeValue);
|
||||
set => AppSettings.AddOrUpdateValue<double>(IdLongitude, value);
|
||||
}
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
namespace eShopOnContainers.Core.Models.Location
|
||||
{
|
||||
public class Location
|
||||
{
|
||||
public double Longitude { get; set; }
|
||||
public double Latitude { get; set; }
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Xamarin.Forms;
|
||||
using System;
|
||||
|
||||
namespace eShopOnContainers.Core.Services.Basket
|
||||
{
|
||||
@ -57,5 +58,10 @@ namespace eShopOnContainers.Core.Services.Basket
|
||||
MockCustomBasket.Items.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public Task CheckoutAsync(BasketCheckout basketCheckout, string token)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
@ -42,6 +42,15 @@ namespace eShopOnContainers.Core.Services.Basket
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task CheckoutAsync(BasketCheckout basketCheckout, string token)
|
||||
{
|
||||
UriBuilder builder = new UriBuilder(GlobalSetting.Instance.BasketEndpoint + "/checkout");
|
||||
|
||||
string uri = builder.ToString();
|
||||
|
||||
await _requestProvider.PostAsync(uri, basketCheckout, token);
|
||||
}
|
||||
|
||||
public async Task ClearBasketAsync(string guidUser, string token)
|
||||
{
|
||||
UriBuilder builder = new UriBuilder(GlobalSetting.Instance.BasketEndpoint);
|
||||
|
@ -7,6 +7,7 @@ namespace eShopOnContainers.Core.Services.Basket
|
||||
{
|
||||
Task<CustomerBasket> GetBasketAsync(string guidUser, string token);
|
||||
Task<CustomerBasket> UpdateBasketAsync(CustomerBasket customerBasket, string token);
|
||||
Task CheckoutAsync(BasketCheckout basketCheckout, string token);
|
||||
Task ClearBasketAsync(string guidUser, string token);
|
||||
}
|
||||
}
|
@ -7,5 +7,8 @@
|
||||
public static string MockCatalogItemId03 = "3";
|
||||
public static string MockCatalogItemId04 = "4";
|
||||
public static string MockCatalogItemId05 = "5";
|
||||
|
||||
public static int MockCampaignd01 = 1;
|
||||
public static int MockCampaignd02 = 2;
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,12 @@
|
||||
namespace eShopOnContainers.Core.Services.Identity
|
||||
using eShopOnContainers.Core.Models.Token;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace eShopOnContainers.Core.Services.Identity
|
||||
{
|
||||
public interface IIdentityService
|
||||
{
|
||||
string CreateAuthorizationRequest();
|
||||
string CreateLogoutRequest(string token);
|
||||
Task<UserToken> GetTokenAsync(string code);
|
||||
}
|
||||
}
|
@ -1,11 +1,22 @@
|
||||
using IdentityModel.Client;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using eShopOnContainers.Core.Services.RequestProvider;
|
||||
using eShopOnContainers.Core.Models.Token;
|
||||
|
||||
namespace eShopOnContainers.Core.Services.Identity
|
||||
{
|
||||
public class IdentityService : IIdentityService
|
||||
{
|
||||
private readonly IRequestProvider _requestProvider;
|
||||
|
||||
public IdentityService(IRequestProvider requestProvider)
|
||||
{
|
||||
_requestProvider = requestProvider;
|
||||
}
|
||||
|
||||
public string CreateAuthorizationRequest()
|
||||
{
|
||||
// Create URI to authorization endpoint
|
||||
@ -13,10 +24,10 @@ namespace eShopOnContainers.Core.Services.Identity
|
||||
|
||||
// Dictionary with values for the authorize request
|
||||
var dic = new Dictionary<string, string>();
|
||||
dic.Add("client_id", "xamarin");
|
||||
dic.Add("response_type", "id_token token");
|
||||
dic.Add("scope", "openid profile basket orders");
|
||||
|
||||
dic.Add("client_id", GlobalSetting.Instance.ClientId);
|
||||
dic.Add("client_secret", GlobalSetting.Instance.ClientSecret);
|
||||
dic.Add("response_type", "code id_token");
|
||||
dic.Add("scope", "openid profile basket orders locations marketing offline_access");
|
||||
dic.Add("redirect_uri", GlobalSetting.Instance.IdentityCallback);
|
||||
dic.Add("nonce", Guid.NewGuid().ToString("N"));
|
||||
|
||||
@ -24,13 +35,13 @@ namespace eShopOnContainers.Core.Services.Identity
|
||||
var currentCSRFToken = Guid.NewGuid().ToString("N");
|
||||
dic.Add("state", currentCSRFToken);
|
||||
|
||||
var authorizeUri = authorizeRequest.Create(dic);
|
||||
var authorizeUri = authorizeRequest.Create(dic);
|
||||
return authorizeUri;
|
||||
}
|
||||
|
||||
public string CreateLogoutRequest(string token)
|
||||
{
|
||||
if(string.IsNullOrEmpty(token))
|
||||
if (string.IsNullOrEmpty(token))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
@ -40,5 +51,12 @@ namespace eShopOnContainers.Core.Services.Identity
|
||||
token,
|
||||
GlobalSetting.Instance.LogoutCallback);
|
||||
}
|
||||
|
||||
public async Task<UserToken> GetTokenAsync(string code)
|
||||
{
|
||||
string data = string.Format("grant_type=authorization_code&code={0}&redirect_uri={1}", code, WebUtility.UrlEncode(GlobalSetting.Instance.IdentityCallback));
|
||||
var token = await _requestProvider.PostAsync<UserToken>(GlobalSetting.Instance.TokenEndpoint, data, GlobalSetting.Instance.ClientId, GlobalSetting.Instance.ClientSecret);
|
||||
return token;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -1,13 +1,15 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using eShopOnContainers.Core.Models.Basket;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace eShopOnContainers.Core.Services.Order
|
||||
{
|
||||
public interface IOrderService
|
||||
{
|
||||
Task CreateOrderAsync(Models.Orders.Order newOrder, string token);
|
||||
//Task CreateOrderAsync(Models.Orders.Order newOrder, string token);
|
||||
Task<ObservableCollection<Models.Orders.Order>> GetOrdersAsync(string token);
|
||||
Task<Models.Orders.Order> GetOrderAsync(int orderId, string token);
|
||||
Task<ObservableCollection<Models.Orders.CardType>> GetCardTypesAsync(string token);
|
||||
BasketCheckout MapOrderToBasket(Models.Orders.Order order);
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using eShopOnContainers.Core.Extensions;
|
||||
using eShopOnContainers.Core.Models.Basket;
|
||||
using eShopOnContainers.Core.Models.Orders;
|
||||
using eShopOnContainers.Core.Models.User;
|
||||
using System;
|
||||
@ -64,17 +65,18 @@ namespace eShopOnContainers.Core.Services.Order
|
||||
new CardType { Id = 3, Name = "MasterCard" },
|
||||
};
|
||||
|
||||
public async Task CreateOrderAsync(Models.Orders.Order newOrder, string token)
|
||||
private static BasketCheckout MockBasketCheckout = new BasketCheckout()
|
||||
{
|
||||
await Task.Delay(500);
|
||||
|
||||
if (!string.IsNullOrEmpty(token))
|
||||
{
|
||||
newOrder.OrderNumber = string.Format("{0}", MockOrders.Count + 1);
|
||||
|
||||
MockOrders.Insert(0, newOrder);
|
||||
}
|
||||
}
|
||||
CardExpiration = DateTime.UtcNow,
|
||||
CardHolderName = "FakeCardHolderName",
|
||||
CardNumber = "122333423224",
|
||||
CardSecurityNumber = "1234",
|
||||
CardTypeId = 1,
|
||||
City = "FakeCity",
|
||||
Country = "FakeCountry",
|
||||
ZipCode = "FakeZipCode",
|
||||
Street = "FakeStreet"
|
||||
};
|
||||
|
||||
public async Task<ObservableCollection<Models.Orders.Order>> GetOrdersAsync(string token)
|
||||
{
|
||||
@ -111,5 +113,10 @@ namespace eShopOnContainers.Core.Services.Order
|
||||
else
|
||||
return new ObservableCollection<CardType>();
|
||||
}
|
||||
|
||||
public BasketCheckout MapOrderToBasket(Models.Orders.Order order)
|
||||
{
|
||||
return MockBasketCheckout;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using eShopOnContainers.Core.Services.RequestProvider;
|
||||
using eShopOnContainers.Core.Models.Basket;
|
||||
using eShopOnContainers.Core.Services.RequestProvider;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
@ -14,17 +15,6 @@ namespace eShopOnContainers.Core.Services.Order
|
||||
_requestProvider = requestProvider;
|
||||
}
|
||||
|
||||
public async Task CreateOrderAsync(Models.Orders.Order newOrder, string token)
|
||||
{
|
||||
UriBuilder builder = new UriBuilder(GlobalSetting.Instance.OrdersEndpoint);
|
||||
|
||||
builder.Path = "api/v1/orders/new";
|
||||
|
||||
string uri = builder.ToString();
|
||||
|
||||
await _requestProvider.PostAsync(uri, newOrder, token, "x-requestid");
|
||||
}
|
||||
|
||||
public async Task<ObservableCollection<Models.Orders.Order>> GetOrdersAsync(string token)
|
||||
{
|
||||
|
||||
@ -82,5 +72,21 @@ namespace eShopOnContainers.Core.Services.Order
|
||||
return new ObservableCollection<Models.Orders.CardType>();
|
||||
}
|
||||
}
|
||||
|
||||
public BasketCheckout MapOrderToBasket(Models.Orders.Order order)
|
||||
{
|
||||
return new BasketCheckout()
|
||||
{
|
||||
CardExpiration = order.CardExpiration,
|
||||
CardHolderName = order.CardHolderName,
|
||||
CardNumber = order.CardNumber,
|
||||
CardSecurityNumber = order.CardSecurityNumber,
|
||||
CardTypeId = order.CardTypeId,
|
||||
City = order.ShippingCity,
|
||||
Country = order.ShippingCountry,
|
||||
ZipCode = order.ShippingZipCode,
|
||||
Street = order.ShippingStreet
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -8,6 +8,8 @@ namespace eShopOnContainers.Core.Services.RequestProvider
|
||||
|
||||
Task<TResult> PostAsync<TResult>(string uri, TResult data, string token = "", string header = "");
|
||||
|
||||
Task<TResult> PostAsync<TResult>(string uri, string data, string clientId, string clientSecret);
|
||||
|
||||
Task DeleteAsync(string uri, string token = "");
|
||||
}
|
||||
}
|
@ -61,6 +61,28 @@ namespace eShopOnContainers.Core.Services.RequestProvider
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<TResult> PostAsync<TResult>(string uri, string data, string clientId, string clientSecret)
|
||||
{
|
||||
HttpClient httpClient = CreateHttpClient(string.Empty);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(clientId) && !string.IsNullOrWhiteSpace(clientSecret))
|
||||
{
|
||||
AddBasicAuthenticationHeader(httpClient, clientId, clientSecret);
|
||||
}
|
||||
|
||||
var content = new StringContent(data);
|
||||
content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
|
||||
HttpResponseMessage response = await httpClient.PostAsync(uri, content);
|
||||
|
||||
await HandleResponse(response);
|
||||
string serialized = await response.Content.ReadAsStringAsync();
|
||||
|
||||
TResult result = await Task.Run(() =>
|
||||
JsonConvert.DeserializeObject<TResult>(serialized, _serializerSettings));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(string uri, string token = "")
|
||||
{
|
||||
HttpClient httpClient = CreateHttpClient(token);
|
||||
@ -90,6 +112,17 @@ namespace eShopOnContainers.Core.Services.RequestProvider
|
||||
httpClient.DefaultRequestHeaders.Add(parameter, Guid.NewGuid().ToString());
|
||||
}
|
||||
|
||||
private void AddBasicAuthenticationHeader(HttpClient httpClient, string clientId, string clientSecret)
|
||||
{
|
||||
if (httpClient == null)
|
||||
return;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(clientId) || string.IsNullOrWhiteSpace(clientSecret))
|
||||
return;
|
||||
|
||||
httpClient.DefaultRequestHeaders.Authorization = new BasicAuthenticationHeaderValue(clientId, clientSecret);
|
||||
}
|
||||
|
||||
private async Task HandleResponse(HttpResponseMessage response)
|
||||
{
|
||||
if (!response.IsSuccessStatusCode)
|
||||
|
@ -11,6 +11,8 @@ using eShopOnContainers.Core.Services.Identity;
|
||||
using eShopOnContainers.Core.Services.Order;
|
||||
using eShopOnContainers.Core.Services.User;
|
||||
using Xamarin.Forms;
|
||||
using eShopOnContainers.Core.Services.Location;
|
||||
using eShopOnContainers.Core.Services.Marketing;
|
||||
|
||||
namespace eShopOnContainers.Core.ViewModels.Base
|
||||
{
|
||||
@ -46,22 +48,26 @@ namespace eShopOnContainers.Core.ViewModels.Base
|
||||
builder.RegisterType<OrderDetailViewModel>();
|
||||
builder.RegisterType<ProfileViewModel>();
|
||||
builder.RegisterType<SettingsViewModel>();
|
||||
builder.RegisterType<CampaignViewModel>();
|
||||
builder.RegisterType<CampaignDetailsViewModel>();
|
||||
|
||||
// Services
|
||||
builder.RegisterType<NavigationService>().As<INavigationService>().SingleInstance();
|
||||
// Services
|
||||
builder.RegisterType<NavigationService>().As<INavigationService>().SingleInstance();
|
||||
builder.RegisterType<DialogService>().As<IDialogService>();
|
||||
builder.RegisterType<OpenUrlService>().As<IOpenUrlService>();
|
||||
builder.RegisterType<IdentityService>().As<IIdentityService>();
|
||||
builder.RegisterType<RequestProvider>().As<IRequestProvider>();
|
||||
builder.RegisterType<LocationService>().As<ILocationService>().SingleInstance();
|
||||
|
||||
if (useMockServices)
|
||||
if (useMockServices)
|
||||
{
|
||||
builder.RegisterInstance(new CatalogMockService()).As<ICatalogService>();
|
||||
builder.RegisterInstance(new BasketMockService()).As<IBasketService>();
|
||||
builder.RegisterInstance(new OrderMockService()).As<IOrderService>();
|
||||
builder.RegisterInstance(new UserMockService()).As<IUserService>();
|
||||
builder.RegisterInstance(new CampaignMockService()).As<ICampaignService>();
|
||||
|
||||
UseMockService = true;
|
||||
UseMockService = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -69,8 +75,9 @@ namespace eShopOnContainers.Core.ViewModels.Base
|
||||
builder.RegisterType<BasketService>().As<IBasketService>().SingleInstance();
|
||||
builder.RegisterType<OrderService>().As<IOrderService>().SingleInstance();
|
||||
builder.RegisterType<UserService>().As<IUserService>().SingleInstance();
|
||||
builder.RegisterType<CampaignService>().As<ICampaignService>().SingleInstance();
|
||||
|
||||
UseMockService = false;
|
||||
UseMockService = false;
|
||||
}
|
||||
|
||||
if (_container != null)
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -132,8 +132,10 @@ namespace eShopOnContainers.Core.ViewModels
|
||||
{
|
||||
var authToken = Settings.AuthAccessToken;
|
||||
|
||||
// Create new order
|
||||
await _orderService.CreateOrderAsync(Order, authToken);
|
||||
var basket = _orderService.MapOrderToBasket(Order);
|
||||
|
||||
// Create basket checkout
|
||||
await _basketService.CheckoutAsync(basket, authToken);
|
||||
|
||||
// Clean Basket
|
||||
await _basketService.ClearBasketAsync(_shippingAddress.Id.ToString(), authToken);
|
||||
|
@ -203,20 +203,22 @@ namespace eShopOnContainers.Core.ViewModels
|
||||
private void Logout()
|
||||
{
|
||||
var authIdToken = Settings.AuthIdToken;
|
||||
|
||||
var logoutRequest = _identityService.CreateLogoutRequest(authIdToken);
|
||||
|
||||
if(!string.IsNullOrEmpty(logoutRequest))
|
||||
if (!string.IsNullOrEmpty(logoutRequest))
|
||||
{
|
||||
// Logout
|
||||
LoginUrl = logoutRequest;
|
||||
}
|
||||
|
||||
if(Settings.UseMocks)
|
||||
if (Settings.UseMocks)
|
||||
{
|
||||
Settings.AuthAccessToken = string.Empty;
|
||||
Settings.AuthIdToken = string.Empty;
|
||||
Settings.AuthIdToken = string.Empty;
|
||||
}
|
||||
|
||||
Settings.UserId = string.Empty;
|
||||
Settings.UseFakeLocation = false;
|
||||
}
|
||||
|
||||
private async Task NavigateAsync(string url)
|
||||
@ -233,14 +235,16 @@ namespace eShopOnContainers.Core.ViewModels
|
||||
else if (unescapedUrl.Contains(GlobalSetting.Instance.IdentityCallback))
|
||||
{
|
||||
var authResponse = new AuthorizeResponse(url);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(authResponse.AccessToken))
|
||||
if (!string.IsNullOrWhiteSpace(authResponse.Code))
|
||||
{
|
||||
if (authResponse.AccessToken != null)
|
||||
{
|
||||
Settings.AuthAccessToken = authResponse.AccessToken;
|
||||
Settings.AuthIdToken = authResponse.IdentityToken;
|
||||
var userToken = await _identityService.GetTokenAsync(authResponse.Code);
|
||||
string accessToken = userToken.AccessToken;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(accessToken))
|
||||
{
|
||||
Settings.AuthAccessToken = accessToken;
|
||||
Settings.AuthIdToken = authResponse.IdentityToken;
|
||||
Settings.UserId = authResponse.Values["sub"];
|
||||
await NavigationService.NavigateToAsync<MainViewModel>();
|
||||
await NavigationService.RemoveLastFromBackStackAsync();
|
||||
}
|
||||
|
@ -4,38 +4,51 @@ using Xamarin.Forms;
|
||||
using System.Threading.Tasks;
|
||||
using eShopOnContainers.Core.Helpers;
|
||||
using eShopOnContainers.Core.Models.User;
|
||||
using System;
|
||||
using eShopOnContainers.Core.Models.Location;
|
||||
using eShopOnContainers.Core.Services.Location;
|
||||
|
||||
namespace eShopOnContainers.Core.ViewModels
|
||||
{
|
||||
public class SettingsViewModel : ViewModelBase
|
||||
{
|
||||
private string _title;
|
||||
private string _description;
|
||||
private string _titleUseAzureServices;
|
||||
private string _descriptionUseAzureServices;
|
||||
private bool _useAzureServices;
|
||||
private string _titleUseFakeLocation;
|
||||
private string _descriptionUseFakeLocation;
|
||||
private bool _useFakeLocation;
|
||||
private string _endpoint;
|
||||
private double _latitude;
|
||||
private double _longitude;
|
||||
private ILocationService _locationService;
|
||||
|
||||
public SettingsViewModel()
|
||||
public SettingsViewModel(ILocationService locationService)
|
||||
{
|
||||
UseAzureServices = !Settings.UseMocks;
|
||||
_useFakeLocation = Settings.UseFakeLocation;
|
||||
_latitude = Settings.Latitude;
|
||||
_longitude = Settings.Longitude;
|
||||
_locationService = locationService;
|
||||
}
|
||||
|
||||
public string Title
|
||||
public string TitleUseAzureServices
|
||||
{
|
||||
get { return _title; }
|
||||
get { return _titleUseAzureServices; }
|
||||
set
|
||||
{
|
||||
_title = value;
|
||||
RaisePropertyChanged(() => Title);
|
||||
_titleUseAzureServices = value;
|
||||
RaisePropertyChanged(() => TitleUseAzureServices);
|
||||
}
|
||||
}
|
||||
|
||||
public string Description
|
||||
public string DescriptionUseAzureServices
|
||||
{
|
||||
get { return _description; }
|
||||
get { return _descriptionUseAzureServices; }
|
||||
set
|
||||
{
|
||||
_description = value;
|
||||
RaisePropertyChanged(() => Description);
|
||||
_descriptionUseAzureServices = value;
|
||||
RaisePropertyChanged(() => DescriptionUseAzureServices);
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,6 +65,39 @@ namespace eShopOnContainers.Core.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public string TitleUseFakeLocation
|
||||
{
|
||||
get { return _titleUseFakeLocation; }
|
||||
set
|
||||
{
|
||||
_titleUseFakeLocation = value;
|
||||
RaisePropertyChanged(() => TitleUseFakeLocation);
|
||||
}
|
||||
}
|
||||
|
||||
public string DescriptionUseFakeLocation
|
||||
{
|
||||
get { return _descriptionUseFakeLocation; }
|
||||
set
|
||||
{
|
||||
_descriptionUseFakeLocation = value;
|
||||
RaisePropertyChanged(() => DescriptionUseFakeLocation);
|
||||
}
|
||||
}
|
||||
|
||||
public bool UseFakeLocation
|
||||
{
|
||||
get { return _useFakeLocation; }
|
||||
set
|
||||
{
|
||||
_useFakeLocation = value;
|
||||
|
||||
// Save use fake location services to local storage
|
||||
Settings.UseFakeLocation = _useFakeLocation;
|
||||
RaisePropertyChanged(() => UseFakeLocation);
|
||||
}
|
||||
}
|
||||
|
||||
public string Endpoint
|
||||
{
|
||||
get { return _endpoint; }
|
||||
@ -68,12 +114,49 @@ namespace eShopOnContainers.Core.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public double Latitude
|
||||
{
|
||||
get { return _latitude; }
|
||||
set
|
||||
{
|
||||
_latitude = value;
|
||||
|
||||
UpdateLatitude(_latitude);
|
||||
|
||||
RaisePropertyChanged(() => Latitude);
|
||||
}
|
||||
}
|
||||
|
||||
public double Longitude
|
||||
{
|
||||
get { return _longitude; }
|
||||
set
|
||||
{
|
||||
_longitude = value;
|
||||
|
||||
UpdateLongitude(_longitude);
|
||||
|
||||
RaisePropertyChanged(() => Longitude);
|
||||
}
|
||||
}
|
||||
|
||||
public bool UserIsLogged => !string.IsNullOrEmpty(Settings.AuthAccessToken);
|
||||
|
||||
public ICommand ToggleMockServicesCommand => new Command(async () => await ToggleMockServicesAsync());
|
||||
|
||||
public ICommand ToggleFakeLocationCommand => new Command(() => ToggleFakeLocationAsync());
|
||||
|
||||
public ICommand ToggleSendLocationCommand => new Command(async () => await ToggleSendLocationAsync());
|
||||
|
||||
public override Task InitializeAsync(object navigationData)
|
||||
{
|
||||
UpdateInfo();
|
||||
UpdateInfoFakeLocation();
|
||||
|
||||
Endpoint = Settings.UrlBase;
|
||||
_latitude = Settings.Latitude;
|
||||
_longitude = Settings.Longitude;
|
||||
_useFakeLocation = Settings.UseFakeLocation;
|
||||
return base.InitializeAsync(navigationData);
|
||||
}
|
||||
|
||||
@ -100,17 +183,52 @@ namespace eShopOnContainers.Core.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateInfo()
|
||||
private void ToggleFakeLocationAsync()
|
||||
{
|
||||
ViewModelLocator.RegisterDependencies(!UseAzureServices);
|
||||
UpdateInfoFakeLocation();
|
||||
}
|
||||
|
||||
private async Task ToggleSendLocationAsync()
|
||||
{
|
||||
if (!Settings.UseMocks)
|
||||
{
|
||||
var locationRequest = new Location
|
||||
{
|
||||
Latitude = _latitude,
|
||||
Longitude = _longitude
|
||||
};
|
||||
var authToken = Settings.AuthAccessToken;
|
||||
|
||||
await _locationService.UpdateUserLocation(locationRequest, authToken);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateInfo()
|
||||
{
|
||||
if (!UseAzureServices)
|
||||
{
|
||||
Title = "Use Mock Services";
|
||||
Description = "Mock Services are simulated objects that mimic the behavior of real services using a controlled approach.";
|
||||
TitleUseAzureServices = "Use Mock Services";
|
||||
DescriptionUseAzureServices = "Mock Services are simulated objects that mimic the behavior of real services using a controlled approach.";
|
||||
}
|
||||
else
|
||||
{
|
||||
Title = "Use Microservices/Containers from eShopOnContainers";
|
||||
Description = "When enabling the use of microservices/containers, the app will attempt to use real services deployed as Docker containers at the specified base endpoint, which will must be reachable through the network.";
|
||||
TitleUseAzureServices = "Use Microservices/Containers from eShopOnContainers";
|
||||
DescriptionUseAzureServices = "When enabling the use of microservices/containers, the app will attempt to use real services deployed as Docker containers at the specified base endpoint, which will must be reachable through the network.";
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateInfoFakeLocation()
|
||||
{
|
||||
if (!UseFakeLocation)
|
||||
{
|
||||
TitleUseFakeLocation = "Use Fake Location";
|
||||
DescriptionUseFakeLocation = "Fake Location are added for marketing campaign testing.";
|
||||
}
|
||||
else
|
||||
{
|
||||
TitleUseFakeLocation = "Use Real Location";
|
||||
DescriptionUseFakeLocation = "When enabling the use of real location, the app will attempt to use real location from the device.";
|
||||
}
|
||||
}
|
||||
|
||||
@ -119,5 +237,17 @@ namespace eShopOnContainers.Core.ViewModels
|
||||
// Update remote endpoint (save to local storage)
|
||||
Settings.UrlBase = endpoint;
|
||||
}
|
||||
|
||||
private void UpdateLatitude(double latitude)
|
||||
{
|
||||
// Update fake latitude (save to local storage)
|
||||
Settings.Latitude = latitude;
|
||||
}
|
||||
|
||||
private void UpdateLongitude(double longitude)
|
||||
{
|
||||
// Update fake longitude (save to local storage)
|
||||
Settings.Longitude = longitude;
|
||||
}
|
||||
}
|
||||
}
|
@ -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>
|
@ -0,0 +1,12 @@
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace eShopOnContainers.Core.Views
|
||||
{
|
||||
public partial class CampaignDetailsView : ContentPage
|
||||
{
|
||||
public CampaignDetailsView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
@ -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>
|
@ -0,0 +1,13 @@
|
||||
namespace eShopOnContainers.Core.Views
|
||||
{
|
||||
using Xamarin.Forms;
|
||||
|
||||
public partial class CampaignView: ContentPage
|
||||
{
|
||||
|
||||
public CampaignView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
@ -63,4 +63,15 @@
|
||||
WinPhone="Assets\menu_cart.png"/>
|
||||
</views:BasketView.Icon>
|
||||
</views:BasketView>
|
||||
<!-- CAMPAIGNS -->
|
||||
<views:CampaignView
|
||||
x:Name="CampaignView">
|
||||
<views:CampaignView.Icon>
|
||||
<OnPlatform
|
||||
x:TypeArguments="FileImageSource"
|
||||
Android="menu_filter"
|
||||
iOS="menu_filter"
|
||||
WinPhone="Assets\menu_filter.png"/>
|
||||
</views:CampaignView.Icon>
|
||||
</views:CampaignView>
|
||||
</TabbedPage>
|
@ -28,12 +28,16 @@ namespace eShopOnContainers.Core.Views
|
||||
case 2:
|
||||
CurrentPage = BasketView;
|
||||
break;
|
||||
case 3:
|
||||
CurrentPage = CampaignView;
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
await ((CatalogViewModel)HomeView.BindingContext).InitializeAsync(null);
|
||||
await ((BasketViewModel)BasketView.BindingContext).InitializeAsync(null);
|
||||
await ((ProfileViewModel)ProfileView.BindingContext).InitializeAsync(null);
|
||||
await ((CampaignViewModel)CampaignView.BindingContext).InitializeAsync(null);
|
||||
}
|
||||
|
||||
protected override async void OnCurrentPageChanged()
|
||||
@ -44,6 +48,7 @@ namespace eShopOnContainers.Core.Views
|
||||
{
|
||||
// Force basket view refresh every time we access it
|
||||
await (BasketView.BindingContext as ViewModelBase).InitializeAsync(null);
|
||||
await (CampaignView.BindingContext as ViewModelBase).InitializeAsync(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -98,6 +98,8 @@
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="1" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid
|
||||
Grid.Row="0"
|
||||
@ -108,11 +110,11 @@
|
||||
Grid.Column="0"
|
||||
Grid.Row="1">
|
||||
<Label
|
||||
Text="{Binding Title}"
|
||||
Text="{Binding TitleUseAzureServices}"
|
||||
TextColor="{StaticResource GreenColor}"
|
||||
Style="{StaticResource SettingsTitleStyle}"/>
|
||||
<Label
|
||||
Text="{Binding Description}"
|
||||
Text="{Binding DescriptionUseAzureServices}"
|
||||
Style="{StaticResource SettingsDescriptionStyle}"/>
|
||||
</StackLayout>
|
||||
<!-- ON / OFF -->
|
||||
@ -160,8 +162,81 @@
|
||||
<Grid
|
||||
Grid.Row="3"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"/>
|
||||
<StackLayout
|
||||
Grid.Column="0"
|
||||
Grid.Row="4"
|
||||
IsVisible="{Binding UserIsLogged}">
|
||||
<Label
|
||||
Text="{Binding TitleUseFakeLocation}"
|
||||
TextColor="{StaticResource GreenColor}"
|
||||
Style="{StaticResource SettingsTitleStyle}"/>
|
||||
<Label
|
||||
Text="{Binding DescriptionUseFakeLocation}"
|
||||
Style="{StaticResource SettingsDescriptionStyle}"/>
|
||||
</StackLayout>
|
||||
<!-- ON / OFF -->
|
||||
<controls:ToggleButton
|
||||
Grid.Column="1"
|
||||
Grid.Row="4"
|
||||
Animate="True"
|
||||
Checked="{Binding UseFakeLocation, Mode=TwoWay}"
|
||||
Command="{Binding ToggleFakeLocationCommand}"
|
||||
Style="{StaticResource SettingsToggleButtonStyle}"
|
||||
IsVisible="{Binding UserIsLogged}">
|
||||
<controls:ToggleButton.CheckedImage>
|
||||
<OnPlatform x:TypeArguments="ImageSource"
|
||||
Android="switch_on.png"
|
||||
iOS="switchOn.png"
|
||||
WinPhone="Assets/switchOn.png"/>
|
||||
</controls:ToggleButton.CheckedImage>
|
||||
<controls:ToggleButton.UnCheckedImage>
|
||||
<OnPlatform x:TypeArguments="ImageSource"
|
||||
Android="switch_off.png"
|
||||
iOS="switchOff.png"
|
||||
WinPhone="Assets/switchOff.png"/>
|
||||
</controls:ToggleButton.UnCheckedImage>
|
||||
</controls:ToggleButton>
|
||||
<!-- FAKE LOCATIONS -->
|
||||
<StackLayout
|
||||
Grid.Row="5"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
BackgroundColor="Gray"/>
|
||||
Margin="12, 0, 12, 12"
|
||||
IsVisible="{Binding UseFakeLocation}">
|
||||
<Label
|
||||
Text="Latitude"
|
||||
Style="{StaticResource HeaderLabelStyle}"/>
|
||||
<Entry
|
||||
Text="{Binding Latitude, Mode=TwoWay}"
|
||||
Keyboard="Text">
|
||||
<Entry.Style>
|
||||
<OnPlatform
|
||||
x:TypeArguments="Style"
|
||||
iOS="{StaticResource EntryStyle}"
|
||||
Android="{StaticResource EntryStyle}"
|
||||
WinPhone="{StaticResource UwpEntryStyle}"/>
|
||||
</Entry.Style>
|
||||
</Entry>
|
||||
<Label
|
||||
Text="Longitude"
|
||||
Style="{StaticResource HeaderLabelStyle}"/>
|
||||
<Entry
|
||||
Text="{Binding Longitude, Mode=TwoWay}"
|
||||
Keyboard="Text">
|
||||
<Entry.Style>
|
||||
<OnPlatform
|
||||
x:TypeArguments="Style"
|
||||
iOS="{StaticResource EntryStyle}"
|
||||
Android="{StaticResource EntryStyle}"
|
||||
WinPhone="{StaticResource UwpEntryStyle}"/>
|
||||
</Entry.Style>
|
||||
</Entry>
|
||||
<Button
|
||||
Command="{Binding ToggleSendLocationCommand}"
|
||||
Text="Send Location"/>
|
||||
</StackLayout>
|
||||
|
||||
</Grid>
|
||||
</StackLayout>
|
||||
</ScrollView>
|
||||
|
@ -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>
|
@ -0,0 +1,12 @@
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace eShopOnContainers.Core.Views.Templates
|
||||
{
|
||||
public partial class CampaignTemplate : ContentView
|
||||
{
|
||||
public CampaignTemplate()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
@ -64,12 +64,16 @@
|
||||
<Compile Include="Helpers\EasingHelper.cs" />
|
||||
<Compile Include="Helpers\ServicesHelper.cs" />
|
||||
<Compile Include="Helpers\Settings.cs" />
|
||||
<Compile Include="Models\Basket\BasketCheckout.cs" />
|
||||
<Compile Include="Models\Basket\BasketItem.cs" />
|
||||
<Compile Include="Models\Basket\CustomerBasket.cs" />
|
||||
<Compile Include="Models\Catalog\CatalogBrand.cs" />
|
||||
<Compile Include="Models\Catalog\CatalogItem.cs" />
|
||||
<Compile Include="Models\Catalog\CatalogRoot.cs" />
|
||||
<Compile Include="Models\Catalog\CatalogType.cs" />
|
||||
<Compile Include="Models\Location\Location.cs" />
|
||||
<Compile Include="Models\Marketing\CampaignItem.cs" />
|
||||
<Compile Include="Models\Marketing\CampaignRoot.cs" />
|
||||
<Compile Include="Models\Navigation\TabParameter.cs" />
|
||||
<Compile Include="Models\Orders\CardType.CS" />
|
||||
<Compile Include="Models\Orders\Order.cs" />
|
||||
@ -91,6 +95,11 @@
|
||||
<Compile Include="Services\Dialog\IDialogService.cs" />
|
||||
<Compile Include="Services\Identity\IdentityService.cs" />
|
||||
<Compile Include="Services\Identity\IIdentityService.cs" />
|
||||
<Compile Include="Services\Location\ILocationService.cs" />
|
||||
<Compile Include="Services\Location\LocationService.cs" />
|
||||
<Compile Include="Services\Marketing\ICampaignService.cs" />
|
||||
<Compile Include="Services\Marketing\CampaignMockService.cs" />
|
||||
<Compile Include="Services\Marketing\CampaignService.cs" />
|
||||
<Compile Include="Services\Navigation\INavigationService.cs" />
|
||||
<Compile Include="Services\Navigation\NavigationService.cs" />
|
||||
<Compile Include="Services\OpenUrl\IOpenUrlService.cs" />
|
||||
@ -113,6 +122,8 @@
|
||||
<Compile Include="ViewModels\Base\ViewModelBase.cs" />
|
||||
<Compile Include="ViewModels\Base\ViewModelLocator.cs" />
|
||||
<Compile Include="ViewModels\BasketViewModel.cs" />
|
||||
<Compile Include="ViewModels\CampaignDetailsViewModel.cs" />
|
||||
<Compile Include="ViewModels\CampaignViewModel.cs" />
|
||||
<Compile Include="ViewModels\CatalogViewModel.cs" />
|
||||
<Compile Include="ViewModels\CheckoutViewModel.cs" />
|
||||
<Compile Include="ViewModels\LoginViewModel.cs" />
|
||||
@ -123,9 +134,15 @@
|
||||
<Compile Include="Views\BasketView.xaml.cs">
|
||||
<DependentUpon>BasketView.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\CampaignView.xaml.cs">
|
||||
<DependentUpon>CampaignView.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\CatalogView.xaml.cs">
|
||||
<DependentUpon>CatalogView.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\CampaignDetailsView.xaml.cs">
|
||||
<DependentUpon>CampaignDetailsView.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\CheckoutView.xaml.cs">
|
||||
<DependentUpon>CheckoutView.xaml</DependentUpon>
|
||||
</Compile>
|
||||
@ -159,6 +176,9 @@
|
||||
<Compile Include="Views\Templates\OrderTemplate.xaml.cs">
|
||||
<DependentUpon>OrderTemplate.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\Templates\CampaignTemplate.xaml.cs">
|
||||
<DependentUpon>CampaignTemplate.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\Templates\ProductTemplate.xaml.cs">
|
||||
<DependentUpon>ProductTemplate.xaml</DependentUpon>
|
||||
</Compile>
|
||||
@ -166,6 +186,7 @@
|
||||
<Compile Include="Converters\FirstValidationErrorConverter.cs" />
|
||||
<Compile Include="Effects\EntryLineColorEffect.cs" />
|
||||
<Compile Include="Behaviors\LineColorBehavior.cs" />
|
||||
<Compile Include="Models\Token\UserToken.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="app.config" />
|
||||
@ -259,6 +280,29 @@
|
||||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Views\CampaignView.xaml">
|
||||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System.ComponentModel.Annotations">
|
||||
<HintPath>..\..\..\..\..\..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETPortable\v4.6\Profile\Profile44\System.ComponentModel.Annotations.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Views\CampaignDetailsView.xaml">
|
||||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Views\Templates\CampaignTemplate.xaml">
|
||||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user