Meged with mainline
This commit is contained in:
commit
d3a203ab5a
3
.gitignore
vendored
3
.gitignore
vendored
@ -25,7 +25,7 @@ bld/
|
|||||||
# Visual Studio 2015 cache/options directory
|
# Visual Studio 2015 cache/options directory
|
||||||
.vs/
|
.vs/
|
||||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||||
#wwwroot/
|
**/wwwroot/lib/
|
||||||
|
|
||||||
**/wwwroot/lib/
|
**/wwwroot/lib/
|
||||||
|
|
||||||
@ -260,3 +260,4 @@ pub/
|
|||||||
|
|
||||||
#Ignore marker-file used to know which docker files we have.
|
#Ignore marker-file used to know which docker files we have.
|
||||||
.eshopdocker_*
|
.eshopdocker_*
|
||||||
|
|
||||||
|
@ -14,11 +14,15 @@ Sample .NET Core reference application, powered by Microsoft, based on a simplif
|
|||||||
**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.
|
**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.
|
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>
|
<p>
|
||||||
The plan is to add 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>
|
<p>
|
||||||
<img src="img/eshop_logo.png">
|
<img src="img/eshop_logo.png">
|
||||||
<img src="img/eShopOnContainers_Architecture_Diagram.png">
|
<img src="img/eShopOnContainers_Architecture_Diagram.png">
|
||||||
<p>
|
<p>
|
||||||
|
The microservices are different in type, meaning different internal architecture patterns approaches depending on it purpose, as shown in the image below.
|
||||||
|
<p>
|
||||||
|
<img src="img/eShopOnContainers_Types_Of_Microservices.png">
|
||||||
|
<p>
|
||||||
<p>
|
<p>
|
||||||
Additional miroservice styles with other frameworks and No-SQL databases will be added, eventually. This is a great opportunity for pull requests from the community, like a new microservice using Nancy, or even other languages like Node, Go, Python or data containers with MongoDB with Azure DocDB compatibility, PostgreSQL, RavenDB, Event Store, MySql, etc. You name it! :)
|
Additional miroservice styles with other frameworks and No-SQL databases will be added, eventually. This is a great opportunity for pull requests from the community, like a new microservice using Nancy, or even other languages like Node, Go, Python or data containers with MongoDB with Azure DocDB compatibility, PostgreSQL, RavenDB, Event Store, MySql, etc. You name it! :)
|
||||||
|
|
||||||
@ -88,7 +92,7 @@ The app was also partially tested on "Docker for Mac" using a development MacOS
|
|||||||
|
|
||||||
## Sending feedback and pull requests
|
## Sending feedback and pull requests
|
||||||
As mentioned, we'd appreciate to your feedback, improvements and ideas.
|
As mentioned, we'd appreciate to your feedback, improvements and ideas.
|
||||||
You can create new issues at the issues section, do pull requests and/or send emails to eshop_feedback@service.microsoft.com
|
You can create new issues at the issues section, do pull requests and/or send emails to **eshop_feedback@service.microsoft.com**
|
||||||
|
|
||||||
## Questions
|
## Questions
|
||||||
[QUESTION] Answer +1 if the solution is working for you (Through VS2017 or CLI environment):
|
[QUESTION] Answer +1 if the solution is working for you (Through VS2017 or CLI environment):
|
||||||
|
@ -6,6 +6,7 @@ projectList=(
|
|||||||
"/src/Services/Identity/Identity.API"
|
"/src/Services/Identity/Identity.API"
|
||||||
"/src/Web/WebMVC"
|
"/src/Web/WebMVC"
|
||||||
"/src/Web/WebSPA"
|
"/src/Web/WebSPA"
|
||||||
|
"/src/Web/WebStatus
|
||||||
)
|
)
|
||||||
|
|
||||||
# Build SPA app
|
# Build SPA app
|
||||||
@ -36,4 +37,4 @@ done
|
|||||||
#fi
|
#fi
|
||||||
|
|
||||||
# No need to build the images, docker build or docker compose will
|
# No need to build the images, docker build or docker compose will
|
||||||
# do that using the images and containers defined in the docker-compose.yml file.
|
# do that using the images and containers defined in the docker-compose.yml file.
|
||||||
|
@ -7,6 +7,7 @@ projectList=(
|
|||||||
"../src/Services/Identity/Identity.API"
|
"../src/Services/Identity/Identity.API"
|
||||||
"../src/Web/WebMVC"
|
"../src/Web/WebMVC"
|
||||||
"../src/Web/WebSPA"
|
"../src/Web/WebSPA"
|
||||||
|
"../src/Web/WebStatus"
|
||||||
)
|
)
|
||||||
|
|
||||||
for project in "${projectList[@]}"
|
for project in "${projectList[@]}"
|
||||||
|
@ -1,160 +0,0 @@
|
|||||||
|
|
||||||
#########################################################################################################
|
|
||||||
# This "expanded Script" can be used when debugging issues when building the .NET Core bits
|
|
||||||
# as it is easier to follow and debug than when using a loop (like in the optimized build-bits.ps1)
|
|
||||||
#########################################################################################################
|
|
||||||
|
|
||||||
Param([string] $rootPath)
|
|
||||||
$scriptPath = Split-Path $script:MyInvocation.MyCommand.Path
|
|
||||||
|
|
||||||
Write-Host "Current script directory is $scriptPath" -ForegroundColor Yellow
|
|
||||||
|
|
||||||
if ([string]::IsNullOrEmpty($rootPath)) {
|
|
||||||
$rootPath = "$scriptPath\.."
|
|
||||||
}
|
|
||||||
Write-Host "Root path used is $rootPath" -ForegroundColor Yellow
|
|
||||||
|
|
||||||
# *** WebMVC paths ***
|
|
||||||
$webMVCPath = $rootPath + "\src\Web\WebMVC"
|
|
||||||
$webMVCPathToProject = $webMVCPath + "\WebMVC.csproj"
|
|
||||||
Write-Host "webMVCPathToProject is $webMVCPathToProject" -ForegroundColor Yellow
|
|
||||||
$webMVCPathToPub = $webMVCPath + "\obj\Docker\publish"
|
|
||||||
Write-Host "webMVCPathToPub is $webMVCPathToPub" -ForegroundColor Yellow
|
|
||||||
|
|
||||||
|
|
||||||
# *** WebSPA paths ***
|
|
||||||
$webSPAPath = $rootPath + "\src\Web\WebSPA"
|
|
||||||
$webSPAPathToProject = $webSPAPath + "\WebSPA.csproj"
|
|
||||||
Write-Host "webSPAPathToProject is $webSPAPathToProject" -ForegroundColor Yellow
|
|
||||||
$webSPAPathToPub = $webSPAPath + "\obj\Docker\publish"
|
|
||||||
Write-Host "webSPAPathToPub is $webSPAPathToPub" -ForegroundColor Yellow
|
|
||||||
|
|
||||||
|
|
||||||
# *** IdentitySvc paths ***
|
|
||||||
$identitySvcPath = $rootPath + "\src\Services\Identity\Identity.API"
|
|
||||||
$identitySvcToProject = $identitySvcPath + "\Identity.API.csproj"
|
|
||||||
Write-Host "identitySvcToProject is $identitySvcToProject" -ForegroundColor Yellow
|
|
||||||
$identitySvcPathToPub = $identitySvcPath + "\obj\Docker\publish"
|
|
||||||
Write-Host "identitySvcPathToPub is $identitySvcPathToPub" -ForegroundColor Yellow
|
|
||||||
|
|
||||||
|
|
||||||
# *** Catalog paths ***
|
|
||||||
$catalogPath = $rootPath + "\src\Services\Catalog\Catalog.API"
|
|
||||||
$catalogPathToProject = $catalogPath + "\Catalog.API.csproj"
|
|
||||||
Write-Host "catalogPathToProject is $catalogPathToProject" -ForegroundColor Yellow
|
|
||||||
$catalogPathToPub = $catalogPath + "\obj\Docker\publish"
|
|
||||||
Write-Host "catalogPathToPub is $catalogPathToPub" -ForegroundColor Yellow
|
|
||||||
|
|
||||||
|
|
||||||
# *** Ordering paths ***
|
|
||||||
$orderingPath = $rootPath + "\src\Services\Ordering\Ordering.API"
|
|
||||||
$orderingPathToProject = $orderingPath + "\Ordering.API.csproj"
|
|
||||||
Write-Host "orderingPathToProject is $orderingPathToProject" -ForegroundColor Yellow
|
|
||||||
$orderingPathToPub = $orderingPath + "\obj\Docker\publish"
|
|
||||||
Write-Host "orderingPathToPub is $orderingPathToPub" -ForegroundColor Yellow
|
|
||||||
|
|
||||||
# *** Basket paths ***
|
|
||||||
$basketPath = $rootPath + "\src\Services\Basket\Basket.API"
|
|
||||||
$basketPathToProject = $basketPath + "\Basket.API.csproj"
|
|
||||||
Write-Host "basketPathToProject is $basketPathToProject" -ForegroundColor Yellow
|
|
||||||
$basketPathToPub = $basketPath + "\obj\Docker\publish"
|
|
||||||
Write-Host "basketPathToPub is $basketPathToPub" -ForegroundColor Yellow
|
|
||||||
|
|
||||||
|
|
||||||
########################################################################################
|
|
||||||
# Delete old eShop dotnet publish bits
|
|
||||||
########################################################################################
|
|
||||||
# Write-Host "Deleting previous dotnet publish bits from all projects" -ForegroundColor Blue
|
|
||||||
|
|
||||||
remove-item -path $WebMVCPathToPub -Force -Recurse -ErrorAction SilentlyContinue
|
|
||||||
remove-item -path $webSPAPathToPub -Force -Recurse -ErrorAction SilentlyContinue
|
|
||||||
remove-item -path $identitySvcPathToPub -Force -Recurse -ErrorAction SilentlyContinue
|
|
||||||
remove-item -path $catalogPathToPub -Force -Recurse -ErrorAction SilentlyContinue
|
|
||||||
remove-item -path $orderingPathToPub -Force -Recurse -ErrorAction SilentlyContinue
|
|
||||||
remove-item -path $basketPathToPub -Force -Recurse -ErrorAction SilentlyContinue
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
########################################################################################
|
|
||||||
# Building DotNet bits
|
|
||||||
########################################################################################
|
|
||||||
|
|
||||||
# WebMVC: Build dotnet bits
|
|
||||||
Write-Host "WebMVC: Restore Dependencies, dotnet build and dotnet publish" -ForegroundColor Blue
|
|
||||||
dotnet restore $WebMVCPathToProject
|
|
||||||
dotnet build $WebMVCPathToProject
|
|
||||||
dotnet publish $WebMVCPathToProject -o $WebMVCPathToPub
|
|
||||||
|
|
||||||
|
|
||||||
# WebSPA: Build dotnet bits
|
|
||||||
Write-Host "WebSPA: Installing npm dependencies"
|
|
||||||
#TEMP COMMENT--- Start-Process -WorkingDirectory $webSPAPath -NoNewWindow -Wait npm i
|
|
||||||
|
|
||||||
Write-Host "WebSPA: Restore Dependencies, dotnet build and dotnet publish" -ForegroundColor Blue
|
|
||||||
dotnet restore $webSPAPathToProject
|
|
||||||
dotnet build $webSPAPathToProject
|
|
||||||
dotnet publish $webSPAPathToProject -o $webSPAPathToPub
|
|
||||||
|
|
||||||
|
|
||||||
# Identity Service: Build dotnet bits
|
|
||||||
Write-Host "Identity Service: Restore Dependencies, dotnet build and dotnet publish" -ForegroundColor Blue
|
|
||||||
dotnet restore $identitySvcToProject
|
|
||||||
dotnet build $identitySvcToProject
|
|
||||||
dotnet publish $identitySvcToProject -o $identitySvcPathToPub
|
|
||||||
|
|
||||||
|
|
||||||
# Catalog Service: Build dotnet bits
|
|
||||||
Write-Host "Catalog Service: Restore Dependencies, dotnet build and dotnet publish" -ForegroundColor Blue
|
|
||||||
dotnet restore $catalogPathToProject
|
|
||||||
dotnet build $catalogPathToProject
|
|
||||||
dotnet publish $catalogPathToProject -o $catalogPathToPub
|
|
||||||
|
|
||||||
|
|
||||||
# Ordering Service: Build dotnet bits
|
|
||||||
Write-Host "Ordering Service: Restore Dependencies, dotnet build and dotnet publish" -ForegroundColor Blue
|
|
||||||
dotnet restore $orderingPathToProject
|
|
||||||
dotnet build $orderingPathToProject
|
|
||||||
dotnet publish $orderingPathToProject -o $orderingPathToPub
|
|
||||||
|
|
||||||
|
|
||||||
# Basket Service: Build dotnet bits
|
|
||||||
Write-Host "Basket Service: Restore Dependencies, dotnet build and dotnet publish" -ForegroundColor Blue
|
|
||||||
dotnet restore $basketPathToProject
|
|
||||||
dotnet build $basketPathToProject
|
|
||||||
dotnet publish $basketPathToProject -o $basketPathToPub
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
########################################################################################
|
|
||||||
# Delete old eShop Docker images
|
|
||||||
########################################################################################
|
|
||||||
|
|
||||||
$imagesToDelete = docker images --filter=reference="eshop/*" -q
|
|
||||||
|
|
||||||
If (-Not $imagesToDelete) {Write-Host "Not deleting eShop images as there are no eShop images in the current local Docker repo."}
|
|
||||||
Else
|
|
||||||
{
|
|
||||||
# Delete all containers
|
|
||||||
Write-Host "Deleting all containers in local Docker Host"
|
|
||||||
docker rm $(docker ps -a -q) -f
|
|
||||||
|
|
||||||
# Delete all eshop images
|
|
||||||
Write-Host "Deleting eShop images in local Docker repo"
|
|
||||||
Write-Host $imagesToDelete
|
|
||||||
docker rmi $(docker images --filter=reference="eshop/*" -q) -f
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
########################################################################################
|
|
||||||
# Build new eShop images
|
|
||||||
########################################################################################
|
|
||||||
|
|
||||||
# WE DON'T NEED DOCKER BUILD AS WE CAN RUN "DOCKER-COMPOSE BUILD" OR "DOCKER-COMPOSE UP" AND IT WILL BUILD ALL THE IMAGES IN THE .YML FOR US
|
|
||||||
|
|
||||||
#*** build docker images ***
|
|
||||||
# docker build -t eshop/web $webPathToPub
|
|
||||||
# docker build -t eshop/catalog.api $catalogPathToPub
|
|
||||||
# docker build -t eshop/ordering.api $orderingApiPathToPub
|
|
||||||
# docker build -t eshop/basket.api $basketPathToPub
|
|
||||||
# docker build -t eshop/webspa $webSPAPathToPub
|
|
||||||
# docker build -t eshop/identity $identitySvcPathToPub
|
|
@ -15,13 +15,14 @@ $projectPaths =
|
|||||||
@{Path="$rootPath\src\Services\Catalog\Catalog.API";Prj="Catalog.API.csproj"},
|
@{Path="$rootPath\src\Services\Catalog\Catalog.API";Prj="Catalog.API.csproj"},
|
||||||
@{Path="$rootPath\src\Services\Ordering\Ordering.API";Prj="Ordering.API.csproj"},
|
@{Path="$rootPath\src\Services\Ordering\Ordering.API";Prj="Ordering.API.csproj"},
|
||||||
@{Path="$rootPath\src\Services\Basket\Basket.API";Prj="Basket.API.csproj"}
|
@{Path="$rootPath\src\Services\Basket\Basket.API";Prj="Basket.API.csproj"}
|
||||||
|
@{Path="$rootPath\src\Web\WebStatus";Prj="WebStatus.csproj"}
|
||||||
|
|
||||||
$projectPaths | foreach {
|
$projectPaths | foreach {
|
||||||
$projectPath = $_.Path
|
$projectPath = $_.Path
|
||||||
$projectFile = $_.Prj
|
$projectFile = $_.Prj
|
||||||
$outPath = $_.Path + "\obj\Docker\publish"
|
$outPath = $_.Path + "\obj\Docker\publish"
|
||||||
$projectPathAndFile = "$projectPath\$projectFile"
|
$projectPathAndFile = "$projectPath\$projectFile"
|
||||||
Write-Host "Deleting $outPath" -ForegroundColor Yellow
|
Write-Host "Deleting old publish files in $outPath" -ForegroundColor Yellow
|
||||||
remove-item -path $outPath -Force -Recurse -ErrorAction SilentlyContinue
|
remove-item -path $outPath -Force -Recurse -ErrorAction SilentlyContinue
|
||||||
Write-Host "Publishing $projectPathAndFile to $outPath" -ForegroundColor Yellow
|
Write-Host "Publishing $projectPathAndFile to $outPath" -ForegroundColor Yellow
|
||||||
dotnet restore $projectPathAndFile
|
dotnet restore $projectPathAndFile
|
||||||
|
@ -33,6 +33,7 @@ services:
|
|||||||
- ASPNETCORE_ENVIRONMENT=Development
|
- ASPNETCORE_ENVIRONMENT=Development
|
||||||
- ASPNETCORE_URLS=http://0.0.0.0:5105
|
- ASPNETCORE_URLS=http://0.0.0.0:5105
|
||||||
- SpaClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5104
|
- SpaClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5104
|
||||||
|
- XamarinCallback=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5105/xamarincallback #localhost do not work for UWP login, so we have to use "external" IP always
|
||||||
- ConnectionStrings__DefaultConnection=Server=sql.data;Database=Microsoft.eShopOnContainers.Service.IdentityDb;User Id=sa;Password=Pass@word
|
- ConnectionStrings__DefaultConnection=Server=sql.data;Database=Microsoft.eShopOnContainers.Service.IdentityDb;User Id=sa;Password=Pass@word
|
||||||
- MvcClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5100 #Local: You need to open your local dev-machine firewall at range 5100-5105.
|
- MvcClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5100 #Local: You need to open your local dev-machine firewall at range 5100-5105.
|
||||||
ports:
|
ports:
|
||||||
@ -86,7 +87,7 @@ services:
|
|||||||
- CatalogUrl=http://catalog.api:5101/hc
|
- CatalogUrl=http://catalog.api:5101/hc
|
||||||
- OrderingUrl=http://ordering.api:5102/hc
|
- OrderingUrl=http://ordering.api:5102/hc
|
||||||
- BasketUrl=http://basket.api:5103/hc
|
- BasketUrl=http://basket.api:5103/hc
|
||||||
- IdentityUrl=http://10.0.75.1:5105/hc
|
- IdentityUrl=http://identity.api:5105/hc
|
||||||
- mvc=http://webmvc:5100/hc
|
- mvc=http://webmvc:5100/hc
|
||||||
- spa=http://webspa:5104/hc
|
- spa=http://webspa:5104/hc
|
||||||
ports:
|
ports:
|
||||||
|
@ -40,6 +40,7 @@ services:
|
|||||||
- SpaClient=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5104
|
- SpaClient=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5104
|
||||||
- ConnectionStrings__DefaultConnection=Server=sql.data;Database=Microsoft.eShopOnContainers.Service.IdentityDb;User Id=sa;Password=Pass@word
|
- ConnectionStrings__DefaultConnection=Server=sql.data;Database=Microsoft.eShopOnContainers.Service.IdentityDb;User Id=sa;Password=Pass@word
|
||||||
- MvcClient=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5100 #Local: You need to open your host's firewall at range 5100-5105.
|
- MvcClient=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5100 #Local: You need to open your host's firewall at range 5100-5105.
|
||||||
|
- XamarinCallback=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5105/xamarincallback
|
||||||
ports:
|
ports:
|
||||||
- "5105:5105"
|
- "5105:5105"
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio 15
|
# Visual Studio 15
|
||||||
VisualStudioVersion = 15.0.26228.0
|
VisualStudioVersion = 15.0.26228.12
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{932D8224-11F6-4D07-B109-DA28AD288A63}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{932D8224-11F6-4D07-B109-DA28AD288A63}"
|
||||||
EndProject
|
EndProject
|
||||||
@ -84,6 +84,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Health
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebStatus", "src\Web\WebStatus\WebStatus.csproj", "{C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebStatus", "src\Web\WebStatus\WebStatus.csproj", "{C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Resilience", "Resilience", "{FBF43D93-F2E7-4FF8-B4AB-186895949B88}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Resilience.Http", "src\BuildingBlocks\Resilience\Resilience.Http\Resilience.Http.csproj", "{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
|
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
|
||||||
@ -914,6 +918,54 @@ Global
|
|||||||
{C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Release|x64.Build.0 = Release|Any CPU
|
{C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Release|x86.ActiveCfg = Release|Any CPU
|
{C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Release|x86.Build.0 = Release|Any CPU
|
{C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.AppStore|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.AppStore|ARM.ActiveCfg = Debug|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.AppStore|ARM.Build.0 = Debug|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.AppStore|iPhone.Build.0 = Debug|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.AppStore|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.AppStore|x64.Build.0 = Debug|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.AppStore|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.AppStore|x86.Build.0 = Debug|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Debug|iPhone.Build.0 = Debug|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Release|ARM.Build.0 = Release|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Release|iPhone.Build.0 = Release|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@ -947,5 +999,7 @@ Global
|
|||||||
{942ED6E8-0050-495F-A0EA-01E97F63760C} = {A81ECBC2-6B00-4DCD-8388-469174033379}
|
{942ED6E8-0050-495F-A0EA-01E97F63760C} = {A81ECBC2-6B00-4DCD-8388-469174033379}
|
||||||
{7804FC60-23E6-490C-8E08-F9FEF829F184} = {A81ECBC2-6B00-4DCD-8388-469174033379}
|
{7804FC60-23E6-490C-8E08-F9FEF829F184} = {A81ECBC2-6B00-4DCD-8388-469174033379}
|
||||||
{C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F} = {E279BF0F-7F66-4F3A-A3AB-2CDA66C1CD04}
|
{C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F} = {E279BF0F-7F66-4F3A-A3AB-2CDA66C1CD04}
|
||||||
|
{FBF43D93-F2E7-4FF8-B4AB-186895949B88} = {DB0EFB20-B024-4E5E-A75C-52143C131D25}
|
||||||
|
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502} = {FBF43D93-F2E7-4FF8-B4AB-186895949B88}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 427 KiB After Width: | Height: | Size: 405 KiB |
BIN
img/eShopOnContainers_Architecture_Diagram_old.png
Normal file
BIN
img/eShopOnContainers_Architecture_Diagram_old.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 427 KiB |
BIN
img/eShopOnContainers_Types_Of_Microservices.png
Normal file
BIN
img/eShopOnContainers_Types_Of_Microservices.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 156 KiB |
@ -0,0 +1,37 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage;
|
||||||
|
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
|
||||||
|
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data.Common;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Utilities
|
||||||
|
{
|
||||||
|
public class ResilientTransaction
|
||||||
|
{
|
||||||
|
private DbContext _context;
|
||||||
|
private ResilientTransaction(DbContext context) =>
|
||||||
|
_context = context ?? throw new ArgumentNullException(nameof(context));
|
||||||
|
|
||||||
|
public static ResilientTransaction New (DbContext context) =>
|
||||||
|
new ResilientTransaction(context);
|
||||||
|
|
||||||
|
public async Task ExecuteAsync(Func<Task> action)
|
||||||
|
{
|
||||||
|
//Use of an EF Core resiliency strategy when using multiple DbContexts within an explicit BeginTransaction():
|
||||||
|
//See: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency
|
||||||
|
var strategy = _context.Database.CreateExecutionStrategy();
|
||||||
|
await strategy.ExecuteAsync(async () =>
|
||||||
|
{
|
||||||
|
using (var transaction = _context.Database.BeginTransaction())
|
||||||
|
{
|
||||||
|
await action();
|
||||||
|
transaction.Commit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,7 @@ using System.Linq;
|
|||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace WebMVC.Services.Utilities
|
namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http
|
||||||
{
|
{
|
||||||
public interface IHttpClient
|
public interface IHttpClient
|
||||||
{
|
{
|
@ -0,0 +1,14 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard1.4</TargetFramework>
|
||||||
|
<RootNamespace>Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http</RootNamespace>
|
||||||
|
</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" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
@ -0,0 +1,10 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http
|
||||||
|
{
|
||||||
|
public class ResiliencePolicy
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -1,14 +1,14 @@
|
|||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Polly;
|
using Polly;
|
||||||
using Polly.Wrap;
|
using Polly.Wrap;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace WebMVC.Services.Utilities
|
namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// HttpClient wrapper that integrates Retry and Circuit
|
/// HttpClient wrapper that integrates Retry and Circuit
|
||||||
@ -21,55 +21,15 @@ namespace WebMVC.Services.Utilities
|
|||||||
private PolicyWrap _policyWrapper;
|
private PolicyWrap _policyWrapper;
|
||||||
private ILogger<ResilientHttpClient> _logger;
|
private ILogger<ResilientHttpClient> _logger;
|
||||||
public HttpClient Inst => _client;
|
public HttpClient Inst => _client;
|
||||||
public ResilientHttpClient(ILogger<ResilientHttpClient> logger)
|
|
||||||
|
public ResilientHttpClient(Policy[] policies, ILogger<ResilientHttpClient> logger)
|
||||||
{
|
{
|
||||||
_client = new HttpClient();
|
_client = new HttpClient();
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
|
||||||
// Add Policies to be applied
|
// Add Policies to be applied
|
||||||
_policyWrapper = Policy.WrapAsync(
|
_policyWrapper = Policy.WrapAsync(policies);
|
||||||
CreateRetryPolicy(),
|
}
|
||||||
CreateCircuitBreakerPolicy()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Policy CreateRetryPolicy() =>
|
|
||||||
Policy.Handle<HttpRequestException>()
|
|
||||||
.WaitAndRetryAsync(
|
|
||||||
// number of retries
|
|
||||||
6,
|
|
||||||
// exponential backofff
|
|
||||||
retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
|
|
||||||
// on retry
|
|
||||||
(exception, timeSpan, retryCount, context) =>
|
|
||||||
{
|
|
||||||
var msg = $"Retry {retryCount} implemented with Polly's RetryPolicy " +
|
|
||||||
$"of {context.PolicyKey} " +
|
|
||||||
$"at {context.ExecutionKey}, " +
|
|
||||||
$"due to: {exception}.";
|
|
||||||
_logger.LogWarning(msg);
|
|
||||||
_logger.LogDebug(msg);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
private Policy CreateCircuitBreakerPolicy() =>
|
|
||||||
Policy.Handle<HttpRequestException>()
|
|
||||||
.CircuitBreakerAsync(
|
|
||||||
// number of exceptions before breaking circuit
|
|
||||||
5,
|
|
||||||
// time circuit opened before retry
|
|
||||||
TimeSpan.FromMinutes(1),
|
|
||||||
(exception, duration) =>
|
|
||||||
{
|
|
||||||
// on circuit opened
|
|
||||||
_logger.LogTrace("Circuit breaker opened");
|
|
||||||
},
|
|
||||||
() =>
|
|
||||||
{
|
|
||||||
// on circuit closed
|
|
||||||
_logger.LogTrace("Circuit breaker reset");
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
public Task<string> GetStringAsync(string uri) =>
|
public Task<string> GetStringAsync(string uri) =>
|
||||||
HttpInvoker(() =>
|
HttpInvoker(() =>
|
@ -4,7 +4,7 @@ using System;
|
|||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace WebMVC.Services.Utilities
|
namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http
|
||||||
{
|
{
|
||||||
public class StandardHttpClient : IHttpClient
|
public class StandardHttpClient : IHttpClient
|
||||||
{
|
{
|
@ -108,8 +108,9 @@
|
|||||||
<converters:InverseBoolConverter x:Key="InverseBoolConverter" />
|
<converters:InverseBoolConverter x:Key="InverseBoolConverter" />
|
||||||
<converters:ItemsToHeightConverter x:Key="ItemsToHeightConverter" />
|
<converters:ItemsToHeightConverter x:Key="ItemsToHeightConverter" />
|
||||||
<converters:ToUpperConverter x:Key="ToUpperConverter" />
|
<converters:ToUpperConverter x:Key="ToUpperConverter" />
|
||||||
<converters:WebNavigatingEventArgsConverter x:Key="WebNavigatingEventArgsConverter" />
|
<converters:WebNavigatingEventArgsConverter x:Key="WebNavigatingEventArgsConverter" />
|
||||||
|
<converters:WebNavigatedEventArgsConverter x:Key="WebNavigatedEventArgsConverter" />
|
||||||
|
|
||||||
<!-- STYLES -->
|
<!-- STYLES -->
|
||||||
<Style x:Key="ValidationErrorLabelStyle"
|
<Style x:Key="ValidationErrorLabelStyle"
|
||||||
TargetType="{x:Type Label}">
|
TargetType="{x:Type Label}">
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
namespace eShopOnContainers.Core.Converters
|
||||||
|
{
|
||||||
|
public class WebNavigatedEventArgsConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
var eventArgs = value as WebNavigatedEventArgs;
|
||||||
|
if (eventArgs == null)
|
||||||
|
throw new ArgumentException("Expected WebNavigatedEventArgs as value", "value");
|
||||||
|
|
||||||
|
return eventArgs.Url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -60,7 +60,7 @@
|
|||||||
IdentityEndpoint = string.Format("{0}:5105/connect/authorize", baseEndpoint);
|
IdentityEndpoint = string.Format("{0}:5105/connect/authorize", baseEndpoint);
|
||||||
UserInfoEndpoint = string.Format("{0}:5105/connect/userinfo", baseEndpoint);
|
UserInfoEndpoint = string.Format("{0}:5105/connect/userinfo", baseEndpoint);
|
||||||
LogoutEndpoint = string.Format("{0}:5105/connect/endsession", baseEndpoint);
|
LogoutEndpoint = string.Format("{0}:5105/connect/endsession", baseEndpoint);
|
||||||
IdentityCallback = "http://eshopxamarin/callback.html";
|
IdentityCallback = string.Format("{0}:5105/xamarincallback", baseEndpoint);
|
||||||
LogoutCallback = string.Format("{0}:5105/Account/Redirecting", baseEndpoint);
|
LogoutCallback = string.Format("{0}:5105/Account/Redirecting", baseEndpoint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,16 @@ namespace eShopOnContainers.Core.Models.Basket
|
|||||||
|
|
||||||
public decimal UnitPrice { get; set; }
|
public decimal UnitPrice { get; set; }
|
||||||
|
|
||||||
|
public decimal OldUnitPrice { get; set; }
|
||||||
|
|
||||||
|
public bool HasNewPrice
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return OldUnitPrice != 0.0m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public int Quantity
|
public int Quantity
|
||||||
{
|
{
|
||||||
get { return _quantity; }
|
get { return _quantity; }
|
||||||
|
@ -34,6 +34,9 @@ namespace eShopOnContainers.Core.Models.Orders
|
|||||||
[JsonProperty("country")]
|
[JsonProperty("country")]
|
||||||
public string ShippingCountry { get; set; }
|
public string ShippingCountry { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("zipCode")]
|
||||||
|
public string ShippingZipCode { get; set; }
|
||||||
|
|
||||||
public int CardTypeId { get; set; }
|
public int CardTypeId { get; set; }
|
||||||
|
|
||||||
public string CardNumber { get; set; }
|
public string CardNumber { get; set; }
|
||||||
|
@ -76,7 +76,9 @@ namespace eShopOnContainers.Core.ViewModels
|
|||||||
|
|
||||||
if (basket != null && basket.Items != null && basket.Items.Any())
|
if (basket != null && basket.Items != null && basket.Items.Any())
|
||||||
{
|
{
|
||||||
|
BadgeCount = 0;
|
||||||
BasketItems.Clear();
|
BasketItems.Clear();
|
||||||
|
|
||||||
foreach (var basketItem in basket.Items)
|
foreach (var basketItem in basket.Items)
|
||||||
{
|
{
|
||||||
BadgeCount += basketItem.Quantity;
|
BadgeCount += basketItem.Quantity;
|
||||||
|
@ -90,7 +90,7 @@ namespace eShopOnContainers.Core.ViewModels
|
|||||||
ZipCode = userInfo?.ZipCode,
|
ZipCode = userInfo?.ZipCode,
|
||||||
State = userInfo?.State,
|
State = userInfo?.State,
|
||||||
Country = userInfo?.Country,
|
Country = userInfo?.Country,
|
||||||
City = string.Empty
|
City = userInfo?.Address
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create Payment Info
|
// Create Payment Info
|
||||||
@ -117,7 +117,8 @@ namespace eShopOnContainers.Core.ViewModels
|
|||||||
ShippingState = _shippingAddress.State,
|
ShippingState = _shippingAddress.State,
|
||||||
ShippingCountry = _shippingAddress.Country,
|
ShippingCountry = _shippingAddress.Country,
|
||||||
ShippingStreet = _shippingAddress.Street,
|
ShippingStreet = _shippingAddress.Street,
|
||||||
ShippingCity = _shippingAddress.City,
|
ShippingCity = _shippingAddress.City,
|
||||||
|
ShippingZipCode = _shippingAddress.ZipCode,
|
||||||
Total = CalculateTotal(CreateOrderItems(orderItems))
|
Total = CalculateTotal(CreateOrderItems(orderItems))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -221,14 +221,16 @@ namespace eShopOnContainers.Core.ViewModels
|
|||||||
|
|
||||||
private async Task NavigateAsync(string url)
|
private async Task NavigateAsync(string url)
|
||||||
{
|
{
|
||||||
if (url.Equals(GlobalSetting.Instance.LogoutCallback))
|
var unescapedUrl = System.Net.WebUtility.UrlDecode(url);
|
||||||
|
|
||||||
|
if (unescapedUrl.Equals(GlobalSetting.Instance.LogoutCallback))
|
||||||
{
|
{
|
||||||
Settings.AuthAccessToken = string.Empty;
|
Settings.AuthAccessToken = string.Empty;
|
||||||
Settings.AuthIdToken = string.Empty;
|
Settings.AuthIdToken = string.Empty;
|
||||||
IsLogin = false;
|
IsLogin = false;
|
||||||
LoginUrl = _identityService.CreateAuthorizeRequest();
|
LoginUrl = _identityService.CreateAuthorizeRequest();
|
||||||
}
|
}
|
||||||
else if (url.Contains(GlobalSetting.Instance.IdentityCallback))
|
else if (unescapedUrl.Contains(GlobalSetting.Instance.IdentityCallback))
|
||||||
{
|
{
|
||||||
var authResponse = new AuthorizeResponse(url);
|
var authResponse = new AuthorizeResponse(url);
|
||||||
|
|
||||||
|
@ -345,10 +345,26 @@
|
|||||||
AbsoluteLayout.LayoutBounds="0, 0, 1, 1"
|
AbsoluteLayout.LayoutBounds="0, 0, 1, 1"
|
||||||
AbsoluteLayout.LayoutFlags="All">
|
AbsoluteLayout.LayoutFlags="All">
|
||||||
<WebView.Behaviors>
|
<WebView.Behaviors>
|
||||||
<behaviors:EventToCommandBehavior
|
<OnPlatform x:TypeArguments="Behavior">
|
||||||
EventName="Navigating"
|
<OnPlatform.Android>
|
||||||
EventArgsConverter="{StaticResource WebNavigatingEventArgsConverter}"
|
<behaviors:EventToCommandBehavior
|
||||||
Command="{Binding NavigateCommand}" />
|
EventName="Navigating"
|
||||||
|
EventArgsConverter="{StaticResource WebNavigatingEventArgsConverter}"
|
||||||
|
Command="{Binding NavigateCommand}" />
|
||||||
|
</OnPlatform.Android>
|
||||||
|
<OnPlatform.iOS>
|
||||||
|
<behaviors:EventToCommandBehavior
|
||||||
|
EventName="Navigating"
|
||||||
|
EventArgsConverter="{StaticResource WebNavigatingEventArgsConverter}"
|
||||||
|
Command="{Binding NavigateCommand}" />
|
||||||
|
</OnPlatform.iOS>
|
||||||
|
<OnPlatform.WinPhone>
|
||||||
|
<behaviors:EventToCommandBehavior
|
||||||
|
EventName="Navigated"
|
||||||
|
EventArgsConverter="{StaticResource WebNavigatedEventArgsConverter}"
|
||||||
|
Command="{Binding NavigateCommand}" />
|
||||||
|
</OnPlatform.WinPhone>
|
||||||
|
</OnPlatform>
|
||||||
</WebView.Behaviors>
|
</WebView.Behaviors>
|
||||||
</WebView>
|
</WebView>
|
||||||
</AbsoluteLayout>
|
</AbsoluteLayout>
|
||||||
|
@ -35,5 +35,16 @@ namespace eShopOnContainers.Core.Views
|
|||||||
await ((BasketViewModel)BasketView.BindingContext).InitializeAsync(null);
|
await ((BasketViewModel)BasketView.BindingContext).InitializeAsync(null);
|
||||||
await ((ProfileViewModel)ProfileView.BindingContext).InitializeAsync(null);
|
await ((ProfileViewModel)ProfileView.BindingContext).InitializeAsync(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override async void OnCurrentPageChanged()
|
||||||
|
{
|
||||||
|
base.OnCurrentPageChanged();
|
||||||
|
|
||||||
|
if (CurrentPage is BasketView)
|
||||||
|
{
|
||||||
|
// Force basket view refresh every time we access it
|
||||||
|
await (BasketView.BindingContext as ViewModelBase).InitializeAsync(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -83,6 +83,7 @@
|
|||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="1" />
|
<RowDefinition Height="1" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<!-- IMAGE -->
|
<!-- IMAGE -->
|
||||||
@ -131,10 +132,24 @@
|
|||||||
Text="{Binding Total, StringFormat='${0:N}'}"
|
Text="{Binding Total, StringFormat='${0:N}'}"
|
||||||
Style="{StaticResource OrderTotalStyle}"/>
|
Style="{StaticResource OrderTotalStyle}"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid
|
<Grid
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Grid.ColumnSpan="2"
|
Grid.ColumnSpan="2"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
|
IsVisible="{Binding HasNewPrice}"
|
||||||
|
BackgroundColor="#F0AD4E">
|
||||||
|
<Label
|
||||||
|
HorizontalOptions="Fill"
|
||||||
|
VerticalOptions="Fill"
|
||||||
|
HorizontalTextAlignment="Center"
|
||||||
|
VerticalTextAlignment="Center"
|
||||||
|
HeightRequest="60"
|
||||||
|
Text="{Binding OldUnitPrice, StringFormat='Note that the price of this article changed in our Catalog. The old price when you originally added it to the basket was $ {0:N2}'}" />
|
||||||
|
</Grid>
|
||||||
|
<Grid
|
||||||
|
Grid.Column="0"
|
||||||
|
Grid.ColumnSpan="2"
|
||||||
|
Grid.Row="2"
|
||||||
BackgroundColor="Gray"/>
|
BackgroundColor="Gray"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</ContentView.Content>
|
</ContentView.Content>
|
||||||
|
@ -58,6 +58,7 @@
|
|||||||
<Compile Include="Converters\ItemsToHeightConverter.cs" />
|
<Compile Include="Converters\ItemsToHeightConverter.cs" />
|
||||||
<Compile Include="Converters\ItemTappedEventArgsConverter.cs" />
|
<Compile Include="Converters\ItemTappedEventArgsConverter.cs" />
|
||||||
<Compile Include="Converters\ToUpperConverter.cs" />
|
<Compile Include="Converters\ToUpperConverter.cs" />
|
||||||
|
<Compile Include="Converters\WebNavigatedEventArgsConverter.cs" />
|
||||||
<Compile Include="Exceptions\ServiceAuthenticationException.cs" />
|
<Compile Include="Exceptions\ServiceAuthenticationException.cs" />
|
||||||
<Compile Include="Extensions\ObservableExtension.cs" />
|
<Compile Include="Extensions\ObservableExtension.cs" />
|
||||||
<Compile Include="GlobalSettings.cs" />
|
<Compile Include="GlobalSettings.cs" />
|
||||||
|
@ -24,5 +24,6 @@
|
|||||||
</Applications>
|
</Applications>
|
||||||
<Capabilities>
|
<Capabilities>
|
||||||
<Capability Name="internetClient" />
|
<Capability Name="internetClient" />
|
||||||
|
<Capability Name="privateNetworkClientServer" />
|
||||||
</Capabilities>
|
</Capabilities>
|
||||||
</Package>
|
</Package>
|
@ -0,0 +1,27 @@
|
|||||||
|
using Basket.API.IntegrationEvents.Events;
|
||||||
|
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
|
||||||
|
using Microsoft.eShopOnContainers.Services.Basket.API.Model;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Basket.API.IntegrationEvents.EventHandling
|
||||||
|
{
|
||||||
|
public class OrderStartedIntegrationEventHandler : IIntegrationEventHandler<OrderStartedIntegrationEvent>
|
||||||
|
{
|
||||||
|
private readonly IBasketRepository _repository;
|
||||||
|
public OrderStartedIntegrationEventHandler(IBasketRepository repository)
|
||||||
|
{
|
||||||
|
_repository = repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Handle(OrderStartedIntegrationEvent @event)
|
||||||
|
{
|
||||||
|
await _repository.DeleteBasketAsync(@event.UserId.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
|||||||
|
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Basket.API.IntegrationEvents.Events
|
||||||
|
{
|
||||||
|
// Integration Events notes:
|
||||||
|
// An Event is “something that has happened in the past”, therefore its name has to be
|
||||||
|
// An Integration Event is an event that can cause side effects to other microsrvices, Bounded-Contexts or external systems.
|
||||||
|
public class OrderStartedIntegrationEvent : IntegrationEvent
|
||||||
|
{
|
||||||
|
public string UserId { get; }
|
||||||
|
|
||||||
|
public OrderStartedIntegrationEvent(string userId) =>
|
||||||
|
UserId = userId;
|
||||||
|
}
|
||||||
|
}
|
@ -17,6 +17,8 @@ using System;
|
|||||||
using Microsoft.Extensions.HealthChecks;
|
using Microsoft.Extensions.HealthChecks;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Basket.API.Infrastructure.Filters;
|
using Basket.API.Infrastructure.Filters;
|
||||||
|
using Basket.API.IntegrationEvents.Events;
|
||||||
|
using Basket.API.IntegrationEvents.EventHandling;
|
||||||
|
|
||||||
namespace Microsoft.eShopOnContainers.Services.Basket.API
|
namespace Microsoft.eShopOnContainers.Services.Basket.API
|
||||||
{
|
{
|
||||||
@ -40,7 +42,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
|
|||||||
|
|
||||||
services.AddHealthChecks(checks =>
|
services.AddHealthChecks(checks =>
|
||||||
{
|
{
|
||||||
checks.AddValueTaskCheck("Always OK", () => new ValueTask<IHealthCheckResult>(HealthCheckResult.Healthy("Ok")));
|
checks.AddValueTaskCheck("HTTP Endpoint", () => new ValueTask<IHealthCheckResult>(HealthCheckResult.Healthy("Ok")));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add framework services.
|
// Add framework services.
|
||||||
@ -91,6 +93,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
|
|||||||
|
|
||||||
services.AddTransient<IBasketRepository, RedisBasketRepository>();
|
services.AddTransient<IBasketRepository, RedisBasketRepository>();
|
||||||
services.AddTransient<IIntegrationEventHandler<ProductPriceChangedIntegrationEvent>, ProductPriceChangedIntegrationEventHandler>();
|
services.AddTransient<IIntegrationEventHandler<ProductPriceChangedIntegrationEvent>, ProductPriceChangedIntegrationEventHandler>();
|
||||||
|
services.AddTransient<IIntegrationEventHandler<OrderStartedIntegrationEvent>, OrderStartedIntegrationEventHandler>();
|
||||||
|
|
||||||
var serviceProvider = services.BuildServiceProvider();
|
var serviceProvider = services.BuildServiceProvider();
|
||||||
var configuration = serviceProvider.GetRequiredService<IOptionsSnapshot<BasketSettings>>().Value;
|
var configuration = serviceProvider.GetRequiredService<IOptionsSnapshot<BasketSettings>>().Value;
|
||||||
@ -117,8 +120,10 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
|
|||||||
.UseSwaggerUi();
|
.UseSwaggerUi();
|
||||||
|
|
||||||
var catalogPriceHandler = app.ApplicationServices.GetService<IIntegrationEventHandler<ProductPriceChangedIntegrationEvent>>();
|
var catalogPriceHandler = app.ApplicationServices.GetService<IIntegrationEventHandler<ProductPriceChangedIntegrationEvent>>();
|
||||||
|
var orderStartedHandler = app.ApplicationServices.GetService<IIntegrationEventHandler<OrderStartedIntegrationEvent>>();
|
||||||
var eventBus = app.ApplicationServices.GetRequiredService<IEventBus>();
|
var eventBus = app.ApplicationServices.GetRequiredService<IEventBus>();
|
||||||
eventBus.Subscribe<ProductPriceChangedIntegrationEvent>(catalogPriceHandler);
|
eventBus.Subscribe<ProductPriceChangedIntegrationEvent>(catalogPriceHandler);
|
||||||
|
eventBus.Subscribe<OrderStartedIntegrationEvent>(orderStartedHandler);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,8 @@ using System.Data.Common;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
|
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
|
||||||
|
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Utilities;
|
||||||
|
using Catalog.API.IntegrationEvents;
|
||||||
|
|
||||||
namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
|
namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
|
||||||
{
|
{
|
||||||
@ -23,15 +25,13 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
|
|||||||
{
|
{
|
||||||
private readonly CatalogContext _catalogContext;
|
private readonly CatalogContext _catalogContext;
|
||||||
private readonly IOptionsSnapshot<Settings> _settings;
|
private readonly IOptionsSnapshot<Settings> _settings;
|
||||||
private readonly IEventBus _eventBus;
|
private readonly ICatalogIntegrationEventService _catalogIntegrationEventService;
|
||||||
private readonly Func<DbConnection, IIntegrationEventLogService> _integrationEventLogServiceFactory;
|
|
||||||
|
|
||||||
public CatalogController(CatalogContext Context, IOptionsSnapshot<Settings> settings, IEventBus eventBus, Func<DbConnection, IIntegrationEventLogService> integrationEventLogServiceFactory)
|
public CatalogController(CatalogContext Context, IOptionsSnapshot<Settings> settings, ICatalogIntegrationEventService catalogIntegrationEventService)
|
||||||
{
|
{
|
||||||
_catalogContext = Context;
|
_catalogContext = Context;
|
||||||
|
_catalogIntegrationEventService = catalogIntegrationEventService;
|
||||||
_settings = settings;
|
_settings = settings;
|
||||||
_eventBus = eventBus;
|
|
||||||
_integrationEventLogServiceFactory = integrationEventLogServiceFactory;
|
|
||||||
|
|
||||||
((DbContext)Context).ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
|
((DbContext)Context).ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
|
||||||
}
|
}
|
||||||
@ -145,51 +145,28 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
|
|||||||
{
|
{
|
||||||
var catalogItem = await _catalogContext.CatalogItems.SingleOrDefaultAsync(i => i.Id == productToUpdate.Id);
|
var catalogItem = await _catalogContext.CatalogItems.SingleOrDefaultAsync(i => i.Id == productToUpdate.Id);
|
||||||
if (catalogItem == null) return NotFound();
|
if (catalogItem == null) return NotFound();
|
||||||
|
var raiseProductPriceChangedEvent = catalogItem.Price != productToUpdate.Price;
|
||||||
bool raiseProductPriceChangedEvent = false;
|
var oldPrice = catalogItem.Price;
|
||||||
IntegrationEvent priceChangedEvent = null;
|
|
||||||
|
// Update current product
|
||||||
if (catalogItem.Price != productToUpdate.Price) raiseProductPriceChangedEvent = true;
|
|
||||||
|
|
||||||
if (raiseProductPriceChangedEvent) // Create event if price has changed
|
|
||||||
{
|
|
||||||
var oldPrice = catalogItem.Price;
|
|
||||||
priceChangedEvent = new ProductPriceChangedIntegrationEvent(catalogItem.Id, productToUpdate.Price, oldPrice);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Update current product
|
|
||||||
catalogItem = productToUpdate;
|
catalogItem = productToUpdate;
|
||||||
|
_catalogContext.CatalogItems.Update(catalogItem);
|
||||||
|
|
||||||
//Use of an EF Core resiliency strategy when using multiple DbContexts within an explicit BeginTransaction():
|
if (raiseProductPriceChangedEvent) // Save and publish integration event if price has changed
|
||||||
//See: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency
|
|
||||||
var strategy = _catalogContext.Database.CreateExecutionStrategy();
|
|
||||||
var eventLogService = _integrationEventLogServiceFactory(_catalogContext.Database.GetDbConnection());
|
|
||||||
await strategy.ExecuteAsync(async () =>
|
|
||||||
{
|
{
|
||||||
|
//Create Integration Event to be published through the Event Bus
|
||||||
|
var priceChangedEvent = new ProductPriceChangedIntegrationEvent(catalogItem.Id, productToUpdate.Price, oldPrice);
|
||||||
|
|
||||||
// Achieving atomicity between original Catalog database operation and the IntegrationEventLog thanks to a local transaction
|
// Achieving atomicity between original Catalog database operation and the IntegrationEventLog thanks to a local transaction
|
||||||
using (var transaction = _catalogContext.Database.BeginTransaction())
|
await _catalogIntegrationEventService.SaveEventAndCatalogContextChangesAsync(priceChangedEvent);
|
||||||
{
|
|
||||||
_catalogContext.CatalogItems.Update(catalogItem);
|
// Publish through the Event Bus and mark the saved event as published
|
||||||
await _catalogContext.SaveChangesAsync();
|
await _catalogIntegrationEventService.PublishThroughEventBusAsync(priceChangedEvent);
|
||||||
|
|
||||||
//Save to EventLog only if product price changed
|
|
||||||
if (raiseProductPriceChangedEvent)
|
|
||||||
{
|
|
||||||
|
|
||||||
await eventLogService.SaveEventAsync(priceChangedEvent, _catalogContext.Database.CurrentTransaction.GetDbTransaction());
|
|
||||||
}
|
|
||||||
|
|
||||||
transaction.Commit();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
//Publish to Event Bus only if product price changed
|
|
||||||
if (raiseProductPriceChangedEvent)
|
|
||||||
{
|
|
||||||
_eventBus.Publish(priceChangedEvent);
|
|
||||||
await eventLogService.MarkEventAsPublishedAsync(priceChangedEvent);
|
|
||||||
}
|
}
|
||||||
|
else // Save updated product
|
||||||
|
{
|
||||||
|
await _catalogContext.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
@ -13,14 +13,12 @@
|
|||||||
public DbSet<CatalogItem> CatalogItems { get; set; }
|
public DbSet<CatalogItem> CatalogItems { get; set; }
|
||||||
public DbSet<CatalogBrand> CatalogBrands { get; set; }
|
public DbSet<CatalogBrand> CatalogBrands { get; set; }
|
||||||
public DbSet<CatalogType> CatalogTypes { get; set; }
|
public DbSet<CatalogType> CatalogTypes { get; set; }
|
||||||
//public DbSet<IntegrationEventLogEntry> IntegrationEventLog { get; set; }
|
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder builder)
|
protected override void OnModelCreating(ModelBuilder builder)
|
||||||
{
|
{
|
||||||
builder.Entity<CatalogBrand>(ConfigureCatalogBrand);
|
builder.Entity<CatalogBrand>(ConfigureCatalogBrand);
|
||||||
builder.Entity<CatalogType>(ConfigureCatalogType);
|
builder.Entity<CatalogType>(ConfigureCatalogType);
|
||||||
builder.Entity<CatalogItem>(ConfigureCatalogItem);
|
builder.Entity<CatalogItem>(ConfigureCatalogItem);
|
||||||
//builder.Entity<IntegrationEventLogEntry>(ConfigureIntegrationEventLogEntry);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigureCatalogItem(EntityTypeBuilder<CatalogItem> builder)
|
void ConfigureCatalogItem(EntityTypeBuilder<CatalogItem> builder)
|
||||||
@ -79,31 +77,5 @@
|
|||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(100);
|
.HasMaxLength(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
//void ConfigureIntegrationEventLogEntry(EntityTypeBuilder<IntegrationEventLogEntry> builder)
|
|
||||||
//{
|
|
||||||
// builder.ToTable("IntegrationEventLog");
|
|
||||||
|
|
||||||
// builder.HasKey(e => e.EventId);
|
|
||||||
|
|
||||||
// builder.Property(e => e.EventId)
|
|
||||||
// .IsRequired();
|
|
||||||
|
|
||||||
// builder.Property(e => e.Content)
|
|
||||||
// .IsRequired();
|
|
||||||
|
|
||||||
// builder.Property(e => e.CreationTime)
|
|
||||||
// .IsRequired();
|
|
||||||
|
|
||||||
// builder.Property(e => e.State)
|
|
||||||
// .IsRequired();
|
|
||||||
|
|
||||||
// builder.Property(e => e.TimesSent)
|
|
||||||
// .IsRequired();
|
|
||||||
|
|
||||||
// builder.Property(e => e.EventTypeName)
|
|
||||||
// .IsRequired();
|
|
||||||
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,48 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage;
|
||||||
|
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
|
||||||
|
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
|
||||||
|
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services;
|
||||||
|
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Utilities;
|
||||||
|
using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure;
|
||||||
|
using System;
|
||||||
|
using System.Data.Common;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Catalog.API.IntegrationEvents
|
||||||
|
{
|
||||||
|
public class CatalogIntegrationEventService : ICatalogIntegrationEventService
|
||||||
|
{
|
||||||
|
private readonly Func<DbConnection, IIntegrationEventLogService> _integrationEventLogServiceFactory;
|
||||||
|
private readonly IEventBus _eventBus;
|
||||||
|
private readonly CatalogContext _catalogContext;
|
||||||
|
private readonly IIntegrationEventLogService _eventLogService;
|
||||||
|
|
||||||
|
public CatalogIntegrationEventService(IEventBus eventBus, CatalogContext catalogContext,
|
||||||
|
Func<DbConnection, IIntegrationEventLogService> integrationEventLogServiceFactory)
|
||||||
|
{
|
||||||
|
_catalogContext = catalogContext ?? throw new ArgumentNullException(nameof(catalogContext));
|
||||||
|
_integrationEventLogServiceFactory = integrationEventLogServiceFactory ?? throw new ArgumentNullException(nameof(integrationEventLogServiceFactory));
|
||||||
|
_eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus));
|
||||||
|
_eventLogService = _integrationEventLogServiceFactory(_catalogContext.Database.GetDbConnection());
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task PublishThroughEventBusAsync(IntegrationEvent evt)
|
||||||
|
{
|
||||||
|
_eventBus.Publish(evt);
|
||||||
|
await _eventLogService.MarkEventAsPublishedAsync(evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SaveEventAndCatalogContextChangesAsync(IntegrationEvent evt)
|
||||||
|
{
|
||||||
|
//Use of an EF Core resiliency strategy when using multiple DbContexts within an explicit BeginTransaction():
|
||||||
|
//See: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency
|
||||||
|
await ResilientTransaction.New(_catalogContext)
|
||||||
|
.ExecuteAsync(async () => {
|
||||||
|
// Achieving atomicity between original catalog database operation and the IntegrationEventLog thanks to a local transaction
|
||||||
|
await _catalogContext.SaveChangesAsync();
|
||||||
|
await _eventLogService.SaveEventAsync(evt, _catalogContext.Database.CurrentTransaction.GetDbTransaction());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Catalog.API.IntegrationEvents
|
||||||
|
{
|
||||||
|
public interface ICatalogIntegrationEventService
|
||||||
|
{
|
||||||
|
Task SaveEventAndCatalogContextChangesAsync(IntegrationEvent evt);
|
||||||
|
Task PublishThroughEventBusAsync(IntegrationEvent evt);
|
||||||
|
}
|
||||||
|
}
|
@ -20,6 +20,7 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Data.Common;
|
using System.Data.Common;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using global::Catalog.API.IntegrationEvents;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
public class Startup
|
public class Startup
|
||||||
@ -49,7 +50,7 @@
|
|||||||
|
|
||||||
services.AddHealthChecks(checks =>
|
services.AddHealthChecks(checks =>
|
||||||
{
|
{
|
||||||
checks.AddSqlCheck("Catalog_Db", Configuration["ConnectionString"]);
|
checks.AddSqlCheck("CatalogDb", Configuration["ConnectionString"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
services.AddMvc(options =>
|
services.AddMvc(options =>
|
||||||
@ -98,10 +99,10 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
services.AddTransient<Func<DbConnection, IIntegrationEventLogService>>(
|
services.AddTransient<Func<DbConnection, IIntegrationEventLogService>>(
|
||||||
sp => (DbConnection c) => new IntegrationEventLogService(c));
|
sp => (DbConnection c) => new IntegrationEventLogService(c));
|
||||||
|
|
||||||
var serviceProvider = services.BuildServiceProvider();
|
var serviceProvider = services.BuildServiceProvider();
|
||||||
var configuration = serviceProvider.GetRequiredService<IOptionsSnapshot<Settings>>().Value;
|
var configuration = serviceProvider.GetRequiredService<IOptionsSnapshot<Settings>>().Value;
|
||||||
|
services.AddTransient<ICatalogIntegrationEventService, CatalogIntegrationEventService>();
|
||||||
services.AddSingleton<IEventBus>(new EventBusRabbitMQ(configuration.EventBusConnection));
|
services.AddSingleton<IEventBus>(new EventBusRabbitMQ(configuration.EventBusConnection));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,31 +1,30 @@
|
|||||||
using IdentityServer4.Models;
|
using IdentityServer4.Models;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using IdentityServer4;
|
||||||
|
|
||||||
namespace Identity.API.Configuration
|
namespace Identity.API.Configuration
|
||||||
{
|
{
|
||||||
public class Config
|
public class Config
|
||||||
{
|
{
|
||||||
// scopes define the resources in your system
|
// ApiResources define the apis in your system
|
||||||
public static IEnumerable<Scope> GetScopes()
|
public static IEnumerable<ApiResource> GetApis()
|
||||||
{
|
{
|
||||||
return new List<Scope>
|
return new List<ApiResource>
|
||||||
{
|
{
|
||||||
//Authentication OpenId uses this scopes;
|
new ApiResource("orders", "Orders Service"),
|
||||||
StandardScopes.OpenId,
|
new ApiResource("basket", "Basket Service")
|
||||||
StandardScopes.Profile,
|
};
|
||||||
|
}
|
||||||
|
|
||||||
//Each api we want to securice;
|
// Identity resources are data like user ID, name, or email address of a user
|
||||||
new Scope
|
// see: http://docs.identityserver.io/en/release/configuration/resources.html
|
||||||
{
|
public static IEnumerable<IdentityResource> GetResources()
|
||||||
Name = "orders",
|
{
|
||||||
Description = "Orders Service"
|
return new List<IdentityResource>
|
||||||
},
|
{
|
||||||
new Scope
|
new IdentityResources.OpenId(),
|
||||||
{
|
new IdentityResources.Profile()
|
||||||
Name = "basket",
|
|
||||||
Description = "Basket Service"
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,8 +46,8 @@ namespace Identity.API.Configuration
|
|||||||
AllowedCorsOrigins = { $"{clientsUrl["Spa"]}" },
|
AllowedCorsOrigins = { $"{clientsUrl["Spa"]}" },
|
||||||
AllowedScopes =
|
AllowedScopes =
|
||||||
{
|
{
|
||||||
StandardScopes.OpenId.Name,
|
IdentityServerConstants.StandardScopes.OpenId,
|
||||||
StandardScopes.Profile.Name,
|
IdentityServerConstants.StandardScopes.Profile,
|
||||||
"orders",
|
"orders",
|
||||||
"basket"
|
"basket"
|
||||||
}
|
}
|
||||||
@ -59,14 +58,14 @@ namespace Identity.API.Configuration
|
|||||||
ClientName = "eShop Xamarin OpenId Client",
|
ClientName = "eShop Xamarin OpenId Client",
|
||||||
AllowedGrantTypes = GrantTypes.Implicit,
|
AllowedGrantTypes = GrantTypes.Implicit,
|
||||||
AllowAccessTokensViaBrowser = true,
|
AllowAccessTokensViaBrowser = true,
|
||||||
RedirectUris = { "http://eshopxamarin/callback.html" },
|
RedirectUris = { clientsUrl["Xamarin"] },
|
||||||
RequireConsent = false,
|
RequireConsent = false,
|
||||||
PostLogoutRedirectUris = { "http://13.88.8.119:5105/Account/Redirecting", "http://10.6.1.234:5105/Account/Redirecting" },
|
PostLogoutRedirectUris = { "http://13.88.8.119:5105/Account/Redirecting", "http://10.6.1.234:5105/Account/Redirecting" },
|
||||||
AllowedCorsOrigins = { "http://eshopxamarin" },
|
AllowedCorsOrigins = { "http://eshopxamarin" },
|
||||||
AllowedScopes =
|
AllowedScopes =
|
||||||
{
|
{
|
||||||
StandardScopes.OpenId.Name,
|
IdentityServerConstants.StandardScopes.OpenId,
|
||||||
StandardScopes.Profile.Name,
|
IdentityServerConstants.StandardScopes.Profile,
|
||||||
"orders",
|
"orders",
|
||||||
"basket"
|
"basket"
|
||||||
}
|
}
|
||||||
@ -82,6 +81,7 @@ namespace Identity.API.Configuration
|
|||||||
ClientUri = $"{clientsUrl["Mvc"]}", // public uri of the client
|
ClientUri = $"{clientsUrl["Mvc"]}", // public uri of the client
|
||||||
AllowedGrantTypes = GrantTypes.Hybrid,
|
AllowedGrantTypes = GrantTypes.Hybrid,
|
||||||
RequireConsent = false,
|
RequireConsent = false,
|
||||||
|
AllowOfflineAccess = true,
|
||||||
RedirectUris = new List<string>
|
RedirectUris = new List<string>
|
||||||
{
|
{
|
||||||
$"{clientsUrl["Mvc"]}/signin-oidc",
|
$"{clientsUrl["Mvc"]}/signin-oidc",
|
||||||
@ -96,9 +96,9 @@ namespace Identity.API.Configuration
|
|||||||
},
|
},
|
||||||
AllowedScopes = new List<string>
|
AllowedScopes = new List<string>
|
||||||
{
|
{
|
||||||
StandardScopes.OpenId.Name,
|
IdentityServerConstants.StandardScopes.OpenId,
|
||||||
StandardScopes.Profile.Name,
|
IdentityServerConstants.StandardScopes.Profile,
|
||||||
StandardScopes.OfflineAccess.Name,
|
IdentityServerConstants.StandardScopes.OfflineAccess,
|
||||||
"orders",
|
"orders",
|
||||||
"basket",
|
"basket",
|
||||||
},
|
},
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
using IdentityModel;
|
using IdentityModel;
|
||||||
using IdentityServer4.Quickstart.UI.Models;
|
using IdentityServer4.Quickstart.UI.Models;
|
||||||
using IdentityServer4.Services;
|
using IdentityServer4.Services;
|
||||||
using IdentityServer4.Services.InMemory;
|
|
||||||
using Microsoft.AspNetCore.Http.Authentication;
|
using Microsoft.AspNetCore.Http.Authentication;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using System;
|
using System;
|
||||||
|
@ -22,7 +22,7 @@ namespace IdentityServer4.Quickstart.UI.Controllers
|
|||||||
{
|
{
|
||||||
private readonly ILogger<ConsentController> _logger;
|
private readonly ILogger<ConsentController> _logger;
|
||||||
private readonly IClientStore _clientStore;
|
private readonly IClientStore _clientStore;
|
||||||
private readonly IScopeStore _scopeStore;
|
private readonly IResourceStore _resourceStore;
|
||||||
private readonly IIdentityServerInteractionService _interaction;
|
private readonly IIdentityServerInteractionService _interaction;
|
||||||
|
|
||||||
|
|
||||||
@ -30,12 +30,12 @@ namespace IdentityServer4.Quickstart.UI.Controllers
|
|||||||
ILogger<ConsentController> logger,
|
ILogger<ConsentController> logger,
|
||||||
IIdentityServerInteractionService interaction,
|
IIdentityServerInteractionService interaction,
|
||||||
IClientStore clientStore,
|
IClientStore clientStore,
|
||||||
IScopeStore scopeStore)
|
IResourceStore resourceStore)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_interaction = interaction;
|
_interaction = interaction;
|
||||||
_clientStore = clientStore;
|
_clientStore = clientStore;
|
||||||
_scopeStore = scopeStore;
|
_resourceStore = resourceStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -120,10 +120,10 @@ namespace IdentityServer4.Quickstart.UI.Controllers
|
|||||||
var client = await _clientStore.FindEnabledClientByIdAsync(request.ClientId);
|
var client = await _clientStore.FindEnabledClientByIdAsync(request.ClientId);
|
||||||
if (client != null)
|
if (client != null)
|
||||||
{
|
{
|
||||||
var scopes = await _scopeStore.FindEnabledScopesAsync(request.ScopesRequested);
|
var resources = await _resourceStore.FindEnabledResourcesByScopeAsync(request.ScopesRequested);
|
||||||
if (scopes != null && scopes.Any())
|
if (resources != null && (resources.IdentityResources.Any() || resources.ApiResources.Any()))
|
||||||
{
|
{
|
||||||
return new ConsentViewModel(model, returnUrl, request, client, scopes);
|
return new ConsentViewModel(model, returnUrl, request, client, resources);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -41,8 +41,8 @@
|
|||||||
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="1.1.0-msbuild3-final">
|
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="1.1.0-msbuild3-final">
|
||||||
<PrivateAssets>All</PrivateAssets>
|
<PrivateAssets>All</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="IdentityServer4.AspNetIdentity" Version="1.0.0-rc3" />
|
<PackageReference Include="IdentityServer4.AspNetIdentity" Version="1.0.0" />
|
||||||
<PackageReference Include="IdentityServer4.EntityFramework" Version="1.0.0-rc3" />
|
<PackageReference Include="IdentityServer4.EntityFramework" Version="1.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PrepublishScript" BeforeTargets="PrepareForPublish">
|
<Target Name="PrepublishScript" BeforeTargets="PrepareForPublish">
|
||||||
|
@ -10,7 +10,7 @@ namespace Identity.API.Models.AccountViewModels
|
|||||||
{
|
{
|
||||||
public class ConsentViewModel : ConsentInputModel
|
public class ConsentViewModel : ConsentInputModel
|
||||||
{
|
{
|
||||||
public ConsentViewModel(ConsentInputModel model, string returnUrl, AuthorizationRequest request, Client client, IEnumerable<Scope> scopes)
|
public ConsentViewModel(ConsentInputModel model, string returnUrl, AuthorizationRequest request, Client client, Resources resources)
|
||||||
{
|
{
|
||||||
RememberConsent = model?.RememberConsent ?? true;
|
RememberConsent = model?.RememberConsent ?? true;
|
||||||
ScopesConsented = model?.ScopesConsented ?? Enumerable.Empty<string>();
|
ScopesConsented = model?.ScopesConsented ?? Enumerable.Empty<string>();
|
||||||
@ -22,8 +22,8 @@ namespace Identity.API.Models.AccountViewModels
|
|||||||
ClientLogoUrl = client.LogoUri;
|
ClientLogoUrl = client.LogoUri;
|
||||||
AllowRememberConsent = client.AllowRememberConsent;
|
AllowRememberConsent = client.AllowRememberConsent;
|
||||||
|
|
||||||
IdentityScopes = scopes.Where(x => x.Type == ScopeType.Identity).Select(x => new ScopeViewModel(x, ScopesConsented.Contains(x.Name) || model == null)).ToArray();
|
IdentityScopes = resources.IdentityResources.Select(x => new ScopeViewModel(x, ScopesConsented.Contains(x.Name) || model == null)).ToArray();
|
||||||
ResourceScopes = scopes.Where(x => x.Type == ScopeType.Resource).Select(x => new ScopeViewModel(x, ScopesConsented.Contains(x.Name) || model == null)).ToArray();
|
ResourceScopes = resources.ApiResources.SelectMany(x => x.Scopes).Select(x => new ScopeViewModel(x, ScopesConsented.Contains(x.Name) || model == null)).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ClientName { get; set; }
|
public string ClientName { get; set; }
|
||||||
@ -47,6 +47,16 @@ namespace Identity.API.Models.AccountViewModels
|
|||||||
Checked = check || scope.Required;
|
Checked = check || scope.Required;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ScopeViewModel(IdentityResource identity, bool check)
|
||||||
|
{
|
||||||
|
Name = identity.Name;
|
||||||
|
DisplayName = identity.DisplayName;
|
||||||
|
Description = identity.Description;
|
||||||
|
Emphasize = identity.Emphasize;
|
||||||
|
Required = identity.Required;
|
||||||
|
Checked = check || identity.Required;
|
||||||
|
}
|
||||||
|
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public string DisplayName { get; set; }
|
public string DisplayName { get; set; }
|
||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
|
@ -73,11 +73,13 @@ namespace eShopOnContainers.Identity
|
|||||||
Dictionary<string, string> clientUrls = new Dictionary<string, string>();
|
Dictionary<string, string> clientUrls = new Dictionary<string, string>();
|
||||||
clientUrls.Add("Mvc", Configuration.GetValue<string>("MvcClient"));
|
clientUrls.Add("Mvc", Configuration.GetValue<string>("MvcClient"));
|
||||||
clientUrls.Add("Spa", Configuration.GetValue<string>("SpaClient"));
|
clientUrls.Add("Spa", Configuration.GetValue<string>("SpaClient"));
|
||||||
|
clientUrls.Add("Xamarin", Configuration.GetValue<string>("XamarinCallback"));
|
||||||
|
|
||||||
// Adds IdentityServer
|
// Adds IdentityServer
|
||||||
services.AddIdentityServer(x => x.IssuerUri = "null")
|
services.AddIdentityServer(x => x.IssuerUri = "null")
|
||||||
.AddSigningCredential(Certificate.Get())
|
.AddSigningCredential(Certificate.Get())
|
||||||
.AddInMemoryScopes(Config.GetScopes())
|
.AddInMemoryApiResources(Config.GetApis())
|
||||||
|
.AddInMemoryIdentityResources(Config.GetResources())
|
||||||
.AddInMemoryClients(Config.GetClients(clientUrls))
|
.AddInMemoryClients(Config.GetClients(clientUrls))
|
||||||
.AddAspNetIdentity<ApplicationUser>()
|
.AddAspNetIdentity<ApplicationUser>()
|
||||||
.Services.AddTransient<IProfileService, ProfileService>();
|
.Services.AddTransient<IProfileService, ProfileService>();
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
},
|
},
|
||||||
"MvcClient": "http://localhost:5100",
|
"MvcClient": "http://localhost:5100",
|
||||||
"SpaClient": "http://localhost:5104",
|
"SpaClient": "http://localhost:5104",
|
||||||
|
"XamarinCallback": "http://localhost:5105/xamarincallback",
|
||||||
"Logging": {
|
"Logging": {
|
||||||
"IncludeScopes": false,
|
"IncludeScopes": false,
|
||||||
"LogLevel": {
|
"LogLevel": {
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
|
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Ordering.API.IntegrationEvents;
|
||||||
|
using Ordering.API.IntegrationEvents.Events;
|
||||||
using Ordering.Domain.Events;
|
using Ordering.Domain.Events;
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@ -11,10 +13,15 @@ namespace Ordering.API.Application.DomainEventHandlers.BuyerAndPaymentMethodVeri
|
|||||||
: IAsyncNotificationHandler<BuyerAndPaymentMethodVerifiedDomainEvent>
|
: IAsyncNotificationHandler<BuyerAndPaymentMethodVerifiedDomainEvent>
|
||||||
{
|
{
|
||||||
private readonly IOrderRepository _orderRepository;
|
private readonly IOrderRepository _orderRepository;
|
||||||
private readonly ILoggerFactory _logger;
|
private readonly IOrderingIntegrationEventService _orderingIntegrationEventService;
|
||||||
public UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler(IOrderRepository orderRepository, ILoggerFactory logger)
|
private readonly ILoggerFactory _logger;
|
||||||
|
|
||||||
|
public UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler(
|
||||||
|
IOrderRepository orderRepository, ILoggerFactory logger,
|
||||||
|
IOrderingIntegrationEventService orderingIntegrationEventService)
|
||||||
{
|
{
|
||||||
_orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository));
|
_orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository));
|
||||||
|
_orderingIntegrationEventService = orderingIntegrationEventService ?? throw new ArgumentNullException(nameof(orderingIntegrationEventService));
|
||||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,12 +33,20 @@ namespace Ordering.API.Application.DomainEventHandlers.BuyerAndPaymentMethodVeri
|
|||||||
var orderToUpdate = await _orderRepository.GetAsync(buyerPaymentMethodVerifiedEvent.OrderId);
|
var orderToUpdate = await _orderRepository.GetAsync(buyerPaymentMethodVerifiedEvent.OrderId);
|
||||||
orderToUpdate.SetBuyerId(buyerPaymentMethodVerifiedEvent.Buyer.Id);
|
orderToUpdate.SetBuyerId(buyerPaymentMethodVerifiedEvent.Buyer.Id);
|
||||||
orderToUpdate.SetPaymentId(buyerPaymentMethodVerifiedEvent.Payment.Id);
|
orderToUpdate.SetPaymentId(buyerPaymentMethodVerifiedEvent.Payment.Id);
|
||||||
|
|
||||||
await _orderRepository.UnitOfWork
|
var orderStartedIntegrationEvent = new OrderStartedIntegrationEvent(buyerPaymentMethodVerifiedEvent.Buyer.IdentityGuid);
|
||||||
.SaveEntitiesAsync();
|
|
||||||
|
// Using a local transaction to achieve atomicity between original Ordering database operation and
|
||||||
|
// the IntegrationEventLog. Only saving event if order has been successfully persisted to db
|
||||||
|
await _orderingIntegrationEventService
|
||||||
|
.SaveEventAndOrderingContextChangesAsync(orderStartedIntegrationEvent);
|
||||||
|
|
||||||
|
// Publish ordering integration event and mark it as published
|
||||||
|
await _orderingIntegrationEventService
|
||||||
|
.PublishThroughEventBusAsync(orderStartedIntegrationEvent);
|
||||||
|
|
||||||
_logger.CreateLogger(nameof(UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler))
|
_logger.CreateLogger(nameof(UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler))
|
||||||
.LogTrace($"Order with Id: {buyerPaymentMethodVerifiedEvent.OrderId} has been successfully updated with a payment method id: { buyerPaymentMethodVerifiedEvent.Payment.Id }");
|
.LogTrace($"Order with Id: {buyerPaymentMethodVerifiedEvent.OrderId} has been successfully updated with a payment method id: { buyerPaymentMethodVerifiedEvent.Payment.Id }");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,43 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF;
|
||||||
|
|
||||||
|
namespace Ordering.API.Infrastructure.IntegrationEventMigrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(IntegrationEventLogContext))]
|
||||||
|
[Migration("20170330131634_IntegrationEventInitial")]
|
||||||
|
partial class IntegrationEventInitial
|
||||||
|
{
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "1.1.1")
|
||||||
|
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.IntegrationEventLogEntry", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("EventId")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<string>("Content")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreationTime");
|
||||||
|
|
||||||
|
b.Property<string>("EventTypeName")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Property<int>("State");
|
||||||
|
|
||||||
|
b.Property<int>("TimesSent");
|
||||||
|
|
||||||
|
b.HasKey("EventId");
|
||||||
|
|
||||||
|
b.ToTable("IntegrationEventLog");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
namespace Ordering.API.Infrastructure.IntegrationEventMigrations
|
||||||
|
{
|
||||||
|
public partial class IntegrationEventInitial : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "IntegrationEventLog",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
EventId = table.Column<Guid>(nullable: false),
|
||||||
|
Content = table.Column<string>(nullable: false),
|
||||||
|
CreationTime = table.Column<DateTime>(nullable: false),
|
||||||
|
EventTypeName = table.Column<string>(nullable: false),
|
||||||
|
State = table.Column<int>(nullable: false),
|
||||||
|
TimesSent = table.Column<int>(nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_IntegrationEventLog", x => x.EventId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "IntegrationEventLog");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF;
|
||||||
|
|
||||||
|
namespace Ordering.API.Infrastructure.IntegrationEventMigrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(IntegrationEventLogContext))]
|
||||||
|
partial class IntegrationEventLogContextModelSnapshot : ModelSnapshot
|
||||||
|
{
|
||||||
|
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "1.1.1")
|
||||||
|
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.IntegrationEventLogEntry", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("EventId")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<string>("Content")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreationTime");
|
||||||
|
|
||||||
|
b.Property<string>("EventTypeName")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Property<int>("State");
|
||||||
|
|
||||||
|
b.Property<int>("TimesSent");
|
||||||
|
|
||||||
|
b.HasKey("EventId");
|
||||||
|
|
||||||
|
b.ToTable("IntegrationEventLog");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,244 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure;
|
||||||
|
|
||||||
|
namespace Ordering.API.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(OrderingContext))]
|
||||||
|
[Migration("20170403082405_NoBuyerPropertyInOrder")]
|
||||||
|
partial class NoBuyerPropertyInOrder
|
||||||
|
{
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "1.1.0-rtm-22752")
|
||||||
|
.HasAnnotation("SqlServer:Sequence:.orderitemseq", "'orderitemseq', '', '1', '10', '', '', 'Int64', 'False'")
|
||||||
|
.HasAnnotation("SqlServer:Sequence:ordering.buyerseq", "'buyerseq', 'ordering', '1', '10', '', '', 'Int64', 'False'")
|
||||||
|
.HasAnnotation("SqlServer:Sequence:ordering.orderseq", "'orderseq', 'ordering', '1', '10', '', '', 'Int64', 'False'")
|
||||||
|
.HasAnnotation("SqlServer:Sequence:ordering.paymentseq", "'paymentseq', 'ordering', '1', '10', '', '', 'Int64', 'False'")
|
||||||
|
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.Buyer", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasAnnotation("SqlServer:HiLoSequenceName", "buyerseq")
|
||||||
|
.HasAnnotation("SqlServer:HiLoSequenceSchema", "ordering")
|
||||||
|
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
|
||||||
|
|
||||||
|
b.Property<string>("IdentityGuid")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200);
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("IdentityGuid")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("buyers","ordering");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.CardType", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.HasDefaultValue(1);
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200);
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("cardtypes","ordering");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.PaymentMethod", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasAnnotation("SqlServer:HiLoSequenceName", "paymentseq")
|
||||||
|
.HasAnnotation("SqlServer:HiLoSequenceSchema", "ordering")
|
||||||
|
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
|
||||||
|
|
||||||
|
b.Property<string>("Alias")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200);
|
||||||
|
|
||||||
|
b.Property<int>("BuyerId");
|
||||||
|
|
||||||
|
b.Property<string>("CardHolderName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200);
|
||||||
|
|
||||||
|
b.Property<string>("CardNumber")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(25);
|
||||||
|
|
||||||
|
b.Property<int>("CardTypeId");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Expiration");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("BuyerId");
|
||||||
|
|
||||||
|
b.HasIndex("CardTypeId");
|
||||||
|
|
||||||
|
b.ToTable("paymentmethods","ordering");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Address", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<string>("City");
|
||||||
|
|
||||||
|
b.Property<string>("Country");
|
||||||
|
|
||||||
|
b.Property<string>("State");
|
||||||
|
|
||||||
|
b.Property<string>("Street");
|
||||||
|
|
||||||
|
b.Property<string>("ZipCode");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("address","ordering");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Order", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasAnnotation("SqlServer:HiLoSequenceName", "orderseq")
|
||||||
|
.HasAnnotation("SqlServer:HiLoSequenceSchema", "ordering")
|
||||||
|
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
|
||||||
|
|
||||||
|
b.Property<int?>("AddressId");
|
||||||
|
|
||||||
|
b.Property<int?>("BuyerId");
|
||||||
|
|
||||||
|
b.Property<DateTime>("OrderDate");
|
||||||
|
|
||||||
|
b.Property<int>("OrderStatusId");
|
||||||
|
|
||||||
|
b.Property<int?>("PaymentMethodId");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AddressId");
|
||||||
|
|
||||||
|
b.HasIndex("BuyerId");
|
||||||
|
|
||||||
|
b.HasIndex("OrderStatusId");
|
||||||
|
|
||||||
|
b.HasIndex("PaymentMethodId");
|
||||||
|
|
||||||
|
b.ToTable("orders","ordering");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.OrderItem", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasAnnotation("SqlServer:HiLoSequenceName", "orderitemseq")
|
||||||
|
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
|
||||||
|
|
||||||
|
b.Property<decimal>("Discount");
|
||||||
|
|
||||||
|
b.Property<int>("OrderId");
|
||||||
|
|
||||||
|
b.Property<string>("PictureUrl");
|
||||||
|
|
||||||
|
b.Property<int>("ProductId");
|
||||||
|
|
||||||
|
b.Property<string>("ProductName")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Property<decimal>("UnitPrice");
|
||||||
|
|
||||||
|
b.Property<int>("Units");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("OrderId");
|
||||||
|
|
||||||
|
b.ToTable("orderItems","ordering");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.OrderStatus", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.HasDefaultValue(1);
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200);
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("orderstatus","ordering");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.ClientRequest", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Property<DateTime>("Time");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("requests","ordering");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.PaymentMethod", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.Buyer")
|
||||||
|
.WithMany("PaymentMethods")
|
||||||
|
.HasForeignKey("BuyerId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.CardType", "CardType")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("CardTypeId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Order", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Address", "Address")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("AddressId");
|
||||||
|
|
||||||
|
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.Buyer")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("BuyerId");
|
||||||
|
|
||||||
|
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.OrderStatus", "OrderStatus")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("OrderStatusId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.PaymentMethod", "PaymentMethod")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("PaymentMethodId");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.OrderItem", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Order")
|
||||||
|
.WithMany("OrderItems")
|
||||||
|
.HasForeignKey("OrderId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
namespace Ordering.API.Migrations
|
||||||
|
{
|
||||||
|
public partial class NoBuyerPropertyInOrder : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,244 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure;
|
||||||
|
|
||||||
|
namespace Ordering.API.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(OrderingContext))]
|
||||||
|
[Migration("20170405110939_NoPaymentMethodPropertyInOrder")]
|
||||||
|
partial class NoPaymentMethodPropertyInOrder
|
||||||
|
{
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "1.1.1")
|
||||||
|
.HasAnnotation("SqlServer:Sequence:.orderitemseq", "'orderitemseq', '', '1', '10', '', '', 'Int64', 'False'")
|
||||||
|
.HasAnnotation("SqlServer:Sequence:ordering.buyerseq", "'buyerseq', 'ordering', '1', '10', '', '', 'Int64', 'False'")
|
||||||
|
.HasAnnotation("SqlServer:Sequence:ordering.orderseq", "'orderseq', 'ordering', '1', '10', '', '', 'Int64', 'False'")
|
||||||
|
.HasAnnotation("SqlServer:Sequence:ordering.paymentseq", "'paymentseq', 'ordering', '1', '10', '', '', 'Int64', 'False'")
|
||||||
|
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.Buyer", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasAnnotation("SqlServer:HiLoSequenceName", "buyerseq")
|
||||||
|
.HasAnnotation("SqlServer:HiLoSequenceSchema", "ordering")
|
||||||
|
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
|
||||||
|
|
||||||
|
b.Property<string>("IdentityGuid")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200);
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("IdentityGuid")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("buyers","ordering");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.CardType", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.HasDefaultValue(1);
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200);
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("cardtypes","ordering");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.PaymentMethod", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasAnnotation("SqlServer:HiLoSequenceName", "paymentseq")
|
||||||
|
.HasAnnotation("SqlServer:HiLoSequenceSchema", "ordering")
|
||||||
|
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
|
||||||
|
|
||||||
|
b.Property<string>("Alias")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200);
|
||||||
|
|
||||||
|
b.Property<int>("BuyerId");
|
||||||
|
|
||||||
|
b.Property<string>("CardHolderName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200);
|
||||||
|
|
||||||
|
b.Property<string>("CardNumber")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(25);
|
||||||
|
|
||||||
|
b.Property<int>("CardTypeId");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Expiration");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("BuyerId");
|
||||||
|
|
||||||
|
b.HasIndex("CardTypeId");
|
||||||
|
|
||||||
|
b.ToTable("paymentmethods","ordering");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Address", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<string>("City");
|
||||||
|
|
||||||
|
b.Property<string>("Country");
|
||||||
|
|
||||||
|
b.Property<string>("State");
|
||||||
|
|
||||||
|
b.Property<string>("Street");
|
||||||
|
|
||||||
|
b.Property<string>("ZipCode");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("address","ordering");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Order", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasAnnotation("SqlServer:HiLoSequenceName", "orderseq")
|
||||||
|
.HasAnnotation("SqlServer:HiLoSequenceSchema", "ordering")
|
||||||
|
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
|
||||||
|
|
||||||
|
b.Property<int?>("AddressId");
|
||||||
|
|
||||||
|
b.Property<int?>("BuyerId");
|
||||||
|
|
||||||
|
b.Property<DateTime>("OrderDate");
|
||||||
|
|
||||||
|
b.Property<int>("OrderStatusId");
|
||||||
|
|
||||||
|
b.Property<int?>("PaymentMethodId");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AddressId");
|
||||||
|
|
||||||
|
b.HasIndex("BuyerId");
|
||||||
|
|
||||||
|
b.HasIndex("OrderStatusId");
|
||||||
|
|
||||||
|
b.HasIndex("PaymentMethodId");
|
||||||
|
|
||||||
|
b.ToTable("orders","ordering");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.OrderItem", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasAnnotation("SqlServer:HiLoSequenceName", "orderitemseq")
|
||||||
|
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
|
||||||
|
|
||||||
|
b.Property<decimal>("Discount");
|
||||||
|
|
||||||
|
b.Property<int>("OrderId");
|
||||||
|
|
||||||
|
b.Property<string>("PictureUrl");
|
||||||
|
|
||||||
|
b.Property<int>("ProductId");
|
||||||
|
|
||||||
|
b.Property<string>("ProductName")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Property<decimal>("UnitPrice");
|
||||||
|
|
||||||
|
b.Property<int>("Units");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("OrderId");
|
||||||
|
|
||||||
|
b.ToTable("orderItems","ordering");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.OrderStatus", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.HasDefaultValue(1);
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200);
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("orderstatus","ordering");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.ClientRequest", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Property<DateTime>("Time");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("requests","ordering");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.PaymentMethod", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.Buyer")
|
||||||
|
.WithMany("PaymentMethods")
|
||||||
|
.HasForeignKey("BuyerId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.CardType", "CardType")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("CardTypeId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Order", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Address", "Address")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("AddressId");
|
||||||
|
|
||||||
|
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.Buyer")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("BuyerId");
|
||||||
|
|
||||||
|
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.OrderStatus", "OrderStatus")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("OrderStatusId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.PaymentMethod")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("PaymentMethodId");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.OrderItem", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Order")
|
||||||
|
.WithMany("OrderItems")
|
||||||
|
.HasForeignKey("OrderId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
namespace Ordering.API.Migrations
|
||||||
|
{
|
||||||
|
public partial class NoPaymentMethodPropertyInOrder : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -13,7 +13,7 @@ namespace Ordering.API.Migrations
|
|||||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
modelBuilder
|
modelBuilder
|
||||||
.HasAnnotation("ProductVersion", "1.1.0-rtm-22752")
|
.HasAnnotation("ProductVersion", "1.1.1")
|
||||||
.HasAnnotation("SqlServer:Sequence:.orderitemseq", "'orderitemseq', '', '1', '10', '', '', 'Int64', 'False'")
|
.HasAnnotation("SqlServer:Sequence:.orderitemseq", "'orderitemseq', '', '1', '10', '', '', 'Int64', 'False'")
|
||||||
.HasAnnotation("SqlServer:Sequence:ordering.buyerseq", "'buyerseq', 'ordering', '1', '10', '', '', 'Int64', 'False'")
|
.HasAnnotation("SqlServer:Sequence:ordering.buyerseq", "'buyerseq', 'ordering', '1', '10', '', '', 'Int64', 'False'")
|
||||||
.HasAnnotation("SqlServer:Sequence:ordering.orderseq", "'orderseq', 'ordering', '1', '10', '', '', 'Int64', 'False'")
|
.HasAnnotation("SqlServer:Sequence:ordering.orderseq", "'orderseq', 'ordering', '1', '10', '', '', 'Int64', 'False'")
|
||||||
@ -217,7 +217,7 @@ namespace Ordering.API.Migrations
|
|||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("AddressId");
|
.HasForeignKey("AddressId");
|
||||||
|
|
||||||
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.Buyer", "Buyer")
|
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.Buyer")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("BuyerId");
|
.HasForeignKey("BuyerId");
|
||||||
|
|
||||||
@ -226,7 +226,7 @@ namespace Ordering.API.Migrations
|
|||||||
.HasForeignKey("OrderStatusId")
|
.HasForeignKey("OrderStatusId")
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.PaymentMethod", "PaymentMethod")
|
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.PaymentMethod")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("PaymentMethodId");
|
.HasForeignKey("PaymentMethodId");
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Ordering.API.IntegrationEvents.Events
|
||||||
|
{
|
||||||
|
// Integration Events notes:
|
||||||
|
// An Event is “something that has happened in the past”, therefore its name has to be
|
||||||
|
// An Integration Event is an event that can cause side effects to other microsrvices, Bounded-Contexts or external systems.
|
||||||
|
public class OrderStartedIntegrationEvent : IntegrationEvent
|
||||||
|
{
|
||||||
|
public string UserId { get; }
|
||||||
|
|
||||||
|
public OrderStartedIntegrationEvent(string userId) =>
|
||||||
|
UserId = userId;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Ordering.API.IntegrationEvents
|
||||||
|
{
|
||||||
|
public interface IOrderingIntegrationEventService
|
||||||
|
{
|
||||||
|
Task SaveEventAndOrderingContextChangesAsync(IntegrationEvent evt);
|
||||||
|
Task PublishThroughEventBusAsync(IntegrationEvent evt);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage;
|
||||||
|
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
|
||||||
|
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
|
||||||
|
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services;
|
||||||
|
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Utilities;
|
||||||
|
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
|
||||||
|
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure;
|
||||||
|
using System;
|
||||||
|
using System.Data.Common;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Ordering.API.IntegrationEvents
|
||||||
|
{
|
||||||
|
public class OrderingIntegrationEventService : IOrderingIntegrationEventService
|
||||||
|
{
|
||||||
|
private readonly Func<DbConnection, IIntegrationEventLogService> _integrationEventLogServiceFactory;
|
||||||
|
private readonly IEventBus _eventBus;
|
||||||
|
private readonly OrderingContext _orderingContext;
|
||||||
|
private readonly IIntegrationEventLogService _eventLogService;
|
||||||
|
|
||||||
|
public OrderingIntegrationEventService (IEventBus eventBus, OrderingContext orderingContext,
|
||||||
|
Func<DbConnection, IIntegrationEventLogService> integrationEventLogServiceFactory)
|
||||||
|
{
|
||||||
|
_orderingContext = orderingContext ?? throw new ArgumentNullException(nameof(orderingContext));
|
||||||
|
_integrationEventLogServiceFactory = integrationEventLogServiceFactory ?? throw new ArgumentNullException(nameof(integrationEventLogServiceFactory));
|
||||||
|
_eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus));
|
||||||
|
_eventLogService = _integrationEventLogServiceFactory(_orderingContext.Database.GetDbConnection());
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task PublishThroughEventBusAsync(IntegrationEvent evt)
|
||||||
|
{
|
||||||
|
_eventBus.Publish(evt);
|
||||||
|
await _eventLogService.MarkEventAsPublishedAsync(evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SaveEventAndOrderingContextChangesAsync(IntegrationEvent evt)
|
||||||
|
{
|
||||||
|
//Use of an EF Core resiliency strategy when using multiple DbContexts within an explicit BeginTransaction():
|
||||||
|
//See: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency
|
||||||
|
await ResilientTransaction.New(_orderingContext)
|
||||||
|
.ExecuteAsync(async () => {
|
||||||
|
// Achieving atomicity between original ordering database operation and the IntegrationEventLog thanks to a local transaction
|
||||||
|
await _orderingContext.SaveChangesAsync();
|
||||||
|
await _eventLogService.SaveEventAsync(evt, _orderingContext.Database.CurrentTransaction.GetDbTransaction());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -23,6 +23,9 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBusRabbitMQ\EventBusRabbitMQ.csproj" />
|
||||||
|
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBus\EventBus.csproj" />
|
||||||
|
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\IntegrationEventLogEF\IntegrationEventLogEF.csproj" />
|
||||||
<ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.AspNetCore.HealthChecks\Microsoft.AspNetCore.HealthChecks.csproj" />
|
<ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.AspNetCore.HealthChecks\Microsoft.AspNetCore.HealthChecks.csproj" />
|
||||||
<ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.Data\Microsoft.Extensions.HealthChecks.Data.csproj" />
|
<ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.Data\Microsoft.Extensions.HealthChecks.Data.csproj" />
|
||||||
<ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks\Microsoft.Extensions.HealthChecks.csproj" />
|
<ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks\Microsoft.Extensions.HealthChecks.csproj" />
|
||||||
@ -38,16 +41,18 @@
|
|||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="1.1.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="1.1.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="1.0.2" />
|
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="1.0.2" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="1.1.1" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="1.1.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="1.1.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="1.1.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="1.1.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="1.1.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="1.1.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="1.1.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="1.1.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="1.1.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="1.1.0" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="1.1.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="1.1.0" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="1.1.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.1.0" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.1.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="1.1.0" />
|
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="1.1.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="1.1.0" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="1.1.1" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="1.1.0" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="1.1.1" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.0" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.Design" Version="1.1.0" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.Design" Version="1.1.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="1.1.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="1.1.0" />
|
||||||
@ -73,4 +78,9 @@
|
|||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Infrastructure\IntegrationEventMigrations\" />
|
||||||
|
<Folder Include="IntegrationEvents\EventHandling\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using Autofac;
|
using Autofac;
|
||||||
using Autofac.Extensions.DependencyInjection;
|
using Autofac.Extensions.DependencyInjection;
|
||||||
using global::Ordering.API.Infrastructure.Middlewares;
|
using global::Ordering.API.Infrastructure.Middlewares;
|
||||||
|
using global::Ordering.API.IntegrationEvents;
|
||||||
using Infrastructure;
|
using Infrastructure;
|
||||||
using Infrastructure.Auth;
|
using Infrastructure.Auth;
|
||||||
using Infrastructure.AutofacModules;
|
using Infrastructure.AutofacModules;
|
||||||
@ -12,12 +13,17 @@
|
|||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
|
||||||
|
using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ;
|
||||||
|
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF;
|
||||||
|
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.HealthChecks;
|
using Microsoft.Extensions.HealthChecks;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Ordering.Infrastructure;
|
using Ordering.Infrastructure;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Data.Common;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
public class Startup
|
public class Startup
|
||||||
@ -53,9 +59,9 @@
|
|||||||
|
|
||||||
services.AddHealthChecks(checks =>
|
services.AddHealthChecks(checks =>
|
||||||
{
|
{
|
||||||
checks.AddSqlCheck("Ordering_Db", Configuration["ConnectionString"]);
|
checks.AddSqlCheck("OrderingDb", Configuration["ConnectionString"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
services.AddEntityFrameworkSqlServer()
|
services.AddEntityFrameworkSqlServer()
|
||||||
.AddDbContext<OrderingContext>(options =>
|
.AddDbContext<OrderingContext>(options =>
|
||||||
{
|
{
|
||||||
@ -95,7 +101,11 @@
|
|||||||
// Add application services.
|
// Add application services.
|
||||||
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
|
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
|
||||||
services.AddTransient<IIdentityService, IdentityService>();
|
services.AddTransient<IIdentityService, IdentityService>();
|
||||||
|
services.AddTransient<Func<DbConnection, IIntegrationEventLogService>>(
|
||||||
|
sp => (DbConnection c) => new IntegrationEventLogService(c));
|
||||||
|
var serviceProvider = services.BuildServiceProvider();
|
||||||
|
services.AddTransient<IOrderingIntegrationEventService, OrderingIntegrationEventService>();
|
||||||
|
services.AddSingleton<IEventBus>(new EventBusRabbitMQ(Configuration["EventBusConnection"]));
|
||||||
services.AddOptions();
|
services.AddOptions();
|
||||||
|
|
||||||
//configure autofac
|
//configure autofac
|
||||||
@ -126,6 +136,12 @@
|
|||||||
.UseSwaggerUi();
|
.UseSwaggerUi();
|
||||||
|
|
||||||
OrderingContextSeed.SeedAsync(app).Wait();
|
OrderingContextSeed.SeedAsync(app).Wait();
|
||||||
|
|
||||||
|
var integrationEventLogContext = new IntegrationEventLogContext(
|
||||||
|
new DbContextOptionsBuilder<IntegrationEventLogContext>()
|
||||||
|
.UseSqlServer(Configuration["ConnectionString"], b => b.MigrationsAssembly("Ordering.API"))
|
||||||
|
.Options);
|
||||||
|
integrationEventLogContext.Database.Migrate();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void ConfigureAuth(IApplicationBuilder app)
|
protected virtual void ConfigureAuth(IApplicationBuilder app)
|
||||||
|
@ -7,7 +7,6 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.B
|
|||||||
public class PaymentMethod
|
public class PaymentMethod
|
||||||
: Entity
|
: Entity
|
||||||
{
|
{
|
||||||
private int _buyerId;
|
|
||||||
private string _alias;
|
private string _alias;
|
||||||
private string _cardNumber;
|
private string _cardNumber;
|
||||||
private string _securityNumber;
|
private string _securityNumber;
|
||||||
|
@ -18,7 +18,6 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O
|
|||||||
|
|
||||||
public Address Address { get; private set; }
|
public Address Address { get; private set; }
|
||||||
|
|
||||||
public Buyer Buyer { get; private set; }
|
|
||||||
private int? _buyerId;
|
private int? _buyerId;
|
||||||
|
|
||||||
public OrderStatus OrderStatus { get; private set; }
|
public OrderStatus OrderStatus { get; private set; }
|
||||||
@ -37,7 +36,6 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O
|
|||||||
// It's much cheaper than .ToList() because it will not have to copy all items in a new collection. (Just one heap alloc for the wrapper instance)
|
// It's much cheaper than .ToList() because it will not have to copy all items in a new collection. (Just one heap alloc for the wrapper instance)
|
||||||
//https://msdn.microsoft.com/en-us/library/e78dcd75(v=vs.110).aspx
|
//https://msdn.microsoft.com/en-us/library/e78dcd75(v=vs.110).aspx
|
||||||
|
|
||||||
public PaymentMethod PaymentMethod { get; private set; }
|
|
||||||
private int? _paymentMethodId;
|
private int? _paymentMethodId;
|
||||||
|
|
||||||
protected Order() { }
|
protected Order() { }
|
||||||
|
@ -12,7 +12,6 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O
|
|||||||
// aligned with DDD Aggregates and Domain Entities (Instead of properties and property collections)
|
// aligned with DDD Aggregates and Domain Entities (Instead of properties and property collections)
|
||||||
private string _productName;
|
private string _productName;
|
||||||
private string _pictureUrl;
|
private string _pictureUrl;
|
||||||
private int _orderId;
|
|
||||||
private decimal _unitPrice;
|
private decimal _unitPrice;
|
||||||
private decimal _discount;
|
private decimal _discount;
|
||||||
private int _units;
|
private int _units;
|
||||||
|
@ -16,8 +16,9 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="1.1.0" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="1.1.1" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="1.1.0" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="1.1.1" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="1.1.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -62,6 +62,10 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
|
|||||||
{
|
{
|
||||||
addressConfiguration.ToTable("address", DEFAULT_SCHEMA);
|
addressConfiguration.ToTable("address", DEFAULT_SCHEMA);
|
||||||
|
|
||||||
|
// DDD Pattern comment: Implementing the Address Id as "Shadow property"
|
||||||
|
// becuase the Address is a Value-Object (VO) and an Id (Identity) is not desired for a VO
|
||||||
|
// EF Core just needs the Id so it is capable to store it in a database table
|
||||||
|
// See: https://docs.microsoft.com/en-us/ef/core/modeling/shadow-properties
|
||||||
addressConfiguration.Property<int>("Id")
|
addressConfiguration.Property<int>("Id")
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
@ -154,13 +158,13 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
|
|||||||
//Set as Field (New since EF 1.1) to access the OrderItem collection property through its field
|
//Set as Field (New since EF 1.1) to access the OrderItem collection property through its field
|
||||||
navigation.SetPropertyAccessMode(PropertyAccessMode.Field);
|
navigation.SetPropertyAccessMode(PropertyAccessMode.Field);
|
||||||
|
|
||||||
orderConfiguration.HasOne(o => o.PaymentMethod)
|
orderConfiguration.HasOne<PaymentMethod>()
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("PaymentMethodId")
|
.HasForeignKey("PaymentMethodId")
|
||||||
.IsRequired(false)
|
.IsRequired(false)
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
|
||||||
orderConfiguration.HasOne(o => o.Buyer)
|
orderConfiguration.HasOne<Buyer>()
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.IsRequired(false)
|
.IsRequired(false)
|
||||||
.HasForeignKey("BuyerId");
|
.HasForeignKey("BuyerId");
|
||||||
|
@ -45,14 +45,11 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers
|
|||||||
var user = _appUserParser.Parse(HttpContext.User);
|
var user = _appUserParser.Parse(HttpContext.User);
|
||||||
await _orderSvc.CreateOrder(model);
|
await _orderSvc.CreateOrder(model);
|
||||||
|
|
||||||
//Empty basket for current user.
|
|
||||||
await _basketSvc.CleanBasket(user);
|
|
||||||
|
|
||||||
//Redirect to historic list.
|
//Redirect to historic list.
|
||||||
return RedirectToAction("Index");
|
return RedirectToAction("Index");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(BrokenCircuitException ex)
|
catch(BrokenCircuitException)
|
||||||
{
|
{
|
||||||
ModelState.AddModelError("Error", "It was not possible to create a new order, please try later on");
|
ModelState.AddModelError("Error", "It was not possible to create a new order, please try later on");
|
||||||
}
|
}
|
||||||
|
10
src/Web/WebMVC/Infrastructure/IResilientHttpClientFactory.cs
Normal file
10
src/Web/WebMVC/Infrastructure/IResilientHttpClientFactory.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.eShopOnContainers.WebMVC.Infrastructure
|
||||||
|
{
|
||||||
|
public interface IResilientHttpClientFactory
|
||||||
|
{
|
||||||
|
ResilientHttpClient CreateResilientHttpClient();
|
||||||
|
}
|
||||||
|
}
|
59
src/Web/WebMVC/Infrastructure/ResilientHttpClientFactory.cs
Normal file
59
src/Web/WebMVC/Infrastructure/ResilientHttpClientFactory.cs
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Polly;
|
||||||
|
using System.Net.Http;
|
||||||
|
|
||||||
|
namespace Microsoft.eShopOnContainers.WebMVC.Infrastructure
|
||||||
|
{
|
||||||
|
public class ResilientHttpClientFactory : IResilientHttpClientFactory
|
||||||
|
{
|
||||||
|
private readonly ILogger<ResilientHttpClient> _logger;
|
||||||
|
|
||||||
|
public ResilientHttpClientFactory(ILogger<ResilientHttpClient> logger)
|
||||||
|
=>_logger = logger;
|
||||||
|
|
||||||
|
public ResilientHttpClient CreateResilientHttpClient()
|
||||||
|
=> new ResilientHttpClient(CreatePolicies(), _logger);
|
||||||
|
|
||||||
|
|
||||||
|
private Policy[] CreatePolicies()
|
||||||
|
=> new Policy[]
|
||||||
|
{
|
||||||
|
Policy.Handle<HttpRequestException>()
|
||||||
|
.WaitAndRetryAsync(
|
||||||
|
// number of retries
|
||||||
|
6,
|
||||||
|
// exponential backofff
|
||||||
|
retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
|
||||||
|
// on retry
|
||||||
|
(exception, timeSpan, retryCount, context) =>
|
||||||
|
{
|
||||||
|
var msg = $"Retry {retryCount} implemented with Polly's RetryPolicy " +
|
||||||
|
$"of {context.PolicyKey} " +
|
||||||
|
$"at {context.ExecutionKey}, " +
|
||||||
|
$"due to: {exception}.";
|
||||||
|
_logger.LogWarning(msg);
|
||||||
|
_logger.LogDebug(msg);
|
||||||
|
}),
|
||||||
|
Policy.Handle<HttpRequestException>()
|
||||||
|
.CircuitBreakerAsync(
|
||||||
|
// number of exceptions before breaking circuit
|
||||||
|
5,
|
||||||
|
// time circuit opened before retry
|
||||||
|
TimeSpan.FromMinutes(1),
|
||||||
|
(exception, duration) =>
|
||||||
|
{
|
||||||
|
// on circuit opened
|
||||||
|
_logger.LogTrace("Circuit breaker opened");
|
||||||
|
},
|
||||||
|
() =>
|
||||||
|
{
|
||||||
|
// on circuit closed
|
||||||
|
_logger.LogTrace("Circuit breaker reset");
|
||||||
|
})};
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http;
|
||||||
using Microsoft.eShopOnContainers.WebMVC.ViewModels;
|
using Microsoft.eShopOnContainers.WebMVC.ViewModels;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
@ -7,7 +8,6 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using WebMVC.Services.Utilities;
|
|
||||||
|
|
||||||
namespace Microsoft.eShopOnContainers.WebMVC.Services
|
namespace Microsoft.eShopOnContainers.WebMVC.Services
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
|
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http;
|
||||||
using Microsoft.eShopOnContainers.WebMVC.ViewModels;
|
using Microsoft.eShopOnContainers.WebMVC.ViewModels;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
@ -6,7 +7,6 @@ using Newtonsoft.Json;
|
|||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using WebMVC.Services.Utilities;
|
|
||||||
|
|
||||||
namespace Microsoft.eShopOnContainers.WebMVC.Services
|
namespace Microsoft.eShopOnContainers.WebMVC.Services
|
||||||
{
|
{
|
||||||
|
@ -8,7 +8,7 @@ using Microsoft.Extensions.Options;
|
|||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using WebMVC.Services.Utilities;
|
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http;
|
||||||
|
|
||||||
namespace Microsoft.eShopOnContainers.WebMVC.Services
|
namespace Microsoft.eShopOnContainers.WebMVC.Services
|
||||||
{
|
{
|
||||||
|
@ -14,8 +14,9 @@ using Microsoft.IdentityModel.Tokens;
|
|||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using WebMVC.Services.Utilities;
|
|
||||||
using Microsoft.Extensions.HealthChecks;
|
using Microsoft.Extensions.HealthChecks;
|
||||||
|
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http;
|
||||||
|
using Microsoft.eShopOnContainers.WebMVC.Infrastructure;
|
||||||
|
|
||||||
namespace Microsoft.eShopOnContainers.WebMVC
|
namespace Microsoft.eShopOnContainers.WebMVC
|
||||||
{
|
{
|
||||||
@ -61,15 +62,16 @@ namespace Microsoft.eShopOnContainers.WebMVC
|
|||||||
services.AddTransient<IBasketService, BasketService>();
|
services.AddTransient<IBasketService, BasketService>();
|
||||||
services.AddTransient<IIdentityParser<ApplicationUser>, IdentityParser>();
|
services.AddTransient<IIdentityParser<ApplicationUser>, IdentityParser>();
|
||||||
|
|
||||||
if(Configuration.GetValue<string>("ActivateCircuitBreaker") == bool.TrueString)
|
if (Configuration.GetValue<string>("UseResilientHttp") == bool.TrueString)
|
||||||
{
|
{
|
||||||
services.AddTransient<IHttpClient, ResilientHttpClient>();
|
services.AddTransient<IResilientHttpClientFactory, ResilientHttpClientFactory>();
|
||||||
|
services.AddTransient<IHttpClient, ResilientHttpClient>(sp => sp.GetService<IResilientHttpClientFactory>().CreateResilientHttpClient());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
services.AddTransient<IHttpClient, StandardHttpClient>();
|
services.AddTransient<IHttpClient, StandardHttpClient>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
|
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
|
||||||
@ -112,15 +114,10 @@ namespace Microsoft.eShopOnContainers.WebMVC
|
|||||||
ResponseType = "code id_token",
|
ResponseType = "code id_token",
|
||||||
SaveTokens = true,
|
SaveTokens = true,
|
||||||
GetClaimsFromUserInfoEndpoint = true,
|
GetClaimsFromUserInfoEndpoint = true,
|
||||||
RequireHttpsMetadata = false,
|
RequireHttpsMetadata = false,
|
||||||
|
Scope = { "openid", "profile", "orders", "basket" }
|
||||||
};
|
};
|
||||||
|
|
||||||
oidcOptions.Scope.Clear();
|
|
||||||
oidcOptions.Scope.Add("openid");
|
|
||||||
oidcOptions.Scope.Add("profile");
|
|
||||||
oidcOptions.Scope.Add("orders");
|
|
||||||
oidcOptions.Scope.Add("basket");
|
|
||||||
|
|
||||||
//Wait untill identity service is ready on compose.
|
//Wait untill identity service is ready on compose.
|
||||||
app.UseOpenIdConnectAuthentication(oidcOptions);
|
app.UseOpenIdConnectAuthentication(oidcOptions);
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@
|
|||||||
<div class="esh-basket-items--border row">
|
<div class="esh-basket-items--border row">
|
||||||
@if (item.OldUnitPrice != 0)
|
@if (item.OldUnitPrice != 0)
|
||||||
{
|
{
|
||||||
<div class="alert alert-warning esh-basket-margin12" role="alert"> Note that the price of this article changed in our Catalog. The old price when you originally added it to the basket was @item.OldUnitPrice $</div>
|
<div class="alert alert-warning esh-basket-margin12" role="alert"> Note that the price of this article changed in our Catalog. The old price when you originally added it to the basket was $ @item.OldUnitPrice </div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<br/>
|
<br/>
|
||||||
|
@ -13,6 +13,13 @@
|
|||||||
<DockerComposeProjectPath>..\..\..\docker-compose.dcproj</DockerComposeProjectPath>
|
<DockerComposeProjectPath>..\..\..\docker-compose.dcproj</DockerComposeProjectPath>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Remove="wwwroot\lib\bootstrap\**" />
|
||||||
|
<Content Remove="wwwroot\lib\bootstrap\**" />
|
||||||
|
<EmbeddedResource Remove="wwwroot\lib\bootstrap\**" />
|
||||||
|
<None Remove="wwwroot\lib\bootstrap\**" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.Cookies" Version="1.1.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.Cookies" Version="1.1.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="1.1.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="1.1.0" />
|
||||||
@ -32,7 +39,6 @@
|
|||||||
<PrivateAssets>All</PrivateAssets>
|
<PrivateAssets>All</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
|
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
|
||||||
<PackageReference Include="Polly" Version="5.0.6" />
|
|
||||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="5.1.0" />
|
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="5.1.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="1.1.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="1.1.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="1.1.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="1.1.0" />
|
||||||
@ -55,6 +61,7 @@
|
|||||||
<ProjectReference Include="..\..\BuildingBlocks\HealthChecks\src\Microsoft.AspNetCore.HealthChecks\Microsoft.AspNetCore.HealthChecks.csproj" />
|
<ProjectReference Include="..\..\BuildingBlocks\HealthChecks\src\Microsoft.AspNetCore.HealthChecks\Microsoft.AspNetCore.HealthChecks.csproj" />
|
||||||
<ProjectReference Include="..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.Data\Microsoft.Extensions.HealthChecks.Data.csproj" />
|
<ProjectReference Include="..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.Data\Microsoft.Extensions.HealthChecks.Data.csproj" />
|
||||||
<ProjectReference Include="..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks\Microsoft.Extensions.HealthChecks.csproj" />
|
<ProjectReference Include="..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks\Microsoft.Extensions.HealthChecks.csproj" />
|
||||||
|
<ProjectReference Include="..\..\BuildingBlocks\Resilience\Resilience.Http\Resilience.Http.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
"BasketUrl": "http://localhost:5103",
|
"BasketUrl": "http://localhost:5103",
|
||||||
"IdentityUrl": "http://localhost:5105",
|
"IdentityUrl": "http://localhost:5105",
|
||||||
"CallBackUrl": "http://localhost:5100/",
|
"CallBackUrl": "http://localhost:5100/",
|
||||||
"ActivateCircuitBreaker": "True",
|
"UseResilientHttp": "True",
|
||||||
"Logging": {
|
"Logging": {
|
||||||
"IncludeScopes": false,
|
"IncludeScopes": false,
|
||||||
"LogLevel": {
|
"LogLevel": {
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
</article>
|
</article>
|
||||||
<br/>
|
<br/>
|
||||||
<div class="esh-basket-items-margin-left1 row">
|
<div class="esh-basket-items-margin-left1 row">
|
||||||
<div class="alert alert-warning" role="alert" *ngIf="item.oldUnitPrice > 0"> Note that the price of this article changed in our Catalog. The old price when you originally added it to the basket was {{item.oldUnitPrice}} $</div>
|
<div class="alert alert-warning" role="alert" *ngIf="item.oldUnitPrice > 0"> Note that the price of this article changed in our Catalog. The old price when you originally added it to the basket was $ {{item.oldUnitPrice}} </div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -18,7 +18,7 @@ export class OrdersNewComponent implements OnInit {
|
|||||||
private errorReceived: Boolean;
|
private errorReceived: Boolean;
|
||||||
private order: IOrder;
|
private order: IOrder;
|
||||||
|
|
||||||
constructor(private service: OrdersService, fb: FormBuilder, private router: Router, private basketEvents: BasketWrapperService) {
|
constructor(private service: OrdersService, fb: FormBuilder, private router: Router) {
|
||||||
// Obtain user profile information
|
// Obtain user profile information
|
||||||
this.order = service.mapBasketAndIdentityInfoNewOrder();
|
this.order = service.mapBasketAndIdentityInfoNewOrder();
|
||||||
this.newOrderForm = fb.group({
|
this.newOrderForm = fb.group({
|
||||||
@ -54,10 +54,7 @@ export class OrdersNewComponent implements OnInit {
|
|||||||
return Observable.throw(errMessage);
|
return Observable.throw(errMessage);
|
||||||
})
|
})
|
||||||
.subscribe(res => {
|
.subscribe(res => {
|
||||||
// this will emit an observable. Basket service is subscribed to this observable, and will react deleting the basket for the current user.
|
this.router.navigate(['orders']);
|
||||||
this.basketEvents.orderCreated();
|
|
||||||
|
|
||||||
this.router.navigate(['orders']);
|
|
||||||
});
|
});
|
||||||
this.errorReceived = false;
|
this.errorReceived = false;
|
||||||
this.isOrderProcessing = true;
|
this.isOrderProcessing = true;
|
||||||
|
@ -9,6 +9,7 @@ namespace eShopConContainers.WebSPA
|
|||||||
{
|
{
|
||||||
var host = new WebHostBuilder()
|
var host = new WebHostBuilder()
|
||||||
.UseKestrel()
|
.UseKestrel()
|
||||||
|
.UseHealthChecks("/hc")
|
||||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
.UseIISIntegration()
|
.UseIISIntegration()
|
||||||
.UseStartup<Startup>()
|
.UseStartup<Startup>()
|
||||||
|
@ -10,6 +10,7 @@ using Microsoft.Extensions.Logging;
|
|||||||
using Newtonsoft.Json.Serialization;
|
using Newtonsoft.Json.Serialization;
|
||||||
using eShopOnContainers.WebSPA;
|
using eShopOnContainers.WebSPA;
|
||||||
using Microsoft.Extensions.HealthChecks;
|
using Microsoft.Extensions.HealthChecks;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace eShopConContainers.WebSPA
|
namespace eShopConContainers.WebSPA
|
||||||
{
|
{
|
||||||
@ -42,7 +43,10 @@ namespace eShopConContainers.WebSPA
|
|||||||
{
|
{
|
||||||
services.AddHealthChecks(checks =>
|
services.AddHealthChecks(checks =>
|
||||||
{
|
{
|
||||||
checks.AddUrlCheck(Configuration["CallBackUrl"]);
|
checks.AddUrlCheck(Configuration["CatalogUrl"]);
|
||||||
|
checks.AddUrlCheck(Configuration["OrderingUrl"]);
|
||||||
|
checks.AddUrlCheck(Configuration["BasketUrl"]);
|
||||||
|
checks.AddUrlCheck(Configuration["IdentityUrl"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
services.Configure<AppSettings>(Configuration);
|
services.Configure<AppSettings>(Configuration);
|
||||||
|
@ -25,7 +25,7 @@ namespace WebStatus.Controllers
|
|||||||
|
|
||||||
foreach (var checkResult in result.Results)
|
foreach (var checkResult in result.Results)
|
||||||
{
|
{
|
||||||
data.AddResult(checkResult.Value);
|
data.AddResult(checkResult.Key, checkResult.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return View(data);
|
return View(data);
|
||||||
|
@ -9,13 +9,13 @@ namespace WebStatus.Viewmodels
|
|||||||
public class HealthStatusViewModel
|
public class HealthStatusViewModel
|
||||||
{
|
{
|
||||||
private readonly CheckStatus _overall;
|
private readonly CheckStatus _overall;
|
||||||
private readonly List<IHealthCheckResult> _results;
|
private readonly Dictionary<string, IHealthCheckResult> _results;
|
||||||
|
|
||||||
public CheckStatus OverallStatus => _overall;
|
public CheckStatus OverallStatus => _overall;
|
||||||
public IEnumerable<IHealthCheckResult> Results => _results;
|
public IEnumerable<NamedCheckResult> Results => _results.Select(kvp => new NamedCheckResult(kvp.Key, kvp.Value));
|
||||||
private HealthStatusViewModel() => _results = new List<IHealthCheckResult>();
|
private HealthStatusViewModel() => _results = new Dictionary<string, IHealthCheckResult>();
|
||||||
public HealthStatusViewModel(CheckStatus overall) : this() => _overall = overall;
|
public HealthStatusViewModel(CheckStatus overall) : this() => _overall = overall;
|
||||||
public void AddResult(IHealthCheckResult result) => _results.Add(result);
|
public void AddResult(string name, IHealthCheckResult result) => _results.Add(name, result);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
20
src/Web/WebStatus/Viewmodels/NamedCheckResult.cs
Normal file
20
src/Web/WebStatus/Viewmodels/NamedCheckResult.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
using Microsoft.Extensions.HealthChecks;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace WebStatus.Viewmodels
|
||||||
|
{
|
||||||
|
public class NamedCheckResult
|
||||||
|
{
|
||||||
|
public string Name { get; }
|
||||||
|
public IHealthCheckResult Result { get; }
|
||||||
|
|
||||||
|
public NamedCheckResult(string name, IHealthCheckResult result)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Result = result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -16,26 +16,30 @@
|
|||||||
{
|
{
|
||||||
<div class="row list-group-status-item">
|
<div class="row list-group-status-item">
|
||||||
<div class="col-md-10">
|
<div class="col-md-10">
|
||||||
<h4 class="list-group-status-item-title">@result.Data["url"]</h4>
|
<h4 class="list-group-status-item-title">@result.Name</h4>
|
||||||
<p class="list-group-item-text">@result.Description</p>
|
<p class="list-group-item-text">
|
||||||
|
@if (result.Result.Data.ContainsKey("url")) {
|
||||||
|
<p>@result.Result.Data["url"]</p>
|
||||||
|
}
|
||||||
|
@result.Result.Description
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-2 list-group-status-item-label">
|
<div class="col-md-2 list-group-status-item-label">
|
||||||
@if (@result.CheckStatus == Microsoft.Extensions.HealthChecks.CheckStatus.Healthy)
|
@if (@result.Result.CheckStatus == Microsoft.Extensions.HealthChecks.CheckStatus.Healthy)
|
||||||
{
|
{
|
||||||
<span class="label label-success">@result.CheckStatus</span>
|
<span class="label label-success">@result.Result.CheckStatus</span>
|
||||||
}
|
}
|
||||||
else if (@result.CheckStatus == Microsoft.Extensions.HealthChecks.CheckStatus.Unhealthy)
|
else if (@result.Result.CheckStatus == Microsoft.Extensions.HealthChecks.CheckStatus.Unhealthy)
|
||||||
{
|
{
|
||||||
<span class="label label-danger">@result.CheckStatus</span>
|
<span class="label label-danger">@result.Result.CheckStatus</span>
|
||||||
}
|
}
|
||||||
else if (@result.CheckStatus == Microsoft.Extensions.HealthChecks.CheckStatus.Warning)
|
else if (@result.Result.CheckStatus == Microsoft.Extensions.HealthChecks.CheckStatus.Warning)
|
||||||
{
|
{
|
||||||
<span class="label label-warning">@result.CheckStatus</span>
|
<span class="label label-warning">@result.Result.CheckStatus</span>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<span class="label label-default">@result.CheckStatus</span>
|
<span class="label label-default">@result.Result.CheckStatus</span>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,6 +4,16 @@
|
|||||||
<TargetFramework>netcoreapp1.1</TargetFramework>
|
<TargetFramework>netcoreapp1.1</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="Services\Catalog\settings.json" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="Services\Catalog\settings.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.DotNet.InternalAbstractions" Version="1.0.0" />
|
<PackageReference Include="Microsoft.DotNet.InternalAbstractions" Version="1.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0-preview-20170106-08" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0-preview-20170106-08" />
|
||||||
@ -23,7 +33,7 @@
|
|||||||
<None Update="appsettings.json">
|
<None Update="appsettings.json">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
<None Update="settings.json">
|
<None Update="Services\Ordering\settings.json">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@ -14,7 +14,7 @@ namespace FunctionalTests.Services.Catalog
|
|||||||
public TestServer CreateServer()
|
public TestServer CreateServer()
|
||||||
{
|
{
|
||||||
var webHostBuilder = new WebHostBuilder();
|
var webHostBuilder = new WebHostBuilder();
|
||||||
webHostBuilder.UseContentRoot(Directory.GetCurrentDirectory());
|
webHostBuilder.UseContentRoot(Directory.GetCurrentDirectory() + "\\Services\\Catalog");
|
||||||
webHostBuilder.UseStartup<Startup>();
|
webHostBuilder.UseStartup<Startup>();
|
||||||
|
|
||||||
return new TestServer(webHostBuilder);
|
return new TestServer(webHostBuilder);
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"ConnectionString": "Server=tcp:127.0.0.1,5433;Initial Catalog=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word",
|
||||||
|
"ExternalCatalogBaseUrl": "http://localhost:5101",
|
||||||
|
"IdentityUrl": "http://localhost:5105",
|
||||||
|
"isTest": "true",
|
||||||
|
"EventBusConnection": "localhost"
|
||||||
|
}
|
@ -12,7 +12,7 @@ namespace FunctionalTests.Services.Ordering
|
|||||||
public TestServer CreateServer()
|
public TestServer CreateServer()
|
||||||
{
|
{
|
||||||
var webHostBuilder = new WebHostBuilder();
|
var webHostBuilder = new WebHostBuilder();
|
||||||
webHostBuilder.UseContentRoot(Directory.GetCurrentDirectory());
|
webHostBuilder.UseContentRoot(Directory.GetCurrentDirectory() + "\\Services\\Ordering");
|
||||||
webHostBuilder.UseStartup<OrderingTestsStartup>();
|
webHostBuilder.UseStartup<OrderingTestsStartup>();
|
||||||
|
|
||||||
return new TestServer(webHostBuilder);
|
return new TestServer(webHostBuilder);
|
||||||
|
@ -12,9 +12,16 @@
|
|||||||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
|
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="Services\Catalog\settings.json" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<!--<Content Include="settings.json;web.config">-->
|
<!--<Content Include="settings.json;web.config">-->
|
||||||
<Content Include="settings.json">
|
<Content Include="Services\Catalog\settings.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
<Content Include="Services\Ordering\settings.json">
|
||||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@ -35,7 +42,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Update="settings.json">
|
<Content Update="Services\Ordering\settings.json">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@ -12,7 +12,7 @@ namespace IntegrationTests.Services.Catalog
|
|||||||
public TestServer CreateServer()
|
public TestServer CreateServer()
|
||||||
{
|
{
|
||||||
var webHostBuilder = new WebHostBuilder();
|
var webHostBuilder = new WebHostBuilder();
|
||||||
webHostBuilder.UseContentRoot(Directory.GetCurrentDirectory());
|
webHostBuilder.UseContentRoot(Directory.GetCurrentDirectory() + "\\Services\\Catalog");
|
||||||
webHostBuilder.UseStartup<Startup>();
|
webHostBuilder.UseStartup<Startup>();
|
||||||
|
|
||||||
return new TestServer(webHostBuilder);
|
return new TestServer(webHostBuilder);
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"ConnectionString": "Server=tcp:127.0.0.1,5433;Initial Catalog=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word",
|
||||||
|
"ExternalCatalogBaseUrl": "http://localhost:5101",
|
||||||
|
"IdentityUrl": "http://localhost:5105",
|
||||||
|
"isTest": "true",
|
||||||
|
"EventBusConnection": "localhost"
|
||||||
|
}
|
@ -10,7 +10,7 @@
|
|||||||
public TestServer CreateServer()
|
public TestServer CreateServer()
|
||||||
{
|
{
|
||||||
var webHostBuilder = new WebHostBuilder();
|
var webHostBuilder = new WebHostBuilder();
|
||||||
webHostBuilder.UseContentRoot(Directory.GetCurrentDirectory());
|
webHostBuilder.UseContentRoot(Directory.GetCurrentDirectory() + "\\Services\\Ordering");
|
||||||
webHostBuilder.UseStartup<OrderingTestsStartup>();
|
webHostBuilder.UseStartup<OrderingTestsStartup>();
|
||||||
|
|
||||||
return new TestServer(webHostBuilder);
|
return new TestServer(webHostBuilder);
|
||||||
|
@ -2,5 +2,6 @@
|
|||||||
"ConnectionString": "Server=tcp:127.0.0.1,5433;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word;",
|
"ConnectionString": "Server=tcp:127.0.0.1,5433;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word;",
|
||||||
"ExternalCatalogBaseUrl": "http://localhost:5101",
|
"ExternalCatalogBaseUrl": "http://localhost:5101",
|
||||||
"IdentityUrl": "http://localhost:5105",
|
"IdentityUrl": "http://localhost:5105",
|
||||||
"isTest": "true"
|
"isTest": "true",
|
||||||
|
"EventBusConnection": "localhost"
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user