Merge branch 'master' into xamarin

This commit is contained in:
David Britch 2017-04-13 10:44:20 +01:00
commit 76f95c0662
362 changed files with 1703 additions and 84271 deletions

5
.gitignore vendored
View File

@ -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/
# MSTest test Results
[Tt]est[Rr]esult*/
@ -256,4 +256,5 @@ pub/
/src/Services/Identity/eShopOnContainers.Identity/Properties/launchSettings.json
#Ignore marker-file used to know which docker files we have.
.eshopdocker_*
.eshopdocker_*
/src/Web/WebMVC/wwwroot/lib

View File

@ -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! :)
@ -30,15 +34,14 @@ Additional miroservice styles with other frameworks and No-SQL databases will be
## Related documentation and guidance
While developing this reference application, we are creating a reference Guide/eBook named <b>"Architecting and Developing Containerized and Microservice based .NET Applications"</b> which explains in detail how to develop this kind of architectural style (microservices, Docker containers, Domain-Driven Design for certain microservices) plus other simpler architectural styles, like monolithic apps that can also live as Docker containers.
<p>
There's also an additional eBook focusing on Containers/Docker lifecycle (DevOps, CI/CD, etc.) with Microsoft Tools, already published.
You can start reviewing these Guides/eBooks here:
There are also additional eBooks focusing on Containers/Docker lifecycle (DevOps, CI/CD, etc.) with Microsoft Tools, already published plus an additional eBook focusing on Enterprise Apps Patterns with Xamarin.Forms.
You can download them and start reviewing these Guides/eBooks here:
<p>
You can download both eBooks from here:
| Architecting & Developing | Containers Lifecycle & CI/CD |
| ------------ | ------------|
| <a href='docs/architecting-and-developing-containerized-and-microservice-based-net-applications-ebook-early-draft.pdf'><img src="img/ebook_arch_dev_microservices_containers_cover.png"> </a> | <a href='https://aka.ms/dockerlifecycleebook'> <img src="img/ebook_containers_lifecycle.png"> </a> |
| <a href='docs/architecting-and-developing-containerized-and-microservice-based-net-applications-ebook-early-draft.pdf'>**Download** (Early DRAFT, still work in progress)</a> | <a href='https://aka.ms/dockerlifecycleebook'>**Download** - First Edition from late 2016</a> |
| Architecting & Developing | Containers Lifecycle & CI/CD | App patterns with Xamarin.Forms |
| ------------ | ------------| ------------|
| <a href='https://aka.ms/microservicesebook'><img src="img/ebook_arch_dev_microservices_containers_cover.png"> </a> | <a href='https://aka.ms/dockerlifecycleebook'> <img src="img/ebook_containers_lifecycle.png"> </a> | <a href='https://aka.ms/xamarinpatternsebook'> <img src="img/xamarin-enterprise-patterns-ebook-cover-small.png"> </a> |
| <sup> <a href='https://aka.ms/microservicesebook'>**Download** (Early DRAFT, still work in progress)</a> </sup> | <sup> <a href='https://aka.ms/dockerlifecycleebook'>**Download** (First Edition from late 2016) </a> </sup> | <sup> <a href='https://aka.ms/xamarinpatternsebook'>**Download** (Early DRAFT, still work in progress) </a> </sup> |
Send feedback to [cesardl@microsoft.com](cesardl@microsoft.com)
<p>
@ -89,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):

View File

@ -6,6 +6,7 @@ projectList=(
"/src/Services/Identity/Identity.API"
"/src/Web/WebMVC"
"/src/Web/WebSPA"
"/src/Web/WebStatus
)
# Build SPA app
@ -36,4 +37,4 @@ done
#fi
# No need to build the images, docker build or docker compose will
# do that using the images and containers defined in the docker-compose.yml file.
# do that using the images and containers defined in the docker-compose.yml file.

View File

@ -7,6 +7,7 @@ projectList=(
"../src/Services/Identity/Identity.API"
"../src/Web/WebMVC"
"../src/Web/WebSPA"
"../src/Web/WebStatus"
)
for project in "${projectList[@]}"

View File

@ -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

View File

@ -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

View File

@ -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:
@ -44,6 +45,7 @@ services:
- ASPNETCORE_URLS=http://0.0.0.0:5102
- ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word
- identityUrl=http://identity.api:5105 #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
- BasketUrl=http://basket.api:5103
- EventBusConnection=rabbitmq
ports:
- "5102:5102"
@ -85,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:

View File

@ -19,6 +19,7 @@ services:
- ASPNETCORE_URLS=http://0.0.0.0:5103
- ConnectionString=basket.data
- identityUrl=http://identity.api:5105 #Local: You need to open your host's firewall at range 5100-5105. at range 5100-5105.
- EventBusConnection=rabbitmq
ports:
- "5103:5103"
@ -28,6 +29,7 @@ services:
- ASPNETCORE_URLS=http://0.0.0.0:5101
- ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word
- ExternalCatalogBaseUrl=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5101 #Local: You need to open your host's firewall at range 5100-5105. at range 5100-5105.
- EventBusConnection=rabbitmq
ports:
- "5101:5101"
@ -38,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"
@ -47,6 +50,8 @@ services:
- ASPNETCORE_URLS=http://0.0.0.0:5102
- ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word
- identityUrl=http://identity.api:5105 #Local: You need to open your host's firewall at range 5100-5105. at range 5100-5105.
- BasketUrl=http://basket.api:5103
- EventBusConnection=rabbitmq
ports:
- "5102:5102"
@ -81,10 +86,13 @@ services:
webstatus:
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_ENVIRONMENT=Production
- ASPNETCORE_URLS=http://0.0.0.0:5107
- 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
- mvc=http://webmvc:5100/hc
- spa=http://webspa:5104/hc
- IdentityUrl=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5105 #Local: Use ${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}, if using external IP or DNS name from browser.
ports:
- "5107:5107"
- "5107:5107"

View File

@ -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
@ -39,24 +39,10 @@ EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ordering.Infrastructure", "src\Services\Ordering\Ordering.Infrastructure\Ordering.Infrastructure.csproj", "{95F1F07C-4D92-4742-BD07-E5B805AAB651}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTest", "test\Services\UnitTest\UnitTest.csproj", "{7796F5D8-31FC-45A4-B673-19DE5BA194CF}"
ProjectSection(ProjectDependencies) = postProject
{A579E108-5445-403D-A407-339AC4D1611B} = {A579E108-5445-403D-A407-339AC4D1611B}
{F16E3C6A-1C94-4EAB-BE91-099618060B68} = {F16E3C6A-1C94-4EAB-BE91-099618060B68}
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Identity.API", "src\Services\Identity\Identity.API\Identity.API.csproj", "{A579E108-5445-403D-A407-339AC4D1611B}"
EndProject
Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{FEA0C318-FFED-4D39-8781-265718CA43DD}"
ProjectSection(ProjectDependencies) = postProject
{A579E108-5445-403D-A407-339AC4D1611B} = {A579E108-5445-403D-A407-339AC4D1611B}
{5B810E3D-112E-4857-B197-F09D2FD41E27} = {5B810E3D-112E-4857-B197-F09D2FD41E27}
{F16E3C6A-1C94-4EAB-BE91-099618060B68} = {F16E3C6A-1C94-4EAB-BE91-099618060B68}
{F0333D8E-0B27-42B7-B2C6-78F3657624E2} = {F0333D8E-0B27-42B7-B2C6-78F3657624E2}
{42681D9D-750A-4DF7-BD9F-9292CFD5C253} = {42681D9D-750A-4DF7-BD9F-9292CFD5C253}
{2110CBB0-3B38-4EE4-A743-DF6968D80D90} = {2110CBB0-3B38-4EE4-A743-DF6968D80D90}
{CFE2FACB-4538-4B99-8A10-306F3882952D} = {CFE2FACB-4538-4B99-8A10-306F3882952D}
{231226CE-690B-4979-8870-9A79D80928E2} = {231226CE-690B-4979-8870-9A79D80928E2}
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebSPA", "src\Web\WebSPA\WebSPA.csproj", "{F16E3C6A-1C94-4EAB-BE91-099618060B68}"
EndProject
@ -84,6 +70,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 +904,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 +985,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

View File

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26228.9
VisualStudioVersion = 15.0.26228.12
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{932D8224-11F6-4D07-B109-DA28AD288A63}"
EndProject
@ -71,10 +71,12 @@ Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-co
ProjectSection(ProjectDependencies) = postProject
{A579E108-5445-403D-A407-339AC4D1611B} = {A579E108-5445-403D-A407-339AC4D1611B}
{9842DB3A-1391-48C7-A49C-2FABD0A18AC2} = {9842DB3A-1391-48C7-A49C-2FABD0A18AC2}
{23FB706A-2701-41E9-8BF9-28936001CA41} = {23FB706A-2701-41E9-8BF9-28936001CA41}
{F0333D8E-0B27-42B7-B2C6-78F3657624E2} = {F0333D8E-0B27-42B7-B2C6-78F3657624E2}
{42681D9D-750A-4DF7-BD9F-9292CFD5C253} = {42681D9D-750A-4DF7-BD9F-9292CFD5C253}
{2110CBB0-3B38-4EE4-A743-DF6968D80D90} = {2110CBB0-3B38-4EE4-A743-DF6968D80D90}
{231226CE-690B-4979-8870-9A79D80928E2} = {231226CE-690B-4979-8870-9A79D80928E2}
{2DA840CE-FCEA-4CF7-B1A1-ADD7775E7357} = {2DA840CE-FCEA-4CF7-B1A1-ADD7775E7357}
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "BuildingBlocks", "BuildingBlocks", "{1EF3AC0F-F27C-46DD-AC53-D762D2C11C45}"
@ -89,6 +91,20 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IntegrationEventLogEF", "sr
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FunctionalTests", "test\Services\FunctionalTests\FunctionalTests.csproj", "{2DA840CE-FCEA-4CF7-B1A1-ADD7775E7357}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HealthChecks", "HealthChecks", "{96CE8CE7-BC97-4A53-899F-5EB63D7BBF7B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.HealthChecks", "src\BuildingBlocks\HealthChecks\src\Microsoft.AspNetCore.HealthChecks\Microsoft.AspNetCore.HealthChecks.csproj", "{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.HealthChecks", "src\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks\Microsoft.Extensions.HealthChecks.csproj", "{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.HealthChecks.Data", "src\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.Data\Microsoft.Extensions.HealthChecks.Data.csproj", "{0E7AEB45-80F2-42B9-96BB-3414669E58AA}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Resilience", "Resilience", "{D13768ED-5AF1-4E09-96DD-FF6E7A2E5E06}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Resilience.Http", "src\BuildingBlocks\Resilience\Resilience.Http\Resilience.Http.csproj", "{D92EB452-7A72-4B26-A8ED-0204CD376BC4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebStatus", "src\Web\WebStatus\WebStatus.csproj", "{23FB706A-2701-41E9-8BF9-28936001CA41}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
@ -1210,6 +1226,246 @@ Global
{2DA840CE-FCEA-4CF7-B1A1-ADD7775E7357}.Release|x64.Build.0 = Release|Any CPU
{2DA840CE-FCEA-4CF7-B1A1-ADD7775E7357}.Release|x86.ActiveCfg = Release|Any CPU
{2DA840CE-FCEA-4CF7-B1A1-ADD7775E7357}.Release|x86.Build.0 = Release|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.AppStore|ARM.ActiveCfg = Debug|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.AppStore|ARM.Build.0 = Debug|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.AppStore|iPhone.Build.0 = Debug|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.AppStore|x64.ActiveCfg = Debug|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.AppStore|x64.Build.0 = Debug|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.AppStore|x86.ActiveCfg = Debug|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.AppStore|x86.Build.0 = Debug|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.Debug|ARM.ActiveCfg = Debug|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.Debug|ARM.Build.0 = Debug|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.Debug|iPhone.Build.0 = Debug|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.Debug|x64.ActiveCfg = Debug|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.Debug|x64.Build.0 = Debug|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.Debug|x86.ActiveCfg = Debug|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.Debug|x86.Build.0 = Debug|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.Release|Any CPU.Build.0 = Release|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.Release|ARM.ActiveCfg = Release|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.Release|ARM.Build.0 = Release|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.Release|iPhone.ActiveCfg = Release|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.Release|iPhone.Build.0 = Release|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.Release|x64.ActiveCfg = Release|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.Release|x64.Build.0 = Release|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.Release|x86.ActiveCfg = Release|Any CPU
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3}.Release|x86.Build.0 = Release|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.AppStore|ARM.ActiveCfg = Debug|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.AppStore|ARM.Build.0 = Debug|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.AppStore|iPhone.Build.0 = Debug|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.AppStore|x64.ActiveCfg = Debug|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.AppStore|x64.Build.0 = Debug|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.AppStore|x86.ActiveCfg = Debug|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.AppStore|x86.Build.0 = Debug|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.Debug|ARM.ActiveCfg = Debug|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.Debug|ARM.Build.0 = Debug|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.Debug|iPhone.Build.0 = Debug|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.Debug|x64.ActiveCfg = Debug|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.Debug|x64.Build.0 = Debug|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.Debug|x86.ActiveCfg = Debug|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.Debug|x86.Build.0 = Debug|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.Release|Any CPU.Build.0 = Release|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.Release|ARM.ActiveCfg = Release|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.Release|ARM.Build.0 = Release|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.Release|iPhone.ActiveCfg = Release|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.Release|iPhone.Build.0 = Release|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.Release|x64.ActiveCfg = Release|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.Release|x64.Build.0 = Release|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.Release|x86.ActiveCfg = Release|Any CPU
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45}.Release|x86.Build.0 = Release|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.AppStore|ARM.ActiveCfg = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.AppStore|ARM.Build.0 = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.AppStore|iPhone.Build.0 = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.AppStore|x64.ActiveCfg = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.AppStore|x64.Build.0 = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.AppStore|x86.ActiveCfg = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.AppStore|x86.Build.0 = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Debug|ARM.ActiveCfg = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Debug|ARM.Build.0 = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Debug|iPhone.Build.0 = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Debug|x64.ActiveCfg = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Debug|x64.Build.0 = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Debug|x86.ActiveCfg = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Debug|x86.Build.0 = Debug|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Release|Any CPU.Build.0 = Release|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Release|ARM.ActiveCfg = Release|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Release|ARM.Build.0 = Release|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Release|iPhone.ActiveCfg = Release|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Release|iPhone.Build.0 = Release|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Release|x64.ActiveCfg = Release|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Release|x64.Build.0 = Release|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Release|x86.ActiveCfg = Release|Any CPU
{0E7AEB45-80F2-42B9-96BB-3414669E58AA}.Release|x86.Build.0 = Release|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.AppStore|ARM.ActiveCfg = Debug|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.AppStore|ARM.Build.0 = Debug|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.AppStore|iPhone.Build.0 = Debug|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.AppStore|x64.ActiveCfg = Debug|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.AppStore|x64.Build.0 = Debug|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.AppStore|x86.ActiveCfg = Debug|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.AppStore|x86.Build.0 = Debug|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.Debug|ARM.ActiveCfg = Debug|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.Debug|ARM.Build.0 = Debug|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.Debug|iPhone.Build.0 = Debug|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.Debug|x64.ActiveCfg = Debug|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.Debug|x64.Build.0 = Debug|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.Debug|x86.ActiveCfg = Debug|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.Debug|x86.Build.0 = Debug|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.Release|Any CPU.Build.0 = Release|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.Release|ARM.ActiveCfg = Release|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.Release|ARM.Build.0 = Release|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.Release|iPhone.ActiveCfg = Release|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.Release|iPhone.Build.0 = Release|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.Release|x64.ActiveCfg = Release|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.Release|x64.Build.0 = Release|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.Release|x86.ActiveCfg = Release|Any CPU
{D92EB452-7A72-4B26-A8ED-0204CD376BC4}.Release|x86.Build.0 = Release|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.AppStore|ARM.ActiveCfg = Debug|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.AppStore|ARM.Build.0 = Debug|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.AppStore|iPhone.Build.0 = Debug|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.AppStore|x64.ActiveCfg = Debug|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.AppStore|x64.Build.0 = Debug|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.AppStore|x86.ActiveCfg = Debug|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.AppStore|x86.Build.0 = Debug|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.Debug|Any CPU.Build.0 = Debug|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.Debug|ARM.ActiveCfg = Debug|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.Debug|ARM.Build.0 = Debug|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.Debug|iPhone.Build.0 = Debug|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.Debug|x64.ActiveCfg = Debug|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.Debug|x64.Build.0 = Debug|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.Debug|x86.ActiveCfg = Debug|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.Debug|x86.Build.0 = Debug|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.Release|Any CPU.ActiveCfg = Release|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.Release|Any CPU.Build.0 = Release|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.Release|ARM.ActiveCfg = Release|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.Release|ARM.Build.0 = Release|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.Release|iPhone.ActiveCfg = Release|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.Release|iPhone.Build.0 = Release|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.Release|x64.ActiveCfg = Release|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.Release|x64.Build.0 = Release|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.Release|x86.ActiveCfg = Release|Any CPU
{23FB706A-2701-41E9-8BF9-28936001CA41}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -1251,5 +1507,12 @@ Global
{52AF222A-258C-4032-ACDD-857D7251BC1E} = {B473B70F-0796-4862-B1AD-BB742D93B868}
{438B774F-5569-4DE2-AA62-3F8BAEB31C55} = {B473B70F-0796-4862-B1AD-BB742D93B868}
{2DA840CE-FCEA-4CF7-B1A1-ADD7775E7357} = {EF0337F2-ED00-4643-89FD-EE10863F1870}
{96CE8CE7-BC97-4A53-899F-5EB63D7BBF7B} = {1EF3AC0F-F27C-46DD-AC53-D762D2C11C45}
{FFFD3E09-A803-4F99-BAC5-C93ABA3E02D3} = {96CE8CE7-BC97-4A53-899F-5EB63D7BBF7B}
{EE65FA8B-1D87-4050-BC21-F305F2F8AE45} = {96CE8CE7-BC97-4A53-899F-5EB63D7BBF7B}
{0E7AEB45-80F2-42B9-96BB-3414669E58AA} = {96CE8CE7-BC97-4A53-899F-5EB63D7BBF7B}
{D13768ED-5AF1-4E09-96DD-FF6E7A2E5E06} = {1EF3AC0F-F27C-46DD-AC53-D762D2C11C45}
{D92EB452-7A72-4B26-A8ED-0204CD376BC4} = {D13768ED-5AF1-4E09-96DD-FF6E7A2E5E06}
{23FB706A-2701-41E9-8BF9-28936001CA41} = {E279BF0F-7F66-4F3A-A3AB-2CDA66C1CD04}
EndGlobalSection
EndGlobal

Binary file not shown.

Before

Width:  |  Height:  |  Size: 427 KiB

After

Width:  |  Height:  |  Size: 405 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 427 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -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();
}
});
}
}
}

View File

@ -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
{

View File

@ -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>

View File

@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http
{
public class ResiliencePolicy
{
}
}

View File

@ -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,55 +21,15 @@ 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()
);
}
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");
}
);
_policyWrapper = Policy.WrapAsync(policies);
}
public Task<string> GetStringAsync(string uri) =>
HttpInvoker(() =>

View File

@ -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
{

View File

@ -108,8 +108,9 @@
<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"
TargetType="{x:Type Label}">

View File

@ -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();
}
}
}

View File

@ -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);
}
}

View File

@ -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; }

View File

@ -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; }

View File

@ -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;

View File

@ -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
@ -117,7 +117,8 @@ namespace eShopOnContainers.Core.ViewModels
ShippingState = _shippingAddress.State,
ShippingCountry = _shippingAddress.Country,
ShippingStreet = _shippingAddress.Street,
ShippingCity = _shippingAddress.City,
ShippingCity = _shippingAddress.City,
ShippingZipCode = _shippingAddress.ZipCode,
Total = CalculateTotal(CreateOrderItems(orderItems))
};

View File

@ -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.CreateAuthorizationRequest();
}
else if (url.Contains(GlobalSetting.Instance.IdentityCallback))
else if (unescapedUrl.Contains(GlobalSetting.Instance.IdentityCallback))
{
var authResponse = new AuthorizeResponse(url);

View File

@ -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>

View File

@ -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);
}
}
}
}

View File

@ -83,6 +83,7 @@
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="1" />
</Grid.RowDefinitions>
<!-- IMAGE -->
@ -131,10 +132,24 @@
Text="{Binding Total, StringFormat='${0:N}'}"
Style="{StaticResource OrderTotalStyle}"/>
</Grid>
<Grid
<Grid
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>

View File

@ -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" />

View File

@ -24,5 +24,6 @@
</Applications>
<Capabilities>
<Capability Name="internetClient" />
<Capability Name="privateNetworkClientServer" />
</Capabilities>
</Package>

View File

@ -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());
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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,51 +145,28 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
{
var catalogItem = await _catalogContext.CatalogItems.SingleOrDefaultAsync(i => i.Id == productToUpdate.Id);
if (catalogItem == null) return NotFound();
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
var raiseProductPriceChangedEvent = catalogItem.Price != productToUpdate.Price;
var oldPrice = catalogItem.Price;
// 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();
//Save to EventLog only if product price changed
if (raiseProductPriceChangedEvent)
{
await eventLogService.SaveEventAsync(priceChangedEvent, _catalogContext.Database.CurrentTransaction.GetDbTransaction());
}
transaction.Commit();
}
});
//Publish to Event Bus only if product price changed
if (raiseProductPriceChangedEvent)
{
_eventBus.Publish(priceChangedEvent);
await eventLogService.MarkEventAsPublishedAsync(priceChangedEvent);
await _catalogIntegrationEventService.SaveEventAndCatalogContextChangesAsync(priceChangedEvent);
// Publish through the Event Bus and mark the saved event as published
await _catalogIntegrationEventService.PublishThroughEventBusAsync(priceChangedEvent);
}
else // Save updated product
{
await _catalogContext.SaveChangesAsync();
}
return Ok();
}

View File

@ -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();
//}
}
}

View File

@ -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());
});
}
}
}

View File

@ -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);
}
}

View File

@ -20,6 +20,8 @@
using System.IO;
using System.Data.Common;
using System.Reflection;
using global::Catalog.API.IntegrationEvents;
using System.Threading.Tasks;
public class Startup
{
@ -48,7 +50,7 @@
services.AddHealthChecks(checks =>
{
checks.AddUrlCheck(Configuration["ExternalCatalogBaseUrl"]);
checks.AddSqlCheck("CatalogDb", Configuration["ConnectionString"]);
});
services.AddMvc(options =>
@ -97,10 +99,10 @@
});
services.AddTransient<Func<DbConnection, IIntegrationEventLogService>>(
sp => (DbConnection c) => new IntegrationEventLogService(c));
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));
}

View File

@ -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",
},

View File

@ -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;

View File

@ -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
{

View File

@ -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">

View File

@ -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; }

View File

@ -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>();

View File

@ -4,6 +4,7 @@
},
"MvcClient": "http://localhost:5100",
"SpaClient": "http://localhost:5104",
"XamarinCallback": "http://localhost:5105/xamarincallback",
"Logging": {
"IncludeScopes": false,
"LogLevel": {

View File

@ -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 ILoggerFactory _logger;
public UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler(IOrderRepository orderRepository, ILoggerFactory logger)
private readonly IOrderingIntegrationEventService _orderingIntegrationEventService;
private readonly 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));
}
@ -26,12 +33,20 @@ namespace Ordering.API.Application.DomainEventHandlers.BuyerAndPaymentMethodVeri
var orderToUpdate = await _orderRepository.GetAsync(buyerPaymentMethodVerifiedEvent.OrderId);
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 }");
.LogTrace($"Order with Id: {buyerPaymentMethodVerifiedEvent.OrderId} has been successfully updated with a payment method id: { buyerPaymentMethodVerifiedEvent.Payment.Id }");
}
}
}
}

View File

@ -31,7 +31,9 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Autof
builder.RegisterAssemblyTypes(typeof(ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler).GetTypeInfo().Assembly)
.As(o => o.GetInterfaces()
.Where(i => i.IsClosedTypeOf(typeof(IAsyncNotificationHandler<>)))
.Select(i => new KeyedService("IAsyncNotificationHandler", i)));
.Select(i => new KeyedService("IAsyncNotificationHandler", i)))
.AsImplementedInterfaces();
builder
.RegisterAssemblyTypes(typeof(CreateOrderCommandValidator).GetTypeInfo().Assembly)

View File

@ -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");
});
}
}
}

View File

@ -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");
}
}
}

View File

@ -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");
});
}
}
}

View File

@ -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);
});
}
}
}

View File

@ -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)
{
}
}
}

View File

@ -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);
});
}
}
}

View File

@ -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)
{
}
}
}

View File

@ -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");
});

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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());
});
}
}
}

View File

@ -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>

View File

@ -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,9 +59,9 @@
services.AddHealthChecks(checks =>
{
checks.AddSqlCheck("Ordering_Db", Configuration["ConnectionString"]);
checks.AddSqlCheck("OrderingDb", Configuration["ConnectionString"]);
});
services.AddEntityFrameworkSqlServer()
.AddDbContext<OrderingContext>(options =>
{
@ -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)

View File

@ -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;

View File

@ -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() { }

View File

@ -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;

View File

@ -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>

View File

@ -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");

View File

@ -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");
}

View File

@ -0,0 +1,10 @@
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http;
using System;
namespace Microsoft.eShopOnContainers.WebMVC.Infrastructure
{
public interface IResilientHttpClientFactory
{
ResilientHttpClient CreateResilientHttpClient();
}
}

View 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");
})};
}
}

View File

@ -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
{

View File

@ -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
{

View File

@ -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
{

View File

@ -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
{
@ -48,7 +49,10 @@ namespace Microsoft.eShopOnContainers.WebMVC
services.AddHealthChecks(checks =>
{
checks.AddUrlCheck(Configuration["CallBackUrl"]);
checks.AddUrlCheck(Configuration["CatalogUrl"]);
checks.AddUrlCheck(Configuration["OrderingUrl"]);
checks.AddUrlCheck(Configuration["BasketUrl"]);
checks.AddUrlCheck(Configuration["IdentityUrl"]);
});
// Add application services.
@ -58,15 +62,16 @@ 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
{
services.AddTransient<IHttpClient, StandardHttpClient>();
}
}
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
@ -109,15 +114,10 @@ namespace Microsoft.eShopOnContainers.WebMVC
ResponseType = "code id_token",
SaveTokens = true,
GetClaimsFromUserInfoEndpoint = true,
RequireHttpsMetadata = false,
RequireHttpsMetadata = false,
Scope = { "openid", "profile", "orders", "basket" }
};
oidcOptions.Scope.Clear();
oidcOptions.Scope.Add("openid");
oidcOptions.Scope.Add("profile");
oidcOptions.Scope.Add("orders");
oidcOptions.Scope.Add("basket");
//Wait untill identity service is ready on compose.
app.UseOpenIdConnectAuthentication(oidcOptions);

View File

@ -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">&nbsp;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">&nbsp;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/>

View File

@ -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>
@ -63,4 +70,8 @@
</None>
</ItemGroup>
<ItemGroup>
<Folder Include="wwwroot\lib\" />
</ItemGroup>
</Project>

View File

@ -4,7 +4,7 @@
"BasketUrl": "http://localhost:5103",
"IdentityUrl": "http://localhost:5105",
"CallBackUrl": "http://localhost:5100/",
"ActivateCircuitBreaker": "True",
"UseResilientHttp": "True",
"Logging": {
"IncludeScopes": false,
"LogLevel": {

View File

@ -1,45 +0,0 @@
{
"name": "bootstrap",
"description": "The most popular front-end framework for developing responsive, mobile first projects on the web.",
"keywords": [
"css",
"js",
"sass",
"mobile-first",
"responsive",
"front-end",
"framework",
"web"
],
"homepage": "https://getbootstrap.com",
"license": "MIT",
"moduleType": "globals",
"main": [
"scss/bootstrap.scss",
"dist/js/bootstrap.js"
],
"ignore": [
"/.*",
"_config.yml",
"CNAME",
"composer.json",
"CONTRIBUTING.md",
"docs",
"js/tests",
"test-infra"
],
"dependencies": {
"jquery": "1.9.1 - 3",
"tether": "^1.3.7"
},
"version": "4.0.0-alpha.5",
"_release": "4.0.0-alpha.5",
"_resolution": {
"type": "version",
"tag": "v4.0.0-alpha.5",
"commit": "b5890e0608ad2262cde4a38e90afa19f1cb5d852"
},
"_source": "https://github.com/twbs/bootstrap.git",
"_target": "4.0.0-alpha.5",
"_originalSource": "bootstrap"
}

View File

@ -1,5 +0,0 @@
Bootstrap uses [GitHub's Releases feature](https://github.com/blog/1547-release-your-software) for its changelogs.
See [the Releases section of our GitHub project](https://github.com/twbs/bootstrap/releases) for changelogs for each release version of Bootstrap.
Release announcement posts on [the official Bootstrap blog](https://blog.getbootstrap.com) contain summaries of the most noteworthy changes made in each release.

View File

@ -1,8 +0,0 @@
source 'https://rubygems.org'
group :development, :test do
gem 'jekyll', '~> 3.3.0'
gem 'jekyll-redirect-from', '~> 0.11.0'
gem 'jekyll-sitemap', '~> 0.11.0'
gem 'scss_lint', '~> 0.50.2'
end

View File

@ -1,56 +0,0 @@
GEM
remote: https://rubygems.org/
specs:
addressable (2.4.0)
colorator (1.1.0)
ffi (1.9.14)
forwardable-extended (2.6.0)
jekyll (3.3.0)
addressable (~> 2.4)
colorator (~> 1.0)
jekyll-sass-converter (~> 1.0)
jekyll-watch (~> 1.1)
kramdown (~> 1.3)
liquid (~> 3.0)
mercenary (~> 0.3.3)
pathutil (~> 0.9)
rouge (~> 1.7)
safe_yaml (~> 1.0)
jekyll-redirect-from (0.11.0)
jekyll (>= 2.0)
jekyll-sass-converter (1.4.0)
sass (~> 3.4)
jekyll-sitemap (0.11.0)
addressable (~> 2.4.0)
jekyll-watch (1.5.0)
listen (~> 3.0, < 3.1)
kramdown (1.12.0)
liquid (3.0.6)
listen (3.0.8)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
mercenary (0.3.6)
pathutil (0.14.0)
forwardable-extended (~> 2.6)
rake (11.3.0)
rb-fsevent (0.9.7)
rb-inotify (0.9.7)
ffi (>= 0.5.0)
rouge (1.11.1)
safe_yaml (1.0.4)
sass (3.4.22)
scss_lint (0.50.2)
rake (>= 0.9, < 12)
sass (~> 3.4.20)
PLATFORMS
ruby
DEPENDENCIES
jekyll (~> 3.3.0)
jekyll-redirect-from (~> 0.11.0)
jekyll-sitemap (~> 0.11.0)
scss_lint (~> 0.50.2)
BUNDLED WITH
1.13.2

View File

@ -1,405 +0,0 @@
/*!
* Bootstrap's Gruntfile
* https://getbootstrap.com
* Copyright 2013-2016 The Bootstrap Authors
* Copyright 2013-2016 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/
module.exports = function (grunt) {
'use strict';
// Force use of Unix newlines
grunt.util.linefeed = '\n';
RegExp.quote = function (string) {
return string.replace(/[-\\^$*+?.()|[\]{}]/g, '\\$&');
};
var fs = require('fs');
var path = require('path');
var isTravis = require('is-travis');
var configBridge = grunt.file.readJSON('./grunt/configBridge.json', { encoding: 'utf8' });
Object.keys(configBridge.paths).forEach(function (key) {
configBridge.paths[key].forEach(function (val, i, arr) {
arr[i] = path.join('./docs', val);
});
});
// Project configuration.
grunt.initConfig({
// Metadata.
pkg: grunt.file.readJSON('package.json'),
banner: '/*!\n' +
' * Bootstrap v<%= pkg.version %> (<%= pkg.homepage %>)\n' +
' * Copyright 2011-<%= grunt.template.today("yyyy") %> <%= pkg.author %>\n' +
' * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n' +
' */\n',
jqueryCheck: 'if (typeof jQuery === \'undefined\') {\n' +
' throw new Error(\'Bootstrap\\\'s JavaScript requires jQuery\')\n' +
'}\n',
jqueryVersionCheck: '+function ($) {\n' +
' var version = $.fn.jquery.split(\' \')[0].split(\'.\')\n' +
' if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1) || (version[0] >= 4)) {\n' +
' throw new Error(\'Bootstrap\\\'s JavaScript requires at least jQuery v1.9.1 but less than v4.0.0\')\n' +
' }\n' +
'}(jQuery);\n\n',
// Task configuration.
clean: {
dist: 'dist',
docs: 'docs/dist'
},
// JS build configuration
babel: {
dev: {
options: {
sourceMap: true
},
files: {
'js/dist/util.js' : 'js/src/util.js',
'js/dist/alert.js' : 'js/src/alert.js',
'js/dist/button.js' : 'js/src/button.js',
'js/dist/carousel.js' : 'js/src/carousel.js',
'js/dist/collapse.js' : 'js/src/collapse.js',
'js/dist/dropdown.js' : 'js/src/dropdown.js',
'js/dist/modal.js' : 'js/src/modal.js',
'js/dist/scrollspy.js' : 'js/src/scrollspy.js',
'js/dist/tab.js' : 'js/src/tab.js',
'js/dist/tooltip.js' : 'js/src/tooltip.js',
'js/dist/popover.js' : 'js/src/popover.js'
}
},
dist: {
options: {
extends: '../../js/.babelrc'
},
files: {
'<%= concat.bootstrap.dest %>' : '<%= concat.bootstrap.dest %>'
}
}
},
stamp: {
options: {
banner: '<%= banner %>\n<%= jqueryCheck %>\n<%= jqueryVersionCheck %>\n+function () {\n',
footer: '\n}();'
},
bootstrap: {
files: {
src: '<%= concat.bootstrap.dest %>'
}
}
},
concat: {
options: {
// Custom function to remove all export and import statements
process: function (src) {
return src.replace(/^(export|import).*/gm, '');
}
},
bootstrap: {
src: [
'js/src/util.js',
'js/src/alert.js',
'js/src/button.js',
'js/src/carousel.js',
'js/src/collapse.js',
'js/src/dropdown.js',
'js/src/modal.js',
'js/src/scrollspy.js',
'js/src/tab.js',
'js/src/tooltip.js',
'js/src/popover.js'
],
dest: 'dist/js/<%= pkg.name %>.js'
}
},
uglify: {
options: {
compress: {
warnings: false
},
mangle: true,
preserveComments: /^!|@preserve|@license|@cc_on/i
},
core: {
src: '<%= concat.bootstrap.dest %>',
dest: 'dist/js/<%= pkg.name %>.min.js'
},
docsJs: {
src: configBridge.paths.docsJs,
dest: 'docs/assets/js/docs.min.js'
}
},
qunit: {
options: {
inject: 'js/tests/unit/phantom.js'
},
files: 'js/tests/index.html'
},
// CSS build configuration
scsslint: {
options: {
bundleExec: true,
config: 'scss/.scss-lint.yml',
reporterOutput: null
},
core: {
src: ['scss/*.scss', '!scss/_normalize.scss']
},
docs: {
src: ['docs/assets/scss/*.scss', '!docs/assets/scss/docs.scss']
}
},
cssmin: {
options: {
compatibility: 'ie9,-properties.zeroUnits',
sourceMap: true,
// sourceMapInlineSources: true,
advanced: false
},
core: {
files: [
{
expand: true,
cwd: 'dist/css',
src: ['*.css', '!*.min.css'],
dest: 'dist/css',
ext: '.min.css'
}
]
},
docs: {
files: [
{
expand: true,
cwd: 'docs/assets/css',
src: ['*.css', '!*.min.css'],
dest: 'docs/assets/css',
ext: '.min.css'
}
]
}
},
copy: {
docs: {
expand: true,
cwd: 'dist/',
src: [
'**/*'
],
dest: 'docs/dist/'
}
},
connect: {
server: {
options: {
port: 3000,
base: '.'
}
}
},
jekyll: {
options: {
bundleExec: true,
config: '_config.yml',
incremental: false
},
docs: {},
github: {
options: {
raw: 'github: true'
}
}
},
htmllint: {
options: {
ignore: [
'Attribute “autocomplete” is only allowed when the input type is “color”, “date”, “datetime”, “datetime-local”, “email”, “hidden”, “month”, “number”, “password”, “range”, “search”, “tel”, “text”, “time”, “url”, or “week”.',
'Attribute “autocomplete” not allowed on element “button” at this point.',
'Consider using the “h1” element as a top-level heading only (all “h1” elements are treated as top-level headings by many screen readers and other tools).',
'Element “div” not allowed as child of element “progress” in this context. (Suppressing further errors from this subtree.)',
'Element “img” is missing required attribute “src”.',
'The “color” input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.',
'The “date” input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.',
'The “datetime” input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.',
'The “datetime-local” input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.',
'The “month” input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.',
'The “time” input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.',
'The “week” input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.'
]
},
src: ['_gh_pages/**/*.html', 'js/tests/visual/*.html']
},
watch: {
src: {
files: '<%= concat.bootstrap.src %>',
tasks: ['babel:dev']
},
sass: {
files: 'scss/**/*.scss',
tasks: ['dist-css', 'docs']
},
docs: {
files: 'docs/assets/scss/**/*.scss',
tasks: ['dist-css', 'docs']
}
},
'saucelabs-qunit': {
all: {
options: {
build: process.env.TRAVIS_JOB_ID,
concurrency: 10,
maxRetries: 3,
maxPollRetries: 4,
urls: ['http://127.0.0.1:3000/js/tests/index.html?hidepassed'],
browsers: grunt.file.readYAML('grunt/sauce_browsers.yml')
}
}
},
exec: {
postcss: {
command: 'npm run postcss'
},
'postcss-docs': {
command: 'npm run postcss-docs'
},
htmlhint: {
command: 'npm run htmlhint'
},
'upload-preview': {
command: './grunt/upload-preview.sh'
}
},
buildcontrol: {
options: {
dir: '_gh_pages',
commit: true,
push: true,
message: 'Built %sourceName% from commit %sourceCommit% on branch %sourceBranch%'
},
pages: {
options: {
remote: 'git@github.com:twbs/derpstrap.git',
branch: 'gh-pages'
}
}
},
compress: {
main: {
options: {
archive: 'bootstrap-<%= pkg.version %>-dist.zip',
mode: 'zip',
level: 9,
pretty: true
},
files: [
{
expand: true,
cwd: 'dist/',
src: ['**'],
dest: 'bootstrap-<%= pkg.version %>-dist'
}
]
}
}
});
// These plugins provide necessary tasks.
require('load-grunt-tasks')(grunt, { scope: 'devDependencies',
// Exclude Sass compilers. We choose the one to load later on.
pattern: ['grunt-*', '!grunt-sass', '!grunt-contrib-sass'] });
require('time-grunt')(grunt);
// Docs HTML validation task
grunt.registerTask('validate-html', ['jekyll:docs', 'htmllint', 'exec:htmlhint']);
var runSubset = function (subset) {
return !process.env.TWBS_TEST || process.env.TWBS_TEST === subset;
};
var isUndefOrNonZero = function (val) {
return val === undefined || val !== '0';
};
// Test task.
var testSubtasks = [];
// Skip core tests if running a different subset of the test suite
if (runSubset('core') &&
// Skip core tests if this is a Savage build
process.env.TRAVIS_REPO_SLUG !== 'twbs-savage/bootstrap') {
testSubtasks = testSubtasks.concat(['dist-css', 'dist-js', 'test-scss', 'qunit', 'docs']);
}
// Skip HTML validation if running a different subset of the test suite
if (runSubset('validate-html') &&
isTravis &&
// Skip HTML5 validator when [skip validator] is in the commit message
isUndefOrNonZero(process.env.TWBS_DO_VALIDATOR)) {
testSubtasks.push('validate-html');
}
// Only run Sauce Labs tests if there's a Sauce access key
if (typeof process.env.SAUCE_ACCESS_KEY !== 'undefined' &&
// Skip Sauce if running a different subset of the test suite
runSubset('sauce-js-unit')) {
testSubtasks = testSubtasks.concat(['dist', 'docs-css', 'docs-js', 'clean:docs', 'copy:docs', 'exec:upload-preview']);
// Skip Sauce on Travis when [skip sauce] is in the commit message
if (isUndefOrNonZero(process.env.TWBS_DO_SAUCE)) {
testSubtasks.push('connect');
testSubtasks.push('saucelabs-qunit');
}
}
grunt.registerTask('test', testSubtasks);
// JS distribution task.
grunt.registerTask('dist-js', ['babel:dev', 'concat', 'babel:dist', 'stamp', 'uglify:core']);
grunt.registerTask('test-scss', ['scsslint:core']);
// CSS distribution task.
// Supported Compilers: sass (Ruby) and libsass.
(function (sassCompilerName) {
require('./grunt/bs-sass-compile/' + sassCompilerName + '.js')(grunt);
})(process.env.TWBS_SASS || 'libsass');
// grunt.registerTask('sass-compile', ['sass:core', 'sass:extras', 'sass:docs']);
grunt.registerTask('sass-compile', ['sass:core', 'sass:extras', 'sass:docs']);
grunt.registerTask('dist-css', ['sass-compile', 'exec:postcss', 'cssmin:core', 'cssmin:docs']);
// Full distribution task.
grunt.registerTask('dist', ['clean:dist', 'dist-css', 'dist-js']);
// Default task.
grunt.registerTask('default', ['clean:dist', 'test']);
// Docs task.
grunt.registerTask('docs-css', ['cssmin:docs', 'exec:postcss-docs']);
grunt.registerTask('lint-docs-css', ['scsslint:docs']);
grunt.registerTask('docs-js', ['uglify:docsJs']);
grunt.registerTask('docs', ['lint-docs-css', 'docs-css', 'docs-js', 'clean:docs', 'copy:docs']);
grunt.registerTask('docs-github', ['jekyll:github']);
grunt.registerTask('prep-release', ['dist', 'docs', 'docs-github', 'compress']);
// Publish to GitHub
grunt.registerTask('publish', ['buildcontrol:pages']);
};

View File

@ -1,22 +0,0 @@
Before opening an issue:
- [Search for duplicate or closed issues](https://github.com/twbs/bootstrap/issues?utf8=%E2%9C%93&q=is%3Aissue)
- [Validate](https://validator.w3.org/nu/) and [lint](https://github.com/twbs/bootlint#in-the-browser) any HTML to avoid common problems
- Prepare a [reduced test case](https://css-tricks.com/reduced-test-cases/) for any bugs
- Read the [contributing guidelines](https://github.com/twbs/bootstrap/blob/master/CONTRIBUTING.md)
When asking general "how to" questions:
- Please do not open an issue here
- Instead, ask for help on [StackOverflow, IRC, or Slack](https://github.com/twbs/bootstrap/blob/master/README.md#community)
When reporting a bug, include:
- Operating system and version (Windows, Mac OS X, Android, iOS, Win10 Mobile)
- Browser and version (Chrome, Firefox, Safari, IE, MS Edge, Opera 15+, Android Browser)
- Reduced test cases and potential fixes using [JS Bin](https://jsbin.com)
When suggesting a feature, include:
- As much detail as possible for what we should add and why it's important to Bootstrap
- Relevant links to prior art, screenshots, or live demos whenever possible

View File

@ -1,22 +0,0 @@
The MIT License (MIT)
Copyright (c) 2011-2016 Twitter, Inc.
Copyright (c) 2011-2016 The Bootstrap Authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -1,135 +0,0 @@
# [Bootstrap](https://getbootstrap.com)
[![Slack](https://bootstrap-slack.herokuapp.com/badge.svg)](https://bootstrap-slack.herokuapp.com)
![Bower version](https://img.shields.io/bower/v/bootstrap.svg)
[![npm version](https://img.shields.io/npm/v/bootstrap.svg)](https://www.npmjs.com/package/bootstrap)
[![Gem version](https://img.shields.io/gem/v/bootstrap.svg)](https://rubygems.org/gems/bootstrap)
[![Build Status](https://img.shields.io/travis/twbs/bootstrap/master.svg)](https://travis-ci.org/twbs/bootstrap)
[![devDependency Status](https://img.shields.io/david/dev/twbs/bootstrap.svg)](https://david-dm.org/twbs/bootstrap?type=dev)
[![Meteor Atmosphere](https://img.shields.io/badge/meteor-twbs%3Abootstrap-blue.svg)](https://atmospherejs.com/twbs/bootstrap)
[![Packagist Prerelease](https://img.shields.io/packagist/vpre/twbs/bootstrap.svg)](https://packagist.org/packages/twbs/bootstrap)
[![NuGet](https://img.shields.io/nuget/vpre/bootstrap.svg)](https://www.nuget.org/packages/bootstrap/4.0.0-alpha5)
[![Selenium Test Status](https://saucelabs.com/browser-matrix/bootstrap.svg)](https://saucelabs.com/u/bootstrap)
Bootstrap is a sleek, intuitive, and powerful front-end framework for faster and easier web development, created by [Mark Otto](https://twitter.com/mdo) and [Jacob Thornton](https://twitter.com/fat), and maintained by the [core team](https://github.com/orgs/twbs/people) with the massive support and involvement of the community.
To get started, check out <https://getbootstrap.com>!
## Table of contents
- [Quick start](#quick-start)
- [Bugs and feature requests](#bugs-and-feature-requests)
- [Documentation](#documentation)
- [Contributing](#contributing)
- [Community](#community)
- [Versioning](#versioning)
- [Creators](#creators)
- [Copyright and license](#copyright-and-license)
## Quick start
Several quick start options are available:
- [Download the latest release.](https://github.com/twbs/bootstrap/archive/v4.0.0-alpha.5.zip)
- Clone the repo: `git clone https://github.com/twbs/bootstrap.git`
- Install with [npm](https://www.npmjs.com): `npm install bootstrap@4.0.0-alpha.5`
- Install with [Meteor](https://www.meteor.com): `meteor add twbs:bootstrap@=4.0.0-alpha.5`
- Install with [Composer](https://getcomposer.org): `composer require twbs/bootstrap`
- Install with [Bower](https://bower.io): `bower install bootstrap#v4.0.0-alpha.5`
- Install with [NuGet](https://www.nuget.org): CSS: `Install-Package bootstrap -Pre` Sass: `Install-Package bootstrap.sass -Pre` (`-Pre` is only required until Bootstrap v4 has a stable release).
Read the [Getting started page](https://getbootstrap.com/getting-started/) for information on the framework contents, templates and examples, and more.
### What's included
Within the download you'll find the following directories and files, logically grouping common assets and providing both compiled and minified variations. You'll see something like this:
```
bootstrap/
├── css/
│ ├── bootstrap.css
│ ├── bootstrap.css.map
│ ├── bootstrap.min.css
│ └── bootstrap.min.css.map
└── js/
├── bootstrap.js
└── bootstrap.min.js
```
We provide compiled CSS and JS (`bootstrap.*`), as well as compiled and minified CSS and JS (`bootstrap.min.*`). CSS [source maps](https://developer.chrome.com/devtools/docs/css-preprocessors) (`bootstrap.*.map`) are available for use with certain browsers' developer tools.
## Bugs and feature requests
Have a bug or a feature request? Please first read the [issue guidelines](https://github.com/twbs/bootstrap/blob/master/CONTRIBUTING.md#using-the-issue-tracker) and search for existing and closed issues. If your problem or idea is not addressed yet, [please open a new issue](https://github.com/twbs/bootstrap/issues/new).
## Documentation
Bootstrap's documentation, included in this repo in the root directory, is built with [Jekyll](https://jekyllrb.com) and publicly hosted on GitHub Pages at <https://getbootstrap.com>. The docs may also be run locally.
### Running documentation locally
1. Run through the [tooling setup](https://github.com/twbs/bootstrap/blob/v4-dev/docs/getting-started/build-tools.md#tooling-setup) to install Jekyll (the site builder) and other Ruby dependencies with `bundle install`.
2. Run `grunt` (or a specific set of Grunt tasks) to rebuild distributed CSS and JavaScript files, as well as our docs assets.
3. From the root `/bootstrap` directory, run `bundle exec jekyll serve` in the command line.
4. Open <http://localhost:9001> in your browser, and voilà.
Learn more about using Jekyll by reading its [documentation](https://jekyllrb.com/docs/home/).
### Documentation for previous releases
Documentation for v2.3.2 has been made available for the time being at <https://getbootstrap.com/2.3.2/> while folks transition to Bootstrap 3.
[Previous releases](https://github.com/twbs/bootstrap/releases) and their documentation are also available for download.
## Contributing
Please read through our [contributing guidelines](https://github.com/twbs/bootstrap/blob/master/CONTRIBUTING.md). Included are directions for opening issues, coding standards, and notes on development.
Moreover, if your pull request contains JavaScript patches or features, you must include [relevant unit tests](https://github.com/twbs/bootstrap/tree/master/js/tests). All HTML and CSS should conform to the [Code Guide](https://github.com/mdo/code-guide), maintained by [Mark Otto](https://github.com/mdo).
Editor preferences are available in the [editor config](https://github.com/twbs/bootstrap/blob/master/.editorconfig) for easy use in common text editors. Read more and download plugins at <http://editorconfig.org>.
## Community
Get updates on Bootstrap's development and chat with the project maintainers and community members.
- Follow [@getbootstrap on Twitter](https://twitter.com/getbootstrap).
- Read and subscribe to [The Official Bootstrap Blog](https://blog.getbootstrap.com).
- Join [the official Slack room](https://bootstrap-slack.herokuapp.com).
- Chat with fellow Bootstrappers in IRC. On the `irc.freenode.net` server, in the `##bootstrap` channel.
- Implementation help may be found at Stack Overflow (tagged [`bootstrap-4`](https://stackoverflow.com/questions/tagged/bootstrap-4)).
- Developers should use the keyword `bootstrap` on packages which modify or add to the functionality of Bootstrap when distributing through [npm](https://www.npmjs.com/browse/keyword/bootstrap) or similar delivery mechanisms for maximum discoverability.
## Versioning
For transparency into our release cycle and in striving to maintain backward compatibility, Bootstrap is maintained under [the Semantic Versioning guidelines](http://semver.org/). Sometimes we screw up, but we'll adhere to those rules whenever possible.
See [the Releases section of our GitHub project](https://github.com/twbs/bootstrap/releases) for changelogs for each release version of Bootstrap. Release announcement posts on [the official Bootstrap blog](https://blog.getbootstrap.com) contain summaries of the most noteworthy changes made in each release.
## Creators
**Mark Otto**
- <https://twitter.com/mdo>
- <https://github.com/mdo>
**Jacob Thornton**
- <https://twitter.com/fat>
- <https://github.com/fat>
## Copyright and license
Code and documentation copyright 2011-2016 the [Bootstrap Authors](https://github.com/twbs/bootstrap/graphs/contributors) and [Twitter, Inc.](https://twitter.com) Code released under the [MIT License](https://github.com/twbs/bootstrap/blob/master/LICENSE). Docs released under [Creative Commons](https://github.com/twbs/bootstrap/blob/master/docs/LICENSE).

View File

@ -1,35 +0,0 @@
{
"name": "bootstrap",
"description": "The most popular front-end framework for developing responsive, mobile first projects on the web.",
"keywords": [
"css",
"js",
"sass",
"mobile-first",
"responsive",
"front-end",
"framework",
"web"
],
"homepage": "https://getbootstrap.com",
"license": "MIT",
"moduleType": "globals",
"main": [
"scss/bootstrap.scss",
"dist/js/bootstrap.js"
],
"ignore": [
"/.*",
"_config.yml",
"CNAME",
"composer.json",
"CONTRIBUTING.md",
"docs",
"js/tests",
"test-infra"
],
"dependencies": {
"jquery": "1.9.1 - 3",
"tether": "^1.3.7"
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,996 +0,0 @@
.container {
margin-left: auto;
margin-right: auto;
padding-left: 15px;
padding-right: 15px;
}
.container::after {
content: "";
display: table;
clear: both;
}
@media (min-width: 576px) {
.container {
width: 540px;
max-width: 100%;
}
}
@media (min-width: 768px) {
.container {
width: 720px;
max-width: 100%;
}
}
@media (min-width: 992px) {
.container {
width: 960px;
max-width: 100%;
}
}
@media (min-width: 1200px) {
.container {
width: 1140px;
max-width: 100%;
}
}
.container-fluid {
margin-left: auto;
margin-right: auto;
padding-left: 15px;
padding-right: 15px;
}
.container-fluid::after {
content: "";
display: table;
clear: both;
}
.row {
margin-right: -15px;
margin-left: -15px;
}
.row::after {
content: "";
display: table;
clear: both;
}
@media (min-width: 576px) {
.row {
margin-right: -15px;
margin-left: -15px;
}
}
@media (min-width: 768px) {
.row {
margin-right: -15px;
margin-left: -15px;
}
}
@media (min-width: 992px) {
.row {
margin-right: -15px;
margin-left: -15px;
}
}
@media (min-width: 1200px) {
.row {
margin-right: -15px;
margin-left: -15px;
}
}
.col-xs, .col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12, .col-sm, .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12, .col-md, .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12, .col-lg, .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12, .col-xl, .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12 {
position: relative;
min-height: 1px;
padding-right: 15px;
padding-left: 15px;
}
@media (min-width: 576px) {
.col-xs, .col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12, .col-sm, .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12, .col-md, .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12, .col-lg, .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12, .col-xl, .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12 {
padding-right: 15px;
padding-left: 15px;
}
}
@media (min-width: 768px) {
.col-xs, .col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12, .col-sm, .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12, .col-md, .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12, .col-lg, .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12, .col-xl, .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12 {
padding-right: 15px;
padding-left: 15px;
}
}
@media (min-width: 992px) {
.col-xs, .col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12, .col-sm, .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12, .col-md, .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12, .col-lg, .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12, .col-xl, .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12 {
padding-right: 15px;
padding-left: 15px;
}
}
@media (min-width: 1200px) {
.col-xs, .col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12, .col-sm, .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12, .col-md, .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12, .col-lg, .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12, .col-xl, .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12 {
padding-right: 15px;
padding-left: 15px;
}
}
.col-xs-1 {
float: left;
width: 8.333333%;
}
.col-xs-2 {
float: left;
width: 16.666667%;
}
.col-xs-3 {
float: left;
width: 25%;
}
.col-xs-4 {
float: left;
width: 33.333333%;
}
.col-xs-5 {
float: left;
width: 41.666667%;
}
.col-xs-6 {
float: left;
width: 50%;
}
.col-xs-7 {
float: left;
width: 58.333333%;
}
.col-xs-8 {
float: left;
width: 66.666667%;
}
.col-xs-9 {
float: left;
width: 75%;
}
.col-xs-10 {
float: left;
width: 83.333333%;
}
.col-xs-11 {
float: left;
width: 91.666667%;
}
.col-xs-12 {
float: left;
width: 100%;
}
.pull-xs-0 {
right: auto;
}
.pull-xs-1 {
right: 8.333333%;
}
.pull-xs-2 {
right: 16.666667%;
}
.pull-xs-3 {
right: 25%;
}
.pull-xs-4 {
right: 33.333333%;
}
.pull-xs-5 {
right: 41.666667%;
}
.pull-xs-6 {
right: 50%;
}
.pull-xs-7 {
right: 58.333333%;
}
.pull-xs-8 {
right: 66.666667%;
}
.pull-xs-9 {
right: 75%;
}
.pull-xs-10 {
right: 83.333333%;
}
.pull-xs-11 {
right: 91.666667%;
}
.pull-xs-12 {
right: 100%;
}
.push-xs-0 {
left: auto;
}
.push-xs-1 {
left: 8.333333%;
}
.push-xs-2 {
left: 16.666667%;
}
.push-xs-3 {
left: 25%;
}
.push-xs-4 {
left: 33.333333%;
}
.push-xs-5 {
left: 41.666667%;
}
.push-xs-6 {
left: 50%;
}
.push-xs-7 {
left: 58.333333%;
}
.push-xs-8 {
left: 66.666667%;
}
.push-xs-9 {
left: 75%;
}
.push-xs-10 {
left: 83.333333%;
}
.push-xs-11 {
left: 91.666667%;
}
.push-xs-12 {
left: 100%;
}
.offset-xs-1 {
margin-left: 8.333333%;
}
.offset-xs-2 {
margin-left: 16.666667%;
}
.offset-xs-3 {
margin-left: 25%;
}
.offset-xs-4 {
margin-left: 33.333333%;
}
.offset-xs-5 {
margin-left: 41.666667%;
}
.offset-xs-6 {
margin-left: 50%;
}
.offset-xs-7 {
margin-left: 58.333333%;
}
.offset-xs-8 {
margin-left: 66.666667%;
}
.offset-xs-9 {
margin-left: 75%;
}
.offset-xs-10 {
margin-left: 83.333333%;
}
.offset-xs-11 {
margin-left: 91.666667%;
}
@media (min-width: 576px) {
.col-sm-1 {
float: left;
width: 8.333333%;
}
.col-sm-2 {
float: left;
width: 16.666667%;
}
.col-sm-3 {
float: left;
width: 25%;
}
.col-sm-4 {
float: left;
width: 33.333333%;
}
.col-sm-5 {
float: left;
width: 41.666667%;
}
.col-sm-6 {
float: left;
width: 50%;
}
.col-sm-7 {
float: left;
width: 58.333333%;
}
.col-sm-8 {
float: left;
width: 66.666667%;
}
.col-sm-9 {
float: left;
width: 75%;
}
.col-sm-10 {
float: left;
width: 83.333333%;
}
.col-sm-11 {
float: left;
width: 91.666667%;
}
.col-sm-12 {
float: left;
width: 100%;
}
.pull-sm-0 {
right: auto;
}
.pull-sm-1 {
right: 8.333333%;
}
.pull-sm-2 {
right: 16.666667%;
}
.pull-sm-3 {
right: 25%;
}
.pull-sm-4 {
right: 33.333333%;
}
.pull-sm-5 {
right: 41.666667%;
}
.pull-sm-6 {
right: 50%;
}
.pull-sm-7 {
right: 58.333333%;
}
.pull-sm-8 {
right: 66.666667%;
}
.pull-sm-9 {
right: 75%;
}
.pull-sm-10 {
right: 83.333333%;
}
.pull-sm-11 {
right: 91.666667%;
}
.pull-sm-12 {
right: 100%;
}
.push-sm-0 {
left: auto;
}
.push-sm-1 {
left: 8.333333%;
}
.push-sm-2 {
left: 16.666667%;
}
.push-sm-3 {
left: 25%;
}
.push-sm-4 {
left: 33.333333%;
}
.push-sm-5 {
left: 41.666667%;
}
.push-sm-6 {
left: 50%;
}
.push-sm-7 {
left: 58.333333%;
}
.push-sm-8 {
left: 66.666667%;
}
.push-sm-9 {
left: 75%;
}
.push-sm-10 {
left: 83.333333%;
}
.push-sm-11 {
left: 91.666667%;
}
.push-sm-12 {
left: 100%;
}
.offset-sm-0 {
margin-left: 0%;
}
.offset-sm-1 {
margin-left: 8.333333%;
}
.offset-sm-2 {
margin-left: 16.666667%;
}
.offset-sm-3 {
margin-left: 25%;
}
.offset-sm-4 {
margin-left: 33.333333%;
}
.offset-sm-5 {
margin-left: 41.666667%;
}
.offset-sm-6 {
margin-left: 50%;
}
.offset-sm-7 {
margin-left: 58.333333%;
}
.offset-sm-8 {
margin-left: 66.666667%;
}
.offset-sm-9 {
margin-left: 75%;
}
.offset-sm-10 {
margin-left: 83.333333%;
}
.offset-sm-11 {
margin-left: 91.666667%;
}
}
@media (min-width: 768px) {
.col-md-1 {
float: left;
width: 8.333333%;
}
.col-md-2 {
float: left;
width: 16.666667%;
}
.col-md-3 {
float: left;
width: 25%;
}
.col-md-4 {
float: left;
width: 33.333333%;
}
.col-md-5 {
float: left;
width: 41.666667%;
}
.col-md-6 {
float: left;
width: 50%;
}
.col-md-7 {
float: left;
width: 58.333333%;
}
.col-md-8 {
float: left;
width: 66.666667%;
}
.col-md-9 {
float: left;
width: 75%;
}
.col-md-10 {
float: left;
width: 83.333333%;
}
.col-md-11 {
float: left;
width: 91.666667%;
}
.col-md-12 {
float: left;
width: 100%;
}
.pull-md-0 {
right: auto;
}
.pull-md-1 {
right: 8.333333%;
}
.pull-md-2 {
right: 16.666667%;
}
.pull-md-3 {
right: 25%;
}
.pull-md-4 {
right: 33.333333%;
}
.pull-md-5 {
right: 41.666667%;
}
.pull-md-6 {
right: 50%;
}
.pull-md-7 {
right: 58.333333%;
}
.pull-md-8 {
right: 66.666667%;
}
.pull-md-9 {
right: 75%;
}
.pull-md-10 {
right: 83.333333%;
}
.pull-md-11 {
right: 91.666667%;
}
.pull-md-12 {
right: 100%;
}
.push-md-0 {
left: auto;
}
.push-md-1 {
left: 8.333333%;
}
.push-md-2 {
left: 16.666667%;
}
.push-md-3 {
left: 25%;
}
.push-md-4 {
left: 33.333333%;
}
.push-md-5 {
left: 41.666667%;
}
.push-md-6 {
left: 50%;
}
.push-md-7 {
left: 58.333333%;
}
.push-md-8 {
left: 66.666667%;
}
.push-md-9 {
left: 75%;
}
.push-md-10 {
left: 83.333333%;
}
.push-md-11 {
left: 91.666667%;
}
.push-md-12 {
left: 100%;
}
.offset-md-0 {
margin-left: 0%;
}
.offset-md-1 {
margin-left: 8.333333%;
}
.offset-md-2 {
margin-left: 16.666667%;
}
.offset-md-3 {
margin-left: 25%;
}
.offset-md-4 {
margin-left: 33.333333%;
}
.offset-md-5 {
margin-left: 41.666667%;
}
.offset-md-6 {
margin-left: 50%;
}
.offset-md-7 {
margin-left: 58.333333%;
}
.offset-md-8 {
margin-left: 66.666667%;
}
.offset-md-9 {
margin-left: 75%;
}
.offset-md-10 {
margin-left: 83.333333%;
}
.offset-md-11 {
margin-left: 91.666667%;
}
}
@media (min-width: 992px) {
.col-lg-1 {
float: left;
width: 8.333333%;
}
.col-lg-2 {
float: left;
width: 16.666667%;
}
.col-lg-3 {
float: left;
width: 25%;
}
.col-lg-4 {
float: left;
width: 33.333333%;
}
.col-lg-5 {
float: left;
width: 41.666667%;
}
.col-lg-6 {
float: left;
width: 50%;
}
.col-lg-7 {
float: left;
width: 58.333333%;
}
.col-lg-8 {
float: left;
width: 66.666667%;
}
.col-lg-9 {
float: left;
width: 75%;
}
.col-lg-10 {
float: left;
width: 83.333333%;
}
.col-lg-11 {
float: left;
width: 91.666667%;
}
.col-lg-12 {
float: left;
width: 100%;
}
.pull-lg-0 {
right: auto;
}
.pull-lg-1 {
right: 8.333333%;
}
.pull-lg-2 {
right: 16.666667%;
}
.pull-lg-3 {
right: 25%;
}
.pull-lg-4 {
right: 33.333333%;
}
.pull-lg-5 {
right: 41.666667%;
}
.pull-lg-6 {
right: 50%;
}
.pull-lg-7 {
right: 58.333333%;
}
.pull-lg-8 {
right: 66.666667%;
}
.pull-lg-9 {
right: 75%;
}
.pull-lg-10 {
right: 83.333333%;
}
.pull-lg-11 {
right: 91.666667%;
}
.pull-lg-12 {
right: 100%;
}
.push-lg-0 {
left: auto;
}
.push-lg-1 {
left: 8.333333%;
}
.push-lg-2 {
left: 16.666667%;
}
.push-lg-3 {
left: 25%;
}
.push-lg-4 {
left: 33.333333%;
}
.push-lg-5 {
left: 41.666667%;
}
.push-lg-6 {
left: 50%;
}
.push-lg-7 {
left: 58.333333%;
}
.push-lg-8 {
left: 66.666667%;
}
.push-lg-9 {
left: 75%;
}
.push-lg-10 {
left: 83.333333%;
}
.push-lg-11 {
left: 91.666667%;
}
.push-lg-12 {
left: 100%;
}
.offset-lg-0 {
margin-left: 0%;
}
.offset-lg-1 {
margin-left: 8.333333%;
}
.offset-lg-2 {
margin-left: 16.666667%;
}
.offset-lg-3 {
margin-left: 25%;
}
.offset-lg-4 {
margin-left: 33.333333%;
}
.offset-lg-5 {
margin-left: 41.666667%;
}
.offset-lg-6 {
margin-left: 50%;
}
.offset-lg-7 {
margin-left: 58.333333%;
}
.offset-lg-8 {
margin-left: 66.666667%;
}
.offset-lg-9 {
margin-left: 75%;
}
.offset-lg-10 {
margin-left: 83.333333%;
}
.offset-lg-11 {
margin-left: 91.666667%;
}
}
@media (min-width: 1200px) {
.col-xl-1 {
float: left;
width: 8.333333%;
}
.col-xl-2 {
float: left;
width: 16.666667%;
}
.col-xl-3 {
float: left;
width: 25%;
}
.col-xl-4 {
float: left;
width: 33.333333%;
}
.col-xl-5 {
float: left;
width: 41.666667%;
}
.col-xl-6 {
float: left;
width: 50%;
}
.col-xl-7 {
float: left;
width: 58.333333%;
}
.col-xl-8 {
float: left;
width: 66.666667%;
}
.col-xl-9 {
float: left;
width: 75%;
}
.col-xl-10 {
float: left;
width: 83.333333%;
}
.col-xl-11 {
float: left;
width: 91.666667%;
}
.col-xl-12 {
float: left;
width: 100%;
}
.pull-xl-0 {
right: auto;
}
.pull-xl-1 {
right: 8.333333%;
}
.pull-xl-2 {
right: 16.666667%;
}
.pull-xl-3 {
right: 25%;
}
.pull-xl-4 {
right: 33.333333%;
}
.pull-xl-5 {
right: 41.666667%;
}
.pull-xl-6 {
right: 50%;
}
.pull-xl-7 {
right: 58.333333%;
}
.pull-xl-8 {
right: 66.666667%;
}
.pull-xl-9 {
right: 75%;
}
.pull-xl-10 {
right: 83.333333%;
}
.pull-xl-11 {
right: 91.666667%;
}
.pull-xl-12 {
right: 100%;
}
.push-xl-0 {
left: auto;
}
.push-xl-1 {
left: 8.333333%;
}
.push-xl-2 {
left: 16.666667%;
}
.push-xl-3 {
left: 25%;
}
.push-xl-4 {
left: 33.333333%;
}
.push-xl-5 {
left: 41.666667%;
}
.push-xl-6 {
left: 50%;
}
.push-xl-7 {
left: 58.333333%;
}
.push-xl-8 {
left: 66.666667%;
}
.push-xl-9 {
left: 75%;
}
.push-xl-10 {
left: 83.333333%;
}
.push-xl-11 {
left: 91.666667%;
}
.push-xl-12 {
left: 100%;
}
.offset-xl-0 {
margin-left: 0%;
}
.offset-xl-1 {
margin-left: 8.333333%;
}
.offset-xl-2 {
margin-left: 16.666667%;
}
.offset-xl-3 {
margin-left: 25%;
}
.offset-xl-4 {
margin-left: 33.333333%;
}
.offset-xl-5 {
margin-left: 41.666667%;
}
.offset-xl-6 {
margin-left: 50%;
}
.offset-xl-7 {
margin-left: 58.333333%;
}
.offset-xl-8 {
margin-left: 66.666667%;
}
.offset-xl-9 {
margin-left: 75%;
}
.offset-xl-10 {
margin-left: 83.333333%;
}
.offset-xl-11 {
margin-left: 91.666667%;
}
}
/*# sourceMappingURL=bootstrap-grid.css.map */

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,453 +0,0 @@
/*! normalize.css v4.2.0 | MIT License | github.com/necolas/normalize.css */
html {
font-family: sans-serif;
line-height: 1.15;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}
body {
margin: 0;
}
article,
aside,
details,
figcaption,
figure,
footer,
header,
main,
menu,
nav,
section,
summary {
display: block;
}
audio,
canvas,
progress,
video {
display: inline-block;
}
audio:not([controls]) {
display: none;
height: 0;
}
progress {
vertical-align: baseline;
}
template,
[hidden] {
display: none;
}
a {
background-color: transparent;
-webkit-text-decoration-skip: objects;
}
a:active,
a:hover {
outline-width: 0;
}
abbr[title] {
border-bottom: none;
text-decoration: underline;
text-decoration: underline dotted;
}
b,
strong {
font-weight: inherit;
}
b,
strong {
font-weight: bolder;
}
dfn {
font-style: italic;
}
h1 {
font-size: 2em;
margin: 0.67em 0;
}
mark {
background-color: #ff0;
color: #000;
}
small {
font-size: 80%;
}
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
img {
border-style: none;
}
svg:not(:root) {
overflow: hidden;
}
code,
kbd,
pre,
samp {
font-family: monospace, monospace;
font-size: 1em;
}
figure {
margin: 1em 40px;
}
hr {
-webkit-box-sizing: content-box;
box-sizing: content-box;
height: 0;
overflow: visible;
}
button,
input,
optgroup,
select,
textarea {
font: inherit;
margin: 0;
}
optgroup {
font-weight: bold;
}
button,
input {
overflow: visible;
}
button,
select {
text-transform: none;
}
button,
html [type="button"],
[type="reset"],
[type="submit"] {
-webkit-appearance: button;
}
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
border-style: none;
padding: 0;
}
button:-moz-focusring,
[type="button"]:-moz-focusring,
[type="reset"]:-moz-focusring,
[type="submit"]:-moz-focusring {
outline: 1px dotted ButtonText;
}
fieldset {
border: 1px solid #c0c0c0;
margin: 0 2px;
padding: 0.35em 0.625em 0.75em;
}
legend {
-webkit-box-sizing: border-box;
box-sizing: border-box;
color: inherit;
display: table;
max-width: 100%;
padding: 0;
white-space: normal;
}
textarea {
overflow: auto;
}
[type="checkbox"],
[type="radio"] {
-webkit-box-sizing: border-box;
box-sizing: border-box;
padding: 0;
}
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
[type="search"] {
-webkit-appearance: textfield;
outline-offset: -2px;
}
[type="search"]::-webkit-search-cancel-button,
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-input-placeholder {
color: inherit;
opacity: 0.54;
}
::-webkit-file-upload-button {
-webkit-appearance: button;
font: inherit;
}
html {
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
*,
*::before,
*::after {
-webkit-box-sizing: inherit;
box-sizing: inherit;
}
@-ms-viewport {
width: device-width;
}
html {
font-size: 16px;
-ms-overflow-style: scrollbar;
-webkit-tap-highlight-color: transparent;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
font-size: 1rem;
line-height: 1.5;
color: #373a3c;
background-color: #fff;
}
[tabindex="-1"]:focus {
outline: none !important;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 0;
margin-bottom: .5rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title],
abbr[data-original-title] {
cursor: help;
border-bottom: 1px dotted #818a91;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: bold;
}
dd {
margin-bottom: .5rem;
margin-left: 0;
}
blockquote {
margin: 0 0 1rem;
}
a {
color: #0275d8;
text-decoration: none;
}
a:focus, a:hover {
color: #014c8c;
text-decoration: underline;
}
a:focus {
outline: 5px auto -webkit-focus-ring-color;
outline-offset: -2px;
}
a:not([href]):not([tabindex]) {
color: inherit;
text-decoration: none;
}
a:not([href]):not([tabindex]):focus, a:not([href]):not([tabindex]):hover {
color: inherit;
text-decoration: none;
}
a:not([href]):not([tabindex]):focus {
outline: none;
}
pre {
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
}
figure {
margin: 0 0 1rem;
}
img {
vertical-align: middle;
}
[role="button"] {
cursor: pointer;
}
a,
area,
button,
[role="button"],
input,
label,
select,
summary,
textarea {
-ms-touch-action: manipulation;
touch-action: manipulation;
}
table {
border-collapse: collapse;
background-color: transparent;
}
caption {
padding-top: 0.75rem;
padding-bottom: 0.75rem;
color: #818a91;
text-align: left;
caption-side: bottom;
}
th {
text-align: left;
}
label {
display: inline-block;
margin-bottom: .5rem;
}
button:focus {
outline: 1px dotted;
outline: 5px auto -webkit-focus-ring-color;
}
input,
button,
select,
textarea {
line-height: inherit;
}
input[type="radio"]:disabled,
input[type="checkbox"]:disabled {
cursor: not-allowed;
}
input[type="date"],
input[type="time"],
input[type="datetime-local"],
input[type="month"] {
-webkit-appearance: listbox;
}
textarea {
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
display: block;
width: 100%;
padding: 0;
margin-bottom: .5rem;
font-size: 1.5rem;
line-height: inherit;
}
input[type="search"] {
-webkit-appearance: none;
}
output {
display: inline-block;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.css.map */

File diff suppressed because one or more lines are too long

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