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
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
**/wwwroot/lib/
|
||||
|
||||
**/wwwroot/lib/
|
||||
|
||||
@ -260,3 +260,4 @@ pub/
|
||||
|
||||
#Ignore marker-file used to know which docker files we have.
|
||||
.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.
|
||||
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>
|
||||
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>
|
||||
<img src="img/eshop_logo.png">
|
||||
<img src="img/eShopOnContainers_Architecture_Diagram.png">
|
||||
<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>
|
||||
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
|
||||
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
|
||||
[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/Web/WebMVC"
|
||||
"/src/Web/WebSPA"
|
||||
"/src/Web/WebStatus
|
||||
)
|
||||
|
||||
# Build SPA app
|
||||
|
@ -7,6 +7,7 @@ projectList=(
|
||||
"../src/Services/Identity/Identity.API"
|
||||
"../src/Web/WebMVC"
|
||||
"../src/Web/WebSPA"
|
||||
"../src/Web/WebStatus"
|
||||
)
|
||||
|
||||
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\Ordering\Ordering.API";Prj="Ordering.API.csproj"},
|
||||
@{Path="$rootPath\src\Services\Basket\Basket.API";Prj="Basket.API.csproj"}
|
||||
@{Path="$rootPath\src\Web\WebStatus";Prj="WebStatus.csproj"}
|
||||
|
||||
$projectPaths | foreach {
|
||||
$projectPath = $_.Path
|
||||
$projectFile = $_.Prj
|
||||
$outPath = $_.Path + "\obj\Docker\publish"
|
||||
$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
|
||||
Write-Host "Publishing $projectPathAndFile to $outPath" -ForegroundColor Yellow
|
||||
dotnet restore $projectPathAndFile
|
||||
|
@ -33,6 +33,7 @@ services:
|
||||
- ASPNETCORE_ENVIRONMENT=Development
|
||||
- ASPNETCORE_URLS=http://0.0.0.0:5105
|
||||
- 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
|
||||
- MvcClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5100 #Local: You need to open your local dev-machine firewall at range 5100-5105.
|
||||
ports:
|
||||
@ -86,7 +87,7 @@ services:
|
||||
- CatalogUrl=http://catalog.api:5101/hc
|
||||
- OrderingUrl=http://ordering.api:5102/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
|
||||
- spa=http://webspa:5104/hc
|
||||
ports:
|
||||
|
@ -40,6 +40,7 @@ services:
|
||||
- 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
|
||||
- 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:
|
||||
- "5105:5105"
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.26228.0
|
||||
VisualStudioVersion = 15.0.26228.12
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{932D8224-11F6-4D07-B109-DA28AD288A63}"
|
||||
EndProject
|
||||
@ -84,6 +84,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Health
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebStatus", "src\Web\WebStatus\WebStatus.csproj", "{C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Resilience", "Resilience", "{FBF43D93-F2E7-4FF8-B4AB-186895949B88}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Resilience.Http", "src\BuildingBlocks\Resilience\Resilience.Http\Resilience.Http.csproj", "{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
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|x86.ActiveCfg = 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
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@ -947,5 +999,7 @@ Global
|
||||
{942ED6E8-0050-495F-A0EA-01E97F63760C} = {A81ECBC2-6B00-4DCD-8388-469174033379}
|
||||
{7804FC60-23E6-490C-8E08-F9FEF829F184} = {A81ECBC2-6B00-4DCD-8388-469174033379}
|
||||
{C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F} = {E279BF0F-7F66-4F3A-A3AB-2CDA66C1CD04}
|
||||
{FBF43D93-F2E7-4FF8-B4AB-186895949B88} = {DB0EFB20-B024-4E5E-A75C-52143C131D25}
|
||||
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502} = {FBF43D93-F2E7-4FF8-B4AB-186895949B88}
|
||||
EndGlobalSection
|
||||
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.Threading.Tasks;
|
||||
|
||||
namespace WebMVC.Services.Utilities
|
||||
namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http
|
||||
{
|
||||
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 Polly;
|
||||
using Polly.Wrap;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace WebMVC.Services.Utilities
|
||||
namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http
|
||||
{
|
||||
/// <summary>
|
||||
/// HttpClient wrapper that integrates Retry and Circuit
|
||||
@ -21,56 +21,16 @@ namespace WebMVC.Services.Utilities
|
||||
private PolicyWrap _policyWrapper;
|
||||
private ILogger<ResilientHttpClient> _logger;
|
||||
public HttpClient Inst => _client;
|
||||
public ResilientHttpClient(ILogger<ResilientHttpClient> logger)
|
||||
|
||||
public ResilientHttpClient(Policy[] policies, ILogger<ResilientHttpClient> logger)
|
||||
{
|
||||
_client = new HttpClient();
|
||||
_logger = logger;
|
||||
|
||||
// Add Policies to be applied
|
||||
_policyWrapper = Policy.WrapAsync(
|
||||
CreateRetryPolicy(),
|
||||
CreateCircuitBreakerPolicy()
|
||||
);
|
||||
_policyWrapper = Policy.WrapAsync(policies);
|
||||
}
|
||||
|
||||
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) =>
|
||||
HttpInvoker(() =>
|
||||
_client.GetStringAsync(uri));
|
@ -4,7 +4,7 @@ using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace WebMVC.Services.Utilities
|
||||
namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http
|
||||
{
|
||||
public class StandardHttpClient : IHttpClient
|
||||
{
|
@ -108,7 +108,8 @@
|
||||
<converters:InverseBoolConverter x:Key="InverseBoolConverter" />
|
||||
<converters:ItemsToHeightConverter x:Key="ItemsToHeightConverter" />
|
||||
<converters:ToUpperConverter x:Key="ToUpperConverter" />
|
||||
<converters:WebNavigatingEventArgsConverter x:Key="WebNavigatingEventArgsConverter" />
|
||||
<converters:WebNavigatingEventArgsConverter x:Key="WebNavigatingEventArgsConverter" />
|
||||
<converters:WebNavigatedEventArgsConverter x:Key="WebNavigatedEventArgsConverter" />
|
||||
|
||||
<!-- STYLES -->
|
||||
<Style x:Key="ValidationErrorLabelStyle"
|
||||
|
@ -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);
|
||||
UserInfoEndpoint = string.Format("{0}:5105/connect/userinfo", 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);
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,16 @@ namespace eShopOnContainers.Core.Models.Basket
|
||||
|
||||
public decimal UnitPrice { get; set; }
|
||||
|
||||
public decimal OldUnitPrice { get; set; }
|
||||
|
||||
public bool HasNewPrice
|
||||
{
|
||||
get
|
||||
{
|
||||
return OldUnitPrice != 0.0m;
|
||||
}
|
||||
}
|
||||
|
||||
public int Quantity
|
||||
{
|
||||
get { return _quantity; }
|
||||
|
@ -34,6 +34,9 @@ namespace eShopOnContainers.Core.Models.Orders
|
||||
[JsonProperty("country")]
|
||||
public string ShippingCountry { get; set; }
|
||||
|
||||
[JsonProperty("zipCode")]
|
||||
public string ShippingZipCode { get; set; }
|
||||
|
||||
public int CardTypeId { get; set; }
|
||||
|
||||
public string CardNumber { get; set; }
|
||||
|
@ -76,7 +76,9 @@ namespace eShopOnContainers.Core.ViewModels
|
||||
|
||||
if (basket != null && basket.Items != null && basket.Items.Any())
|
||||
{
|
||||
BadgeCount = 0;
|
||||
BasketItems.Clear();
|
||||
|
||||
foreach (var basketItem in basket.Items)
|
||||
{
|
||||
BadgeCount += basketItem.Quantity;
|
||||
|
@ -90,7 +90,7 @@ namespace eShopOnContainers.Core.ViewModels
|
||||
ZipCode = userInfo?.ZipCode,
|
||||
State = userInfo?.State,
|
||||
Country = userInfo?.Country,
|
||||
City = string.Empty
|
||||
City = userInfo?.Address
|
||||
};
|
||||
|
||||
// Create Payment Info
|
||||
@ -118,6 +118,7 @@ namespace eShopOnContainers.Core.ViewModels
|
||||
ShippingCountry = _shippingAddress.Country,
|
||||
ShippingStreet = _shippingAddress.Street,
|
||||
ShippingCity = _shippingAddress.City,
|
||||
ShippingZipCode = _shippingAddress.ZipCode,
|
||||
Total = CalculateTotal(CreateOrderItems(orderItems))
|
||||
};
|
||||
|
||||
|
@ -221,14 +221,16 @@ namespace eShopOnContainers.Core.ViewModels
|
||||
|
||||
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.AuthIdToken = string.Empty;
|
||||
IsLogin = false;
|
||||
LoginUrl = _identityService.CreateAuthorizeRequest();
|
||||
}
|
||||
else if (url.Contains(GlobalSetting.Instance.IdentityCallback))
|
||||
else if (unescapedUrl.Contains(GlobalSetting.Instance.IdentityCallback))
|
||||
{
|
||||
var authResponse = new AuthorizeResponse(url);
|
||||
|
||||
|
@ -345,10 +345,26 @@
|
||||
AbsoluteLayout.LayoutBounds="0, 0, 1, 1"
|
||||
AbsoluteLayout.LayoutFlags="All">
|
||||
<WebView.Behaviors>
|
||||
<behaviors:EventToCommandBehavior
|
||||
EventName="Navigating"
|
||||
EventArgsConverter="{StaticResource WebNavigatingEventArgsConverter}"
|
||||
Command="{Binding NavigateCommand}" />
|
||||
<OnPlatform x:TypeArguments="Behavior">
|
||||
<OnPlatform.Android>
|
||||
<behaviors:EventToCommandBehavior
|
||||
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>
|
||||
</AbsoluteLayout>
|
||||
|
@ -35,5 +35,16 @@ namespace eShopOnContainers.Core.Views
|
||||
await ((BasketViewModel)BasketView.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.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="1" />
|
||||
</Grid.RowDefinitions>
|
||||
<!-- IMAGE -->
|
||||
@ -135,6 +136,20 @@
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
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"/>
|
||||
</Grid>
|
||||
</ContentView.Content>
|
||||
|
@ -58,6 +58,7 @@
|
||||
<Compile Include="Converters\ItemsToHeightConverter.cs" />
|
||||
<Compile Include="Converters\ItemTappedEventArgsConverter.cs" />
|
||||
<Compile Include="Converters\ToUpperConverter.cs" />
|
||||
<Compile Include="Converters\WebNavigatedEventArgsConverter.cs" />
|
||||
<Compile Include="Exceptions\ServiceAuthenticationException.cs" />
|
||||
<Compile Include="Extensions\ObservableExtension.cs" />
|
||||
<Compile Include="GlobalSettings.cs" />
|
||||
|
@ -24,5 +24,6 @@
|
||||
</Applications>
|
||||
<Capabilities>
|
||||
<Capability Name="internetClient" />
|
||||
<Capability Name="privateNetworkClientServer" />
|
||||
</Capabilities>
|
||||
</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 System.Threading.Tasks;
|
||||
using Basket.API.Infrastructure.Filters;
|
||||
using Basket.API.IntegrationEvents.Events;
|
||||
using Basket.API.IntegrationEvents.EventHandling;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.Services.Basket.API
|
||||
{
|
||||
@ -40,7 +42,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
|
||||
|
||||
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.
|
||||
@ -91,6 +93,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
|
||||
|
||||
services.AddTransient<IBasketRepository, RedisBasketRepository>();
|
||||
services.AddTransient<IIntegrationEventHandler<ProductPriceChangedIntegrationEvent>, ProductPriceChangedIntegrationEventHandler>();
|
||||
services.AddTransient<IIntegrationEventHandler<OrderStartedIntegrationEvent>, OrderStartedIntegrationEventHandler>();
|
||||
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
var configuration = serviceProvider.GetRequiredService<IOptionsSnapshot<BasketSettings>>().Value;
|
||||
@ -117,8 +120,10 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
|
||||
.UseSwaggerUi();
|
||||
|
||||
var catalogPriceHandler = app.ApplicationServices.GetService<IIntegrationEventHandler<ProductPriceChangedIntegrationEvent>>();
|
||||
var orderStartedHandler = app.ApplicationServices.GetService<IIntegrationEventHandler<OrderStartedIntegrationEvent>>();
|
||||
var eventBus = app.ApplicationServices.GetRequiredService<IEventBus>();
|
||||
eventBus.Subscribe<ProductPriceChangedIntegrationEvent>(catalogPriceHandler);
|
||||
eventBus.Subscribe<OrderStartedIntegrationEvent>(orderStartedHandler);
|
||||
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,8 @@ using System.Data.Common;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Utilities;
|
||||
using Catalog.API.IntegrationEvents;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
|
||||
{
|
||||
@ -23,15 +25,13 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
|
||||
{
|
||||
private readonly CatalogContext _catalogContext;
|
||||
private readonly IOptionsSnapshot<Settings> _settings;
|
||||
private readonly IEventBus _eventBus;
|
||||
private readonly Func<DbConnection, IIntegrationEventLogService> _integrationEventLogServiceFactory;
|
||||
private readonly ICatalogIntegrationEventService _catalogIntegrationEventService;
|
||||
|
||||
public CatalogController(CatalogContext Context, IOptionsSnapshot<Settings> settings, IEventBus eventBus, Func<DbConnection, IIntegrationEventLogService> integrationEventLogServiceFactory)
|
||||
public CatalogController(CatalogContext Context, IOptionsSnapshot<Settings> settings, ICatalogIntegrationEventService catalogIntegrationEventService)
|
||||
{
|
||||
_catalogContext = Context;
|
||||
_catalogIntegrationEventService = catalogIntegrationEventService;
|
||||
_settings = settings;
|
||||
_eventBus = eventBus;
|
||||
_integrationEventLogServiceFactory = integrationEventLogServiceFactory;
|
||||
|
||||
((DbContext)Context).ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
|
||||
}
|
||||
@ -145,50 +145,27 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
|
||||
{
|
||||
var catalogItem = await _catalogContext.CatalogItems.SingleOrDefaultAsync(i => i.Id == productToUpdate.Id);
|
||||
if (catalogItem == null) return NotFound();
|
||||
var raiseProductPriceChangedEvent = catalogItem.Price != productToUpdate.Price;
|
||||
var oldPrice = catalogItem.Price;
|
||||
|
||||
bool raiseProductPriceChangedEvent = false;
|
||||
IntegrationEvent priceChangedEvent = null;
|
||||
|
||||
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
|
||||
// Update current product
|
||||
catalogItem = productToUpdate;
|
||||
_catalogContext.CatalogItems.Update(catalogItem);
|
||||
|
||||
//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 = _catalogContext.Database.CreateExecutionStrategy();
|
||||
var eventLogService = _integrationEventLogServiceFactory(_catalogContext.Database.GetDbConnection());
|
||||
await strategy.ExecuteAsync(async () =>
|
||||
if (raiseProductPriceChangedEvent) // Save and publish integration event if price has changed
|
||||
{
|
||||
//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
|
||||
using (var transaction = _catalogContext.Database.BeginTransaction())
|
||||
{
|
||||
_catalogContext.CatalogItems.Update(catalogItem);
|
||||
await _catalogContext.SaveChangesAsync();
|
||||
await _catalogIntegrationEventService.SaveEventAndCatalogContextChangesAsync(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)
|
||||
// Publish through the Event Bus and mark the saved event as published
|
||||
await _catalogIntegrationEventService.PublishThroughEventBusAsync(priceChangedEvent);
|
||||
}
|
||||
else // Save updated product
|
||||
{
|
||||
_eventBus.Publish(priceChangedEvent);
|
||||
await eventLogService.MarkEventAsPublishedAsync(priceChangedEvent);
|
||||
await _catalogContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return Ok();
|
||||
|
@ -13,14 +13,12 @@
|
||||
public DbSet<CatalogItem> CatalogItems { get; set; }
|
||||
public DbSet<CatalogBrand> CatalogBrands { get; set; }
|
||||
public DbSet<CatalogType> CatalogTypes { get; set; }
|
||||
//public DbSet<IntegrationEventLogEntry> IntegrationEventLog { get; set; }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
builder.Entity<CatalogBrand>(ConfigureCatalogBrand);
|
||||
builder.Entity<CatalogType>(ConfigureCatalogType);
|
||||
builder.Entity<CatalogItem>(ConfigureCatalogItem);
|
||||
//builder.Entity<IntegrationEventLogEntry>(ConfigureIntegrationEventLogEntry);
|
||||
}
|
||||
|
||||
void ConfigureCatalogItem(EntityTypeBuilder<CatalogItem> builder)
|
||||
@ -79,31 +77,5 @@
|
||||
.IsRequired()
|
||||
.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.Data.Common;
|
||||
using System.Reflection;
|
||||
using global::Catalog.API.IntegrationEvents;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public class Startup
|
||||
@ -49,7 +50,7 @@
|
||||
|
||||
services.AddHealthChecks(checks =>
|
||||
{
|
||||
checks.AddSqlCheck("Catalog_Db", Configuration["ConnectionString"]);
|
||||
checks.AddSqlCheck("CatalogDb", Configuration["ConnectionString"]);
|
||||
});
|
||||
|
||||
services.AddMvc(options =>
|
||||
@ -99,9 +100,9 @@
|
||||
|
||||
services.AddTransient<Func<DbConnection, IIntegrationEventLogService>>(
|
||||
sp => (DbConnection c) => new IntegrationEventLogService(c));
|
||||
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
var configuration = serviceProvider.GetRequiredService<IOptionsSnapshot<Settings>>().Value;
|
||||
services.AddTransient<ICatalogIntegrationEventService, CatalogIntegrationEventService>();
|
||||
services.AddSingleton<IEventBus>(new EventBusRabbitMQ(configuration.EventBusConnection));
|
||||
}
|
||||
|
||||
|
@ -1,31 +1,30 @@
|
||||
using IdentityServer4.Models;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Collections.Generic;
|
||||
using IdentityServer4;
|
||||
|
||||
namespace Identity.API.Configuration
|
||||
{
|
||||
public class Config
|
||||
{
|
||||
// scopes define the resources in your system
|
||||
public static IEnumerable<Scope> GetScopes()
|
||||
// ApiResources define the apis in your system
|
||||
public static IEnumerable<ApiResource> GetApis()
|
||||
{
|
||||
return new List<Scope>
|
||||
return new List<ApiResource>
|
||||
{
|
||||
//Authentication OpenId uses this scopes;
|
||||
StandardScopes.OpenId,
|
||||
StandardScopes.Profile,
|
||||
new ApiResource("orders", "Orders Service"),
|
||||
new ApiResource("basket", "Basket Service")
|
||||
};
|
||||
}
|
||||
|
||||
//Each api we want to securice;
|
||||
new Scope
|
||||
{
|
||||
Name = "orders",
|
||||
Description = "Orders Service"
|
||||
},
|
||||
new Scope
|
||||
{
|
||||
Name = "basket",
|
||||
Description = "Basket Service"
|
||||
}
|
||||
// Identity resources are data like user ID, name, or email address of a user
|
||||
// see: http://docs.identityserver.io/en/release/configuration/resources.html
|
||||
public static IEnumerable<IdentityResource> GetResources()
|
||||
{
|
||||
return new List<IdentityResource>
|
||||
{
|
||||
new IdentityResources.OpenId(),
|
||||
new IdentityResources.Profile()
|
||||
};
|
||||
}
|
||||
|
||||
@ -47,8 +46,8 @@ namespace Identity.API.Configuration
|
||||
AllowedCorsOrigins = { $"{clientsUrl["Spa"]}" },
|
||||
AllowedScopes =
|
||||
{
|
||||
StandardScopes.OpenId.Name,
|
||||
StandardScopes.Profile.Name,
|
||||
IdentityServerConstants.StandardScopes.OpenId,
|
||||
IdentityServerConstants.StandardScopes.Profile,
|
||||
"orders",
|
||||
"basket"
|
||||
}
|
||||
@ -59,14 +58,14 @@ namespace Identity.API.Configuration
|
||||
ClientName = "eShop Xamarin OpenId Client",
|
||||
AllowedGrantTypes = GrantTypes.Implicit,
|
||||
AllowAccessTokensViaBrowser = true,
|
||||
RedirectUris = { "http://eshopxamarin/callback.html" },
|
||||
RedirectUris = { clientsUrl["Xamarin"] },
|
||||
RequireConsent = false,
|
||||
PostLogoutRedirectUris = { "http://13.88.8.119:5105/Account/Redirecting", "http://10.6.1.234:5105/Account/Redirecting" },
|
||||
AllowedCorsOrigins = { "http://eshopxamarin" },
|
||||
AllowedScopes =
|
||||
{
|
||||
StandardScopes.OpenId.Name,
|
||||
StandardScopes.Profile.Name,
|
||||
IdentityServerConstants.StandardScopes.OpenId,
|
||||
IdentityServerConstants.StandardScopes.Profile,
|
||||
"orders",
|
||||
"basket"
|
||||
}
|
||||
@ -82,6 +81,7 @@ namespace Identity.API.Configuration
|
||||
ClientUri = $"{clientsUrl["Mvc"]}", // public uri of the client
|
||||
AllowedGrantTypes = GrantTypes.Hybrid,
|
||||
RequireConsent = false,
|
||||
AllowOfflineAccess = true,
|
||||
RedirectUris = new List<string>
|
||||
{
|
||||
$"{clientsUrl["Mvc"]}/signin-oidc",
|
||||
@ -96,9 +96,9 @@ namespace Identity.API.Configuration
|
||||
},
|
||||
AllowedScopes = new List<string>
|
||||
{
|
||||
StandardScopes.OpenId.Name,
|
||||
StandardScopes.Profile.Name,
|
||||
StandardScopes.OfflineAccess.Name,
|
||||
IdentityServerConstants.StandardScopes.OpenId,
|
||||
IdentityServerConstants.StandardScopes.Profile,
|
||||
IdentityServerConstants.StandardScopes.OfflineAccess,
|
||||
"orders",
|
||||
"basket",
|
||||
},
|
||||
|
@ -5,7 +5,6 @@
|
||||
using IdentityModel;
|
||||
using IdentityServer4.Quickstart.UI.Models;
|
||||
using IdentityServer4.Services;
|
||||
using IdentityServer4.Services.InMemory;
|
||||
using Microsoft.AspNetCore.Http.Authentication;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System;
|
||||
|
@ -22,7 +22,7 @@ namespace IdentityServer4.Quickstart.UI.Controllers
|
||||
{
|
||||
private readonly ILogger<ConsentController> _logger;
|
||||
private readonly IClientStore _clientStore;
|
||||
private readonly IScopeStore _scopeStore;
|
||||
private readonly IResourceStore _resourceStore;
|
||||
private readonly IIdentityServerInteractionService _interaction;
|
||||
|
||||
|
||||
@ -30,12 +30,12 @@ namespace IdentityServer4.Quickstart.UI.Controllers
|
||||
ILogger<ConsentController> logger,
|
||||
IIdentityServerInteractionService interaction,
|
||||
IClientStore clientStore,
|
||||
IScopeStore scopeStore)
|
||||
IResourceStore resourceStore)
|
||||
{
|
||||
_logger = logger;
|
||||
_interaction = interaction;
|
||||
_clientStore = clientStore;
|
||||
_scopeStore = scopeStore;
|
||||
_resourceStore = resourceStore;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -120,10 +120,10 @@ namespace IdentityServer4.Quickstart.UI.Controllers
|
||||
var client = await _clientStore.FindEnabledClientByIdAsync(request.ClientId);
|
||||
if (client != null)
|
||||
{
|
||||
var scopes = await _scopeStore.FindEnabledScopesAsync(request.ScopesRequested);
|
||||
if (scopes != null && scopes.Any())
|
||||
var resources = await _resourceStore.FindEnabledResourcesByScopeAsync(request.ScopesRequested);
|
||||
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
|
||||
{
|
||||
|
@ -41,8 +41,8 @@
|
||||
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="1.1.0-msbuild3-final">
|
||||
<PrivateAssets>All</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="IdentityServer4.AspNetIdentity" Version="1.0.0-rc3" />
|
||||
<PackageReference Include="IdentityServer4.EntityFramework" Version="1.0.0-rc3" />
|
||||
<PackageReference Include="IdentityServer4.AspNetIdentity" Version="1.0.0" />
|
||||
<PackageReference Include="IdentityServer4.EntityFramework" Version="1.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PrepublishScript" BeforeTargets="PrepareForPublish">
|
||||
|
@ -10,7 +10,7 @@ namespace Identity.API.Models.AccountViewModels
|
||||
{
|
||||
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;
|
||||
ScopesConsented = model?.ScopesConsented ?? Enumerable.Empty<string>();
|
||||
@ -22,8 +22,8 @@ namespace Identity.API.Models.AccountViewModels
|
||||
ClientLogoUrl = client.LogoUri;
|
||||
AllowRememberConsent = client.AllowRememberConsent;
|
||||
|
||||
IdentityScopes = scopes.Where(x => x.Type == ScopeType.Identity).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();
|
||||
IdentityScopes = resources.IdentityResources.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; }
|
||||
@ -47,6 +47,16 @@ namespace Identity.API.Models.AccountViewModels
|
||||
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 DisplayName { get; set; }
|
||||
public string Description { get; set; }
|
||||
|
@ -73,11 +73,13 @@ namespace eShopOnContainers.Identity
|
||||
Dictionary<string, string> clientUrls = new Dictionary<string, string>();
|
||||
clientUrls.Add("Mvc", Configuration.GetValue<string>("MvcClient"));
|
||||
clientUrls.Add("Spa", Configuration.GetValue<string>("SpaClient"));
|
||||
clientUrls.Add("Xamarin", Configuration.GetValue<string>("XamarinCallback"));
|
||||
|
||||
// Adds IdentityServer
|
||||
services.AddIdentityServer(x => x.IssuerUri = "null")
|
||||
.AddSigningCredential(Certificate.Get())
|
||||
.AddInMemoryScopes(Config.GetScopes())
|
||||
.AddInMemoryApiResources(Config.GetApis())
|
||||
.AddInMemoryIdentityResources(Config.GetResources())
|
||||
.AddInMemoryClients(Config.GetClients(clientUrls))
|
||||
.AddAspNetIdentity<ApplicationUser>()
|
||||
.Services.AddTransient<IProfileService, ProfileService>();
|
||||
|
@ -4,6 +4,7 @@
|
||||
},
|
||||
"MvcClient": "http://localhost:5100",
|
||||
"SpaClient": "http://localhost:5104",
|
||||
"XamarinCallback": "http://localhost:5105/xamarincallback",
|
||||
"Logging": {
|
||||
"IncludeScopes": false,
|
||||
"LogLevel": {
|
||||
|
@ -1,6 +1,8 @@
|
||||
using MediatR;
|
||||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Ordering.API.IntegrationEvents;
|
||||
using Ordering.API.IntegrationEvents.Events;
|
||||
using Ordering.Domain.Events;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
@ -11,10 +13,15 @@ namespace Ordering.API.Application.DomainEventHandlers.BuyerAndPaymentMethodVeri
|
||||
: IAsyncNotificationHandler<BuyerAndPaymentMethodVerifiedDomainEvent>
|
||||
{
|
||||
private readonly IOrderRepository _orderRepository;
|
||||
private readonly IOrderingIntegrationEventService _orderingIntegrationEventService;
|
||||
private readonly ILoggerFactory _logger;
|
||||
public UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler(IOrderRepository orderRepository, ILoggerFactory logger)
|
||||
|
||||
public UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler(
|
||||
IOrderRepository orderRepository, ILoggerFactory logger,
|
||||
IOrderingIntegrationEventService orderingIntegrationEventService)
|
||||
{
|
||||
_orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository));
|
||||
_orderingIntegrationEventService = orderingIntegrationEventService ?? throw new ArgumentNullException(nameof(orderingIntegrationEventService));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
@ -27,8 +34,16 @@ namespace Ordering.API.Application.DomainEventHandlers.BuyerAndPaymentMethodVeri
|
||||
orderToUpdate.SetBuyerId(buyerPaymentMethodVerifiedEvent.Buyer.Id);
|
||||
orderToUpdate.SetPaymentId(buyerPaymentMethodVerifiedEvent.Payment.Id);
|
||||
|
||||
await _orderRepository.UnitOfWork
|
||||
.SaveEntitiesAsync();
|
||||
var orderStartedIntegrationEvent = new OrderStartedIntegrationEvent(buyerPaymentMethodVerifiedEvent.Buyer.IdentityGuid);
|
||||
|
||||
// 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))
|
||||
.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)
|
||||
{
|
||||
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:ordering.buyerseq", "'buyerseq', '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()
|
||||
.HasForeignKey("AddressId");
|
||||
|
||||
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.Buyer", "Buyer")
|
||||
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.Buyer")
|
||||
.WithMany()
|
||||
.HasForeignKey("BuyerId");
|
||||
|
||||
@ -226,7 +226,7 @@ namespace Ordering.API.Migrations
|
||||
.HasForeignKey("OrderStatusId")
|
||||
.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()
|
||||
.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>
|
||||
<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.Extensions.HealthChecks.Data\Microsoft.Extensions.HealthChecks.Data.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.Server.IISIntegration" Version="1.1.0" />
|
||||
<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.FileExtensions" 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.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.Debug" 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" Version="1.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="1.1.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="1.1.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.Design" Version="1.1.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="1.1.0" />
|
||||
@ -73,4 +78,9 @@
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Infrastructure\IntegrationEventMigrations\" />
|
||||
<Folder Include="IntegrationEvents\EventHandling\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -4,6 +4,7 @@
|
||||
using Autofac;
|
||||
using Autofac.Extensions.DependencyInjection;
|
||||
using global::Ordering.API.Infrastructure.Middlewares;
|
||||
using global::Ordering.API.IntegrationEvents;
|
||||
using Infrastructure;
|
||||
using Infrastructure.Auth;
|
||||
using Infrastructure.AutofacModules;
|
||||
@ -12,12 +13,17 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
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.DependencyInjection;
|
||||
using Microsoft.Extensions.HealthChecks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Ordering.Infrastructure;
|
||||
using System;
|
||||
using System.Data.Common;
|
||||
using System.Reflection;
|
||||
|
||||
public class Startup
|
||||
@ -53,7 +59,7 @@
|
||||
|
||||
services.AddHealthChecks(checks =>
|
||||
{
|
||||
checks.AddSqlCheck("Ordering_Db", Configuration["ConnectionString"]);
|
||||
checks.AddSqlCheck("OrderingDb", Configuration["ConnectionString"]);
|
||||
});
|
||||
|
||||
services.AddEntityFrameworkSqlServer()
|
||||
@ -95,7 +101,11 @@
|
||||
// Add application services.
|
||||
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
|
||||
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();
|
||||
|
||||
//configure autofac
|
||||
@ -126,6 +136,12 @@
|
||||
.UseSwaggerUi();
|
||||
|
||||
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)
|
||||
|
@ -7,7 +7,6 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.B
|
||||
public class PaymentMethod
|
||||
: Entity
|
||||
{
|
||||
private int _buyerId;
|
||||
private string _alias;
|
||||
private string _cardNumber;
|
||||
private string _securityNumber;
|
||||
|
@ -18,7 +18,6 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O
|
||||
|
||||
public Address Address { get; private set; }
|
||||
|
||||
public Buyer Buyer { get; private set; }
|
||||
private int? _buyerId;
|
||||
|
||||
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)
|
||||
//https://msdn.microsoft.com/en-us/library/e78dcd75(v=vs.110).aspx
|
||||
|
||||
public PaymentMethod PaymentMethod { get; private set; }
|
||||
private int? _paymentMethodId;
|
||||
|
||||
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)
|
||||
private string _productName;
|
||||
private string _pictureUrl;
|
||||
private int _orderId;
|
||||
private decimal _unitPrice;
|
||||
private decimal _discount;
|
||||
private int _units;
|
||||
|
@ -16,8 +16,9 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="1.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="1.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="1.1.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="1.1.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="1.1.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -62,6 +62,10 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
|
||||
{
|
||||
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")
|
||||
.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
|
||||
navigation.SetPropertyAccessMode(PropertyAccessMode.Field);
|
||||
|
||||
orderConfiguration.HasOne(o => o.PaymentMethod)
|
||||
orderConfiguration.HasOne<PaymentMethod>()
|
||||
.WithMany()
|
||||
.HasForeignKey("PaymentMethodId")
|
||||
.IsRequired(false)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
orderConfiguration.HasOne(o => o.Buyer)
|
||||
orderConfiguration.HasOne<Buyer>()
|
||||
.WithMany()
|
||||
.IsRequired(false)
|
||||
.HasForeignKey("BuyerId");
|
||||
|
@ -45,14 +45,11 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers
|
||||
var user = _appUserParser.Parse(HttpContext.User);
|
||||
await _orderSvc.CreateOrder(model);
|
||||
|
||||
//Empty basket for current user.
|
||||
await _basketSvc.CleanBasket(user);
|
||||
|
||||
//Redirect to historic list.
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
}
|
||||
catch(BrokenCircuitException ex)
|
||||
catch(BrokenCircuitException)
|
||||
{
|
||||
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.Http;
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http;
|
||||
using Microsoft.eShopOnContainers.WebMVC.ViewModels;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Newtonsoft.Json;
|
||||
@ -7,7 +8,6 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using WebMVC.Services.Utilities;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.WebMVC.Services
|
||||
{
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http;
|
||||
using Microsoft.eShopOnContainers.WebMVC.ViewModels;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
@ -6,7 +7,6 @@ using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using WebMVC.Services.Utilities;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.WebMVC.Services
|
||||
{
|
||||
|
@ -8,7 +8,7 @@ using Microsoft.Extensions.Options;
|
||||
using System.Net.Http;
|
||||
using Newtonsoft.Json;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using WebMVC.Services.Utilities;
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.WebMVC.Services
|
||||
{
|
||||
|
@ -14,8 +14,9 @@ using Microsoft.IdentityModel.Tokens;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System.Threading;
|
||||
using Microsoft.Extensions.Options;
|
||||
using WebMVC.Services.Utilities;
|
||||
using Microsoft.Extensions.HealthChecks;
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http;
|
||||
using Microsoft.eShopOnContainers.WebMVC.Infrastructure;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.WebMVC
|
||||
{
|
||||
@ -61,9 +62,10 @@ namespace Microsoft.eShopOnContainers.WebMVC
|
||||
services.AddTransient<IBasketService, BasketService>();
|
||||
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
|
||||
{
|
||||
@ -113,14 +115,9 @@ namespace Microsoft.eShopOnContainers.WebMVC
|
||||
SaveTokens = true,
|
||||
GetClaimsFromUserInfoEndpoint = true,
|
||||
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.
|
||||
app.UseOpenIdConnectAuthentication(oidcOptions);
|
||||
|
||||
|
@ -38,7 +38,7 @@
|
||||
<div class="esh-basket-items--border row">
|
||||
@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>
|
||||
<br/>
|
||||
|
@ -13,6 +13,13 @@
|
||||
<DockerComposeProjectPath>..\..\..\docker-compose.dcproj</DockerComposeProjectPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="wwwroot\lib\bootstrap\**" />
|
||||
<Content Remove="wwwroot\lib\bootstrap\**" />
|
||||
<EmbeddedResource Remove="wwwroot\lib\bootstrap\**" />
|
||||
<None Remove="wwwroot\lib\bootstrap\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.Cookies" Version="1.1.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="1.1.0" />
|
||||
@ -32,7 +39,6 @@
|
||||
<PrivateAssets>All</PrivateAssets>
|
||||
</PackageReference>
|
||||
<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="Microsoft.AspNetCore.Authentication.OpenIdConnect" 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.Extensions.HealthChecks.Data\Microsoft.Extensions.HealthChecks.Data.csproj" />
|
||||
<ProjectReference Include="..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks\Microsoft.Extensions.HealthChecks.csproj" />
|
||||
<ProjectReference Include="..\..\BuildingBlocks\Resilience\Resilience.Http\Resilience.Http.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -4,7 +4,7 @@
|
||||
"BasketUrl": "http://localhost:5103",
|
||||
"IdentityUrl": "http://localhost:5105",
|
||||
"CallBackUrl": "http://localhost:5100/",
|
||||
"ActivateCircuitBreaker": "True",
|
||||
"UseResilientHttp": "True",
|
||||
"Logging": {
|
||||
"IncludeScopes": false,
|
||||
"LogLevel": {
|
||||
|
@ -29,7 +29,7 @@
|
||||
</article>
|
||||
<br/>
|
||||
<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>
|
||||
|
@ -18,7 +18,7 @@ export class OrdersNewComponent implements OnInit {
|
||||
private errorReceived: Boolean;
|
||||
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
|
||||
this.order = service.mapBasketAndIdentityInfoNewOrder();
|
||||
this.newOrderForm = fb.group({
|
||||
@ -54,10 +54,7 @@ export class OrdersNewComponent implements OnInit {
|
||||
return Observable.throw(errMessage);
|
||||
})
|
||||
.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.basketEvents.orderCreated();
|
||||
|
||||
this.router.navigate(['orders']);
|
||||
this.router.navigate(['orders']);
|
||||
});
|
||||
this.errorReceived = false;
|
||||
this.isOrderProcessing = true;
|
||||
|
@ -9,6 +9,7 @@ namespace eShopConContainers.WebSPA
|
||||
{
|
||||
var host = new WebHostBuilder()
|
||||
.UseKestrel()
|
||||
.UseHealthChecks("/hc")
|
||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||
.UseIISIntegration()
|
||||
.UseStartup<Startup>()
|
||||
|
@ -10,6 +10,7 @@ using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using eShopOnContainers.WebSPA;
|
||||
using Microsoft.Extensions.HealthChecks;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace eShopConContainers.WebSPA
|
||||
{
|
||||
@ -42,7 +43,10 @@ namespace eShopConContainers.WebSPA
|
||||
{
|
||||
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);
|
||||
|
@ -25,7 +25,7 @@ namespace WebStatus.Controllers
|
||||
|
||||
foreach (var checkResult in result.Results)
|
||||
{
|
||||
data.AddResult(checkResult.Value);
|
||||
data.AddResult(checkResult.Key, checkResult.Value);
|
||||
}
|
||||
|
||||
return View(data);
|
||||
|
@ -9,13 +9,13 @@ namespace WebStatus.Viewmodels
|
||||
public class HealthStatusViewModel
|
||||
{
|
||||
private readonly CheckStatus _overall;
|
||||
private readonly List<IHealthCheckResult> _results;
|
||||
private readonly Dictionary<string, IHealthCheckResult> _results;
|
||||
|
||||
public CheckStatus OverallStatus => _overall;
|
||||
public IEnumerable<IHealthCheckResult> Results => _results;
|
||||
private HealthStatusViewModel() => _results = new List<IHealthCheckResult>();
|
||||
public IEnumerable<NamedCheckResult> Results => _results.Select(kvp => new NamedCheckResult(kvp.Key, kvp.Value));
|
||||
private HealthStatusViewModel() => _results = new Dictionary<string, IHealthCheckResult>();
|
||||
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="col-md-10">
|
||||
<h4 class="list-group-status-item-title">@result.Data["url"]</h4>
|
||||
<p class="list-group-item-text">@result.Description</p>
|
||||
|
||||
<h4 class="list-group-status-item-title">@result.Name</h4>
|
||||
<p class="list-group-item-text">
|
||||
@if (result.Result.Data.ContainsKey("url")) {
|
||||
<p>@result.Result.Data["url"]</p>
|
||||
}
|
||||
@result.Result.Description
|
||||
</p>
|
||||
</div>
|
||||
<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
|
||||
{
|
||||
<span class="label label-default">@result.CheckStatus</span>
|
||||
<span class="label label-default">@result.Result.CheckStatus</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -4,6 +4,16 @@
|
||||
<TargetFramework>netcoreapp1.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Services\Catalog\settings.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Services\Catalog\settings.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.DotNet.InternalAbstractions" Version="1.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0-preview-20170106-08" />
|
||||
@ -23,7 +33,7 @@
|
||||
<None Update="appsettings.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="settings.json">
|
||||
<None Update="Services\Ordering\settings.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
@ -14,7 +14,7 @@ namespace FunctionalTests.Services.Catalog
|
||||
public TestServer CreateServer()
|
||||
{
|
||||
var webHostBuilder = new WebHostBuilder();
|
||||
webHostBuilder.UseContentRoot(Directory.GetCurrentDirectory());
|
||||
webHostBuilder.UseContentRoot(Directory.GetCurrentDirectory() + "\\Services\\Catalog");
|
||||
webHostBuilder.UseStartup<Startup>();
|
||||
|
||||
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()
|
||||
{
|
||||
var webHostBuilder = new WebHostBuilder();
|
||||
webHostBuilder.UseContentRoot(Directory.GetCurrentDirectory());
|
||||
webHostBuilder.UseContentRoot(Directory.GetCurrentDirectory() + "\\Services\\Ordering");
|
||||
webHostBuilder.UseStartup<OrderingTestsStartup>();
|
||||
|
||||
return new TestServer(webHostBuilder);
|
||||
|
@ -12,9 +12,16 @@
|
||||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Services\Catalog\settings.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!--<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>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
@ -35,7 +42,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Update="settings.json">
|
||||
<Content Update="Services\Ordering\settings.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
@ -12,7 +12,7 @@ namespace IntegrationTests.Services.Catalog
|
||||
public TestServer CreateServer()
|
||||
{
|
||||
var webHostBuilder = new WebHostBuilder();
|
||||
webHostBuilder.UseContentRoot(Directory.GetCurrentDirectory());
|
||||
webHostBuilder.UseContentRoot(Directory.GetCurrentDirectory() + "\\Services\\Catalog");
|
||||
webHostBuilder.UseStartup<Startup>();
|
||||
|
||||
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()
|
||||
{
|
||||
var webHostBuilder = new WebHostBuilder();
|
||||
webHostBuilder.UseContentRoot(Directory.GetCurrentDirectory());
|
||||
webHostBuilder.UseContentRoot(Directory.GetCurrentDirectory() + "\\Services\\Ordering");
|
||||
webHostBuilder.UseStartup<OrderingTestsStartup>();
|
||||
|
||||
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;",
|
||||
"ExternalCatalogBaseUrl": "http://localhost:5101",
|
||||
"IdentityUrl": "http://localhost:5105",
|
||||
"isTest": "true"
|
||||
"isTest": "true",
|
||||
"EventBusConnection": "localhost"
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user