From 7b1d11e4a8fd448d495549fcfb3bac6742c6dfe4 Mon Sep 17 00:00:00 2001 From: Andre Passos Date: Thu, 13 Apr 2017 13:06:22 -0300 Subject: [PATCH 01/15] update to angular-cli and angular 4 --- .vscode/launch.json | 42 +++++++++ .vscode/tasks.json | 16 ++++ cli-linux/build-bits-linux.sh | 37 ++++---- docker-compose.ci.build.yml | 2 +- src/Web/WebMVC/wwwroot/css/site.min.css | 2 +- src/Web/WebSPA/.angular-cli.json | 57 +++++++++++ src/Web/WebSPA/.gitignore | 5 +- src/Web/WebSPA/Client/assets/.gitkeep | 0 .../Client/{ => assets}/images/arrow-down.png | Bin .../{ => assets}/images/arrow-right.svg | 0 .../Client/{ => assets}/images/brand.png | Bin .../Client/{ => assets}/images/brand_dark.png | Bin .../Client/{ => assets}/images/cart.png | Bin .../Client/{ => assets}/images/logout.png | Bin .../{ => assets}/images/main_banner.png | Bin .../{ => assets}/images/main_banner_text.png | Bin .../Client/{ => assets}/images/my_orders.png | Bin src/Web/WebSPA/Client/custom-typings.d.ts | 2 - .../Client/environments/environment.prod.ts | 3 + .../WebSPA/Client/environments/environment.ts | 8 ++ src/Web/WebSPA/Client/favicon.ico | Bin 0 -> 15086 bytes .../Client/{globals.scss => global.scss} | 1 + src/Web/WebSPA/Client/index.html | 18 ++++ src/Web/WebSPA/Client/main.ts | 20 +--- .../WebSPA/Client/modules/app.component.html | 4 +- .../WebSPA/Client/modules/app.component.ts | 2 +- src/Web/WebSPA/Client/modules/app.module.ts | 2 +- .../modules/catalog/catalog.component.html | 2 +- .../modules/catalog/catalog.component.scss | 4 +- src/Web/WebSPA/Client/polyfills.ts | 87 ++++++++++++----- src/Web/WebSPA/Client/test.ts | 32 +++++++ src/Web/WebSPA/Client/tsconfig.app.json | 13 +++ src/Web/WebSPA/Client/tsconfig.spec.json | 20 ++++ src/Web/WebSPA/Client/typings.d.ts | 5 + src/Web/WebSPA/Client/vendor.ts | 20 ---- .../Server/Controllers/HomeController.cs | 18 ---- src/Web/WebSPA/Startup.cs | 40 ++++---- src/Web/WebSPA/Views/Home/Index.cshtml | 12 --- src/Web/WebSPA/Views/Shared/_Layout.cshtml | 16 ---- src/Web/WebSPA/Views/_ViewImports.cshtml | 3 - src/Web/WebSPA/Views/_ViewStart.cshtml | 3 - src/Web/WebSPA/WebSPA.csproj | 15 +-- src/Web/WebSPA/config/helpers.js | 23 ----- src/Web/WebSPA/config/webpack.config.dev.js | 3 - src/Web/WebSPA/config/webpack.config.js | 77 --------------- src/Web/WebSPA/config/webpack.config.prod.js | 23 ----- .../WebSPA/config/webpack.config.vendor.js | 74 --------------- src/Web/WebSPA/package.json | 89 +++++------------- src/Web/WebSPA/tsconfig.json | 46 +++------ src/Web/WebSPA/wwwroot/web.config | 9 -- 50 files changed, 373 insertions(+), 482 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/tasks.json mode change 100644 => 100755 cli-linux/build-bits-linux.sh create mode 100644 src/Web/WebSPA/.angular-cli.json create mode 100644 src/Web/WebSPA/Client/assets/.gitkeep rename src/Web/WebSPA/Client/{ => assets}/images/arrow-down.png (100%) rename src/Web/WebSPA/Client/{ => assets}/images/arrow-right.svg (100%) rename src/Web/WebSPA/Client/{ => assets}/images/brand.png (100%) rename src/Web/WebSPA/Client/{ => assets}/images/brand_dark.png (100%) rename src/Web/WebSPA/Client/{ => assets}/images/cart.png (100%) rename src/Web/WebSPA/Client/{ => assets}/images/logout.png (100%) rename src/Web/WebSPA/Client/{ => assets}/images/main_banner.png (100%) rename src/Web/WebSPA/Client/{ => assets}/images/main_banner_text.png (100%) rename src/Web/WebSPA/Client/{ => assets}/images/my_orders.png (100%) delete mode 100644 src/Web/WebSPA/Client/custom-typings.d.ts create mode 100644 src/Web/WebSPA/Client/environments/environment.prod.ts create mode 100644 src/Web/WebSPA/Client/environments/environment.ts create mode 100644 src/Web/WebSPA/Client/favicon.ico rename src/Web/WebSPA/Client/{globals.scss => global.scss} (95%) create mode 100644 src/Web/WebSPA/Client/index.html create mode 100644 src/Web/WebSPA/Client/test.ts create mode 100644 src/Web/WebSPA/Client/tsconfig.app.json create mode 100644 src/Web/WebSPA/Client/tsconfig.spec.json create mode 100644 src/Web/WebSPA/Client/typings.d.ts delete mode 100644 src/Web/WebSPA/Client/vendor.ts delete mode 100644 src/Web/WebSPA/Views/Home/Index.cshtml delete mode 100644 src/Web/WebSPA/Views/Shared/_Layout.cshtml delete mode 100644 src/Web/WebSPA/Views/_ViewImports.cshtml delete mode 100644 src/Web/WebSPA/Views/_ViewStart.cshtml delete mode 100644 src/Web/WebSPA/config/helpers.js delete mode 100644 src/Web/WebSPA/config/webpack.config.dev.js delete mode 100644 src/Web/WebSPA/config/webpack.config.js delete mode 100644 src/Web/WebSPA/config/webpack.config.prod.js delete mode 100644 src/Web/WebSPA/config/webpack.config.vendor.js delete mode 100644 src/Web/WebSPA/wwwroot/web.config diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..f4432a933 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,42 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": ".NET Core Launch (web)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceRoot}/src/Services/Basket/Basket.API/bin/Debug/netcoreapp1.1/Basket.API.dll", + "args": [], + "cwd": "${workspaceRoot}/src/Services/Basket/Basket.API", + "stopAtEntry": false, + "internalConsoleOptions": "openOnSessionStart", + "launchBrowser": { + "enabled": true, + "args": "${auto-detect-url}", + "windows": { + "command": "cmd.exe", + "args": "/C start ${auto-detect-url}" + }, + "osx": { + "command": "open" + }, + "linux": { + "command": "xdg-open" + } + }, + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "sourceFileMap": { + "/Views": "${workspaceRoot}/Views" + } + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach", + "processId": "${command:pickProcess}" + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 000000000..4994c32bf --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,16 @@ +{ + "version": "0.1.0", + "command": "dotnet", + "isShellCommand": true, + "args": [], + "tasks": [ + { + "taskName": "build", + "args": [ + "${workspaceRoot}/src/Services/Basket/Basket.API/Basket.API.csproj" + ], + "isBuildCommand": true, + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/cli-linux/build-bits-linux.sh b/cli-linux/build-bits-linux.sh old mode 100644 new mode 100755 index e99df361b..7cda26d8c --- a/cli-linux/build-bits-linux.sh +++ b/cli-linux/build-bits-linux.sh @@ -1,18 +1,17 @@ - -projectList=( - "/src/Services/Catalog/Catalog.API" - "/src/Services/Basket/Basket.API" - "/src/Services/Ordering/Ordering.API" - "/src/Services/Identity/Identity.API" - "/src/Web/WebMVC" - "/src/Web/WebSPA" - "/src/Web/WebStatus +#!/bin/bash +declare -a projectList=( + '../src/Services/Catalog/Catalog.API' + '../src/Services/Basket/Basket.API' + '../src/Services/Ordering/Ordering.API' + '../src/Services/Identity/Identity.API' + '../src/Web/WebMVC' + '../src/Web/WebSPA' + '../src/Web/WebStatus' ) # Build SPA app -pushd $(pwd)/src/Web/WebSPA -npm rebuild node-sass -npm run build:prod +# pushd $(pwd)../src/Web/WebSPA +# npm run build:prod for project in "${projectList[@]}" do @@ -28,13 +27,13 @@ do done # remove old docker images: -#images=$(docker images --filter=reference="eshop/*" -q) -#if [ -n "$images" ]; then -# docker rm $(docker ps -a -q) -f -# echo "Deleting eShop images in local Docker repo" -# echo $images -# docker rmi $(docker images --filter=reference="eshop/*" -q) -f -#fi +images=$(docker images --filter=reference="eshop/*" -q) +if [ -n "$images" ]; then + docker rm $(docker ps -a -q) -f + echo "Deleting eShop images in local Docker repo" + echo $images + docker rmi $(docker images --filter=reference="eshop/*" -q) -f +fi # No need to build the images, docker build or docker compose will # do that using the images and containers defined in the docker-compose.yml file. diff --git a/docker-compose.ci.build.yml b/docker-compose.ci.build.yml index ef9705c8e..546b7690f 100644 --- a/docker-compose.ci.build.yml +++ b/docker-compose.ci.build.yml @@ -6,5 +6,5 @@ services: volumes: - .:/src working_dir: /src - command: /bin/bash -c "pushd ./src/Web/WebSPA && npm rebuild node-sass && pushd ./../../.. && dotnet restore ./eShopOnContainers-ServicesAndWebApps.sln && dotnet publish ./eShopOnContainers-ServicesAndWebApps.sln -c Release -o ./obj/Docker/publish" + command: /bin/bash -c "dotnet restore ./eShopOnContainers-ServicesAndWebApps.sln && dotnet publish ./eShopOnContainers-ServicesAndWebApps.sln -c Release -o ./obj/Docker/publish" diff --git a/src/Web/WebMVC/wwwroot/css/site.min.css b/src/Web/WebMVC/wwwroot/css/site.min.css index 4d03fa783..f5fc90999 100644 --- a/src/Web/WebMVC/wwwroot/css/site.min.css +++ b/src/Web/WebMVC/wwwroot/css/site.min.css @@ -1 +1 @@ -.esh-app-footer{background-color:#000;border-top:1px solid #eee;margin-top:2.5rem;padding-bottom:2.5rem;padding-top:2.5rem;width:100%}.esh-app-footer-brand{height:50px;width:230px}.esh-app-footer-text{color:#83d01b;line-height:50px;text-align:right;width:100%}@font-face{font-family:Montserrat;font-weight:400;src:url("../fonts/Montserrat-Regular.eot?") format("eot"),url("../fonts/Montserrat-Regular.woff") format("woff"),url("../fonts/Montserrat-Regular.ttf") format("truetype"),url("../fonts/Montserrat-Regular.svg#Montserrat") format("svg")}@font-face{font-family:Montserrat;font-weight:700;src:url("../fonts/Montserrat-Bold.eot?") format("eot"),url("../fonts/Montserrat-Bold.woff") format("woff"),url("../fonts/Montserrat-Bold.ttf") format("truetype"),url("../fonts/Montserrat-Bold.svg#Montserrat") format("svg")}html,body{font-family:Montserrat,sans-serif;font-size:16px;font-weight:400;z-index:10}*,*::after,*::before{box-sizing:border-box}.preloading{color:#00a69c;display:block;font-size:1.5rem;left:50%;position:fixed;top:50%;transform:translate(-50%,-50%)}select::-ms-expand{display:none}@media screen and (min-width:992px){.form-input{max-width:360px;width:360px}}.form-input{border-radius:0;height:45px;padding:10px}.form-input-small{max-width:100px !important}.form-input-medium{width:150px !important}.alert{padding-left:0}.alert-danger{background-color:transparent;border:0;color:#fb0d0d;font-size:12px}a,a:active,a:hover,a:visited{color:#000;text-decoration:none;transition:color .35s}a:hover,a:active{color:#75b918;transition:color .35s}.esh-basket{min-height:80vh}.esh-basket-titles{padding-bottom:1rem;padding-top:2rem}.esh-basket-titles--clean{padding-bottom:0;padding-top:0}.esh-basket-title{text-transform:uppercase}.esh-basket-items--border{border-bottom:1px solid #eee;padding:.5rem 0}.esh-basket-items--border:last-of-type{border-color:transparent}.esh-basket-item{font-size:1rem;font-weight:300}.esh-basket-item--middle{line-height:8rem}@media screen and (max-width:1024px){.esh-basket-item--middle{line-height:1rem}}.esh-basket-item--mark{color:#00a69c}.esh-basket-image{height:8rem}.esh-basket-input{line-height:1rem;width:100%}.esh-basket-checkout{border:none;border-radius:0;background-color:#83d01b;color:#fff;display:inline-block;font-size:1rem;font-weight:400;margin-top:1rem;padding:1rem 1.5rem;text-align:center;text-transform:uppercase;transition:all .35s}.esh-basket-checkout:hover{background-color:#4a760f;transition:all .35s}.esh-basket-margin12{margin-left:12px}.esh-basketstatus{cursor:pointer;display:inline-block;float:right;position:relative;transition:all .35s}.esh-basketstatus.is-disabled{opacity:.5;pointer-events:none}.esh-basketstatus-image{height:36px;margin-top:.5rem}.esh-basketstatus-badge{background-color:#83d01b;border-radius:50%;color:#fff;display:block;height:1.5rem;left:50%;position:absolute;text-align:center;top:0;transform:translateX(-38%);transition:all .35s;width:1.5rem}.esh-basketstatus:hover .esh-basketstatus-badge{background-color:transparent;color:#75b918;transition:all .35s}.esh-catalog-hero{background-image:url("../images/main_banner.png");background-size:cover;height:260px;width:100%}.esh-catalog-title{position:relative;top:74.28571px}.esh-catalog-filters{background-color:#00a69c;height:65px}.esh-catalog-filter{background-color:transparent;border-color:#00d9cc;color:#fff;cursor:pointer;margin-right:1rem;margin-top:.5rem;outline-color:#83d01b;padding-bottom:0;padding-left:.5rem;padding-right:.5rem;padding-top:1.5rem;min-width:140px;-webkit-appearance:none}.esh-catalog-filter option{background-color:#00a69c}.esh-catalog-label{display:inline-block;position:relative;z-index:0}.esh-catalog-label::before{color:rgba(255,255,255,.5);content:attr(data-title);font-size:.65rem;margin-top:.65rem;margin-left:.5rem;position:absolute;text-transform:uppercase;z-index:1}.esh-catalog-label::after{background-image:url("../images/arrow-down.png");height:7px;content:'';position:absolute;right:1.5rem;top:2.5rem;width:10px;z-index:1}.esh-catalog-send{background-color:#83d01b;color:#fff;cursor:pointer;font-size:1rem;transform:translateY(.5rem);padding:.5rem;transition:all .35s}.esh-catalog-send:hover{background-color:#4a760f;transition:all .35s}.esh-catalog-items{margin-top:1rem}.esh-catalog-item{text-align:center;margin-bottom:1.5rem;width:33%;display:inline-block;float:none !important}@media screen and (max-width:1024px){.esh-catalog-item{width:50%}}@media screen and (max-width:768px){.esh-catalog-item{width:100%}}.esh-catalog-thumbnail{max-width:370px;width:100%}.esh-catalog-button{background-color:#83d01b;border:none;color:#fff;cursor:pointer;font-size:1rem;height:3rem;margin-top:1rem;transition:all .35s;width:80%}.esh-catalog-button.is-disabled{opacity:.5;pointer-events:none}.esh-catalog-button:hover{background-color:#4a760f;transition:all .35s}.esh-catalog-name{font-size:1rem;font-weight:300;margin-top:.5rem;text-align:center;text-transform:uppercase}.esh-catalog-price{text-align:center;font-weight:900;font-size:28px}.esh-catalog-price::before{content:'$'}.esh-orders{min-height:80vh;overflow-x:hidden}.esh-orders-header{background-color:#00a69c;height:4rem}.esh-orders-back{color:rgba(255,255,255,.4);line-height:4rem;text-transform:uppercase;text-decoration:none;transition:color .35s}.esh-orders-back:hover{color:#fff;transition:color .35s}.esh-orders-titles{padding-bottom:1rem;padding-top:2rem}.esh-orders-title{text-transform:uppercase}.esh-orders-items{height:2rem;line-height:2rem;position:relative}.esh-orders-items:nth-of-type(2n+1):before{background-color:#eef;content:'';height:100%;left:0;margin-left:-100vw;position:absolute;top:0;width:200vw;z-index:-1}.esh-orders-item{font-weight:300}.esh-orders-item--hover{opacity:0;pointer-events:none}.esh-orders-items:hover .esh-orders-item--hover{opacity:1;pointer-events:all}.esh-orders-link{color:#83d01b;text-decoration:none;transition:color .35s}.esh-orders-link:hover{color:#75b918;transition:color .35s}.esh-orders_detail{min-height:80vh}.esh-orders_detail-section{padding:1rem 0}.esh-orders_detail-section--right{text-align:right}.esh-orders_detail-titles{padding-bottom:1rem;padding-top:2rem}.esh-orders_detail-title{text-transform:uppercase}.esh-orders_detail-items--border{border-bottom:1px solid #eee;padding:.5rem 0}.esh-orders_detail-items--border:last-of-type{border-color:transparent}.esh-orders_detail-item{font-size:1rem;font-weight:300}.esh-orders_detail-item--middle{line-height:8rem}@media screen and (max-width:768px){.esh-orders_detail-item--middle{line-height:1rem}}.esh-orders_detail-item--mark{color:#83d01b}.esh-orders_detail-image{height:8rem}.esh-orders_new{min-height:80vh}.esh-orders_new-header{background-color:#00a69c;height:4rem}.esh-orders_new-back{color:rgba(255,255,255,.4);line-height:4rem;text-decoration:none;text-transform:uppercase;transition:color .35s}.esh-orders_new-back:hover{color:#fff;transition:color .35s}.esh-orders_new-section{padding:1rem 0}.esh-orders_new-section--right{text-align:right}.esh-orders_new-placeOrder{background-color:#83d01b;border:0;border-radius:0;color:#fff;display:inline-block;font-size:1rem;font-weight:400;margin-top:1rem;padding:1rem 1.5rem;text-align:center;text-transform:uppercase;transition:all .35s}.esh-orders_new-placeOrder:hover{background-color:#4a760f;transition:all .35s}.esh-orders_new-titles{padding-bottom:1rem;padding-top:2rem}.esh-orders_new-title{font-size:1.25rem;text-transform:uppercase}.esh-orders_new-items--border{border-bottom:1px solid #eee;padding:.5rem 0}.esh-orders_new-items--border:last-of-type{border-color:transparent}.esh-orders_new-item{font-size:1rem;font-weight:300}.esh-orders_new-item--middle{line-height:8rem}@media screen and (max-width:768px){.esh-orders_new-item--middle{line-height:1rem}}.esh-orders_new-item--mark{color:#83d01b}.esh-orders_new-image{height:8rem}.esh-header{background-color:#00a69c;height:4rem}.esh-header-back{color:rgba(255,255,255,.5) !important;line-height:4rem;text-transform:uppercase;text-decoration:none;transition:color .35s}.esh-header-back:hover{color:#fff !important;transition:color .35s}.esh-identity{line-height:3rem;position:relative;text-align:right}.esh-identity-section{display:inline-block;width:100%}.esh-identity-name{display:inline-block}.esh-identity-name--upper{text-transform:uppercase}@media screen and (max-width:768px){.esh-identity-name{font-size:.85rem}}.esh-identity-image{display:inline-block}.esh-identity-drop{background:#fff;height:0;min-width:14rem;right:0;overflow:hidden;padding:.5rem;position:absolute;top:2.5rem;transition:height .35s}.esh-identity:hover .esh-identity-drop{border:1px solid #eee;height:7rem;transition:height .35s}.esh-identity-item{cursor:pointer;display:block;transition:color .35s}.esh-identity-item:hover{color:#75b918;transition:color .35s}.esh-pager-wrapper{padding-top:1rem;text-align:center}.esh-pager-item{margin:0 5vw}.esh-pager-item--navigable{display:inline-block;cursor:pointer}.esh-pager-item--navigable.is-disabled{opacity:0;pointer-events:none}.esh-pager-item--navigable:hover{color:#83d01b}@media screen and (max-width:1280px){.esh-pager-item{font-size:.85rem}}@media screen and (max-width:1024px){.esh-pager-item{margin:0 4vw}} \ No newline at end of file +.esh-app-footer{background-color:#000;border-top:1px solid #eee;margin-top:2.5rem;padding-bottom:2.5rem;padding-top:2.5rem;width:100%}.esh-app-footer-brand{height:50px;width:230px}.esh-app-footer-text{color:#83d01b;line-height:50px;text-align:right;width:100%}@font-face{font-family:Montserrat;font-weight:400;src:url("../fonts/Montserrat-Regular.eot?") format("eot"),url("../fonts/Montserrat-Regular.woff") format("woff"),url("../fonts/Montserrat-Regular.ttf") format("truetype"),url("../fonts/Montserrat-Regular.svg#Montserrat") format("svg")}@font-face{font-family:Montserrat;font-weight:700;src:url("../fonts/Montserrat-Bold.eot?") format("eot"),url("../fonts/Montserrat-Bold.woff") format("woff"),url("../fonts/Montserrat-Bold.ttf") format("truetype"),url("../fonts/Montserrat-Bold.svg#Montserrat") format("svg")}html,body{font-family:Montserrat,sans-serif;font-size:16px;font-weight:400;z-index:10}*,*::after,*::before{box-sizing:border-box}.preloading{color:#00a69c;display:block;font-size:1.5rem;left:50%;position:fixed;top:50%;transform:translate(-50%,-50%)}select::-ms-expand{display:none}@media screen and (min-width:992px){.form-input{max-width:360px;width:360px}}.form-input{border-radius:0;height:45px;padding:10px}.form-input-small{max-width:100px !important}.form-input-medium{width:150px !important}.alert{padding-left:0}.alert-danger{background-color:transparent;border:0;color:#fb0d0d;font-size:12px}a,a:active,a:hover,a:visited{color:#000;text-decoration:none;transition:color .35s}a:hover,a:active{color:#75b918;transition:color .35s}.esh-basket{min-height:80vh}.esh-basket-titles{padding-bottom:1rem;padding-top:2rem}.esh-basket-titles--clean{padding-bottom:0;padding-top:0}.esh-basket-title{text-transform:uppercase}.esh-basket-items--border{border-bottom:1px solid #eee;padding:.5rem 0}.esh-basket-items--border:last-of-type{border-color:transparent}.esh-basket-item{font-size:1rem;font-weight:300}.esh-basket-item--middle{line-height:8rem}@media screen and (max-width:1024px){.esh-basket-item--middle{line-height:1rem}}.esh-basket-item--mark{color:#00a69c}.esh-basket-image{height:8rem}.esh-basket-input{line-height:1rem;width:100%}.esh-basket-checkout{border:none;border-radius:0;background-color:#83d01b;color:#fff;display:inline-block;font-size:1rem;font-weight:400;margin-top:1rem;padding:1rem 1.5rem;text-align:center;text-transform:uppercase;transition:all .35s}.esh-basket-checkout:hover{background-color:#4a760f;transition:all .35s}.esh-basket-margin12{margin-left:12px}.esh-basketstatus{cursor:pointer;display:inline-block;float:right;position:relative;transition:all .35s}.esh-basketstatus.is-disabled{opacity:.5;pointer-events:none}.esh-basketstatus-image{height:36px;margin-top:.5rem}.esh-basketstatus-badge{background-color:#83d01b;border-radius:50%;color:#fff;display:block;height:1.5rem;left:50%;position:absolute;text-align:center;top:0;transform:translateX(-38%);transition:all .35s;width:1.5rem}.esh-basketstatus:hover .esh-basketstatus-badge{background-color:transparent;color:#75b918;transition:all .35s}.esh-catalog-hero{background-image:url("../images/main_banner.png");background-size:cover;height:260px;width:100%}.esh-catalog-title{position:relative;top:74.28571px}.esh-catalog-filters{background-color:#00a69c;height:65px}.esh-catalog-filter{background-color:transparent;border-color:#00d9cc;color:#fff;cursor:pointer;margin-right:1rem;margin-top:.5rem;outline-color:#83d01b;padding-bottom:0;padding-left:.5rem;padding-right:.5rem;padding-top:1.5rem;min-width:140px;-webkit-appearance:none}.esh-catalog-filter option{background-color:#00a69c}.esh-catalog-label{display:inline-block;position:relative;z-index:0}.esh-catalog-label::before{color:rgba(255,255,255,.5);content:attr(data-title);font-size:.65rem;margin-top:.65rem;margin-left:.5rem;position:absolute;text-transform:uppercase;z-index:1}.esh-catalog-label::after{background-image:url("../images/arrow-down.png");height:7px;content:'';position:absolute;right:1.5rem;top:2.5rem;width:10px;z-index:1}.esh-catalog-send{background-color:#83d01b;color:#fff;cursor:pointer;font-size:1rem;transform:translateY(.5rem);padding:.5rem;transition:all .35s}.esh-catalog-send:hover{background-color:#4a760f;transition:all .35s}.esh-catalog-items{margin-top:1rem}.esh-catalog-item{text-align:center;margin-bottom:1.5rem;width:33%;display:inline-block;float:none !important}@media screen and (max-width:1024px){.esh-catalog-item{width:50%}}@media screen and (max-width:768px){.esh-catalog-item{width:100%}}.esh-catalog-thumbnail{max-width:370px;width:100%}.esh-catalog-button{background-color:#83d01b;border:none;color:#fff;cursor:pointer;font-size:1rem;height:3rem;margin-top:1rem;transition:all .35s;width:80%}.esh-catalog-button.is-disabled{opacity:.5;pointer-events:none}.esh-catalog-button:hover{background-color:#4a760f;transition:all .35s}.esh-catalog-name{font-size:1rem;font-weight:300;margin-top:.5rem;text-align:center;text-transform:uppercase}.esh-catalog-price{text-align:center;font-weight:900;font-size:28px}.esh-catalog-price::before{content:'$'}.esh-orders{min-height:80vh;overflow-x:hidden}.esh-orders-header{background-color:#00a69c;height:4rem}.esh-orders-back{color:rgba(255,255,255,.4);line-height:4rem;text-transform:uppercase;text-decoration:none;transition:color .35s}.esh-orders-back:hover{color:#fff;transition:color .35s}.esh-orders-titles{padding-bottom:1rem;padding-top:2rem}.esh-orders-title{text-transform:uppercase}.esh-orders-items{height:2rem;line-height:2rem;position:relative}.esh-orders-items:nth-of-type(2n+1):before{background-color:#eef;content:'';height:100%;left:0;margin-left:-100vw;position:absolute;top:0;width:200vw;z-index:-1}.esh-orders-item{font-weight:300}.esh-orders-item--hover{opacity:0;pointer-events:none}.esh-orders-items:hover .esh-orders-item--hover{opacity:1;pointer-events:all}.esh-orders-link{color:#83d01b;text-decoration:none;transition:color .35s}.esh-orders-link:hover{color:#75b918;transition:color .35s}.esh-orders_new{min-height:80vh}.esh-orders_new-header{background-color:#00a69c;height:4rem}.esh-orders_new-back{color:rgba(255,255,255,.4);line-height:4rem;text-decoration:none;text-transform:uppercase;transition:color .35s}.esh-orders_new-back:hover{color:#fff;transition:color .35s}.esh-orders_new-section{padding:1rem 0}.esh-orders_new-section--right{text-align:right}.esh-orders_new-placeOrder{background-color:#83d01b;border:0;border-radius:0;color:#fff;display:inline-block;font-size:1rem;font-weight:400;margin-top:1rem;padding:1rem 1.5rem;text-align:center;text-transform:uppercase;transition:all .35s}.esh-orders_new-placeOrder:hover{background-color:#4a760f;transition:all .35s}.esh-orders_new-titles{padding-bottom:1rem;padding-top:2rem}.esh-orders_new-title{font-size:1.25rem;text-transform:uppercase}.esh-orders_new-items--border{border-bottom:1px solid #eee;padding:.5rem 0}.esh-orders_new-items--border:last-of-type{border-color:transparent}.esh-orders_new-item{font-size:1rem;font-weight:300}.esh-orders_new-item--middle{line-height:8rem}@media screen and (max-width:768px){.esh-orders_new-item--middle{line-height:1rem}}.esh-orders_new-item--mark{color:#83d01b}.esh-orders_new-image{height:8rem}.esh-orders_detail{min-height:80vh}.esh-orders_detail-section{padding:1rem 0}.esh-orders_detail-section--right{text-align:right}.esh-orders_detail-titles{padding-bottom:1rem;padding-top:2rem}.esh-orders_detail-title{text-transform:uppercase}.esh-orders_detail-items--border{border-bottom:1px solid #eee;padding:.5rem 0}.esh-orders_detail-items--border:last-of-type{border-color:transparent}.esh-orders_detail-item{font-size:1rem;font-weight:300}.esh-orders_detail-item--middle{line-height:8rem}@media screen and (max-width:768px){.esh-orders_detail-item--middle{line-height:1rem}}.esh-orders_detail-item--mark{color:#83d01b}.esh-orders_detail-image{height:8rem}.esh-identity{line-height:3rem;position:relative;text-align:right}.esh-identity-section{display:inline-block;width:100%}.esh-identity-name{display:inline-block}.esh-identity-name--upper{text-transform:uppercase}@media screen and (max-width:768px){.esh-identity-name{font-size:.85rem}}.esh-identity-image{display:inline-block}.esh-identity-drop{background:#fff;height:0;min-width:14rem;right:0;overflow:hidden;padding:.5rem;position:absolute;top:2.5rem;transition:height .35s}.esh-identity:hover .esh-identity-drop{border:1px solid #eee;height:7rem;transition:height .35s}.esh-identity-item{cursor:pointer;display:block;transition:color .35s}.esh-identity-item:hover{color:#75b918;transition:color .35s}.esh-header{background-color:#00a69c;height:4rem}.esh-header-back{color:rgba(255,255,255,.5) !important;line-height:4rem;text-transform:uppercase;text-decoration:none;transition:color .35s}.esh-header-back:hover{color:#fff !important;transition:color .35s}.esh-pager-wrapper{padding-top:1rem;text-align:center}.esh-pager-item{margin:0 5vw}.esh-pager-item--navigable{display:inline-block;cursor:pointer}.esh-pager-item--navigable.is-disabled{opacity:0;pointer-events:none}.esh-pager-item--navigable:hover{color:#83d01b}@media screen and (max-width:1280px){.esh-pager-item{font-size:.85rem}}@media screen and (max-width:1024px){.esh-pager-item{margin:0 4vw}} \ No newline at end of file diff --git a/src/Web/WebSPA/.angular-cli.json b/src/Web/WebSPA/.angular-cli.json new file mode 100644 index 000000000..df26c160c --- /dev/null +++ b/src/Web/WebSPA/.angular-cli.json @@ -0,0 +1,57 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "project": { + "name": "WebSPA" + }, + "apps": [ + { + "root": "Client", + "outDir": "wwwroot", + "assets": [ + "assets", + "favicon.ico" + ], + "index": "index.html", + "main": "main.ts", + "polyfills": "polyfills.ts", + "test": "test.ts", + "tsconfig": "tsconfig.app.json", + "testTsconfig": "tsconfig.spec.json", + "prefix": "app", + "styles": [ + "global.scss" + ], + "scripts": [], + "environmentSource": "environments/environment.ts", + "environments": { + "dev": "environments/environment.ts", + "prod": "environments/environment.prod.ts" + } + } + ], + "e2e": { + "protractor": { + "config": "./protractor.conf.js" + } + }, + "lint": [ + { + "project": "Client/tsconfig.app.json" + }, + { + "project": "Client/tsconfig.spec.json" + }, + { + "project": "e2e/tsconfig.e2e.json" + } + ], + "test": { + "karma": { + "config": "./karma.conf.js" + } + }, + "defaults": { + "styleExt": "scss", + "component": {} + } +} diff --git a/src/Web/WebSPA/.gitignore b/src/Web/WebSPA/.gitignore index 280332f79..a4cb1babf 100644 --- a/src/Web/WebSPA/.gitignore +++ b/src/Web/WebSPA/.gitignore @@ -177,10 +177,7 @@ ClientBin/ *.publishsettings node_modules/ bower_components/ -**/wwwroot/tmp/ -**/wwwroot/*.bundle.map -**/wwwroot/*.js -/wwwroot/dist/ +wwwroot/ orleans.codegen.cs diff --git a/src/Web/WebSPA/Client/assets/.gitkeep b/src/Web/WebSPA/Client/assets/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/src/Web/WebSPA/Client/images/arrow-down.png b/src/Web/WebSPA/Client/assets/images/arrow-down.png similarity index 100% rename from src/Web/WebSPA/Client/images/arrow-down.png rename to src/Web/WebSPA/Client/assets/images/arrow-down.png diff --git a/src/Web/WebSPA/Client/images/arrow-right.svg b/src/Web/WebSPA/Client/assets/images/arrow-right.svg similarity index 100% rename from src/Web/WebSPA/Client/images/arrow-right.svg rename to src/Web/WebSPA/Client/assets/images/arrow-right.svg diff --git a/src/Web/WebSPA/Client/images/brand.png b/src/Web/WebSPA/Client/assets/images/brand.png similarity index 100% rename from src/Web/WebSPA/Client/images/brand.png rename to src/Web/WebSPA/Client/assets/images/brand.png diff --git a/src/Web/WebSPA/Client/images/brand_dark.png b/src/Web/WebSPA/Client/assets/images/brand_dark.png similarity index 100% rename from src/Web/WebSPA/Client/images/brand_dark.png rename to src/Web/WebSPA/Client/assets/images/brand_dark.png diff --git a/src/Web/WebSPA/Client/images/cart.png b/src/Web/WebSPA/Client/assets/images/cart.png similarity index 100% rename from src/Web/WebSPA/Client/images/cart.png rename to src/Web/WebSPA/Client/assets/images/cart.png diff --git a/src/Web/WebSPA/Client/images/logout.png b/src/Web/WebSPA/Client/assets/images/logout.png similarity index 100% rename from src/Web/WebSPA/Client/images/logout.png rename to src/Web/WebSPA/Client/assets/images/logout.png diff --git a/src/Web/WebSPA/Client/images/main_banner.png b/src/Web/WebSPA/Client/assets/images/main_banner.png similarity index 100% rename from src/Web/WebSPA/Client/images/main_banner.png rename to src/Web/WebSPA/Client/assets/images/main_banner.png diff --git a/src/Web/WebSPA/Client/images/main_banner_text.png b/src/Web/WebSPA/Client/assets/images/main_banner_text.png similarity index 100% rename from src/Web/WebSPA/Client/images/main_banner_text.png rename to src/Web/WebSPA/Client/assets/images/main_banner_text.png diff --git a/src/Web/WebSPA/Client/images/my_orders.png b/src/Web/WebSPA/Client/assets/images/my_orders.png similarity index 100% rename from src/Web/WebSPA/Client/images/my_orders.png rename to src/Web/WebSPA/Client/assets/images/my_orders.png diff --git a/src/Web/WebSPA/Client/custom-typings.d.ts b/src/Web/WebSPA/Client/custom-typings.d.ts deleted file mode 100644 index 8e46a4e30..000000000 --- a/src/Web/WebSPA/Client/custom-typings.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -// Extra variables that live on Global that will be replaced by webpack DefinePlugin -// declare var process: any; diff --git a/src/Web/WebSPA/Client/environments/environment.prod.ts b/src/Web/WebSPA/Client/environments/environment.prod.ts new file mode 100644 index 000000000..3612073bc --- /dev/null +++ b/src/Web/WebSPA/Client/environments/environment.prod.ts @@ -0,0 +1,3 @@ +export const environment = { + production: true +}; diff --git a/src/Web/WebSPA/Client/environments/environment.ts b/src/Web/WebSPA/Client/environments/environment.ts new file mode 100644 index 000000000..b7f639aec --- /dev/null +++ b/src/Web/WebSPA/Client/environments/environment.ts @@ -0,0 +1,8 @@ +// The file contents for the current environment will overwrite these during build. +// The build system defaults to the dev environment which uses `environment.ts`, but if you do +// `ng build --env=prod` then `environment.prod.ts` will be used instead. +// The list of which env maps to which file can be found in `.angular-cli.json`. + +export const environment = { + production: false +}; diff --git a/src/Web/WebSPA/Client/favicon.ico b/src/Web/WebSPA/Client/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..e499a7453653d1e8662122a9d116bcebee99882d GIT binary patch literal 15086 zcmeHO33yaj6`laCt;(WZ)Vee-RccE`YO%F0)V5ZupG&oBKee>_(XYj-lr9uXEdjw& zt6iijwFv}5b~4%bg+KxcA$vj+vXMPx-zSspc>X)QnKy6lOkQ3F_|)F-yLoSB?!D*S zbMHOpzvrBoOg^TCre(`aTzpNBFEW`{Vb8 zbG#p&uF0aRGe>A*!s6PQzd_sH{&H&T%%IkeG)m6;4o%NYiT)=GBkg^rrma+WHkdMU zeoAp^U!~OS?^9{z%QQT4*4}1%dXmzP{?MiW_;@cRX5K^Z1p9C`In}_3B~(?r7o3o+ z|IEy^poxBw2{+N%7Bh{EwNc+dCDY$c2O>W}`$FGCT|N2I{=NNW6cBnjx4D7_hw8+9 z?F{ovS8oB&D@1%Rqkp>gpMqcikcAW;_gPBHyq9tdek)|%(w;;EgEcg6?dAT{Qu>8u ze-!;gW3DH2(ibV{@U{HeAqW4EMHCcuE#(zHCGC%O#oUkP{aXBPwGPnGa09PZDpl6( zq0pF5ir))LUy%0i8>patVOP+Bh-K8zGQ)Z+Wpu2A4zm2fPjg#>v_IPC7e6Q3V2o3x zTY105iZzRjzfsVRPF_t@Q{&S9pf$~V=M?j|Km2{7y<-pfP+aPrG&IsESdz(K{BZuy z;7~1{KJyR3Phr_h)Y6_J=qhcr{lmN4Ifcc3+Np-e-9Y8l zuZumZ47~e|d+Ehpt0*#ND@Da`v)7oUoz&gq(9cTy*`A=^-CIaS<=gq(zG$xnC0i)i z{87p|`2@ipC~+4J9Mu5xX_yg zrCX?=vQA-D7 z(lYzJmG*k+C$kM#@29}<#T1{uhB9*>qLam&1a<+|9b)V{Jw2feKkHL3eXZ=(yg)8E zi#TUE^Vk>ADMya~ii%lB)SnHbcE%HZjLAnC=YpmpGd=XDrmO<*I@4U>BoF7`w7Osc zW5uNu9{U;QYc<~o?q`0E2-)bPZ*kH!I77!z{f#y%AFA#uim&nG5FSf(9(52&S{z9MRDiK5ceuLx`%pZ0Z%i2EGl;SbvT zVho%`;BCG1(JnUS4*MAU!mgy+#vuC|ih(45z@OFH&-Z|h_P(HxdxK@XTlCN~e>5zv z*vVr!)*N%g*s~Sv9kf+shiBT+9}&(iieCD_4p?KyI6iD^H#0uycPj_js`{odzN=hm z?-vky8SB5-xzF^R=wlC*RsEI6ap)ktCuH0u?hLT=N*BV~W9+sNKM(cim& zE$g-c)^X$ZIyq@|I~Upb>hHi4^Z{rp73T&vL5BaI_xn8g2d$%h!B5!hfzT(}c2ui} z)%6bfcbv->bbzCj3LgmkCtCXPDZ2@G5N&;R&;8WgTB?htx)JC%x5W!z5Ac|}CT84C zXBrOJb&|T@rFd7l0xrob{1kAJKEl=Rr(j>HwyCN1wdhV_mml zF`Xy~aT{|mG9U0Vz&1SS@YNnM1TaM)W2dvNF)kPaK3(v>{+X)@w&5-Rhop7y{)fdn z!G3zHfd7%v7QSozhJ6hWN^9ED2iU*%F)_cI`T!UFrSQLH#|o;f&J=qD`$SuZhI`m% z+k6Jz;veGx6CnP8_=~I#4%Z7Dg*EWR|JI$W*e;iQlQpl5g?mo=v7gK;f1~_Eeirus zzz5mT93gy5RUBVJ{HwO{Am2@rMO6ze#;vexq_yFJwPR;%SW56#9-*gE3 z_TUeOpACNMsKhS_tOP#`e8-3>>h<|xeiwrOp1u;si;oN6EBtBNJBRGEf5-xH7d)J> zg3|d+!Y6|m)@;R_`~!0%))k%nCE6eMUR`|4=dy@>u-A)wW^cb|EbJ}*aZkuEepc)a z=R3H=rz7Ll({}+JK=e-`n3+>hO_n~(7AK)-9teE5fb zgx@nd`Bo|K0qi?4C#@8GA`axKz~@lc7))j!3qF6@df;50>ptN$%zXH_@}7sDfzL`y z54^+Q1OL}t$|U18^)%M=k^M6STbJGkhkKv&dY{hxQ$N z1$i2fjXCL7_U|A^#TDBc}J-1arf zKVpxWIX{=m9{w=oaEw{b@p_L)<>rKYG;Toc6I5aU7q5PYRFf1f8_WxpBaA3W)eQ{pTG^FU7}v(FHC9EP#w->9=I zSLE72-q2Ordz6fKd}oyZikdg2dtbI5?!H5u1F;XpK#<=B+yi|E%!j!_M!=`toG*oV z2z0FW3>)DevCZO&Kk?pC`|1^4=bh(1#!tYi&~J$UX7YZ_Em%*G8}2MhF~UD^nv&zC zm2F`760ujbzI|;wqn~3_?{KnVM)=3MD=PDop53IZ`-CsPp($L%$WL;<#F66hsGpcS)(>ZK z%+e|5hP+6e(YB6ss%;1qd4-6pX!!@8#u-rSCZqf#cTkqwa*%yBgTt;lm+CpVdxvg; z?$pKvm3aUY0RyRvQT_*pYWUv&fQyWvlW>p488=$;ik_BYLgbPn=TKdY@{jx!#C#Dm zROF*P;S1~NI;tk4`+bDtQ@F1vW2v8UX|AuTq$xUx;SSRd;-4>k2DE8id<^wQsimN z^f6=so0wnptP6Gk{rSir{ZWwj1--1!BT&{r{lvWxSSLRH8^WGN@w{+XP`A;(WZf}2y$azQJl9mUfpMhI}eY!dC#a;0h{}>-S75NReJP_j;0rLgU!2M{p zdz?4<#~hF|10M-|p6<@Ir}e=40ZYS%jLmZQ0zGf@k1iA` zx2UNlR>X>IK3lo3P&SJT$-kDKP1lbyzUvhj556Jfjq0&)+Od?+7n6U5rZ=B@`QN<# zW-6^nVhmp|t%y@ja_)+^3)T#AQQR426@D80{}gnBr})=n8-U_sZ>j zCyQkaV;pyC=Z<{^EDn7HTPLr#oWIk_`A3{_li!UVW50;6KK9g7%0Cq@@{qKA82t?X z@xNBc=k%m=6?@U#mgJ(hfRix?#8Exf-o6&TH|on();k~YkGmSM1Mrfkia0U+t@F_Z WdilCo?QHo#_@5}0cb^wq4*vo1a-z%t literal 0 HcmV?d00001 diff --git a/src/Web/WebSPA/Client/globals.scss b/src/Web/WebSPA/Client/global.scss similarity index 95% rename from src/Web/WebSPA/Client/globals.scss rename to src/Web/WebSPA/Client/global.scss index fde578e3d..e69b955dc 100644 --- a/src/Web/WebSPA/Client/globals.scss +++ b/src/Web/WebSPA/Client/global.scss @@ -1,3 +1,4 @@ +/* You can add global styles to this file, and also import other style files */ @import './modules/variables'; $dist: './fonts/Montserrat-Regular'; diff --git a/src/Web/WebSPA/Client/index.html b/src/Web/WebSPA/Client/index.html new file mode 100644 index 000000000..5d9432f70 --- /dev/null +++ b/src/Web/WebSPA/Client/index.html @@ -0,0 +1,18 @@ + + + + + eShopConContainers.WebSPA + + + + + + + +
+ +
+
+ + diff --git a/src/Web/WebSPA/Client/main.ts b/src/Web/WebSPA/Client/main.ts index ff50b8628..1f87381f8 100644 --- a/src/Web/WebSPA/Client/main.ts +++ b/src/Web/WebSPA/Client/main.ts @@ -1,23 +1,11 @@ -import './polyfills'; - -import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { enableProdMode } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; -import { AppModule } from './modules/app.module'; +import { AppModule } from './modules/app.module'; +import { environment } from './environments/environment'; -if (process.env.ENV === 'Development') { - // Development -} else { - // Production +if (environment.production) { enableProdMode(); } platformBrowserDynamic().bootstrapModule(AppModule); - -// Basic hot reloading support. Automatically reloads and restarts the Angular 2 app each time -// you modify source files. This will not preserve any application state other than the URL. -declare var module: any; - -if (module.hot) { - module.hot.accept(); -} diff --git a/src/Web/WebSPA/Client/modules/app.component.html b/src/Web/WebSPA/Client/modules/app.component.html index bd50dc066..14e322e61 100644 --- a/src/Web/WebSPA/Client/modules/app.component.html +++ b/src/Web/WebSPA/Client/modules/app.component.html @@ -4,7 +4,7 @@
- +
@@ -28,7 +28,7 @@
- +
diff --git a/src/Web/WebSPA/Client/modules/app.component.ts b/src/Web/WebSPA/Client/modules/app.component.ts index 00abe4ad4..dffd344e7 100644 --- a/src/Web/WebSPA/Client/modules/app.component.ts +++ b/src/Web/WebSPA/Client/modules/app.component.ts @@ -13,7 +13,7 @@ import { ConfigurationService } from './shared/services/configuration.service'; */ @Component({ - selector: 'esh-app.esh-app', + selector: 'esh-app', styleUrls: ['./app.component.scss'], templateUrl: './app.component.html' }) diff --git a/src/Web/WebSPA/Client/modules/app.module.ts b/src/Web/WebSPA/Client/modules/app.module.ts index 997eacbfb..f05d466d8 100644 --- a/src/Web/WebSPA/Client/modules/app.module.ts +++ b/src/Web/WebSPA/Client/modules/app.module.ts @@ -2,7 +2,7 @@ import { NgModule, NgModuleFactoryLoader } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; // import { FormsModule } from '@angular/forms'; import { HttpModule } from '@angular/http'; -import { RouterModule } from '@angular/Router'; +import { RouterModule } from '@angular/router'; import { routing } from './app.routes'; import { AppService } from './app.service'; diff --git a/src/Web/WebSPA/Client/modules/catalog/catalog.component.html b/src/Web/WebSPA/Client/modules/catalog/catalog.component.html index c0aa46ad5..d3c40af62 100644 --- a/src/Web/WebSPA/Client/modules/catalog/catalog.component.html +++ b/src/Web/WebSPA/Client/modules/catalog/catalog.component.html @@ -16,7 +16,7 @@ - +
diff --git a/src/Web/WebSPA/Client/modules/catalog/catalog.component.scss b/src/Web/WebSPA/Client/modules/catalog/catalog.component.scss index 663215ff6..9158bf858 100644 --- a/src/Web/WebSPA/Client/modules/catalog/catalog.component.scss +++ b/src/Web/WebSPA/Client/modules/catalog/catalog.component.scss @@ -4,7 +4,7 @@ $banner-height: 260px; &-hero { - background-image: url('../../images/main_banner.png'); + background-image: url('../../assets/images/main_banner.png'); background-size: cover; height: $banner-height; width: 100%; @@ -61,7 +61,7 @@ } &::after { - background-image: url('../../images/arrow-down.png'); + background-image: url('../../assets/images/arrow-down.png'); content: ''; height: 7px; //png height position: absolute; diff --git a/src/Web/WebSPA/Client/polyfills.ts b/src/Web/WebSPA/Client/polyfills.ts index 2cdf1a036..53bdaf1b8 100644 --- a/src/Web/WebSPA/Client/polyfills.ts +++ b/src/Web/WebSPA/Client/polyfills.ts @@ -1,23 +1,68 @@ -// Added parts of es6 which are necessary for your project or your browser support requirements. -import 'core-js/es6/symbol'; -import 'core-js/es6/object'; -import 'core-js/es6/function'; -import 'core-js/es6/parse-int'; -import 'core-js/es6/parse-float'; -import 'core-js/es6/number'; -import 'core-js/es6/math'; -import 'core-js/es6/string'; -import 'core-js/es6/date'; -import 'core-js/es6/array'; -import 'core-js/es6/regexp'; -import 'core-js/es6/map'; -import 'core-js/es6/set'; -import 'core-js/es6/weak-map'; -import 'core-js/es6/weak-set'; -import 'core-js/es6/typed'; -import 'core-js/es6/reflect'; -// see issue https://github.com/AngularClass/angular2-webpack-starter/issues/709 -// import 'core-js/es6/promise'; +/** + * This file includes polyfills needed by Angular and is loaded before the app. + * You can add your own extra polyfills to this file. + * + * This file is divided into 2 sections: + * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. + * 2. Application imports. Files imported after ZoneJS that should be loaded before your main + * file. + * + * The current setup is for so-called "evergreen" browsers; the last versions of browsers that + * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), + * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. + * + * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html + */ + +/*************************************************************************************************** + * BROWSER POLYFILLS + */ + +/** IE9, IE10 and IE11 requires all of the following polyfills. **/ +// import 'core-js/es6/symbol'; +// import 'core-js/es6/object'; +// import 'core-js/es6/function'; +// import 'core-js/es6/parse-int'; +// import 'core-js/es6/parse-float'; +// import 'core-js/es6/number'; +// import 'core-js/es6/math'; +// import 'core-js/es6/string'; +// import 'core-js/es6/date'; +// import 'core-js/es6/array'; +// import 'core-js/es6/regexp'; +// import 'core-js/es6/map'; +// import 'core-js/es6/set'; + +/** IE10 and IE11 requires the following for NgClass support on SVG elements */ +// import 'classlist.js'; // Run `npm install --save classlist.js`. +/** IE10 and IE11 requires the following to support `@angular/animation`. */ +// import 'web-animations-js'; // Run `npm install --save web-animations-js`. + + +/** Evergreen browsers require these. **/ +import 'core-js/es6/reflect'; import 'core-js/es7/reflect'; -import 'zone.js/dist/zone'; + + +/** ALL Firefox browsers require the following to support `@angular/animation`. **/ +// import 'web-animations-js'; // Run `npm install --save web-animations-js`. + + + +/*************************************************************************************************** + * Zone JS is required by Angular itself. + */ +import 'zone.js/dist/zone'; // Included with Angular CLI. + + + +/*************************************************************************************************** + * APPLICATION IMPORTS + */ + +/** + * Date, currency, decimal and percent pipes. + * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 + */ +// import 'intl'; // Run `npm install --save intl`. diff --git a/src/Web/WebSPA/Client/test.ts b/src/Web/WebSPA/Client/test.ts new file mode 100644 index 000000000..9bf72267e --- /dev/null +++ b/src/Web/WebSPA/Client/test.ts @@ -0,0 +1,32 @@ +// This file is required by karma.conf.js and loads recursively all the .spec and framework files + +import 'zone.js/dist/long-stack-trace-zone'; +import 'zone.js/dist/proxy.js'; +import 'zone.js/dist/sync-test'; +import 'zone.js/dist/jasmine-patch'; +import 'zone.js/dist/async-test'; +import 'zone.js/dist/fake-async-test'; +import { getTestBed } from '@angular/core/testing'; +import { + BrowserDynamicTestingModule, + platformBrowserDynamicTesting +} from '@angular/platform-browser-dynamic/testing'; + +// Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. +declare var __karma__: any; +declare var require: any; + +// Prevent Karma from running prematurely. +__karma__.loaded = function () {}; + +// First, initialize the Angular testing environment. +getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting() +); +// Then we find all the tests. +const context = require.context('./', true, /\.spec\.ts$/); +// And load the modules. +context.keys().map(context); +// Finally, start Karma to run the tests. +__karma__.start(); diff --git a/src/Web/WebSPA/Client/tsconfig.app.json b/src/Web/WebSPA/Client/tsconfig.app.json new file mode 100644 index 000000000..5e2507db5 --- /dev/null +++ b/src/Web/WebSPA/Client/tsconfig.app.json @@ -0,0 +1,13 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/app", + "module": "es2015", + "baseUrl": "", + "types": [] + }, + "exclude": [ + "test.ts", + "**/*.spec.ts" + ] +} diff --git a/src/Web/WebSPA/Client/tsconfig.spec.json b/src/Web/WebSPA/Client/tsconfig.spec.json new file mode 100644 index 000000000..510e3f1fd --- /dev/null +++ b/src/Web/WebSPA/Client/tsconfig.spec.json @@ -0,0 +1,20 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/spec", + "module": "commonjs", + "target": "es5", + "baseUrl": "", + "types": [ + "jasmine", + "node" + ] + }, + "files": [ + "test.ts" + ], + "include": [ + "**/*.spec.ts", + "**/*.d.ts" + ] +} diff --git a/src/Web/WebSPA/Client/typings.d.ts b/src/Web/WebSPA/Client/typings.d.ts new file mode 100644 index 000000000..ef5c7bd62 --- /dev/null +++ b/src/Web/WebSPA/Client/typings.d.ts @@ -0,0 +1,5 @@ +/* SystemJS module definition */ +declare var module: NodeModule; +interface NodeModule { + id: string; +} diff --git a/src/Web/WebSPA/Client/vendor.ts b/src/Web/WebSPA/Client/vendor.ts deleted file mode 100644 index 4f40d6b4a..000000000 --- a/src/Web/WebSPA/Client/vendor.ts +++ /dev/null @@ -1,20 +0,0 @@ -// For vendors for example jQuery, Lodash, angular2-jwt just import them here unless you plan on -// chunking vendors files for async loading. You would need to import the async loaded vendors -// at the entry point of the async loaded file. Also see custom-typings.d.ts as you also need to -// run `typings install x` where `x` is your module - -// Angular 2 -import '@angular/platform-browser'; -import '@angular/platform-browser-dynamic'; -import '@angular/core'; -import '@angular/common'; -import '@angular/forms'; -import '@angular/http'; -import '@angular/router'; - -// RxJS -import 'rxjs/add/operator/map'; -import 'rxjs/add/operator/mergeMap'; -import 'rxjs/add/operator/catch'; -import 'rxjs/add/operator/finally'; -import 'rxjs/add/observable/throw'; diff --git a/src/Web/WebSPA/Server/Controllers/HomeController.cs b/src/Web/WebSPA/Server/Controllers/HomeController.cs index 7e78cd41a..3bb021943 100644 --- a/src/Web/WebSPA/Server/Controllers/HomeController.cs +++ b/src/Web/WebSPA/Server/Controllers/HomeController.cs @@ -1,6 +1,5 @@ // For more information on enabling MVC for empty projects, visit http://go.microsoft.com/fwlink/?LinkID=397860 -using System.Linq; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; @@ -18,23 +17,6 @@ namespace eShopConContainers.WebSPA.Server.Controllers _env = env; _settings = settings; } - - public IActionResult Index() - { - ViewBag.HashedMain = GetHashedMainDotJs(); - - return View(); - } - - public string GetHashedMainDotJs() - { - var basePath = _env.WebRootPath + "//dist//"; - var info = new System.IO.DirectoryInfo(basePath); - var file = info.GetFiles().Where(f => f.Name.StartsWith("main.") && !f.Name.EndsWith("bundle.map")).FirstOrDefault(); - - return file.Name; - } - public IActionResult Configuration() { return Json(_settings.Value); diff --git a/src/Web/WebSPA/Startup.cs b/src/Web/WebSPA/Startup.cs index a572f7961..916ad4e16 100644 --- a/src/Web/WebSPA/Startup.cs +++ b/src/Web/WebSPA/Startup.cs @@ -1,16 +1,15 @@ using System; +using System.IO; using Microsoft.AspNetCore.Antiforgery; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.SpaServices.Webpack; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.HealthChecks; using Newtonsoft.Json.Serialization; using eShopOnContainers.WebSPA; -using Microsoft.Extensions.HealthChecks; -using System.Threading.Tasks; namespace eShopConContainers.WebSPA { @@ -76,29 +75,34 @@ namespace eShopConContainers.WebSPA // Configure XSRF middleware, This pattern is for SPA style applications where XSRF token is added on Index page // load and passed back token on every subsequent async request + // app.Use(async (context, next) => + // { + // if (string.Equals(context.Request.Path.Value, "/", StringComparison.OrdinalIgnoreCase)) + // { + // var tokens = antiforgery.GetAndStoreTokens(context); + // context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken, new CookieOptions() { HttpOnly = false }); + // } + // await next.Invoke(); + // }); + app.Use(async (context, next) => { - if (string.Equals(context.Request.Path.Value, "/", StringComparison.OrdinalIgnoreCase)) + await next(); + + // If there's no available file and the request doesn't contain an extension, we're probably trying to access a page. + // Rewrite request to use app root + if (context.Response.StatusCode == 404 && !Path.HasExtension(context.Request.Path.Value) && !context.Request.Path.Value.StartsWith("/api")) { - var tokens = antiforgery.GetAndStoreTokens(context); - context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken, new CookieOptions() { HttpOnly = false }); + context.Request.Path = "/index.html"; + context.Response.StatusCode = 200; // Make sure we update the status code, otherwise it returns 404 + await next(); } - await next.Invoke(); }); + app.UseDefaultFiles(); app.UseStaticFiles(); - - app.UseMvc(routes => - { - routes.MapRoute( - name: "default", - template: "{controller=Home}/{action=Index}/{id?}"); - - routes.MapSpaFallbackRoute( - name: "spa-fallback", - defaults: new { controller = "Home", action = "Index" }); - }); + app.UseMvc(); } } } diff --git a/src/Web/WebSPA/Views/Home/Index.cshtml b/src/Web/WebSPA/Views/Home/Index.cshtml deleted file mode 100644 index 931ef55c7..000000000 --- a/src/Web/WebSPA/Views/Home/Index.cshtml +++ /dev/null @@ -1,12 +0,0 @@ - -
- -
-
- - - - - diff --git a/src/Web/WebSPA/Views/Shared/_Layout.cshtml b/src/Web/WebSPA/Views/Shared/_Layout.cshtml deleted file mode 100644 index c7d974e27..000000000 --- a/src/Web/WebSPA/Views/Shared/_Layout.cshtml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - eShopConContainers.WebSPA - - - - - - @RenderBody() - - - \ No newline at end of file diff --git a/src/Web/WebSPA/Views/_ViewImports.cshtml b/src/Web/WebSPA/Views/_ViewImports.cshtml deleted file mode 100644 index 1c0d391f9..000000000 --- a/src/Web/WebSPA/Views/_ViewImports.cshtml +++ /dev/null @@ -1,3 +0,0 @@ - -@addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers" -@addTagHelper "*, Microsoft.AspNetCore.SpaServices" diff --git a/src/Web/WebSPA/Views/_ViewStart.cshtml b/src/Web/WebSPA/Views/_ViewStart.cshtml deleted file mode 100644 index 820a2f6e0..000000000 --- a/src/Web/WebSPA/Views/_ViewStart.cshtml +++ /dev/null @@ -1,3 +0,0 @@ -@{ - Layout = "_Layout"; -} diff --git a/src/Web/WebSPA/WebSPA.csproj b/src/Web/WebSPA/WebSPA.csproj index e81a11d59..db39dc0e3 100644 --- a/src/Web/WebSPA/WebSPA.csproj +++ b/src/Web/WebSPA/WebSPA.csproj @@ -20,21 +20,9 @@ PreserveNewest - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - PreserveNewest - - PreserveNewest - PreserveNewest @@ -49,7 +37,6 @@ - @@ -69,7 +56,7 @@ - + diff --git a/src/Web/WebSPA/config/helpers.js b/src/Web/WebSPA/config/helpers.js deleted file mode 100644 index 9d37e78a8..000000000 --- a/src/Web/WebSPA/config/helpers.js +++ /dev/null @@ -1,23 +0,0 @@ -/** - * @author: @AngularClass - */ - -var path = require('path'); - -// Helper functions -var ROOT = path.resolve(__dirname, '..'); - -console.log('root directory:', root() + '\n'); - -function hasProcessFlag(flag) { - return process.argv.join('').indexOf(flag) > -1; -} - -function root(args) { - args = Array.prototype.slice.call(arguments, 0); - return path.join.apply(path, [ROOT].concat(args)); -} - - -exports.hasProcessFlag = hasProcessFlag; -exports.root = root; diff --git a/src/Web/WebSPA/config/webpack.config.dev.js b/src/Web/WebSPA/config/webpack.config.dev.js deleted file mode 100644 index d87a27ee4..000000000 --- a/src/Web/WebSPA/config/webpack.config.dev.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - //devtool: 'cheap-module-source-map' -}; diff --git a/src/Web/WebSPA/config/webpack.config.js b/src/Web/WebSPA/config/webpack.config.js deleted file mode 100644 index 2bf3a804d..000000000 --- a/src/Web/WebSPA/config/webpack.config.js +++ /dev/null @@ -1,77 +0,0 @@ -var path = require('path'); -var webpack = require('webpack'); -var merge = require('extendify')({ isDeep: true, arrays: 'concat' }); -var ExtractTextPlugin = require('extract-text-webpack-plugin'); -var extractCSS = new ExtractTextPlugin('styles.css'); -var ForkCheckerPlugin = require('awesome-typescript-loader').ForkCheckerPlugin; -var devConfig = require('./webpack.config.dev'); -var prodConfig = require('./webpack.config.prod'); -var CopyWebpackPlugin = require('copy-webpack-plugin'); -var isDevelopment = process.env.ASPNETCORE_ENVIRONMENT === 'Development'; - -console.log("==========Dev Mode = " + isDevelopment + " ============" ) - -module.exports = merge({ - resolve: { - extensions: ['.js', '.ts'] - }, - module: { - rules: [ - { - test: /\.ts$/, exclude: [/\.(spec|e2e)\.ts$/], - loaders: ['awesome-typescript-loader?forkChecker=true ', 'angular2-template-loader', 'angular2-router-loader'] - }, - { test: /\.html$/, loader: "html" }, - { test: /\.scss$/, loader: 'exports-loader?module.exports.toString()!css-loader!sass-loader' }, - { test: /\.json$/, loader: 'json-loader' }, - { - test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, - loader: "file-loader" - }, { - test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, - loader: "file-loader" - }, { - test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, - loader: "file-loader" - }, { - test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, - loader: "file-loader" - }, - { - test: /\.(png|jpg|gif|svg)$/, - loader: "file-loader?name=images/[name].[ext]" - } - ] - }, - entry: { - 'main': './Client/main.ts' - }, - output: { - path: path.join(__dirname, '../wwwroot', 'dist'), - filename: '[name].js', - publicPath: '/dist/' - }, - profile: true, - plugins: [ - extractCSS, - new webpack.DllReferencePlugin({ - context: __dirname, - manifest: require('../wwwroot/dist/vendor-manifest.json') - }), - // To eliminate warning - // https://github.com/AngularClass/angular2-webpack-starter/issues/993 - new webpack.ContextReplacementPlugin( - /angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/, - __dirname - ), - new ForkCheckerPlugin(), - new webpack.DefinePlugin({ - 'process.env': { - 'ENV': JSON.stringify(process.env.ASPNETCORE_ENVIRONMENT) - } - }), - new CopyWebpackPlugin([ - { from: 'Client/fonts', to: 'fonts' } - ]) - ] -}, isDevelopment ? devConfig : prodConfig); diff --git a/src/Web/WebSPA/config/webpack.config.prod.js b/src/Web/WebSPA/config/webpack.config.prod.js deleted file mode 100644 index 95277c818..000000000 --- a/src/Web/WebSPA/config/webpack.config.prod.js +++ /dev/null @@ -1,23 +0,0 @@ -var webpack = require('webpack'); -const WebpackMd5Hash = require('webpack-md5-hash'); - -module.exports = { - devtool: 'source-map', - output: { - filename: '[name].[chunkhash].bundle.js', - sourceMapFilename: '[name].[chunkhash].bundle.map', - chunkFilename: '[id].[chunkhash].chunk.js' - }, - plugins: [ - // new webpack.LoaderOptionsPlugin({ - // minimize: true, - // debug: false - // }), - new WebpackMd5Hash(), - new webpack.optimize.UglifyJsPlugin({ - beautify: false, - comments: false, - sourceMap: true - }) - ] -}; diff --git a/src/Web/WebSPA/config/webpack.config.vendor.js b/src/Web/WebSPA/config/webpack.config.vendor.js deleted file mode 100644 index 9009e8ba6..000000000 --- a/src/Web/WebSPA/config/webpack.config.vendor.js +++ /dev/null @@ -1,74 +0,0 @@ -var path = require('path'); -var webpack = require('webpack'); -var ExtractTextPlugin = require('extract-text-webpack-plugin'); -var extractCSS = new ExtractTextPlugin('vendor.css'); -var isDevelopment = process.env.ASPNETCORE_ENVIRONMENT === 'Development'; - -module.exports = { - resolve: { - extensions: ['.js'] - }, - module: { - rules: [ - { test: /\.(png|woff|woff2|eot|ttf|svg)$/, loader: 'url-loader?limit=100000' }, - { test: /\.scss$/i, loader: extractCSS.extract(['css?minimize', 'sass']) }, - { test: /\.json$/, loader: 'json-loader' } - ] - }, - entry: { - // polyfills: [ - // 'core-js/es6/symbol', - // 'core-js/es6/object', - // 'core-js/es6/function', - // 'core-js/es6/parse-int', - // 'core-js/es6/parse-float', - // 'core-js/es6/number', - // 'core-js/es6/math', - // 'core-js/es6/string', - // 'core-js/es6/date', - // 'core-js/es6/array', - // 'core-js/es6/regexp', - // 'core-js/es6/map', - // 'core-js/es6/set', - // 'core-js/es6/reflect', - // 'core-js/es7/reflect', - // 'zone.js/dist/zone' - // ], - vendor: [ - 'font-awesome/scss/font-awesome.scss', - 'bootstrap/scss/bootstrap.scss', - '@angular/common', - '@angular/compiler', - '@angular/core', - '@angular/http', - '@angular/forms', - '@angular/platform-browser', - '@angular/platform-browser-dynamic', - '@angular/router', - './Client/globals.scss' - ] - }, - output: { - path: path.join(__dirname, '../wwwroot', 'dist'), - filename: '[name].js', - library: '[name]_[hash]', - }, - plugins: [ - extractCSS, - // To eliminate warning - // https://github.com/AngularClass/angular2-webpack-starter/issues/993 - new webpack.ContextReplacementPlugin( - /angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/, - __dirname - ), - new webpack.DllPlugin({ - path: path.join(__dirname, '../wwwroot', 'dist', '[name]-manifest.json'), - name: '[name]_[hash]' - }) - ].concat(isDevelopment ? [] : [ - new webpack.optimize.UglifyJsPlugin({ - beautify: false, - comments: false - }) - ]) -}; diff --git a/src/Web/WebSPA/package.json b/src/Web/WebSPA/package.json index b863e2f1c..0def49351 100644 --- a/src/Web/WebSPA/package.json +++ b/src/Web/WebSPA/package.json @@ -16,90 +16,51 @@ "email": "cesardl@microsoft.com" }, "scripts": { - "rimraf": "rimraf", - "typings": "typings", - "webpack": "webpack", - "clean": "npm cache clean && npm run rimraf -- node_modules doc typings coverage wwwroot/dist", - "clean:dist": "npm run rimraf -- wwwroot/dist", - "preclean:install": "npm run clean", - "clean:install": "npm set progress=false && npm install", - "preclean:start": "npm run clean", - "clean:start": "npm start", - "build:vendor": "node node_modules/webpack/bin/webpack.js --config config/webpack.config.vendor.js", - "build:main": "node node_modules/webpack/bin/webpack.js --config config/webpack.config.js", - "setdev": "set ASPNETCORE_ENVIRONMENT=Development", - "setprod": "set ASPNETCORE_ENVIRONMENT=Production", - "build:dev": "npm run setdev && npm run clean:dist && npm run build:vendor && npm run build:main", - "build:prod": "npm run setprod && npm run clean:dist && npm run build:vendor && npm run build:main", - "version": "npm run build", + "ng": "ng", + "start": "ng serve", + "build:dev": "ng build", + "build:prod": "ng build", "lint:sass": "sass-lint -c .sass-lint.yml Client/**/*.scss --verbose", "lint:ts": "tslint -c tslint.json Client/**/*.ts" }, "dependencies": { - "@angular/common": "2.1.2", - "@angular/compiler": "2.1.2", - "@angular/compiler-cli": "2.1.2", - "@angular/core": "2.1.2", - "@angular/forms": "2.1.2", - "@angular/http": "2.1.2", - "@angular/platform-browser": "2.1.2", - "@angular/platform-browser-dynamic": "2.1.2", - "@angular/platform-server": "2.1.2", - "@angular/router": "3.1.2", - "@ng-bootstrap/ng-bootstrap": "1.0.0-alpha.11", + "@angular/common": "^4.0.0", + "@angular/compiler": "^4.0.0", + "@angular/core": "^4.0.0", + "@angular/forms": "^4.0.0", + "@angular/http": "^4.0.0", + "@angular/platform-browser": "^4.0.0", + "@angular/platform-browser-dynamic": "^4.0.0", + "@angular/router": "^4.0.0", + "@ng-bootstrap/ng-bootstrap": "1.0.0-alpha.22", "aspnet-prerendering": "1.0.7", "aspnet-webpack": "1.0.24", "bootstrap": "4.0.0-alpha.5", - "core-js": "2.4.1", + "core-js": "^2.4.1", "file-loader": "0.9.0", "font-awesome": "4.6.3", "isomorphic-fetch": "2.2.1", "normalize.css": "5.0.0", "preboot": "4.5.2", - "rxjs": "5.0.0-beta.12", - "zone.js": "0.6.26" + "rxjs": "^5.1.0", + "zone.js": "^0.8.4" }, "devDependencies": { + "@angular/cli": "1.0.0", + "@angular/compiler-cli": "^4.0.0", + "@types/jasmine": "2.5.38", + "@types/node": "~6.0.60", "@types/core-js": "0.9.34", "@types/hammerjs": "2.0.33", - "@types/jasmine": "2.5.35", - "@types/node": "6.0.45", "@types/protractor": "1.5.20", "@types/selenium-webdriver": "2.44.26", - "@types/sinon": "1.16.31", - "@types/source-map": "0.1.28", - "@types/uglify-js": "2.6.28", - "@types/webpack": "1.12.35", - "angular2-router-loader": "0.3.4", - "angular2-template-loader": "0.6.0", - "awesome-typescript-loader": "2.2.4", - "codelyzer": "1.0.0-beta.3", - "copy-webpack-plugin": "4.0.1", - "css": "2.2.1", - "css-loader": "0.25.0", - "es6-promise": "3.2.1", - "es6-promise-loader": "1.0.2", - "exports-loader": "0.6.3", - "extendify": "1.0.0", - "extract-text-webpack-plugin": "2.0.0-beta.4", - "file-loader": "0.9.0", - "html-loader": "0.4.4", - "html-webpack-plugin": "2.24.1", - "json-loader": "0.5.4", - "node-sass": "4.5.0", - "parse5": "2.1.5", - "rimraf": "2.5.4", + "codelyzer": "~2.0.0", "sass-lint": "1.10.2", - "sass-loader": "4.0.2", "ts-helpers": "1.1.1", - "ts-node": "1.4.3", - "tslint": "3.15.1", + "ts-node": "~2.0.0", + "tslint": "~4.5.0", "typedoc": "0.5.0", - "typescript": "2.0.6", - "url-loader": "0.5.7", - "webpack": "2.1.0-beta.25", - "webpack-externals-plugin": "1.0.0", - "webpack-hot-middleware": "2.13.0", - "webpack-md5-hash": "0.0.5" + "typescript": "~2.2.0", + "url-loader": "0.5.7" } } diff --git a/src/Web/WebSPA/tsconfig.json b/src/Web/WebSPA/tsconfig.json index e04adfb21..a35a8ee3a 100644 --- a/src/Web/WebSPA/tsconfig.json +++ b/src/Web/WebSPA/tsconfig.json @@ -1,42 +1,20 @@ { + "compileOnSave": false, "compilerOptions": { - "target": "es5", - "module": "commonjs", - "moduleResolution": "node", + "outDir": "./dist/out-tsc", + "baseUrl": "src", + "sourceMap": true, "declaration": false, + "moduleResolution": "node", "emitDecoratorMetadata": true, "experimentalDecorators": true, - "allowSyntheticDefaultImports": true, - "sourceMap": true, - "strictNullChecks": false, - "baseUrl": "./src", - "paths": {}, - "lib": [ - "dom", - "es6" + "target": "es5", + "typeRoots": [ + "node_modules/@types" ], - "types": [ - "hammerjs", - "jasmine", - "node", - "protractor", - "selenium-webdriver", - "source-map", - "uglify-js", - "webpack" + "lib": [ + "es2016", + "dom" ] - }, - "exclude": [ - "node_modules", - "wwwroot" - ], - "awesomeTypescriptLoaderOptions": { - "forkChecker": true, - "useWebpackText": true - }, - "compileOnSave": false, - "buildOnSave": false, - "atom": { - "rewriteTsconfig": false } -} \ No newline at end of file +} diff --git a/src/Web/WebSPA/wwwroot/web.config b/src/Web/WebSPA/wwwroot/web.config deleted file mode 100644 index e70a7778d..000000000 --- a/src/Web/WebSPA/wwwroot/web.config +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - From 28426e95c09d1f461610f6761d28ca1a2e3cec3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Passos?= Date: Mon, 17 Apr 2017 09:11:43 -0300 Subject: [PATCH 02/15] fix controller routes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: André Passos --- src/Web/WebSPA/.angular-cli.json | 3 ++- .../WebSPA/Client/{global.scss => globals.scss} | 0 src/Web/WebSPA/Startup.cs | 4 ++-- src/Web/WebSPA/wwwroot/favicon.ico | Bin 15086 -> 0 bytes 4 files changed, 4 insertions(+), 3 deletions(-) rename src/Web/WebSPA/Client/{global.scss => globals.scss} (100%) delete mode 100644 src/Web/WebSPA/wwwroot/favicon.ico diff --git a/src/Web/WebSPA/.angular-cli.json b/src/Web/WebSPA/.angular-cli.json index df26c160c..131f2897c 100644 --- a/src/Web/WebSPA/.angular-cli.json +++ b/src/Web/WebSPA/.angular-cli.json @@ -19,7 +19,8 @@ "testTsconfig": "tsconfig.spec.json", "prefix": "app", "styles": [ - "global.scss" + "globals.scss", + "../node_modules/bootstrap/scss/bootstrap.scss" ], "scripts": [], "environmentSource": "environments/environment.ts", diff --git a/src/Web/WebSPA/Client/global.scss b/src/Web/WebSPA/Client/globals.scss similarity index 100% rename from src/Web/WebSPA/Client/global.scss rename to src/Web/WebSPA/Client/globals.scss diff --git a/src/Web/WebSPA/Startup.cs b/src/Web/WebSPA/Startup.cs index 916ad4e16..f06cb8ee1 100644 --- a/src/Web/WebSPA/Startup.cs +++ b/src/Web/WebSPA/Startup.cs @@ -101,8 +101,8 @@ namespace eShopConContainers.WebSPA app.UseDefaultFiles(); app.UseStaticFiles(); - - app.UseMvc(); + + app.UseMvcWithDefaultRoute(); } } } diff --git a/src/Web/WebSPA/wwwroot/favicon.ico b/src/Web/WebSPA/wwwroot/favicon.ico deleted file mode 100644 index e499a7453653d1e8662122a9d116bcebee99882d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15086 zcmeHO33yaj6`laCt;(WZ)Vee-RccE`YO%F0)V5ZupG&oBKee>_(XYj-lr9uXEdjw& zt6iijwFv}5b~4%bg+KxcA$vj+vXMPx-zSspc>X)QnKy6lOkQ3F_|)F-yLoSB?!D*S zbMHOpzvrBoOg^TCre(`aTzpNBFEW`{Vb8 zbG#p&uF0aRGe>A*!s6PQzd_sH{&H&T%%IkeG)m6;4o%NYiT)=GBkg^rrma+WHkdMU zeoAp^U!~OS?^9{z%QQT4*4}1%dXmzP{?MiW_;@cRX5K^Z1p9C`In}_3B~(?r7o3o+ z|IEy^poxBw2{+N%7Bh{EwNc+dCDY$c2O>W}`$FGCT|N2I{=NNW6cBnjx4D7_hw8+9 z?F{ovS8oB&D@1%Rqkp>gpMqcikcAW;_gPBHyq9tdek)|%(w;;EgEcg6?dAT{Qu>8u ze-!;gW3DH2(ibV{@U{HeAqW4EMHCcuE#(zHCGC%O#oUkP{aXBPwGPnGa09PZDpl6( zq0pF5ir))LUy%0i8>patVOP+Bh-K8zGQ)Z+Wpu2A4zm2fPjg#>v_IPC7e6Q3V2o3x zTY105iZzRjzfsVRPF_t@Q{&S9pf$~V=M?j|Km2{7y<-pfP+aPrG&IsESdz(K{BZuy z;7~1{KJyR3Phr_h)Y6_J=qhcr{lmN4Ifcc3+Np-e-9Y8l zuZumZ47~e|d+Ehpt0*#ND@Da`v)7oUoz&gq(9cTy*`A=^-CIaS<=gq(zG$xnC0i)i z{87p|`2@ipC~+4J9Mu5xX_yg zrCX?=vQA-D7 z(lYzJmG*k+C$kM#@29}<#T1{uhB9*>qLam&1a<+|9b)V{Jw2feKkHL3eXZ=(yg)8E zi#TUE^Vk>ADMya~ii%lB)SnHbcE%HZjLAnC=YpmpGd=XDrmO<*I@4U>BoF7`w7Osc zW5uNu9{U;QYc<~o?q`0E2-)bPZ*kH!I77!z{f#y%AFA#uim&nG5FSf(9(52&S{z9MRDiK5ceuLx`%pZ0Z%i2EGl;SbvT zVho%`;BCG1(JnUS4*MAU!mgy+#vuC|ih(45z@OFH&-Z|h_P(HxdxK@XTlCN~e>5zv z*vVr!)*N%g*s~Sv9kf+shiBT+9}&(iieCD_4p?KyI6iD^H#0uycPj_js`{odzN=hm z?-vky8SB5-xzF^R=wlC*RsEI6ap)ktCuH0u?hLT=N*BV~W9+sNKM(cim& zE$g-c)^X$ZIyq@|I~Upb>hHi4^Z{rp73T&vL5BaI_xn8g2d$%h!B5!hfzT(}c2ui} z)%6bfcbv->bbzCj3LgmkCtCXPDZ2@G5N&;R&;8WgTB?htx)JC%x5W!z5Ac|}CT84C zXBrOJb&|T@rFd7l0xrob{1kAJKEl=Rr(j>HwyCN1wdhV_mml zF`Xy~aT{|mG9U0Vz&1SS@YNnM1TaM)W2dvNF)kPaK3(v>{+X)@w&5-Rhop7y{)fdn z!G3zHfd7%v7QSozhJ6hWN^9ED2iU*%F)_cI`T!UFrSQLH#|o;f&J=qD`$SuZhI`m% z+k6Jz;veGx6CnP8_=~I#4%Z7Dg*EWR|JI$W*e;iQlQpl5g?mo=v7gK;f1~_Eeirus zzz5mT93gy5RUBVJ{HwO{Am2@rMO6ze#;vexq_yFJwPR;%SW56#9-*gE3 z_TUeOpACNMsKhS_tOP#`e8-3>>h<|xeiwrOp1u;si;oN6EBtBNJBRGEf5-xH7d)J> zg3|d+!Y6|m)@;R_`~!0%))k%nCE6eMUR`|4=dy@>u-A)wW^cb|EbJ}*aZkuEepc)a z=R3H=rz7Ll({}+JK=e-`n3+>hO_n~(7AK)-9teE5fb zgx@nd`Bo|K0qi?4C#@8GA`axKz~@lc7))j!3qF6@df;50>ptN$%zXH_@}7sDfzL`y z54^+Q1OL}t$|U18^)%M=k^M6STbJGkhkKv&dY{hxQ$N z1$i2fjXCL7_U|A^#TDBc}J-1arf zKVpxWIX{=m9{w=oaEw{b@p_L)<>rKYG;Toc6I5aU7q5PYRFf1f8_WxpBaA3W)eQ{pTG^FU7}v(FHC9EP#w->9=I zSLE72-q2Ordz6fKd}oyZikdg2dtbI5?!H5u1F;XpK#<=B+yi|E%!j!_M!=`toG*oV z2z0FW3>)DevCZO&Kk?pC`|1^4=bh(1#!tYi&~J$UX7YZ_Em%*G8}2MhF~UD^nv&zC zm2F`760ujbzI|;wqn~3_?{KnVM)=3MD=PDop53IZ`-CsPp($L%$WL;<#F66hsGpcS)(>ZK z%+e|5hP+6e(YB6ss%;1qd4-6pX!!@8#u-rSCZqf#cTkqwa*%yBgTt;lm+CpVdxvg; z?$pKvm3aUY0RyRvQT_*pYWUv&fQyWvlW>p488=$;ik_BYLgbPn=TKdY@{jx!#C#Dm zROF*P;S1~NI;tk4`+bDtQ@F1vW2v8UX|AuTq$xUx;SSRd;-4>k2DE8id<^wQsimN z^f6=so0wnptP6Gk{rSir{ZWwj1--1!BT&{r{lvWxSSLRH8^WGN@w{+XP`A;(WZf}2y$azQJl9mUfpMhI}eY!dC#a;0h{}>-S75NReJP_j;0rLgU!2M{p zdz?4<#~hF|10M-|p6<@Ir}e=40ZYS%jLmZQ0zGf@k1iA` zx2UNlR>X>IK3lo3P&SJT$-kDKP1lbyzUvhj556Jfjq0&)+Od?+7n6U5rZ=B@`QN<# zW-6^nVhmp|t%y@ja_)+^3)T#AQQR426@D80{}gnBr})=n8-U_sZ>j zCyQkaV;pyC=Z<{^EDn7HTPLr#oWIk_`A3{_li!UVW50;6KK9g7%0Cq@@{qKA82t?X z@xNBc=k%m=6?@U#mgJ(hfRix?#8Exf-o6&TH|on();k~YkGmSM1Mrfkia0U+t@F_Z WdilCo?QHo#_@5}0cb^wq4*vo1a-z%t From b06bcdcd583863e733fb980c49188d3db45cd03e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Passos?= Date: Mon, 17 Apr 2017 09:59:01 -0300 Subject: [PATCH 03/15] Changes to make SPA compatible with angular-cli AOT MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: André Passos --- src/Web/WebSPA/Client/modules/app.component.ts | 2 +- .../basket-status/basket-status.component.html | 2 +- .../orders-detail/orders-detail.component.ts | 11 +++++------ .../orders/orders-new/orders-new.component.ts | 8 ++++---- .../WebSPA/Client/modules/orders/orders.service.ts | 3 ++- .../shared/components/identity/identity.html | 6 +++--- .../modules/shared/components/identity/identity.ts | 2 +- .../modules/shared/models/order-detail.model.ts | 14 ++++++++++++++ .../WebSPA/Client/modules/shared/shared.module.ts | 12 ++++++++++-- src/Web/WebSPA/WebSPA.csproj | 2 +- src/Web/WebSPA/package.json | 6 +++--- 11 files changed, 45 insertions(+), 23 deletions(-) create mode 100644 src/Web/WebSPA/Client/modules/shared/models/order-detail.model.ts diff --git a/src/Web/WebSPA/Client/modules/app.component.ts b/src/Web/WebSPA/Client/modules/app.component.ts index dffd344e7..37bba914b 100644 --- a/src/Web/WebSPA/Client/modules/app.component.ts +++ b/src/Web/WebSPA/Client/modules/app.component.ts @@ -18,7 +18,7 @@ import { ConfigurationService } from './shared/services/configuration.service'; templateUrl: './app.component.html' }) export class AppComponent implements OnInit { - private Authenticated: boolean = false; + Authenticated: boolean = false; subscription: Subscription; constructor(private titleService: Title, private securityService: SecurityService, private configurationService: ConfigurationService) { diff --git a/src/Web/WebSPA/Client/modules/basket/basket-status/basket-status.component.html b/src/Web/WebSPA/Client/modules/basket/basket-status/basket-status.component.html index 7509a2281..b15127a60 100644 --- a/src/Web/WebSPA/Client/modules/basket/basket-status/basket-status.component.html +++ b/src/Web/WebSPA/Client/modules/basket/basket-status/basket-status.component.html @@ -3,7 +3,7 @@ [routerLink]="['basket']">
- +
{{badge}} diff --git a/src/Web/WebSPA/Client/modules/orders/orders-detail/orders-detail.component.ts b/src/Web/WebSPA/Client/modules/orders/orders-detail/orders-detail.component.ts index 31da11594..c9c5c79c0 100644 --- a/src/Web/WebSPA/Client/modules/orders/orders-detail/orders-detail.component.ts +++ b/src/Web/WebSPA/Client/modules/orders/orders-detail/orders-detail.component.ts @@ -1,6 +1,6 @@ -import { Component, OnInit } from '@angular/core'; -import { OrdersService } from '../orders.service'; -import { IOrder } from '../../shared/models/order.model'; +import { Component, OnInit } from '@angular/core'; +import { OrdersService } from '../orders.service'; +import { IOrderDetail } from '../../shared/models/order-detail.model'; import { ActivatedRoute } from '@angular/router'; @Component({ @@ -9,7 +9,7 @@ import { ActivatedRoute } from '@angular/router'; templateUrl: './orders-detail.component.html' }) export class OrdersDetailComponent implements OnInit { - order = {}; // new order + public order: IOrderDetail = {}; constructor(private service: OrdersService, private route: ActivatedRoute) { } @@ -27,5 +27,4 @@ export class OrdersDetailComponent implements OnInit { console.log(this.order); }); } -} - +} \ No newline at end of file diff --git a/src/Web/WebSPA/Client/modules/orders/orders-new/orders-new.component.ts b/src/Web/WebSPA/Client/modules/orders/orders-new/orders-new.component.ts index 8f8a10eb0..4f6f82a9f 100644 --- a/src/Web/WebSPA/Client/modules/orders/orders-new/orders-new.component.ts +++ b/src/Web/WebSPA/Client/modules/orders/orders-new/orders-new.component.ts @@ -13,10 +13,10 @@ import { Router } from '@angular/router'; templateUrl: './orders-new.component.html' }) export class OrdersNewComponent implements OnInit { - private newOrderForm: FormGroup; // new order form - private isOrderProcessing: Boolean; - private errorReceived: Boolean; - private order: IOrder; + newOrderForm: FormGroup; // new order form + isOrderProcessing: boolean; + errorReceived: boolean; + order: IOrder; constructor(private service: OrdersService, fb: FormBuilder, private router: Router) { // Obtain user profile information diff --git a/src/Web/WebSPA/Client/modules/orders/orders.service.ts b/src/Web/WebSPA/Client/modules/orders/orders.service.ts index 24991056a..355670cdd 100644 --- a/src/Web/WebSPA/Client/modules/orders/orders.service.ts +++ b/src/Web/WebSPA/Client/modules/orders/orders.service.ts @@ -4,6 +4,7 @@ import { Response } from '@angular/http'; import { DataService } from '../shared/services/data.service'; import { IOrder } from '../shared/models/order.model'; import { IOrderItem } from '../shared/models/orderItem.model'; +import { IOrderDetail } from "../shared/models/order-detail.model"; import { SecurityService } from '../shared/services/security.service'; import { ConfigurationService } from '../shared/services/configuration.service'; import { BasketWrapperService } from '../shared/services/basket.wrapper.service'; @@ -35,7 +36,7 @@ export class OrdersService { }); } - getOrder(id: number): Observable { + getOrder(id: number): Observable { let url = this.ordersUrl + '/api/v1/orders/' + id; return this.service.get(url).map((response: Response) => { diff --git a/src/Web/WebSPA/Client/modules/shared/components/identity/identity.html b/src/Web/WebSPA/Client/modules/shared/components/identity/identity.html index f15dda395..9dce33adc 100644 --- a/src/Web/WebSPA/Client/modules/shared/components/identity/identity.html +++ b/src/Web/WebSPA/Client/modules/shared/components/identity/identity.html @@ -12,7 +12,7 @@ *ngIf="authenticated">
{{userName}}
- +
My orders
- +
Log Out
- +
diff --git a/src/Web/WebSPA/Client/modules/shared/components/identity/identity.ts b/src/Web/WebSPA/Client/modules/shared/components/identity/identity.ts index 606249c0a..505cdc05d 100644 --- a/src/Web/WebSPA/Client/modules/shared/components/identity/identity.ts +++ b/src/Web/WebSPA/Client/modules/shared/components/identity/identity.ts @@ -10,7 +10,7 @@ import { SecurityService } from '../../services/security.service'; styleUrls: ['./identity.scss'] }) export class Identity implements OnInit { - private authenticated: boolean = false; + authenticated: boolean = false; private subscription: Subscription; private userName: string = ''; diff --git a/src/Web/WebSPA/Client/modules/shared/models/order-detail.model.ts b/src/Web/WebSPA/Client/modules/shared/models/order-detail.model.ts new file mode 100644 index 000000000..25a869f9c --- /dev/null +++ b/src/Web/WebSPA/Client/modules/shared/models/order-detail.model.ts @@ -0,0 +1,14 @@ +import {IOrderItem} from './orderItem.model'; + +export interface IOrderDetail { + ordernumber: string; + status: string; + street: string; + date: Date; + city: number; + state: string; + zipcode: string; + country: number; + total: number; + orderitems: IOrderItem[]; +} diff --git a/src/Web/WebSPA/Client/modules/shared/shared.module.ts b/src/Web/WebSPA/Client/modules/shared/shared.module.ts index 64441396f..da7667df6 100644 --- a/src/Web/WebSPA/Client/modules/shared/shared.module.ts +++ b/src/Web/WebSPA/Client/modules/shared/shared.module.ts @@ -16,6 +16,10 @@ import { StorageService } from './services/storage.service'; import { Pager } from './components/pager/pager'; import { Header } from './components/header/header'; import { Identity } from './components/identity/identity'; +import { PageNotFoundComponent } from './components/page-not-found/page-not-found.component'; + +// Pipes: +import { UppercasePipe } from './pipes/uppercase.pipe'; @NgModule({ imports: [ @@ -31,7 +35,9 @@ import { Identity } from './components/identity/identity'; declarations: [ Pager, Header, - Identity + Identity, + PageNotFoundComponent, + UppercasePipe ], exports: [ // Modules @@ -43,7 +49,9 @@ import { Identity } from './components/identity/identity'; // Providers, Components, directive, pipes Pager, Header, - Identity + Identity, + PageNotFoundComponent, + UppercasePipe ] }) export class SharedModule { diff --git a/src/Web/WebSPA/WebSPA.csproj b/src/Web/WebSPA/WebSPA.csproj index db39dc0e3..e275ee51e 100644 --- a/src/Web/WebSPA/WebSPA.csproj +++ b/src/Web/WebSPA/WebSPA.csproj @@ -56,7 +56,7 @@ - + diff --git a/src/Web/WebSPA/package.json b/src/Web/WebSPA/package.json index 0def49351..003f220ac 100644 --- a/src/Web/WebSPA/package.json +++ b/src/Web/WebSPA/package.json @@ -17,9 +17,11 @@ }, "scripts": { "ng": "ng", + "rimraf": "rimraf", + "clean": "npm cache clean && npm run rimraf -- node_modules doc typings coverage wwwroot", "start": "ng serve", "build:dev": "ng build", - "build:prod": "ng build", + "build:prod": "ng build --prod --aot --extract-css", "lint:sass": "sass-lint -c .sass-lint.yml Client/**/*.scss --verbose", "lint:ts": "tslint -c tslint.json Client/**/*.ts" }, @@ -33,8 +35,6 @@ "@angular/platform-browser-dynamic": "^4.0.0", "@angular/router": "^4.0.0", "@ng-bootstrap/ng-bootstrap": "1.0.0-alpha.22", - "aspnet-prerendering": "1.0.7", - "aspnet-webpack": "1.0.24", "bootstrap": "4.0.0-alpha.5", "core-js": "^2.4.1", "file-loader": "0.9.0", From d34b80393e3b77bd545a5292db06ef942741a5f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Passos?= Date: Mon, 17 Apr 2017 10:43:39 -0300 Subject: [PATCH 04/15] remove .vscode folder and add to .gitignore --- .gitignore | 1 + .vscode/launch.json | 42 ------------------------------------------ .vscode/tasks.json | 16 ---------------- 3 files changed, 1 insertion(+), 58 deletions(-) delete mode 100644 .vscode/launch.json delete mode 100644 .vscode/tasks.json diff --git a/.gitignore b/.gitignore index 963bee262..b641d5755 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ *.user *.userosscache *.sln.docstates +.vscode/ # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index f4432a933..000000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "name": ".NET Core Launch (web)", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - "program": "${workspaceRoot}/src/Services/Basket/Basket.API/bin/Debug/netcoreapp1.1/Basket.API.dll", - "args": [], - "cwd": "${workspaceRoot}/src/Services/Basket/Basket.API", - "stopAtEntry": false, - "internalConsoleOptions": "openOnSessionStart", - "launchBrowser": { - "enabled": true, - "args": "${auto-detect-url}", - "windows": { - "command": "cmd.exe", - "args": "/C start ${auto-detect-url}" - }, - "osx": { - "command": "open" - }, - "linux": { - "command": "xdg-open" - } - }, - "env": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "sourceFileMap": { - "/Views": "${workspaceRoot}/Views" - } - }, - { - "name": ".NET Core Attach", - "type": "coreclr", - "request": "attach", - "processId": "${command:pickProcess}" - } - ] -} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index 4994c32bf..000000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "version": "0.1.0", - "command": "dotnet", - "isShellCommand": true, - "args": [], - "tasks": [ - { - "taskName": "build", - "args": [ - "${workspaceRoot}/src/Services/Basket/Basket.API/Basket.API.csproj" - ], - "isBuildCommand": true, - "problemMatcher": "$msCompile" - } - ] -} \ No newline at end of file From ac9775c90cdfb2cf7acadf12ad0a826430a62372 Mon Sep 17 00:00:00 2001 From: Steve Smith Date: Sat, 22 Apr 2017 15:14:42 -0400 Subject: [PATCH 05/15] Cleaning up and fixing typos --- .../Filters/HttpGlobalExceptionFilter.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/Services/Basket/Basket.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs b/src/Services/Basket/Basket.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs index 5acd0bbdc..ab7989973 100644 --- a/src/Services/Basket/Basket.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs +++ b/src/Services/Basket/Basket.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs @@ -4,11 +4,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Linq; using System.Net; -using System.Threading.Tasks; namespace Basket.API.Infrastructure.Filters { @@ -43,12 +39,12 @@ namespace Basket.API.Infrastructure.Filters { var json = new JsonErrorResponse { - Messages = new[] { "An error ocurr.Try it again." } + Messages = new[] { "An error occurred. Try it again." } }; if (env.IsDevelopment()) { - json.DeveloperMeesage = context.Exception; + json.DeveloperMessage = context.Exception; } context.Result = new InternalServerErrorObjectResult(json); @@ -61,7 +57,7 @@ namespace Basket.API.Infrastructure.Filters { public string[] Messages { get; set; } - public object DeveloperMeesage { get; set; } + public object DeveloperMessage { get; set; } } } } From c4da4ce935d16fd7982c485900c0847b02f5b953 Mon Sep 17 00:00:00 2001 From: Eduard Tomas Date: Tue, 25 Apr 2017 11:48:51 +0200 Subject: [PATCH 06/15] windows images named differently than linux --- docker-compose-windows.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docker-compose-windows.yml b/docker-compose-windows.yml index b8eaabfc8..f53bb8d26 100644 --- a/docker-compose-windows.yml +++ b/docker-compose-windows.yml @@ -2,7 +2,7 @@ version: '2.1' services: basket.api: - image: eshop/basket.api + image: eshop/basket.api-win build: context: ./src/Services/Basket/Basket.API dockerfile: Dockerfile.nanowin @@ -11,7 +11,7 @@ services: - identity.api catalog.api: - image: eshop/catalog.api + image: eshop/catalog.api-win build: context: ./src/Services/Catalog/Catalog.API dockerfile: Dockerfile.nanowin @@ -19,7 +19,7 @@ services: - sql.data identity.api: - image: eshop/identity.api + image: eshop/identity.api-win build: context: ./src/Services/Identity/Identity.API dockerfile: Dockerfile.nanowin @@ -27,7 +27,7 @@ services: - sql.data ordering.api: - image: eshop/ordering.api + image: eshop/ordering.api-win build: context: ./src/Services/Ordering/Ordering.API dockerfile: Dockerfile.nanowin @@ -35,7 +35,7 @@ services: - sql.data webspa: - image: eshop/webspa + image: eshop/webspa-win build: context: ./src/Web/WebSPA dockerfile: Dockerfile.nanowin @@ -44,7 +44,7 @@ services: - basket.api webmvc: - image: eshop/webmvc + image: eshop/webmvc-win build: context: ./src/Web/WebMVC dockerfile: Dockerfile.nanowin @@ -58,7 +58,7 @@ services: image: microsoft/mssql-server-windows basket.data: - image: redis + image: redis-win build: context: ./_docker/redis dockerfile: Dockerfile.nanowin @@ -66,7 +66,7 @@ services: - "6379:6379" rabbitmq: - image: rabbitmq + image: rabbitmq-win build: context: ./_docker/rabbitmq dockerfile: Dockerfile.nanowin From f2f2f2fbfa25fea81055e3103900b889dd4254df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Tom=C3=A1s?= Date: Tue, 25 Apr 2017 16:11:12 +0200 Subject: [PATCH 07/15] Fix: rabbitmq and redis image names were not correctly set in docker compose windows file Redis and rabbitmq images now are not created when executing built. We force to pull them from docker hub. --- docker-compose-windows.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docker-compose-windows.yml b/docker-compose-windows.yml index f53bb8d26..5a08a9302 100644 --- a/docker-compose-windows.yml +++ b/docker-compose-windows.yml @@ -58,18 +58,18 @@ services: image: microsoft/mssql-server-windows basket.data: - image: redis-win - build: - context: ./_docker/redis - dockerfile: Dockerfile.nanowin + image: eshop/redis-win +# build: +# context: ./_docker/redis +# dockerfile: Dockerfile.nanowin ports: - "6379:6379" rabbitmq: - image: rabbitmq-win - build: - context: ./_docker/rabbitmq - dockerfile: Dockerfile.nanowin + image: eshop/rabbitmq-win +# build: +# context: ./_docker/rabbitmq +# dockerfile: Dockerfile.nanowin ports: - "5672:5672" From 077868e51d822a6da272a3121421df977847159a Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Thu, 27 Apr 2017 17:45:34 -0400 Subject: [PATCH 08/15] Fix API signatures for collections The methods that return collections should return Task> not Task --- .../Ordering.API/Application/Queries/IOrderQueries.cs | 5 +++-- .../Ordering.API/Application/Queries/OrderQueries.cs | 8 ++++---- .../UnitTest/Ordering/Application/OrdersWebApiTest.cs | 5 +++-- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Services/Ordering/Ordering.API/Application/Queries/IOrderQueries.cs b/src/Services/Ordering/Ordering.API/Application/Queries/IOrderQueries.cs index 8d78524ea..253b01e9c 100644 --- a/src/Services/Ordering/Ordering.API/Application/Queries/IOrderQueries.cs +++ b/src/Services/Ordering/Ordering.API/Application/Queries/IOrderQueries.cs @@ -1,13 +1,14 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Queries { + using System.Collections.Generic; using System.Threading.Tasks; public interface IOrderQueries { Task GetOrderAsync(int id); - Task GetOrdersAsync(); + Task> GetOrdersAsync(); - Task GetCardTypesAsync(); + Task> GetCardTypesAsync(); } } diff --git a/src/Services/Ordering/Ordering.API/Application/Queries/OrderQueries.cs b/src/Services/Ordering/Ordering.API/Application/Queries/OrderQueries.cs index 9d909e254..afcfc71b5 100644 --- a/src/Services/Ordering/Ordering.API/Application/Queries/OrderQueries.cs +++ b/src/Services/Ordering/Ordering.API/Application/Queries/OrderQueries.cs @@ -44,13 +44,13 @@ } } - public async Task GetOrdersAsync() + public Task> GetOrdersAsync() { using (var connection = new SqlConnection(_connectionString)) { connection.Open(); - return await connection.QueryAsync(@"SELECT o.[Id] as ordernumber,o.[OrderDate] as [date],os.[Name] as [status],SUM(oi.units*oi.unitprice) as total + return connection.QueryAsync(@"SELECT o.[Id] as ordernumber,o.[OrderDate] as [date],os.[Name] as [status],SUM(oi.units*oi.unitprice) as total FROM [ordering].[Orders] o LEFT JOIN[ordering].[orderitems] oi ON o.Id = oi.orderid LEFT JOIN[ordering].[orderstatus] os on o.OrderStatusId = os.Id @@ -58,13 +58,13 @@ } } - public async Task GetCardTypesAsync() + public Task> GetCardTypesAsync() { using (var connection = new SqlConnection(_connectionString)) { connection.Open(); - return await connection.QueryAsync("SELECT * FROM ordering.cardtypes"); + return connection.QueryAsync("SELECT * FROM ordering.cardtypes"); } } diff --git a/test/Services/UnitTest/Ordering/Application/OrdersWebApiTest.cs b/test/Services/UnitTest/Ordering/Application/OrdersWebApiTest.cs index 8c7659862..c0656f050 100644 --- a/test/Services/UnitTest/Ordering/Application/OrdersWebApiTest.cs +++ b/test/Services/UnitTest/Ordering/Application/OrdersWebApiTest.cs @@ -6,6 +6,7 @@ using Microsoft.eShopOnContainers.Services.Ordering.API.Controllers; using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services; using Moq; using System; +using System.Linq; using System.Threading.Tasks; using Xunit; @@ -59,7 +60,7 @@ namespace UnitTest.Ordering.Application public async Task Get_orders_success() { //Arrange - var fakeDynamicResult = new Object(); + var fakeDynamicResult = Enumerable.Empty(); _orderQueriesMock.Setup(x => x.GetOrdersAsync()) .Returns(Task.FromResult(fakeDynamicResult)); @@ -92,7 +93,7 @@ namespace UnitTest.Ordering.Application public async Task Get_cardTypes_success() { //Arrange - var fakeDynamicResult = new Object(); + var fakeDynamicResult = Enumerable.Empty(); _orderQueriesMock.Setup(x => x.GetCardTypesAsync()) .Returns(Task.FromResult(fakeDynamicResult)); From ee787dcaadbe85898dc9aeac869769fbe4995cfd Mon Sep 17 00:00:00 2001 From: Cesar De la Torre Date: Sat, 29 Apr 2017 15:30:17 -0700 Subject: [PATCH 09/15] Revert "Fix API signatures for collections" --- .../Ordering.API/Application/Queries/IOrderQueries.cs | 5 ++--- .../Ordering.API/Application/Queries/OrderQueries.cs | 8 ++++---- .../UnitTest/Ordering/Application/OrdersWebApiTest.cs | 5 ++--- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/Services/Ordering/Ordering.API/Application/Queries/IOrderQueries.cs b/src/Services/Ordering/Ordering.API/Application/Queries/IOrderQueries.cs index 253b01e9c..8d78524ea 100644 --- a/src/Services/Ordering/Ordering.API/Application/Queries/IOrderQueries.cs +++ b/src/Services/Ordering/Ordering.API/Application/Queries/IOrderQueries.cs @@ -1,14 +1,13 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Queries { - using System.Collections.Generic; using System.Threading.Tasks; public interface IOrderQueries { Task GetOrderAsync(int id); - Task> GetOrdersAsync(); + Task GetOrdersAsync(); - Task> GetCardTypesAsync(); + Task GetCardTypesAsync(); } } diff --git a/src/Services/Ordering/Ordering.API/Application/Queries/OrderQueries.cs b/src/Services/Ordering/Ordering.API/Application/Queries/OrderQueries.cs index afcfc71b5..9d909e254 100644 --- a/src/Services/Ordering/Ordering.API/Application/Queries/OrderQueries.cs +++ b/src/Services/Ordering/Ordering.API/Application/Queries/OrderQueries.cs @@ -44,13 +44,13 @@ } } - public Task> GetOrdersAsync() + public async Task GetOrdersAsync() { using (var connection = new SqlConnection(_connectionString)) { connection.Open(); - return connection.QueryAsync(@"SELECT o.[Id] as ordernumber,o.[OrderDate] as [date],os.[Name] as [status],SUM(oi.units*oi.unitprice) as total + return await connection.QueryAsync(@"SELECT o.[Id] as ordernumber,o.[OrderDate] as [date],os.[Name] as [status],SUM(oi.units*oi.unitprice) as total FROM [ordering].[Orders] o LEFT JOIN[ordering].[orderitems] oi ON o.Id = oi.orderid LEFT JOIN[ordering].[orderstatus] os on o.OrderStatusId = os.Id @@ -58,13 +58,13 @@ } } - public Task> GetCardTypesAsync() + public async Task GetCardTypesAsync() { using (var connection = new SqlConnection(_connectionString)) { connection.Open(); - return connection.QueryAsync("SELECT * FROM ordering.cardtypes"); + return await connection.QueryAsync("SELECT * FROM ordering.cardtypes"); } } diff --git a/test/Services/UnitTest/Ordering/Application/OrdersWebApiTest.cs b/test/Services/UnitTest/Ordering/Application/OrdersWebApiTest.cs index c0656f050..8c7659862 100644 --- a/test/Services/UnitTest/Ordering/Application/OrdersWebApiTest.cs +++ b/test/Services/UnitTest/Ordering/Application/OrdersWebApiTest.cs @@ -6,7 +6,6 @@ using Microsoft.eShopOnContainers.Services.Ordering.API.Controllers; using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services; using Moq; using System; -using System.Linq; using System.Threading.Tasks; using Xunit; @@ -60,7 +59,7 @@ namespace UnitTest.Ordering.Application public async Task Get_orders_success() { //Arrange - var fakeDynamicResult = Enumerable.Empty(); + var fakeDynamicResult = new Object(); _orderQueriesMock.Setup(x => x.GetOrdersAsync()) .Returns(Task.FromResult(fakeDynamicResult)); @@ -93,7 +92,7 @@ namespace UnitTest.Ordering.Application public async Task Get_cardTypes_success() { //Arrange - var fakeDynamicResult = Enumerable.Empty(); + var fakeDynamicResult = new Object(); _orderQueriesMock.Setup(x => x.GetCardTypesAsync()) .Returns(Task.FromResult(fakeDynamicResult)); From d143c9eea00ba4a9fbe611b0aa3b8cd8d4837474 Mon Sep 17 00:00:00 2001 From: Cesar De la Torre Date: Sat, 29 Apr 2017 15:40:35 -0700 Subject: [PATCH 10/15] Removing the BasketUrl from the Ordering microservice since it is not used and it shouldn't be used in any case. Don't want synchronous Http communication between the microservices which would impact resiliency. Http only between the client apps and the first level of microservices (or API Gateways, if we had them). --- docker-compose.override.yml | 1 - docker-compose.prod.yml | 1 - 2 files changed, 2 deletions(-) diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 8b0b4602b..f96a8d177 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -45,7 +45,6 @@ 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" diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 5a88f2c6c..58bfe5f82 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -50,7 +50,6 @@ 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" From aeddf51a339ee31072228b81aaa2b11a689676d1 Mon Sep 17 00:00:00 2001 From: Cesar De la Torre Date: Sat, 29 Apr 2017 21:58:11 -0700 Subject: [PATCH 11/15] Refactoring to better terms --- .../DefaultRabbitMQPersisterConnection.cs | 12 +++---- .../EventBusRabbitMQ/EventBusRabbitMQ.cs | 31 +++++++++---------- .../IRabbitMQPersisterConnection.cs | 3 +- src/Services/Basket/Basket.API/Startup.cs | 6 ++-- src/Services/Catalog/Catalog.API/Startup.cs | 6 ++-- src/Services/Ordering/Ordering.API/Startup.cs | 6 ++-- 6 files changed, 31 insertions(+), 33 deletions(-) diff --git a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/DefaultRabbitMQPersisterConnection.cs b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/DefaultRabbitMQPersisterConnection.cs index 894afb4e4..0aafaf90a 100644 --- a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/DefaultRabbitMQPersisterConnection.cs +++ b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/DefaultRabbitMQPersisterConnection.cs @@ -10,18 +10,18 @@ using System.Net.Sockets; namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ { - public class DefaultRabbitMQPersisterConnection - : IRabbitMQPersisterConnection + public class DefaultRabbitMQPersistentConnection + : IRabbitMQPersistentConnection { private readonly IConnectionFactory _connectionFactory; - private readonly ILogger _logger; + private readonly ILogger _logger; IConnection _connection; bool _disposed; object sync_root = new object(); - public DefaultRabbitMQPersisterConnection(IConnectionFactory connectionFactory,ILogger logger) + public DefaultRabbitMQPersistentConnection(IConnectionFactory connectionFactory,ILogger logger) { _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); @@ -87,13 +87,13 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ _connection.CallbackException += OnCallbackException; _connection.ConnectionBlocked += OnConnectionBlocked; - _logger.LogInformation($"RabbitMQ persister connection acquire a connection {_connection.Endpoint.HostName} and is subscribed to failure events"); + _logger.LogInformation($"RabbitMQ persistent connection acquired a connection {_connection.Endpoint.HostName} and is subscribed to failure events"); return true; } else { - _logger.LogCritical("FATAL ERROR: RabbitMQ connections can't be created and opened"); + _logger.LogCritical("FATAL ERROR: RabbitMQ connections could not be created and opened"); return false; } diff --git a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs index e7a493c10..0eb29b72b 100644 --- a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs +++ b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs @@ -21,7 +21,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ { const string BROKER_NAME = "eshop_event_bus"; - private readonly IRabbitMQPersisterConnection _persisterConnection; + private readonly IRabbitMQPersistentConnection _persistentConnection; private readonly ILogger _logger; private readonly Dictionary> _handlers @@ -33,20 +33,19 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ private IModel _consumerChannel; private string _queueName; - public EventBusRabbitMQ(IRabbitMQPersisterConnection persisterConnection, ILogger logger) + public EventBusRabbitMQ(IRabbitMQPersistentConnection persistentConnection, ILogger logger) { - _persisterConnection = persisterConnection ?? throw new ArgumentNullException(nameof(persisterConnection)); + _persistentConnection = persistentConnection ?? throw new ArgumentNullException(nameof(persistentConnection)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _consumerChannel = CreateConsumerChannel(); } - public void Publish(IntegrationEvent @event) { - if (!_persisterConnection.IsConnected) + if (!_persistentConnection.IsConnected) { - _persisterConnection.TryConnect(); + _persistentConnection.TryConnect(); } var policy = RetryPolicy.Handle() @@ -56,7 +55,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ _logger.LogWarning(ex.ToString()); }); - using (var channel = _persisterConnection.CreateModel()) + using (var channel = _persistentConnection.CreateModel()) { var eventName = @event.GetType() .Name; @@ -87,12 +86,12 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ } else { - if (!_persisterConnection.IsConnected) + if (!_persistentConnection.IsConnected) { - _persisterConnection.TryConnect(); + _persistentConnection.TryConnect(); } - using (var channel = _persisterConnection.CreateModel()) + using (var channel = _persistentConnection.CreateModel()) { channel.QueueBind(queue: _queueName, exchange: BROKER_NAME, @@ -125,12 +124,12 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ { _eventTypes.Remove(eventType); - if (!_persisterConnection.IsConnected) + if (!_persistentConnection.IsConnected) { - _persisterConnection.TryConnect(); + _persistentConnection.TryConnect(); } - using (var channel = _persisterConnection.CreateModel()) + using (var channel = _persistentConnection.CreateModel()) { channel.QueueUnbind(queue: _queueName, exchange: BROKER_NAME, @@ -160,12 +159,12 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ private IModel CreateConsumerChannel() { - if (!_persisterConnection.IsConnected) + if (!_persistentConnection.IsConnected) { - _persisterConnection.TryConnect(); + _persistentConnection.TryConnect(); } - var channel = _persisterConnection.CreateModel(); + var channel = _persistentConnection.CreateModel(); channel.ExchangeDeclare(exchange: BROKER_NAME, type: "direct"); diff --git a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/IRabbitMQPersisterConnection.cs b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/IRabbitMQPersisterConnection.cs index b9debe743..5893791c5 100644 --- a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/IRabbitMQPersisterConnection.cs +++ b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/IRabbitMQPersisterConnection.cs @@ -3,8 +3,7 @@ using System; namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ { - - public interface IRabbitMQPersisterConnection + public interface IRabbitMQPersistentConnection : IDisposable { bool IsConnected { get; } diff --git a/src/Services/Basket/Basket.API/Startup.cs b/src/Services/Basket/Basket.API/Startup.cs index 60fc46de2..da9baac48 100644 --- a/src/Services/Basket/Basket.API/Startup.cs +++ b/src/Services/Basket/Basket.API/Startup.cs @@ -68,16 +68,16 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API }); - services.AddSingleton(sp => + services.AddSingleton(sp => { var settings = sp.GetRequiredService>().Value; - var logger = sp.GetRequiredService>(); + var logger = sp.GetRequiredService>(); var factory = new ConnectionFactory() { HostName = settings.EventBusConnection }; - return new DefaultRabbitMQPersisterConnection(factory, logger); + return new DefaultRabbitMQPersistentConnection(factory, logger); }); services.AddSingleton(); diff --git a/src/Services/Catalog/Catalog.API/Startup.cs b/src/Services/Catalog/Catalog.API/Startup.cs index c13ac2d1b..1c1408b67 100644 --- a/src/Services/Catalog/Catalog.API/Startup.cs +++ b/src/Services/Catalog/Catalog.API/Startup.cs @@ -103,16 +103,16 @@ services.AddTransient(); - services.AddSingleton(sp => + services.AddSingleton(sp => { var settings = sp.GetRequiredService>().Value; - var logger = sp.GetRequiredService>(); + var logger = sp.GetRequiredService>(); var factory = new ConnectionFactory() { HostName = settings.EventBusConnection }; - return new DefaultRabbitMQPersisterConnection(factory, logger); + return new DefaultRabbitMQPersistentConnection(factory, logger); }); services.AddSingleton(); diff --git a/src/Services/Ordering/Ordering.API/Startup.cs b/src/Services/Ordering/Ordering.API/Startup.cs index 0d6e222b6..6665908a6 100644 --- a/src/Services/Ordering/Ordering.API/Startup.cs +++ b/src/Services/Ordering/Ordering.API/Startup.cs @@ -107,16 +107,16 @@ var serviceProvider = services.BuildServiceProvider(); services.AddTransient(); - services.AddSingleton(sp => + services.AddSingleton(sp => { - var logger = sp.GetRequiredService>(); + var logger = sp.GetRequiredService>(); var factory = new ConnectionFactory() { HostName = Configuration["EventBusConnection"] }; - return new DefaultRabbitMQPersisterConnection(factory, logger); + return new DefaultRabbitMQPersistentConnection(factory, logger); }); services.AddSingleton(); From a4b7d485cd52550428d21c3e741792d22fb25698 Mon Sep 17 00:00:00 2001 From: Cesar De la Torre Date: Mon, 1 May 2017 17:56:33 -0700 Subject: [PATCH 12/15] Minor comment typo changeed --- .../Ordering/Ordering.Infrastructure/OrderingContext.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs b/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs index 5b95ee23c..18d66534b 100644 --- a/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs +++ b/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs @@ -252,7 +252,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure // After executing this line all the changes (from the Command Handler and Domain Event Handlers) - // performed thought the DbContext will be commited + // performed throught the DbContext will be commited var result = await base.SaveChangesAsync(); return true; From 22cc8daa65eee3b59bdb5441309b68552ced0418 Mon Sep 17 00:00:00 2001 From: Eduard Tomas Date: Tue, 2 May 2017 10:29:24 +0200 Subject: [PATCH 13/15] Updated healthcheck lib --- eShopOnContainers-ServicesAndWebApps.sln | 206 +++++++++--------- .../HealthCheckMiddleware.cs | 22 +- .../HealthCheckStartupFilter.cs | 15 +- .../HealthCheckWebHostBuilderExtension.cs | 21 +- .../HealthCheckBuilderSqlServerExtensions.cs} | 4 +- ....Extensions.HealthChecks.SqlServer.csproj} | 0 .../CachedHealthCheck.cs | 109 +++++++++ .../CachedHealthCheckExtensions.cs | 19 ++ .../Checks/AddCheck.cs | 54 +++-- .../Checks/NumericChecks.cs | 10 +- .../Checks/UrlChecks.cs | 65 +----- .../CompositeHealthCheckResult.cs | 19 +- .../HealthCheck.cs | 64 ++---- .../HealthCheckBuilder.cs | 109 ++++++++- .../HealthCheckExtensions.cs | 18 -- .../HealthCheckGroup.cs | 37 ++++ .../HealthCheckResult.cs | 1 - .../HealthCheckService.cs | 117 +++++++--- .../HealthCheckServiceCollectionExtensions.cs | 18 +- .../HealthCheckServiceExtensions.cs | 38 ---- .../IHealthCheck.cs | 5 +- .../IHealthCheckService.cs | 47 +++- .../Internal/UrlChecker.cs | 60 ++--- .../HealthChecks/src/common/Guard.cs | 32 ++- .../Basket/Basket.API/Basket.API.csproj | 1 - .../Catalog/Catalog.API/Catalog.API.csproj | 2 +- .../Identity/Identity.API/Identity.API.csproj | 2 +- .../Ordering/Ordering.API/Ordering.API.csproj | 2 +- src/Web/WebMVC/WebMVC.csproj | 1 - src/Web/WebSPA/WebSPA.csproj | 1 - src/Web/WebStatus/WebStatus.csproj | 1 - 31 files changed, 670 insertions(+), 430 deletions(-) rename src/BuildingBlocks/HealthChecks/src/{Microsoft.Extensions.HealthChecks.Data/HealthCheckBuilderDataExtensions.cs => Microsoft.Extensions.HealthChecks.SqlServer/HealthCheckBuilderSqlServerExtensions.cs} (94%) rename src/BuildingBlocks/HealthChecks/src/{Microsoft.Extensions.HealthChecks.Data/Microsoft.Extensions.HealthChecks.Data.csproj => Microsoft.Extensions.HealthChecks.SqlServer/Microsoft.Extensions.HealthChecks.SqlServer.csproj} (100%) create mode 100644 src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CachedHealthCheck.cs create mode 100644 src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CachedHealthCheckExtensions.cs delete mode 100644 src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckExtensions.cs create mode 100644 src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckGroup.cs delete mode 100644 src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckServiceExtensions.cs diff --git a/eShopOnContainers-ServicesAndWebApps.sln b/eShopOnContainers-ServicesAndWebApps.sln index 53a9ceada..27434ec86 100644 --- a/eShopOnContainers-ServicesAndWebApps.sln +++ b/eShopOnContainers-ServicesAndWebApps.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26228.12 +VisualStudioVersion = 15.0.26403.7 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{932D8224-11F6-4D07-B109-DA28AD288A63}" EndProject @@ -62,18 +62,18 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IntegrationEventLogEF", "sr EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HealthChecks", "HealthChecks", "{A81ECBC2-6B00-4DCD-8388-469174033379}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.HealthChecks", "src\BuildingBlocks\HealthChecks\src\Microsoft.AspNetCore.HealthChecks\Microsoft.AspNetCore.HealthChecks.csproj", "{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.HealthChecks", "src\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks\Microsoft.Extensions.HealthChecks.csproj", "{942ED6E8-0050-495F-A0EA-01E97F63760C}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.HealthChecks.Data", "src\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.Data\Microsoft.Extensions.HealthChecks.Data.csproj", "{7804FC60-23E6-490C-8E08-F9FEF829F184}" -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 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.HealthChecks", "src\BuildingBlocks\HealthChecks\src\Microsoft.AspNetCore.HealthChecks\Microsoft.AspNetCore.HealthChecks.csproj", "{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.HealthChecks.SqlServer", "src\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.SqlServer\Microsoft.Extensions.HealthChecks.SqlServer.csproj", "{4BD76717-3102-4969-8C2C-BAAA3F0263B6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Ad-Hoc|Any CPU = Ad-Hoc|Any CPU @@ -712,54 +712,6 @@ Global {9EE28E45-1533-472B-8267-56C48855BA0E}.Release|x64.Build.0 = Release|Any CPU {9EE28E45-1533-472B-8267-56C48855BA0E}.Release|x86.ActiveCfg = Release|Any CPU {9EE28E45-1533-472B-8267-56C48855BA0E}.Release|x86.Build.0 = Release|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|x64.Build.0 = Debug|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|x86.Build.0 = Debug|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|Any CPU.Build.0 = Debug|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|ARM.ActiveCfg = Debug|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|ARM.Build.0 = Debug|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|iPhone.ActiveCfg = Debug|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|iPhone.Build.0 = Debug|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|x64.ActiveCfg = Debug|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|x64.Build.0 = Debug|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|x86.ActiveCfg = Debug|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|x86.Build.0 = Debug|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|ARM.ActiveCfg = Debug|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|ARM.Build.0 = Debug|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|iPhone.Build.0 = Debug|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|x64.ActiveCfg = Debug|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|x64.Build.0 = Debug|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|x86.ActiveCfg = Debug|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|x86.Build.0 = Debug|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|Any CPU.Build.0 = Release|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|ARM.ActiveCfg = Release|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|ARM.Build.0 = Release|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|iPhone.ActiveCfg = Release|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|iPhone.Build.0 = Release|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|x64.ActiveCfg = Release|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|x64.Build.0 = Release|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|x86.ActiveCfg = Release|Any CPU - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|x86.Build.0 = Release|Any CPU {942ED6E8-0050-495F-A0EA-01E97F63760C}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU {942ED6E8-0050-495F-A0EA-01E97F63760C}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU {942ED6E8-0050-495F-A0EA-01E97F63760C}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU @@ -808,54 +760,6 @@ Global {942ED6E8-0050-495F-A0EA-01E97F63760C}.Release|x64.Build.0 = Release|Any CPU {942ED6E8-0050-495F-A0EA-01E97F63760C}.Release|x86.ActiveCfg = Release|Any CPU {942ED6E8-0050-495F-A0EA-01E97F63760C}.Release|x86.Build.0 = Release|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|x64.Build.0 = Debug|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|x86.Build.0 = Debug|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|Any CPU.Build.0 = Debug|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|ARM.ActiveCfg = Debug|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|ARM.Build.0 = Debug|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|iPhone.ActiveCfg = Debug|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|iPhone.Build.0 = Debug|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|x64.ActiveCfg = Debug|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|x64.Build.0 = Debug|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|x86.ActiveCfg = Debug|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|x86.Build.0 = Debug|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|ARM.ActiveCfg = Debug|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|ARM.Build.0 = Debug|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|iPhone.Build.0 = Debug|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|x64.ActiveCfg = Debug|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|x64.Build.0 = Debug|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|x86.ActiveCfg = Debug|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|x86.Build.0 = Debug|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|Any CPU.Build.0 = Release|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|ARM.ActiveCfg = Release|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|ARM.Build.0 = Release|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|iPhone.ActiveCfg = Release|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|iPhone.Build.0 = Release|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|x64.ActiveCfg = Release|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|x64.Build.0 = Release|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|x86.ActiveCfg = Release|Any CPU - {7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|x86.Build.0 = Release|Any CPU {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU @@ -952,6 +856,102 @@ Global {D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Release|x64.Build.0 = Release|Any CPU {D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Release|x86.ActiveCfg = Release|Any CPU {D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Release|x86.Build.0 = Release|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|x64.Build.0 = Debug|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|x86.Build.0 = Debug|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|ARM.ActiveCfg = Debug|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|ARM.Build.0 = Debug|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|iPhone.Build.0 = Debug|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|x64.ActiveCfg = Debug|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|x64.Build.0 = Debug|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|x86.ActiveCfg = Debug|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|x86.Build.0 = Debug|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|ARM.ActiveCfg = Debug|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|ARM.Build.0 = Debug|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|iPhone.Build.0 = Debug|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|x64.ActiveCfg = Debug|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|x64.Build.0 = Debug|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|x86.ActiveCfg = Debug|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|x86.Build.0 = Debug|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|Any CPU.Build.0 = Release|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|ARM.ActiveCfg = Release|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|ARM.Build.0 = Release|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|iPhone.ActiveCfg = Release|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|iPhone.Build.0 = Release|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|x64.ActiveCfg = Release|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|x64.Build.0 = Release|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|x86.ActiveCfg = Release|Any CPU + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|x86.Build.0 = Release|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|x64.Build.0 = Debug|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|x86.Build.0 = Debug|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|ARM.ActiveCfg = Debug|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|ARM.Build.0 = Debug|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|iPhone.Build.0 = Debug|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|x64.ActiveCfg = Debug|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|x64.Build.0 = Debug|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|x86.ActiveCfg = Debug|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|x86.Build.0 = Debug|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|ARM.ActiveCfg = Debug|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|ARM.Build.0 = Debug|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|iPhone.Build.0 = Debug|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|x64.ActiveCfg = Debug|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|x64.Build.0 = Debug|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|x86.ActiveCfg = Debug|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|x86.Build.0 = Debug|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|Any CPU.Build.0 = Release|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|ARM.ActiveCfg = Release|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|ARM.Build.0 = Release|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|iPhone.ActiveCfg = Release|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|iPhone.Build.0 = Release|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|x64.ActiveCfg = Release|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|x64.Build.0 = Release|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|x86.ActiveCfg = Release|Any CPU + {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -981,11 +981,11 @@ Global {8088F3FC-6787-45FA-A924-816EC81CBFAC} = {807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F} {9EE28E45-1533-472B-8267-56C48855BA0E} = {807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F} {A81ECBC2-6B00-4DCD-8388-469174033379} = {DB0EFB20-B024-4E5E-A75C-52143C131D25} - {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E} = {A81ECBC2-6B00-4DCD-8388-469174033379} {942ED6E8-0050-495F-A0EA-01E97F63760C} = {A81ECBC2-6B00-4DCD-8388-469174033379} - {7804FC60-23E6-490C-8E08-F9FEF829F184} = {A81ECBC2-6B00-4DCD-8388-469174033379} {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} + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5} = {A81ECBC2-6B00-4DCD-8388-469174033379} + {4BD76717-3102-4969-8C2C-BAAA3F0263B6} = {A81ECBC2-6B00-4DCD-8388-469174033379} EndGlobalSection EndGlobal diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckMiddleware.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckMiddleware.cs index 64b4f48c2..f8e68c957 100644 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckMiddleware.cs +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckMiddleware.cs @@ -1,6 +1,8 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; @@ -11,30 +13,34 @@ namespace Microsoft.AspNetCore.HealthChecks { public class HealthCheckMiddleware { - private RequestDelegate _next; - private string _path; - private int? _port; - private IHealthCheckService _service; + private readonly RequestDelegate _next; + private readonly string _path; + private readonly int? _port; + private readonly IHealthCheckService _service; + private readonly TimeSpan _timeout; - public HealthCheckMiddleware(RequestDelegate next, IHealthCheckService service, int port) + public HealthCheckMiddleware(RequestDelegate next, IHealthCheckService service, int port, TimeSpan timeout) { _port = port; _service = service; _next = next; + _timeout = timeout; } - public HealthCheckMiddleware(RequestDelegate next, IHealthCheckService service, string path) + public HealthCheckMiddleware(RequestDelegate next, IHealthCheckService service, string path, TimeSpan timeout) { _path = path; _service = service; _next = next; + _timeout = timeout; } public async Task Invoke(HttpContext context) { if (IsHealthCheckRequest(context)) { - var result = await _service.CheckHealthAsync(); + var timeoutTokenSource = new CancellationTokenSource(_timeout); + var result = await _service.CheckHealthAsync(timeoutTokenSource.Token); var status = result.CheckStatus; if (status != CheckStatus.Healthy) @@ -60,7 +66,9 @@ namespace Microsoft.AspNetCore.HealthChecks } if (context.Request.Path == _path) + { return true; + } return false; } diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckStartupFilter.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckStartupFilter.cs index 4aa91b070..cac4b1188 100644 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckStartupFilter.cs +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckStartupFilter.cs @@ -11,15 +11,18 @@ namespace Microsoft.AspNetCore.HealthChecks { private string _path; private int? _port; + private TimeSpan _timeout; - public HealthCheckStartupFilter(int port) + public HealthCheckStartupFilter(int port, TimeSpan timeout) { _port = port; + _timeout = timeout; } - public HealthCheckStartupFilter(string path) + public HealthCheckStartupFilter(string path, TimeSpan timeout) { _path = path; + _timeout = timeout; } public Action Configure(Action next) @@ -27,9 +30,13 @@ namespace Microsoft.AspNetCore.HealthChecks return app => { if (_port.HasValue) - app.UseMiddleware(_port); + { + app.UseMiddleware(_port, _timeout); + } else - app.UseMiddleware(_path); + { + app.UseMiddleware(_path, _timeout); + } next(app); }; diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckWebHostBuilderExtension.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckWebHostBuilderExtension.cs index 0b806ca06..467293137 100644 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckWebHostBuilderExtension.cs +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckWebHostBuilderExtension.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using Microsoft.AspNetCore.HealthChecks; using Microsoft.Extensions.DependencyInjection; @@ -8,28 +9,38 @@ namespace Microsoft.AspNetCore.Hosting { public static class HealthCheckWebHostBuilderExtension { + public static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(10); + public static IWebHostBuilder UseHealthChecks(this IWebHostBuilder builder, int port) + => UseHealthChecks(builder, port, DefaultTimeout); + + public static IWebHostBuilder UseHealthChecks(this IWebHostBuilder builder, int port, TimeSpan timeout) { - Guard.ArgumentValid(port > 0 && port < 65536, nameof(port), "Port must be a value between 1 and 65535"); + Guard.ArgumentValid(port > 0 && port < 65536, nameof(port), "Port must be a value between 1 and 65535."); + Guard.ArgumentValid(timeout > TimeSpan.Zero, nameof(timeout), "Health check timeout must be a positive time span."); builder.ConfigureServices(services => { var existingUrl = builder.GetSetting(WebHostDefaults.ServerUrlsKey); builder.UseSetting(WebHostDefaults.ServerUrlsKey, $"{existingUrl};http://localhost:{port}"); - services.AddSingleton(new HealthCheckStartupFilter(port)); + services.AddSingleton(new HealthCheckStartupFilter(port, timeout)); }); return builder; } public static IWebHostBuilder UseHealthChecks(this IWebHostBuilder builder, string path) + => UseHealthChecks(builder, path, DefaultTimeout); + + public static IWebHostBuilder UseHealthChecks(this IWebHostBuilder builder, string path, TimeSpan timeout) { Guard.ArgumentNotNull(nameof(path), path); // REVIEW: Is there a better URL path validator somewhere? - Guard.ArgumentValid(!path.Contains("?"), nameof(path), "Path cannot contain query string values"); - Guard.ArgumentValid(path.StartsWith("/"), nameof(path), "Path should start with /"); + Guard.ArgumentValid(!path.Contains("?"), nameof(path), "Path cannot contain query string values."); + Guard.ArgumentValid(path.StartsWith("/"), nameof(path), "Path should start with '/'."); + Guard.ArgumentValid(timeout > TimeSpan.Zero, nameof(timeout), "Health check timeout must be a positive time span."); - builder.ConfigureServices(services => services.AddSingleton(new HealthCheckStartupFilter(path))); + builder.ConfigureServices(services => services.AddSingleton(new HealthCheckStartupFilter(path, timeout))); return builder; } } diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.Data/HealthCheckBuilderDataExtensions.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.SqlServer/HealthCheckBuilderSqlServerExtensions.cs similarity index 94% rename from src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.Data/HealthCheckBuilderDataExtensions.cs rename to src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.SqlServer/HealthCheckBuilderSqlServerExtensions.cs index e209dfdaf..4998c91ed 100644 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.Data/HealthCheckBuilderDataExtensions.cs +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.SqlServer/HealthCheckBuilderSqlServerExtensions.cs @@ -7,7 +7,7 @@ using System.Data.SqlClient; namespace Microsoft.Extensions.HealthChecks { - public static class HealthCheckBuilderDataExtensions + public static class HealthCheckBuilderSqlServerExtensions { public static HealthCheckBuilder AddSqlCheck(this HealthCheckBuilder builder, string name, string connectionString) { @@ -33,7 +33,7 @@ namespace Microsoft.Extensions.HealthChecks } } } - catch(Exception ex) + catch (Exception ex) { return HealthCheckResult.Unhealthy($"SqlCheck({name}): Exception during check: {ex.GetType().FullName}"); } diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.Data/Microsoft.Extensions.HealthChecks.Data.csproj b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.SqlServer/Microsoft.Extensions.HealthChecks.SqlServer.csproj similarity index 100% rename from src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.Data/Microsoft.Extensions.HealthChecks.Data.csproj rename to src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.SqlServer/Microsoft.Extensions.HealthChecks.SqlServer.csproj diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CachedHealthCheck.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CachedHealthCheck.cs new file mode 100644 index 000000000..39ed087eb --- /dev/null +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CachedHealthCheck.cs @@ -0,0 +1,109 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.Extensions.HealthChecks +{ + public abstract class CachedHealthCheck + { + private static readonly TypeInfo HealthCheckTypeInfo = typeof(IHealthCheck).GetTypeInfo(); + + private volatile int _writerCount; + + public CachedHealthCheck(string name, TimeSpan cacheDuration) + { + Guard.ArgumentNotNullOrEmpty(nameof(name), name); + Guard.ArgumentValid(cacheDuration.TotalMilliseconds >= 0, nameof(cacheDuration), "Cache duration must be zero (disabled) or greater than zero."); + + Name = name; + CacheDuration = cacheDuration; + } + + public IHealthCheckResult CachedResult { get; internal set; } + + public TimeSpan CacheDuration { get; } + + public DateTimeOffset CacheExpiration { get; internal set; } + + public string Name { get; } + + protected virtual DateTimeOffset UtcNow => DateTimeOffset.UtcNow; + + protected abstract IHealthCheck Resolve(IServiceProvider serviceProvider); + + public async ValueTask RunAsync(IServiceProvider serviceProvider, CancellationToken cancellationToken = default(CancellationToken)) + { + while (CacheExpiration <= UtcNow) + { + // Can't use a standard lock here because of async, so we'll use this flag to determine when we should write a value, + // and the waiters who aren't allowed to write will just spin wait for the new value. + if (Interlocked.Exchange(ref _writerCount, 1) != 0) + { + await Task.Delay(5, cancellationToken).ConfigureAwait(false); + continue; + } + + try + { + var check = Resolve(serviceProvider); + CachedResult = await check.CheckAsync(cancellationToken); + } + catch (OperationCanceledException) + { + CachedResult = HealthCheckResult.Unhealthy("The health check operation timed out"); + } + catch (Exception ex) + { + CachedResult = HealthCheckResult.Unhealthy($"Exception during check: {ex.GetType().FullName}"); + } + + CacheExpiration = UtcNow + CacheDuration; + _writerCount = 0; + break; + } + + return CachedResult; + } + + public static CachedHealthCheck FromHealthCheck(string name, TimeSpan cacheDuration, IHealthCheck healthCheck) + { + Guard.ArgumentNotNull(nameof(healthCheck), healthCheck); + + return new TypeOrHealthCheck_HealthCheck(name, cacheDuration, healthCheck); + } + + public static CachedHealthCheck FromType(string name, TimeSpan cacheDuration, Type healthCheckType) + { + Guard.ArgumentNotNull(nameof(healthCheckType), healthCheckType); + Guard.ArgumentValid(HealthCheckTypeInfo.IsAssignableFrom(healthCheckType.GetTypeInfo()), nameof(healthCheckType), $"Health check must implement '{typeof(IHealthCheck).FullName}'."); + + return new TypeOrHealthCheck_Type(name, cacheDuration, healthCheckType); + } + + class TypeOrHealthCheck_HealthCheck : CachedHealthCheck + { + private readonly IHealthCheck _healthCheck; + + public TypeOrHealthCheck_HealthCheck(string name, TimeSpan cacheDuration, IHealthCheck healthCheck) : base(name, cacheDuration) + => _healthCheck = healthCheck; + + protected override IHealthCheck Resolve(IServiceProvider serviceProvider) => _healthCheck; + } + + class TypeOrHealthCheck_Type : CachedHealthCheck + { + private readonly Type _healthCheckType; + + public TypeOrHealthCheck_Type(string name, TimeSpan cacheDuration, Type healthCheckType) : base(name, cacheDuration) + => _healthCheckType = healthCheckType; + + protected override IHealthCheck Resolve(IServiceProvider serviceProvider) + => (IHealthCheck)serviceProvider.GetRequiredService(_healthCheckType); + } + } +} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CachedHealthCheckExtensions.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CachedHealthCheckExtensions.cs new file mode 100644 index 000000000..2c3388709 --- /dev/null +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CachedHealthCheckExtensions.cs @@ -0,0 +1,19 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Extensions.HealthChecks +{ + public static class CachedHealthCheckExtensions + { + public static ValueTask RunAsync(this CachedHealthCheck check, IServiceProvider serviceProvider) + { + Guard.ArgumentNotNull(nameof(check), check); + + return check.RunAsync(serviceProvider, CancellationToken.None); + } + } +} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/AddCheck.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/AddCheck.cs index cd10fb93d..5b7b49af0 100644 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/AddCheck.cs +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/AddCheck.cs @@ -15,96 +15,102 @@ namespace Microsoft.Extensions.HealthChecks { Guard.ArgumentNotNull(nameof(builder), builder); - builder.AddCheck(name, HealthCheck.FromCheck(check, builder.DefaultCacheDuration)); - return builder; + return builder.AddCheck(name, HealthCheck.FromCheck(check), builder.DefaultCacheDuration); } public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func check) { Guard.ArgumentNotNull(nameof(builder), builder); - builder.AddCheck(name, HealthCheck.FromCheck(check, builder.DefaultCacheDuration)); - return builder; + return builder.AddCheck(name, HealthCheck.FromCheck(check), builder.DefaultCacheDuration); } public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func check, TimeSpan cacheDuration) { Guard.ArgumentNotNull(nameof(builder), builder); - builder.AddCheck(name, HealthCheck.FromCheck(check, cacheDuration)); - return builder; + return builder.AddCheck(name, HealthCheck.FromCheck(check), cacheDuration); } public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func check, TimeSpan cacheDuration) { Guard.ArgumentNotNull(nameof(builder), builder); - builder.AddCheck(name, HealthCheck.FromCheck(check, cacheDuration)); - return builder; + return builder.AddCheck(name, HealthCheck.FromCheck(check), cacheDuration); } public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func> check) { Guard.ArgumentNotNull(nameof(builder), builder); - builder.AddCheck(name, HealthCheck.FromTaskCheck(check, builder.DefaultCacheDuration)); - return builder; + return builder.AddCheck(name, HealthCheck.FromTaskCheck(check), builder.DefaultCacheDuration); } public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func> check) { Guard.ArgumentNotNull(nameof(builder), builder); - builder.AddCheck(name, HealthCheck.FromTaskCheck(check, builder.DefaultCacheDuration)); - return builder; + return builder.AddCheck(name, HealthCheck.FromTaskCheck(check), builder.DefaultCacheDuration); } public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func> check, TimeSpan cacheDuration) { Guard.ArgumentNotNull(nameof(builder), builder); - builder.AddCheck(name, HealthCheck.FromTaskCheck(check, cacheDuration)); - return builder; + return builder.AddCheck(name, HealthCheck.FromTaskCheck(check), cacheDuration); } public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func> check, TimeSpan cacheDuration) { Guard.ArgumentNotNull(nameof(builder), builder); - builder.AddCheck(name, HealthCheck.FromTaskCheck(check, cacheDuration)); - return builder; + return builder.AddCheck(name, HealthCheck.FromTaskCheck(check), cacheDuration); } public static HealthCheckBuilder AddValueTaskCheck(this HealthCheckBuilder builder, string name, Func> check) { Guard.ArgumentNotNull(nameof(builder), builder); - builder.AddCheck(name, HealthCheck.FromValueTaskCheck(check, builder.DefaultCacheDuration)); - return builder; + return builder.AddCheck(name, HealthCheck.FromValueTaskCheck(check), builder.DefaultCacheDuration); } public static HealthCheckBuilder AddValueTaskCheck(this HealthCheckBuilder builder, string name, Func> check) { Guard.ArgumentNotNull(nameof(builder), builder); - builder.AddCheck(name, HealthCheck.FromValueTaskCheck(check, builder.DefaultCacheDuration)); - return builder; + return builder.AddCheck(name, HealthCheck.FromValueTaskCheck(check), builder.DefaultCacheDuration); } public static HealthCheckBuilder AddValueTaskCheck(this HealthCheckBuilder builder, string name, Func> check, TimeSpan cacheDuration) { Guard.ArgumentNotNull(nameof(builder), builder); - builder.AddCheck(name, HealthCheck.FromValueTaskCheck(check, cacheDuration)); - return builder; + return builder.AddCheck(name, HealthCheck.FromValueTaskCheck(check), cacheDuration); } public static HealthCheckBuilder AddValueTaskCheck(this HealthCheckBuilder builder, string name, Func> check, TimeSpan cacheDuration) { Guard.ArgumentNotNull(nameof(builder), builder); - builder.AddCheck(name, HealthCheck.FromValueTaskCheck(check, cacheDuration)); - return builder; + return builder.AddCheck(name, HealthCheck.FromValueTaskCheck(check), cacheDuration); + } + + // IHealthCheck versions of AddCheck + + public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string checkName, IHealthCheck check) + { + Guard.ArgumentNotNull(nameof(builder), builder); + + return builder.AddCheck(checkName, check, builder.DefaultCacheDuration); + } + + // Type versions of AddCheck + + public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name) where TCheck : class, IHealthCheck + { + Guard.ArgumentNotNull(nameof(builder), builder); + + return builder.AddCheck(name, builder.DefaultCacheDuration); } } } diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/NumericChecks.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/NumericChecks.cs index cb97eec46..f3c795629 100644 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/NumericChecks.cs +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/NumericChecks.cs @@ -14,7 +14,7 @@ namespace Microsoft.Extensions.HealthChecks where T : IComparable { Guard.ArgumentNotNull(nameof(builder), builder); - Guard.ArgumentNotNullOrWhitespace(nameof(name), name); + Guard.ArgumentNotNullOrEmpty(nameof(name), name); Guard.ArgumentNotNull(nameof(currentValueFunc), currentValueFunc); builder.AddCheck(name, () => @@ -23,7 +23,7 @@ namespace Microsoft.Extensions.HealthChecks var status = currentValue.CompareTo(minValue) >= 0 ? CheckStatus.Healthy : CheckStatus.Unhealthy; return HealthCheckResult.FromStatus( status, - $"{name}: min={minValue}, current={currentValue}", + $"min={minValue}, current={currentValue}", new Dictionary { { "min", minValue }, { "current", currentValue } } ); }); @@ -35,16 +35,16 @@ namespace Microsoft.Extensions.HealthChecks where T : IComparable { Guard.ArgumentNotNull(nameof(builder), builder); - Guard.ArgumentNotNullOrWhitespace(nameof(name), name); + Guard.ArgumentNotNullOrEmpty(nameof(name), name); Guard.ArgumentNotNull(nameof(currentValueFunc), currentValueFunc); - builder.AddCheck($"{name}", () => + builder.AddCheck(name, () => { var currentValue = currentValueFunc(); var status = currentValue.CompareTo(maxValue) <= 0 ? CheckStatus.Healthy : CheckStatus.Unhealthy; return HealthCheckResult.FromStatus( status, - $"{name}: max={maxValue}, current={currentValue}", + $"max={maxValue}, current={currentValue}", new Dictionary { { "max", maxValue }, { "current", currentValue } } ); }); diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/UrlChecks.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/UrlChecks.cs index 6ab393547..d7df58def 100644 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/UrlChecks.cs +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/UrlChecks.cs @@ -2,8 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; -using System.Linq; using System.Net.Http; using System.Threading.Tasks; using Microsoft.Extensions.HealthChecks.Internal; @@ -37,73 +35,12 @@ namespace Microsoft.Extensions.HealthChecks Func> checkFunc) { Guard.ArgumentNotNull(nameof(builder), builder); - Guard.ArgumentNotNullOrWhitespace(nameof(url), url); + Guard.ArgumentNotNullOrEmpty(nameof(url), url); Guard.ArgumentNotNull(nameof(checkFunc), checkFunc); var urlCheck = new UrlChecker(checkFunc, url); builder.AddCheck($"UrlCheck({url})", () => urlCheck.CheckAsync()); return builder; } - - public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable urlItems, string groupName) - => AddUrlChecks(builder, urlItems, groupName, CheckStatus.Warning, response => UrlChecker.DefaultUrlCheck(response)); - - public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable urlItems, string groupName, - Func checkFunc) - { - Guard.ArgumentNotNull(nameof(checkFunc), checkFunc); - - return AddUrlChecks(builder, urlItems, groupName, CheckStatus.Warning, response => new ValueTask(checkFunc(response))); - } - - public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable urlItems, string groupName, - Func> checkFunc) - { - Guard.ArgumentNotNull(nameof(checkFunc), checkFunc); - - return AddUrlChecks(builder, urlItems, groupName, CheckStatus.Warning, response => new ValueTask(checkFunc(response))); - } - - public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable urlItems, string groupName, - Func> checkFunc) - { - Guard.ArgumentNotNull(nameof(checkFunc), checkFunc); - - return AddUrlChecks(builder, urlItems, groupName, CheckStatus.Warning, response => checkFunc(response)); - } - - public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable urlItems, string groupName, - CheckStatus partialSuccessStatus) - => AddUrlChecks(builder, urlItems, groupName, partialSuccessStatus, response => UrlChecker.DefaultUrlCheck(response)); - - public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable urlItems, string groupName, - CheckStatus partialSuccessStatus, Func checkFunc) - { - Guard.ArgumentNotNull(nameof(checkFunc), checkFunc); - - return AddUrlChecks(builder, urlItems, groupName, partialSuccessStatus, response => new ValueTask(checkFunc(response))); - } - - public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable urlItems, string groupName, - CheckStatus partialSuccessStatus, Func> checkFunc) - { - Guard.ArgumentNotNull(nameof(checkFunc), checkFunc); - - return AddUrlChecks(builder, urlItems, groupName, partialSuccessStatus, response => new ValueTask(checkFunc(response))); - } - - public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable urlItems, string groupName, - CheckStatus partialSuccessStatus, Func> checkFunc) - { - var urls = urlItems?.ToArray(); - - Guard.ArgumentNotNull(nameof(builder), builder); - Guard.ArgumentNotNullOrEmpty(nameof(urlItems), urls); - Guard.ArgumentNotNullOrWhitespace(nameof(groupName), groupName); - - var urlChecker = new UrlChecker(checkFunc, urls) { PartiallyHealthyStatus = partialSuccessStatus }; - builder.AddCheck($"UrlChecks({groupName})", () => urlChecker.CheckAsync()); - return builder; - } } } diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CompositeHealthCheckResult.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CompositeHealthCheckResult.cs index 5a3367843..6894ce85f 100644 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CompositeHealthCheckResult.cs +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CompositeHealthCheckResult.cs @@ -7,7 +7,6 @@ using System.Linq; namespace Microsoft.Extensions.HealthChecks { - // REVIEW: Does this need to be thread safe? /// /// Represents a composite health check result built from several results. /// @@ -31,17 +30,23 @@ namespace Microsoft.Extensions.HealthChecks { var checkStatuses = new HashSet(_results.Select(x => x.Value.CheckStatus)); if (checkStatuses.Count == 0) + { return _initialStatus; + } if (checkStatuses.Count == 1) + { return checkStatuses.First(); + } if (checkStatuses.Contains(CheckStatus.Healthy)) + { return _partiallyHealthyStatus; + } return CheckStatus.Unhealthy; } } - public string Description => string.Join(Environment.NewLine, _results.Select(r => r.Value.Description)); + public string Description => string.Join(Environment.NewLine, _results.Select(r => $"{r.Key}: {r.Value.Description}")); public IReadOnlyDictionary Data { @@ -58,23 +63,21 @@ namespace Microsoft.Extensions.HealthChecks public IReadOnlyDictionary Results => _results; - // REVIEW: Should description be required? Seems redundant for success checks. - public void Add(string name, CheckStatus status, string description) => Add(name, status, description, null); public void Add(string name, CheckStatus status, string description, Dictionary data) { - Guard.ArgumentNotNullOrWhitespace(nameof(name), name); - Guard.ArgumentValid(status != CheckStatus.Unknown, nameof(status), "Cannot add unknown status to composite health check result"); - Guard.ArgumentNotNullOrWhitespace(nameof(description), description); + Guard.ArgumentNotNullOrEmpty(nameof(name), name); + Guard.ArgumentValid(status != CheckStatus.Unknown, nameof(status), "Cannot add 'Unknown' status to composite health check result."); + Guard.ArgumentNotNullOrEmpty(nameof(description), description); _results.Add(name, HealthCheckResult.FromStatus(status, description, data)); } public void Add(string name, IHealthCheckResult checkResult) { - Guard.ArgumentNotNullOrWhitespace(nameof(name), name); + Guard.ArgumentNotNullOrEmpty(nameof(name), name); Guard.ArgumentNotNull(nameof(checkResult), checkResult); _results.Add(name, checkResult); diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheck.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheck.cs index d068a7d05..5e1caa2ff 100644 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheck.cs +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheck.cs @@ -9,68 +9,34 @@ namespace Microsoft.Extensions.HealthChecks { public class HealthCheck : IHealthCheck { - private DateTimeOffset _cacheExpiration; - private IHealthCheckResult _cachedResult; - private volatile int _writerCount; - - protected HealthCheck(Func> check, TimeSpan cacheDuration) + protected HealthCheck(Func> check) { Guard.ArgumentNotNull(nameof(check), check); - Guard.ArgumentValid(cacheDuration >= TimeSpan.Zero, nameof(cacheDuration), "Cache duration must either be zero (disabled) or a positive value"); Check = check; - CacheDuration = cacheDuration; } - public TimeSpan CacheDuration { get; } - protected Func> Check { get; } - protected virtual DateTimeOffset UtcNow => DateTimeOffset.UtcNow; - - public async ValueTask CheckAsync(CancellationToken cancellationToken) - { - while (_cacheExpiration <= UtcNow) - { - // Can't use a standard lock here because of async, so we'll use this flag to determine when we should write a value, - // and the waiters who aren't allowed to write will just spin wait for the new value. - if (Interlocked.Exchange(ref _writerCount, 1) != 0) - { - await Task.Delay(5, cancellationToken).ConfigureAwait(false); - continue; - } - - try - { - _cachedResult = await Check(cancellationToken).ConfigureAwait(false); - _cacheExpiration = UtcNow + CacheDuration; - break; - } - finally - { - _writerCount = 0; - } - } - - return _cachedResult; - } + public ValueTask CheckAsync(CancellationToken cancellationToken = default(CancellationToken)) + => Check(cancellationToken); - public static HealthCheck FromCheck(Func check, TimeSpan cacheDuration) - => new HealthCheck(token => new ValueTask(check()), cacheDuration); + public static HealthCheck FromCheck(Func check) + => new HealthCheck(token => new ValueTask(check())); - public static HealthCheck FromCheck(Func check, TimeSpan cacheDuration) - => new HealthCheck(token => new ValueTask(check(token)), cacheDuration); + public static HealthCheck FromCheck(Func check) + => new HealthCheck(token => new ValueTask(check(token))); - public static HealthCheck FromTaskCheck(Func> check, TimeSpan cacheDuration) - => new HealthCheck(token => new ValueTask(check()), cacheDuration); + public static HealthCheck FromTaskCheck(Func> check) + => new HealthCheck(token => new ValueTask(check())); - public static HealthCheck FromTaskCheck(Func> check, TimeSpan cacheDuration) - => new HealthCheck(token => new ValueTask(check(token)), cacheDuration); + public static HealthCheck FromTaskCheck(Func> check) + => new HealthCheck(token => new ValueTask(check(token))); - public static HealthCheck FromValueTaskCheck(Func> check, TimeSpan cacheDuration) - => new HealthCheck(token => check(), cacheDuration); + public static HealthCheck FromValueTaskCheck(Func> check) + => new HealthCheck(token => check()); - public static HealthCheck FromValueTaskCheck(Func> check, TimeSpan cacheDuration) - => new HealthCheck(check, cacheDuration); + public static HealthCheck FromValueTaskCheck(Func> check) + => new HealthCheck(check); } } diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckBuilder.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckBuilder.cs index f6cc17304..006e4a6ef 100644 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckBuilder.cs +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckBuilder.cs @@ -8,33 +8,128 @@ namespace Microsoft.Extensions.HealthChecks { public class HealthCheckBuilder { - private readonly Dictionary _checks; + private readonly Dictionary _checksByName; + private readonly HealthCheckGroup _currentGroup; + private readonly Dictionary _groups; public HealthCheckBuilder() { - _checks = new Dictionary(StringComparer.OrdinalIgnoreCase); + _checksByName = new Dictionary(StringComparer.OrdinalIgnoreCase); + _currentGroup = new HealthCheckGroup(string.Empty, CheckStatus.Unhealthy); + _groups = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + [string.Empty] = _currentGroup + }; + DefaultCacheDuration = TimeSpan.FromMinutes(5); } - public IReadOnlyDictionary Checks => _checks; + /// + /// This constructor should only be used when creating a grouped health check builder. + /// + public HealthCheckBuilder(HealthCheckBuilder rootBuilder, HealthCheckGroup currentGroup) + { + Guard.ArgumentNotNull(nameof(rootBuilder), rootBuilder); + Guard.ArgumentNotNull(nameof(currentGroup), currentGroup); + + _checksByName = rootBuilder._checksByName; + _currentGroup = currentGroup; + _groups = rootBuilder._groups; + + DefaultCacheDuration = rootBuilder.DefaultCacheDuration; + } + + /// + /// Gets the registered checks, indexed by check name. + /// + public IReadOnlyDictionary ChecksByName => _checksByName; + /// + /// Gets the current default cache duration used when registering checks. + /// public TimeSpan DefaultCacheDuration { get; private set; } - public HealthCheckBuilder AddCheck(string name, IHealthCheck check) + /// + /// Gets the registered groups, indexed by group name. The root group's name is . + /// + public IReadOnlyDictionary Groups => _groups; + + /// + /// Registers a health check type that will later be resolved via dependency + /// injection. + /// + public HealthCheckBuilder AddCheck(string checkName, TimeSpan cacheDuration) where TCheck : class, IHealthCheck { - Guard.ArgumentNotNullOrWhitespace(nameof(name), name); + Guard.ArgumentNotNullOrEmpty(nameof(checkName), checkName); + Guard.ArgumentValid(!_checksByName.ContainsKey(checkName), nameof(checkName), $"A check with name '{checkName}' has already been registered."); + + var namedCheck = CachedHealthCheck.FromType(checkName, cacheDuration, typeof(TCheck)); + + _checksByName.Add(checkName, namedCheck); + _currentGroup.ChecksInternal.Add(namedCheck); + + return this; + } + + /// + /// Registers a concrete health check to the builder. + /// + public HealthCheckBuilder AddCheck(string checkName, IHealthCheck check, TimeSpan cacheDuration) + { + Guard.ArgumentNotNullOrEmpty(nameof(checkName), checkName); Guard.ArgumentNotNull(nameof(check), check); + Guard.ArgumentValid(!_checksByName.ContainsKey(checkName), nameof(checkName), $"A check with name '{checkName}' has already been registered."); + + var namedCheck = CachedHealthCheck.FromHealthCheck(checkName, cacheDuration, check); + + _checksByName.Add(checkName, namedCheck); + _currentGroup.ChecksInternal.Add(namedCheck); + + return this; + } + + /// + /// Creates a new health check group, to which you can add one or more health + /// checks. Uses when the group is + /// partially successful. + /// + public HealthCheckBuilder AddHealthCheckGroup(string groupName, Action groupChecks) + => AddHealthCheckGroup(groupName, groupChecks, CheckStatus.Unhealthy); + + /// + /// Creates a new health check group, to which you can add one or more health + /// checks. + /// + public HealthCheckBuilder AddHealthCheckGroup(string groupName, Action groupChecks, CheckStatus partialSuccessStatus) + { + Guard.ArgumentNotNullOrEmpty(nameof(groupName), groupName); + Guard.ArgumentNotNull(nameof(groupChecks), groupChecks); + Guard.ArgumentValid(partialSuccessStatus != CheckStatus.Unknown, nameof(partialSuccessStatus), "Check status 'Unknown' is not valid for partial success."); + Guard.ArgumentValid(!_groups.ContainsKey(groupName), nameof(groupName), $"A group with name '{groupName}' has already been registered."); + Guard.OperationValid(_currentGroup.GroupName == string.Empty, "Nested groups are not supported by HealthCheckBuilder."); + + var group = new HealthCheckGroup(groupName, partialSuccessStatus); + _groups.Add(groupName, group); + + var innerBuilder = new HealthCheckBuilder(this, group); + groupChecks(innerBuilder); - _checks.Add(name, check); return this; } public HealthCheckBuilder WithDefaultCacheDuration(TimeSpan duration) { - Guard.ArgumentValid(duration >= TimeSpan.Zero, nameof(duration), "Duration must be zero (disabled) or a positive duration"); + Guard.ArgumentValid(duration >= TimeSpan.Zero, nameof(duration), "Duration must be zero (disabled) or a positive duration."); DefaultCacheDuration = duration; return this; } + + public HealthCheckBuilder WithPartialSuccessStatus(CheckStatus partiallyHealthyStatus) + { + _currentGroup.PartiallyHealthyStatus = partiallyHealthyStatus; + + return this; + } } } diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckExtensions.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckExtensions.cs deleted file mode 100644 index 2669afc77..000000000 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckExtensions.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Extensions.HealthChecks -{ - public static class HealthCheckExtensions - { - public static ValueTask CheckAsync(this IHealthCheck healthCheck) - { - Guard.ArgumentNotNull(nameof(healthCheck), healthCheck); - - return healthCheck.CheckAsync(CancellationToken.None); - } - } -} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckGroup.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckGroup.cs new file mode 100644 index 000000000..18c55132b --- /dev/null +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckGroup.cs @@ -0,0 +1,37 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; + +namespace Microsoft.Extensions.HealthChecks +{ + public class HealthCheckGroup + { + private CheckStatus _partialSuccessStatus; + + public HealthCheckGroup(string groupName, CheckStatus partialSuccessStatus) + { + Guard.ArgumentNotNull(nameof(groupName), groupName); + + GroupName = groupName; + PartiallyHealthyStatus = partialSuccessStatus; + } + + public IReadOnlyList Checks => ChecksInternal.AsReadOnly(); + + internal List ChecksInternal { get; } = new List(); + + public string GroupName { get; } + + public CheckStatus PartiallyHealthyStatus + { + get => _partialSuccessStatus; + internal set + { + Guard.ArgumentValid(value != CheckStatus.Unknown, nameof(value), "Check status 'Unknown' is not valid for partial success."); + + _partialSuccessStatus = value; + } + } + } +} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckResult.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckResult.cs index 7ea31b42a..d8ef80dc4 100644 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckResult.cs +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckResult.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; -using System.Linq; namespace Microsoft.Extensions.HealthChecks { diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckService.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckService.cs index 6e16bed61..1d2934e0e 100644 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckService.cs +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckService.cs @@ -3,52 +3,117 @@ using System; using System.Collections.Generic; -using System.Text; +using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.DependencyInjection; namespace Microsoft.Extensions.HealthChecks { public class HealthCheckService : IHealthCheckService { - public IReadOnlyDictionary _checks; + private readonly HealthCheckBuilder _builder; + private readonly IReadOnlyList _groups; + private readonly HealthCheckGroup _root; + private readonly IServiceProvider _serviceProvider; + private readonly IServiceScopeFactory _serviceScopeFactory; - private ILogger _logger; - - public HealthCheckService(HealthCheckBuilder builder, ILogger logger) + public HealthCheckService(HealthCheckBuilder builder, IServiceProvider serviceProvider, IServiceScopeFactory serviceScopeFactory) { - _checks = builder.Checks; - _logger = logger; + _builder = builder; + _groups = GetGroups().Where(group => group.GroupName != string.Empty).ToList(); + _root = GetGroup(string.Empty); + _serviceProvider = serviceProvider; + _serviceScopeFactory = serviceScopeFactory; } - public async Task CheckHealthAsync(CheckStatus partiallyHealthyStatus, CancellationToken cancellationToken) + public async Task CheckHealthAsync(CancellationToken cancellationToken = default(CancellationToken)) { - var logMessage = new StringBuilder(); - var result = new CompositeHealthCheckResult(partiallyHealthyStatus); - - foreach (var check in _checks) + using (var scope = GetServiceScope()) { - try - { - var healthCheckResult = await check.Value.CheckAsync().ConfigureAwait(false); - logMessage.AppendLine($"HealthCheck: {check.Key} : {healthCheckResult.CheckStatus}"); - result.Add(check.Key, healthCheckResult); - } - catch (Exception ex) + var scopeServiceProvider = scope.ServiceProvider; + var groupTasks = _groups.Select(group => new { Group = group, Task = RunGroupAsync(scopeServiceProvider, group, cancellationToken) }).ToList(); + var result = await RunGroupAsync(scopeServiceProvider, _root, cancellationToken).ConfigureAwait(false); + + await Task.WhenAll(groupTasks.Select(x => x.Task)); + + foreach (var groupTask in groupTasks) { - logMessage.AppendLine($"HealthCheck: {check.Key} : Exception {ex.GetType().FullName} thrown"); - result.Add(check.Key, CheckStatus.Unhealthy, $"Exception during check: {ex.GetType().FullName}"); + result.Add($"Group({groupTask.Group.GroupName})", groupTask.Task.Result); } + + return result; } + } + + public IReadOnlyList GetAllChecks() + => _builder.ChecksByName.Values.ToList().AsReadOnly(); - if (logMessage.Length == 0) - logMessage.AppendLine("HealthCheck: No checks have been registered"); + public CachedHealthCheck GetCheck(string checkName) + => _builder.ChecksByName[checkName]; + + public HealthCheckGroup GetGroup(string groupName) + => _builder.Groups[groupName]; + + public IReadOnlyList GetGroups() + => _builder.Groups.Values.ToList().AsReadOnly(); + + private IServiceScope GetServiceScope() + => _serviceScopeFactory == null ? new UnscopedServiceProvider(_serviceProvider) : _serviceScopeFactory.CreateScope(); + + public async ValueTask RunCheckAsync(CachedHealthCheck healthCheck, CancellationToken cancellationToken = default(CancellationToken)) + { + using (var scope = GetServiceScope()) + { + return await RunCheckAsync(scope.ServiceProvider, healthCheck, cancellationToken).ConfigureAwait(false); + } + } + + /// + /// Uses the provided service provider and executes the provided check. + /// + public ValueTask RunCheckAsync(IServiceProvider serviceProvider, CachedHealthCheck healthCheck, CancellationToken cancellationToken = default(CancellationToken)) + { + Guard.ArgumentNotNull(nameof(serviceProvider), serviceProvider); + Guard.ArgumentNotNull(nameof(healthCheck), healthCheck); + + return healthCheck.RunAsync(serviceProvider, cancellationToken); + } + + /// + /// Creates a new resolution scope from the default service provider and executes the checks in the given group. + /// + public async Task RunGroupAsync(HealthCheckGroup group, CancellationToken cancellationToken = default(CancellationToken)) + { + using (var scope = GetServiceScope()) + return await RunGroupAsync(scope.ServiceProvider, group, cancellationToken).ConfigureAwait(false); + } + + /// + /// Uses the provided service provider and executes the checks in the given group. + /// + public async Task RunGroupAsync(IServiceProvider serviceProvider, HealthCheckGroup group, CancellationToken cancellationToken = default(CancellationToken)) + { + var result = new CompositeHealthCheckResult(group.PartiallyHealthyStatus); + var checkTasks = group.Checks.Select(check => new { Check = check, Task = check.RunAsync(serviceProvider, cancellationToken).AsTask() }).ToList(); + await Task.WhenAll(checkTasks.Select(checkTask => checkTask.Task)); + + foreach (var checkTask in checkTasks) + { + result.Add(checkTask.Check.Name, checkTask.Task.Result); + } - _logger.Log((result.CheckStatus == CheckStatus.Healthy ? LogLevel.Information : LogLevel.Error), 0, logMessage.ToString(), null, MessageFormatter); return result; } - private static string MessageFormatter(string state, Exception error) => state; + private class UnscopedServiceProvider : IServiceScope + { + public UnscopedServiceProvider(IServiceProvider serviceProvider) + => ServiceProvider = serviceProvider; + + public IServiceProvider ServiceProvider { get; } + + public void Dispose() { } + } } } diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckServiceCollectionExtensions.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckServiceCollectionExtensions.cs index cdd763d9f..678731737 100644 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckServiceCollectionExtensions.cs +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckServiceCollectionExtensions.cs @@ -2,20 +2,28 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Linq; using Microsoft.Extensions.HealthChecks; namespace Microsoft.Extensions.DependencyInjection { public static class HealthCheckServiceCollectionExtensions { - public static IServiceCollection AddHealthChecks(this IServiceCollection services, Action checkupAction) + private static readonly Type HealthCheckServiceInterface = typeof(IHealthCheckService); + + public static IServiceCollection AddHealthChecks(this IServiceCollection services, Action checks) { - var checkupBuilder = new HealthCheckBuilder(); + Guard.OperationValid(!services.Any(descriptor => descriptor.ServiceType == HealthCheckServiceInterface), "AddHealthChecks may only be called once."); + + var builder = new HealthCheckBuilder(); - checkupAction.Invoke(checkupBuilder); + services.AddSingleton(serviceProvider => + { + var serviceScopeFactory = serviceProvider.GetService(); + return new HealthCheckService(builder, serviceProvider, serviceScopeFactory); + }); - services.AddSingleton(checkupBuilder); - services.AddSingleton(); + checks(builder); return services; } } diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckServiceExtensions.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckServiceExtensions.cs deleted file mode 100644 index 98ab54b70..000000000 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckServiceExtensions.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Extensions.HealthChecks -{ - public static class HealthCheckServiceExtensions - { - public static Task CheckHealthAsync(this IHealthCheckService service) - { - Guard.ArgumentNotNull(nameof(service), service); - - return service.CheckHealthAsync(CheckStatus.Unhealthy, CancellationToken.None); - } - - public static Task CheckHealthAsync(this IHealthCheckService service, CheckStatus partiallyHealthyStatus) - { - Guard.ArgumentNotNull(nameof(service), service); - - return service.CheckHealthAsync(partiallyHealthyStatus, CancellationToken.None); - } - - public static Task CheckHealthAsync(this IHealthCheckService service, CancellationToken cancellationToken) - { - Guard.ArgumentNotNull(nameof(service), service); - - return service.CheckHealthAsync(CheckStatus.Unhealthy, cancellationToken); - } - public static Task CheckHealthAsync(this IHealthCheckService service, CheckStatus partiallyHealthyStatus, CancellationToken cancellationToken) - { - Guard.ArgumentNotNull(nameof(service), service); - - return service.CheckHealthAsync(partiallyHealthyStatus, cancellationToken); - } - } -} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/IHealthCheck.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/IHealthCheck.cs index b5b95405f..e4aa45d28 100644 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/IHealthCheck.cs +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/IHealthCheck.cs @@ -1,7 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using System.Threading; using System.Threading.Tasks; @@ -9,8 +8,6 @@ namespace Microsoft.Extensions.HealthChecks { public interface IHealthCheck { - TimeSpan CacheDuration { get; } - - ValueTask CheckAsync(CancellationToken cancellationToken); + ValueTask CheckAsync(CancellationToken cancellationToken = default(CancellationToken)); } } diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/IHealthCheckService.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/IHealthCheckService.cs index 60e917264..17a49cb00 100644 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/IHealthCheckService.cs +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/IHealthCheckService.cs @@ -1,6 +1,8 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -8,6 +10,49 @@ namespace Microsoft.Extensions.HealthChecks { public interface IHealthCheckService { - Task CheckHealthAsync(CheckStatus partiallyHealthyStatus, CancellationToken cancellationToken); + /// + /// Runs all registered health checks. + /// + Task CheckHealthAsync(CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// Gets all registered health checks as a flat list. + /// + IReadOnlyList GetAllChecks(); + + /// + /// Gets a health check by name. + /// + CachedHealthCheck GetCheck(string checkName); + + /// + /// Gets all health checks in a group. + /// + HealthCheckGroup GetGroup(string groupName); + + /// + /// Gets all the health check groups. + /// + IReadOnlyList GetGroups(); + + /// + /// Creates a new resolution scope from the default service provider and executes the provided check. + /// + ValueTask RunCheckAsync(CachedHealthCheck healthCheck, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// Uses the provided service provider and executes the provided check. + /// + ValueTask RunCheckAsync(IServiceProvider serviceProvider, CachedHealthCheck healthCheck, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// Creates a new resolution scope from the default service provider and executes the checks in the given group. + /// + Task RunGroupAsync(HealthCheckGroup group, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// Uses the provided service provider and executes the checks in the given group. + /// + Task RunGroupAsync(IServiceProvider serviceProvider, HealthCheckGroup group, CancellationToken cancellationToken = default(CancellationToken)); } } diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Internal/UrlChecker.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Internal/UrlChecker.cs index ad021b149..56800d334 100644 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Internal/UrlChecker.cs +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Internal/UrlChecker.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Threading.Tasks; @@ -13,55 +12,33 @@ namespace Microsoft.Extensions.HealthChecks.Internal public class UrlChecker { private readonly Func> _checkFunc; - private readonly string[] _urls; + private readonly string _url; - // REVIEW: Cache timeout here? - public UrlChecker(Func> checkFunc, params string[] urls) + public UrlChecker(Func> checkFunc, string url) { Guard.ArgumentNotNull(nameof(checkFunc), checkFunc); - Guard.ArgumentNotNullOrEmpty(nameof(urls), urls); + Guard.ArgumentNotNullOrEmpty(nameof(url), url); _checkFunc = checkFunc; - _urls = urls; + _url = url; } public CheckStatus PartiallyHealthyStatus { get; set; } = CheckStatus.Warning; - public Task CheckAsync() - => _urls.Length == 1 ? CheckSingleAsync() : CheckMultiAsync(); - - public async Task CheckSingleAsync() - { - var httpClient = CreateHttpClient(); - var result = default(IHealthCheckResult); - await CheckUrlAsync(httpClient, _urls[0], (_, checkResult) => result = checkResult).ConfigureAwait(false); - return result; - } - - public async Task CheckMultiAsync() + public async Task CheckAsync() { - var composite = new CompositeHealthCheckResult(PartiallyHealthyStatus); - var httpClient = CreateHttpClient(); - - // REVIEW: Should these be done in parallel? - foreach (var url in _urls) - await CheckUrlAsync(httpClient, url, (name, checkResult) => composite.Add(name, checkResult)).ConfigureAwait(false); - - return composite; - } - - private async Task CheckUrlAsync(HttpClient httpClient, string url, Action adder) - { - var name = $"UrlCheck({url})"; - try - { - var response = await httpClient.GetAsync(url).ConfigureAwait(false); - var result = await _checkFunc(response); - adder(name, result); - } - catch (Exception ex) + using (var httpClient = CreateHttpClient()) { - adder(name, HealthCheckResult.Unhealthy($"Exception during check: {ex.GetType().FullName}")); + try + { + var response = await httpClient.GetAsync(_url).ConfigureAwait(false); + return await _checkFunc(response); + } + catch (Exception ex) + { + var data = new Dictionary { { "url", _url } }; + return HealthCheckResult.Unhealthy($"Exception during check: {ex.GetType().FullName}", data); + } } } @@ -74,8 +51,7 @@ namespace Microsoft.Extensions.HealthChecks.Internal public static async ValueTask DefaultUrlCheck(HttpResponseMessage response) { - // REVIEW: Should this be an explicit 200 check, or just an "is success" check? - var status = response.StatusCode == HttpStatusCode.OK ? CheckStatus.Healthy : CheckStatus.Unhealthy; + var status = response.IsSuccessStatusCode ? CheckStatus.Healthy : CheckStatus.Unhealthy; var data = new Dictionary { { "url", response.RequestMessage.RequestUri.ToString() }, @@ -83,7 +59,7 @@ namespace Microsoft.Extensions.HealthChecks.Internal { "reason", response.ReasonPhrase }, { "body", await response.Content?.ReadAsStringAsync() } }; - return HealthCheckResult.FromStatus(status, $"UrlCheck({response.RequestMessage.RequestUri}): status code {response.StatusCode} ({(int)response.StatusCode})", data); + return HealthCheckResult.FromStatus(status, $"status code {response.StatusCode} ({(int)response.StatusCode})", data); } protected virtual HttpClient GetHttpClient() diff --git a/src/BuildingBlocks/HealthChecks/src/common/Guard.cs b/src/BuildingBlocks/HealthChecks/src/common/Guard.cs index 8ed91054e..9f394be51 100644 --- a/src/BuildingBlocks/HealthChecks/src/common/Guard.cs +++ b/src/BuildingBlocks/HealthChecks/src/common/Guard.cs @@ -9,37 +9,49 @@ static class Guard public static void ArgumentNotNull(string argumentName, object value) { if (value == null) + { throw new ArgumentNullException(argumentName); + } } - public static void ArgumentNotNullOrEmpty(string argumentName, string value) + public static void ArgumentNotNullOrEmpty(string argumentName, string value) { if (value == null) + { throw new ArgumentNullException(argumentName); + } if (string.IsNullOrEmpty(value)) - throw new ArgumentException("Value cannot be an empty string", argumentName); + { + throw new ArgumentException("Value cannot be an empty string.", argumentName); + } } // Use IReadOnlyCollection instead of IEnumerable to discourage double enumeration public static void ArgumentNotNullOrEmpty(string argumentName, IReadOnlyCollection items) { if (items == null) + { throw new ArgumentNullException(argumentName); + } if (items.Count == 0) - throw new ArgumentException("Collection must contain at least one item", argumentName); + { + throw new ArgumentException("Collection must contain at least one item.", argumentName); + } } - public static void ArgumentNotNullOrWhitespace(string argumentName, string value) + public static void ArgumentValid(bool valid, string argumentName, string exceptionMessage) { - if (value == null) - throw new ArgumentNullException(argumentName); - if (string.IsNullOrWhiteSpace(value)) - throw new ArgumentException("Value must contain a non-whitespace value", argumentName); + if (!valid) + { + throw new ArgumentException(exceptionMessage, argumentName); + } } - public static void ArgumentValid(bool valid, string argumentName, string exceptionMessage) + public static void OperationValid(bool valid, string exceptionMessage) { if (!valid) - throw new ArgumentException(exceptionMessage, argumentName); + { + throw new InvalidOperationException(exceptionMessage); + } } } diff --git a/src/Services/Basket/Basket.API/Basket.API.csproj b/src/Services/Basket/Basket.API/Basket.API.csproj index ce20513cc..b3ba97b10 100644 --- a/src/Services/Basket/Basket.API/Basket.API.csproj +++ b/src/Services/Basket/Basket.API/Basket.API.csproj @@ -43,7 +43,6 @@ - diff --git a/src/Services/Catalog/Catalog.API/Catalog.API.csproj b/src/Services/Catalog/Catalog.API/Catalog.API.csproj index d805e06e0..4306d6922 100644 --- a/src/Services/Catalog/Catalog.API/Catalog.API.csproj +++ b/src/Services/Catalog/Catalog.API/Catalog.API.csproj @@ -59,7 +59,7 @@ - + diff --git a/src/Services/Identity/Identity.API/Identity.API.csproj b/src/Services/Identity/Identity.API/Identity.API.csproj index 043595232..dda24079e 100644 --- a/src/Services/Identity/Identity.API/Identity.API.csproj +++ b/src/Services/Identity/Identity.API/Identity.API.csproj @@ -63,7 +63,7 @@ - + diff --git a/src/Services/Ordering/Ordering.API/Ordering.API.csproj b/src/Services/Ordering/Ordering.API/Ordering.API.csproj index 6eed3a13c..975645ce4 100644 --- a/src/Services/Ordering/Ordering.API/Ordering.API.csproj +++ b/src/Services/Ordering/Ordering.API/Ordering.API.csproj @@ -27,7 +27,7 @@ - + diff --git a/src/Web/WebMVC/WebMVC.csproj b/src/Web/WebMVC/WebMVC.csproj index 20a8baca0..3f0f6b84b 100644 --- a/src/Web/WebMVC/WebMVC.csproj +++ b/src/Web/WebMVC/WebMVC.csproj @@ -59,7 +59,6 @@ - diff --git a/src/Web/WebSPA/WebSPA.csproj b/src/Web/WebSPA/WebSPA.csproj index 33412407a..cebe4377d 100644 --- a/src/Web/WebSPA/WebSPA.csproj +++ b/src/Web/WebSPA/WebSPA.csproj @@ -74,7 +74,6 @@ - diff --git a/src/Web/WebStatus/WebStatus.csproj b/src/Web/WebStatus/WebStatus.csproj index 47a3e20f0..e3028722d 100644 --- a/src/Web/WebStatus/WebStatus.csproj +++ b/src/Web/WebStatus/WebStatus.csproj @@ -21,7 +21,6 @@ - From 69d7399eecfc22017e08a8088fbf473f8da9fed0 Mon Sep 17 00:00:00 2001 From: Cesar De la Torre Date: Tue, 2 May 2017 17:42:59 -0700 Subject: [PATCH 14/15] Fixing the CreateOrderCommand so it is 100% immutable. It shouldn't have the AddOrderItem() method. In any case, it was not really used but in the tets, since this Command is serialized in the client side, then deserialized in the service level. --- .../Commands/CreateOrderCommand.cs | 8 ++----- .../Services/Ordering/OrderingScenarios.cs | 21 +++++++++------- .../Services/Ordering/OrderingScenarios.cs | 24 ++++++++++++------- .../IdentifierCommandHandlerTest.cs | 1 + .../Application/NewOrderCommandHandlerTest.cs | 1 + 5 files changed, 31 insertions(+), 24 deletions(-) diff --git a/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommand.cs b/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommand.cs index 1485a536f..950c4bdc5 100644 --- a/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommand.cs +++ b/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommand.cs @@ -61,20 +61,16 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands [DataMember] public IEnumerable OrderItems => _orderItems; - public void AddOrderItem(OrderItemDTO item) - { - _orderItems.Add(item); - } - public CreateOrderCommand() { _orderItems = new List(); } - public CreateOrderCommand(string city, string street, string state, string country, string zipcode, + public CreateOrderCommand(List orderItems, string city, string street, string state, string country, string zipcode, string cardNumber, string cardHolderName, DateTime cardExpiration, string cardSecurityNumber, int cardTypeId, int paymentId, int buyerId) : this() { + _orderItems = orderItems; City = city; Street = street; State = state; diff --git a/test/Services/FunctionalTests/Services/Ordering/OrderingScenarios.cs b/test/Services/FunctionalTests/Services/Ordering/OrderingScenarios.cs index 5f52e1771..5b2424114 100644 --- a/test/Services/FunctionalTests/Services/Ordering/OrderingScenarios.cs +++ b/test/Services/FunctionalTests/Services/Ordering/OrderingScenarios.cs @@ -45,7 +45,19 @@ namespace FunctionalTests.Services.Ordering string BuildOrder() { + List orderItemsList = new List(); + orderItemsList.Add(new OrderItemDTO() + { + ProductId = 1, + Discount = 8M, + UnitPrice = 10, + Units = 1, + ProductName = "Some name" + } + ); + var order = new CreateOrderCommand( + orderItemsList, cardExpiration: DateTime.UtcNow.AddYears(1), cardNumber: "5145-555-5555", cardHolderName: "Jhon Senna", @@ -60,15 +72,6 @@ namespace FunctionalTests.Services.Ordering buyerId: 3 ); - order.AddOrderItem(new OrderItemDTO() - { - ProductId = 1, - Discount = 8M, - UnitPrice = 10, - Units = 1, - ProductName = "Some name" - }); - return JsonConvert.SerializeObject(order); } } diff --git a/test/Services/IntegrationTests/Services/Ordering/OrderingScenarios.cs b/test/Services/IntegrationTests/Services/Ordering/OrderingScenarios.cs index 3e2350c9d..49f04fa3b 100644 --- a/test/Services/IntegrationTests/Services/Ordering/OrderingScenarios.cs +++ b/test/Services/IntegrationTests/Services/Ordering/OrderingScenarios.cs @@ -9,7 +9,9 @@ using System.Text; using System.Threading.Tasks; using Xunit; + using System.Collections; using static Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands.CreateOrderCommand; + using System.Collections.Generic; public class OrderingScenarios : OrderingScenarioBase @@ -59,7 +61,19 @@ string BuildOrder() { + List orderItemsList = new List(); + orderItemsList.Add(new OrderItemDTO() + { + ProductId = 1, + Discount = 10M, + UnitPrice = 10, + Units = 1, + ProductName = "Some name" + } + ); + var order = new CreateOrderCommand( + orderItemsList, cardExpiration: DateTime.UtcNow.AddYears(1), cardNumber: "5145-555-5555", cardHolderName: "Jhon Senna", @@ -74,20 +88,12 @@ buyerId: 1 ); - order.AddOrderItem(new OrderItemDTO() - { - ProductId = 1, - Discount = 10M, - UnitPrice = 10, - Units = 1, - ProductName = "Some name" - }); - return JsonConvert.SerializeObject(order); } string BuildOrderWithInvalidExperationTime() { var order = new CreateOrderCommand( + null, cardExpiration: DateTime.UtcNow.AddYears(-1), cardNumber: "5145-555-5555", cardHolderName: "Jhon Senna", diff --git a/test/Services/UnitTest/Ordering/Application/IdentifierCommandHandlerTest.cs b/test/Services/UnitTest/Ordering/Application/IdentifierCommandHandlerTest.cs index 66070c497..2a4a356ec 100644 --- a/test/Services/UnitTest/Ordering/Application/IdentifierCommandHandlerTest.cs +++ b/test/Services/UnitTest/Ordering/Application/IdentifierCommandHandlerTest.cs @@ -70,6 +70,7 @@ namespace UnitTest.Ordering.Application private CreateOrderCommand FakeOrderRequest(Dictionary args = null) { return new CreateOrderCommand( + null, city: args != null && args.ContainsKey("city") ? (string)args["city"] : null, street: args != null && args.ContainsKey("street") ? (string)args["street"] : null, state: args != null && args.ContainsKey("state") ? (string)args["state"] : null, diff --git a/test/Services/UnitTest/Ordering/Application/NewOrderCommandHandlerTest.cs b/test/Services/UnitTest/Ordering/Application/NewOrderCommandHandlerTest.cs index 22760fc7d..9a4a70bf8 100644 --- a/test/Services/UnitTest/Ordering/Application/NewOrderCommandHandlerTest.cs +++ b/test/Services/UnitTest/Ordering/Application/NewOrderCommandHandlerTest.cs @@ -72,6 +72,7 @@ namespace UnitTest.Ordering.Application private CreateOrderCommand FakeOrderRequestWithBuyer(Dictionary args = null) { return new CreateOrderCommand( + null, city: args != null && args.ContainsKey("city") ? (string)args["city"] : null, street: args != null && args.ContainsKey("street") ? (string)args["street"] : null, state: args != null && args.ContainsKey("state") ? (string)args["state"] : null, From a09d7fd3a542a019a4bec3fdfc2dcd9ad9c6c786 Mon Sep 17 00:00:00 2001 From: Eduard Tomas Date: Wed, 3 May 2017 10:59:36 +0200 Subject: [PATCH 15/15] EventBus refactor. Instead to register EventHandlers we register Func which solves scope problems (having transient/scoped objects owned by singletons) --- eShopOnContainers-ServicesAndWebApps.sln | 51 ++++++++ .../EventBus.Tests/EventBus.Tests.csproj | 22 ++++ .../InMemory_SubscriptionManager_Tests.cs | 56 ++++++++ .../EventBus.Tests/TestIntegrationEvent.cs | 11 ++ .../TestIntegrationEventHandler.cs | 23 ++++ .../TestIntegrationOtherEventHandler.cs | 23 ++++ .../EventBus/Abstractions/IEventBus.cs | 10 +- .../EventBus/IEventBusSubscriptionsManager.cs | 26 ++++ .../InMemoryEventBusSubscriptionsManager.cs | 115 +++++++++++++++++ .../EventBusRabbitMQ/EventBusRabbitMQ.cs | 120 +++++++++--------- src/Services/Basket/Basket.API/Startup.cs | 25 ++-- src/Services/Catalog/Catalog.API/Startup.cs | 2 + src/Services/Ordering/Ordering.API/Startup.cs | 2 + 13 files changed, 414 insertions(+), 72 deletions(-) create mode 100644 src/BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj create mode 100644 src/BuildingBlocks/EventBus/EventBus.Tests/InMemory_SubscriptionManager_Tests.cs create mode 100644 src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationEvent.cs create mode 100644 src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationEventHandler.cs create mode 100644 src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationOtherEventHandler.cs create mode 100644 src/BuildingBlocks/EventBus/EventBus/IEventBusSubscriptionsManager.cs create mode 100644 src/BuildingBlocks/EventBus/EventBus/InMemoryEventBusSubscriptionsManager.cs diff --git a/eShopOnContainers-ServicesAndWebApps.sln b/eShopOnContainers-ServicesAndWebApps.sln index 27434ec86..cf2be1c26 100644 --- a/eShopOnContainers-ServicesAndWebApps.sln +++ b/eShopOnContainers-ServicesAndWebApps.sln @@ -74,6 +74,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Health EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.HealthChecks.SqlServer", "src\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.SqlServer\Microsoft.Extensions.HealthChecks.SqlServer.csproj", "{4BD76717-3102-4969-8C2C-BAAA3F0263B6}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventBus.Tests", "src\BuildingBlocks\EventBus\EventBus.Tests\EventBus.Tests.csproj", "{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Ad-Hoc|Any CPU = Ad-Hoc|Any CPU @@ -952,6 +954,54 @@ Global {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|x64.Build.0 = Release|Any CPU {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|x86.ActiveCfg = Release|Any CPU {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|x86.Build.0 = Release|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|x64.Build.0 = Debug|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|x86.Build.0 = Debug|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|ARM.ActiveCfg = Debug|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|ARM.Build.0 = Debug|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|iPhone.Build.0 = Debug|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|x64.ActiveCfg = Debug|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|x64.Build.0 = Debug|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|x86.ActiveCfg = Debug|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|x86.Build.0 = Debug|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|ARM.ActiveCfg = Debug|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|ARM.Build.0 = Debug|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|iPhone.Build.0 = Debug|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|x64.ActiveCfg = Debug|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|x64.Build.0 = Debug|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|x86.ActiveCfg = Debug|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|x86.Build.0 = Debug|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|Any CPU.Build.0 = Release|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|ARM.ActiveCfg = Release|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|ARM.Build.0 = Release|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|iPhone.ActiveCfg = Release|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|iPhone.Build.0 = Release|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|x64.ActiveCfg = Release|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|x64.Build.0 = Release|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|x86.ActiveCfg = Release|Any CPU + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -987,5 +1037,6 @@ Global {D1C47FF1-91F1-4CAF-9ABB-AD642B821502} = {FBF43D93-F2E7-4FF8-B4AB-186895949B88} {22A0F9C1-2D4A-4107-95B7-8459E6688BC5} = {A81ECBC2-6B00-4DCD-8388-469174033379} {4BD76717-3102-4969-8C2C-BAAA3F0263B6} = {A81ECBC2-6B00-4DCD-8388-469174033379} + {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D} = {807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F} EndGlobalSection EndGlobal diff --git a/src/BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj b/src/BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj new file mode 100644 index 000000000..1387a74dd --- /dev/null +++ b/src/BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj @@ -0,0 +1,22 @@ + + + + netcoreapp1.1 + + + + + + + + + + + + + + + + + + diff --git a/src/BuildingBlocks/EventBus/EventBus.Tests/InMemory_SubscriptionManager_Tests.cs b/src/BuildingBlocks/EventBus/EventBus.Tests/InMemory_SubscriptionManager_Tests.cs new file mode 100644 index 000000000..dd5f7f5b4 --- /dev/null +++ b/src/BuildingBlocks/EventBus/EventBus.Tests/InMemory_SubscriptionManager_Tests.cs @@ -0,0 +1,56 @@ +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus; +using System; +using System.Linq; +using Xunit; + +namespace EventBus.Tests +{ + public class InMemory_SubscriptionManager_Tests + { + [Fact] + public void After_Creation_Should_Be_Empty() + { + var manager = new InMemoryEventBusSubscriptionsManager(); + Assert.True(manager.IsEmpty); + } + + [Fact] + public void After_One_Event_Subscription_Should_Contain_The_Event() + { + var manager = new InMemoryEventBusSubscriptionsManager(); + manager.AddSubscription(() => new TestIntegrationEventHandler()); + Assert.True(manager.HasSubscriptionsForEvent()); + } + + [Fact] + public void After_All_Subscriptions_Are_Deleted_Event_Should_No_Longer_Exists() + { + var manager = new InMemoryEventBusSubscriptionsManager(); + manager.AddSubscription(() => new TestIntegrationEventHandler()); + manager.RemoveSubscription(); + Assert.False(manager.HasSubscriptionsForEvent()); + } + + [Fact] + public void Deleting_Last_Subscription_Should_Raise_On_Deleted_Event() + { + bool raised = false; + var manager = new InMemoryEventBusSubscriptionsManager(); + manager.OnEventRemoved += (o, e) => raised = true; + manager.AddSubscription(() => new TestIntegrationEventHandler()); + manager.RemoveSubscription(); + Assert.True(raised); + } + + [Fact] + public void Get_Handlers_For_Event_Should_Return_All_Handlers() + { + var manager = new InMemoryEventBusSubscriptionsManager(); + manager.AddSubscription(() => new TestIntegrationEventHandler()); + manager.AddSubscription(() => new TestIntegrationOtherEventHandler()); + var handlers = manager.GetHandlersForEvent(); + Assert.Equal(2, handlers.Count()); + } + + } +} diff --git a/src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationEvent.cs b/src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationEvent.cs new file mode 100644 index 000000000..a77f3ef6f --- /dev/null +++ b/src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationEvent.cs @@ -0,0 +1,11 @@ +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; +using System; +using System.Collections.Generic; +using System.Text; + +namespace EventBus.Tests +{ + public class TestIntegrationEvent : IntegrationEvent + { + } +} diff --git a/src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationEventHandler.cs b/src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationEventHandler.cs new file mode 100644 index 000000000..0b5b793ee --- /dev/null +++ b/src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationEventHandler.cs @@ -0,0 +1,23 @@ +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace EventBus.Tests +{ + public class TestIntegrationOtherEventHandler : IIntegrationEventHandler + { + public bool Handled { get; private set; } + + public TestIntegrationOtherEventHandler() + { + Handled = false; + } + + public async Task Handle(TestIntegrationEvent @event) + { + Handled = true; + } + } +} diff --git a/src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationOtherEventHandler.cs b/src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationOtherEventHandler.cs new file mode 100644 index 000000000..72e1ed2cd --- /dev/null +++ b/src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationOtherEventHandler.cs @@ -0,0 +1,23 @@ +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace EventBus.Tests +{ + public class TestIntegrationEventHandler : IIntegrationEventHandler + { + public bool Handled { get; private set; } + + public TestIntegrationEventHandler() + { + Handled = false; + } + + public async Task Handle(TestIntegrationEvent @event) + { + Handled = true; + } + } +} diff --git a/src/BuildingBlocks/EventBus/EventBus/Abstractions/IEventBus.cs b/src/BuildingBlocks/EventBus/EventBus/Abstractions/IEventBus.cs index 63f9f1b99..9ab7a4499 100644 --- a/src/BuildingBlocks/EventBus/EventBus/Abstractions/IEventBus.cs +++ b/src/BuildingBlocks/EventBus/EventBus/Abstractions/IEventBus.cs @@ -1,11 +1,17 @@ using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; +using System; namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions { public interface IEventBus { - void Subscribe(IIntegrationEventHandler handler) where T: IntegrationEvent; - void Unsubscribe(IIntegrationEventHandler handler) where T : IntegrationEvent; + void Subscribe(Func handler) + where T : IntegrationEvent + where TH : IIntegrationEventHandler; + void Unsubscribe() + where TH : IIntegrationEventHandler + where T : IntegrationEvent; + void Publish(IntegrationEvent @event); } } diff --git a/src/BuildingBlocks/EventBus/EventBus/IEventBusSubscriptionsManager.cs b/src/BuildingBlocks/EventBus/EventBus/IEventBusSubscriptionsManager.cs new file mode 100644 index 000000000..2fdefc039 --- /dev/null +++ b/src/BuildingBlocks/EventBus/EventBus/IEventBusSubscriptionsManager.cs @@ -0,0 +1,26 @@ +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; +using System; +using System.Collections.Generic; + +namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus +{ + public interface IEventBusSubscriptionsManager + { + bool IsEmpty { get; } + event EventHandler OnEventRemoved; + void AddSubscription(Func handler) + where T : IntegrationEvent + where TH : IIntegrationEventHandler; + + void RemoveSubscription() + where TH : IIntegrationEventHandler + where T : IntegrationEvent; + bool HasSubscriptionsForEvent() where T : IntegrationEvent; + bool HasSubscriptionsForEvent(string eventName); + Type GetEventTypeByName(string eventName); + void Clear(); + IEnumerable GetHandlersForEvent() where T : IntegrationEvent; + IEnumerable GetHandlersForEvent(string eventName); + } +} \ No newline at end of file diff --git a/src/BuildingBlocks/EventBus/EventBus/InMemoryEventBusSubscriptionsManager.cs b/src/BuildingBlocks/EventBus/EventBus/InMemoryEventBusSubscriptionsManager.cs new file mode 100644 index 000000000..11fdba3c5 --- /dev/null +++ b/src/BuildingBlocks/EventBus/EventBus/InMemoryEventBusSubscriptionsManager.cs @@ -0,0 +1,115 @@ +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus +{ + public class InMemoryEventBusSubscriptionsManager : IEventBusSubscriptionsManager + { + private readonly Dictionary> _handlers; + private readonly List _eventTypes; + + public event EventHandler OnEventRemoved; + + public InMemoryEventBusSubscriptionsManager() + { + _handlers = new Dictionary>(); + _eventTypes = new List(); + } + + public bool IsEmpty => !_handlers.Keys.Any(); + public void Clear() => _handlers.Clear(); + + public void AddSubscription(Func handler) + where T : IntegrationEvent + where TH : IIntegrationEventHandler + { + var key = GetEventKey(); + if (!HasSubscriptionsForEvent()) + { + _handlers.Add(key, new List()); + } + _handlers[key].Add(handler); + _eventTypes.Add(typeof(T)); + } + + public void RemoveSubscription() + where TH : IIntegrationEventHandler + where T : IntegrationEvent + { + var handlerToRemove = FindHandlerToRemove(); + if (handlerToRemove != null) + { + var key = GetEventKey(); + _handlers[key].Remove(handlerToRemove); + if (!_handlers[key].Any()) + { + _handlers.Remove(key); + var eventType = _eventTypes.SingleOrDefault(e => e.Name == key); + if (eventType != null) + { + _eventTypes.Remove(eventType); + RaiseOnEventRemoved(eventType.Name); + } + } + + } + } + + public IEnumerable GetHandlersForEvent() where T : IntegrationEvent + { + var key = GetEventKey(); + return GetHandlersForEvent(key); + } + public IEnumerable GetHandlersForEvent(string eventName) => _handlers[eventName]; + + private void RaiseOnEventRemoved(string eventName) + { + var handler = OnEventRemoved; + if (handler != null) + { + OnEventRemoved(this, eventName); + } + } + + private Delegate FindHandlerToRemove() + where T : IntegrationEvent + where TH : IIntegrationEventHandler + { + if (!HasSubscriptionsForEvent()) + { + return null; + } + + var key = GetEventKey(); + foreach (var func in _handlers[key]) + { + var genericArgs = func.GetType().GetGenericArguments(); + if (genericArgs.SingleOrDefault() == typeof(TH)) + { + return func; + } + } + + return null; + } + + public bool HasSubscriptionsForEvent() where T : IntegrationEvent + { + var key = GetEventKey(); + return HasSubscriptionsForEvent(key); + } + public bool HasSubscriptionsForEvent(string eventName) => _handlers.ContainsKey(eventName); + + public Type GetEventTypeByName(string eventName) => _eventTypes.Single(t => t.Name == eventName); + + private string GetEventKey() + { + return typeof(T).Name; + } + } +} diff --git a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs index 0eb29b72b..adbc52ad1 100644 --- a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs +++ b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs @@ -1,4 +1,5 @@ -using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; using Microsoft.Extensions.Logging; using Newtonsoft.Json; @@ -23,22 +24,41 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ private readonly IRabbitMQPersistentConnection _persistentConnection; private readonly ILogger _logger; - - private readonly Dictionary> _handlers - = new Dictionary>(); - - private readonly List _eventTypes - = new List(); + private readonly IEventBusSubscriptionsManager _subsManager; + private IModel _consumerChannel; private string _queueName; - public EventBusRabbitMQ(IRabbitMQPersistentConnection persistentConnection, ILogger logger) + public EventBusRabbitMQ(IRabbitMQPersistentConnection persistentConnection, ILogger logger, IEventBusSubscriptionsManager subsManager) { _persistentConnection = persistentConnection ?? throw new ArgumentNullException(nameof(persistentConnection)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - + _subsManager = subsManager ?? new InMemoryEventBusSubscriptionsManager(); _consumerChannel = CreateConsumerChannel(); + + _subsManager.OnEventRemoved += SubsManager_OnEventRemoved; + } + + private void SubsManager_OnEventRemoved(object sender, string eventName) + { + if (!_persistentConnection.IsConnected) + { + _persistentConnection.TryConnect(); + } + + using (var channel = _persistentConnection.CreateModel()) + { + channel.QueueUnbind(queue: _queueName, + exchange: BROKER_NAME, + routingKey: eventName); + + if (_subsManager.IsEmpty) + { + _queueName = string.Empty; + _consumerChannel.Close(); + } + } } public void Publish(IntegrationEvent @event) @@ -76,15 +96,13 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ } } - public void Subscribe(IIntegrationEventHandler handler) where T : IntegrationEvent + public void Subscribe(Func handler) + where T : IntegrationEvent + where TH : IIntegrationEventHandler { var eventName = typeof(T).Name; - - if (_handlers.ContainsKey(eventName)) - { - _handlers[eventName].Add(handler); - } - else + var containsKey = _subsManager.HasSubscriptionsForEvent(); + if (!containsKey) { if (!_persistentConnection.IsConnected) { @@ -96,55 +114,31 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ channel.QueueBind(queue: _queueName, exchange: BROKER_NAME, routingKey: eventName); - - _handlers.Add(eventName, new List()); - _handlers[eventName].Add(handler); - _eventTypes.Add(typeof(T)); } - } + _subsManager.AddSubscription(handler); + } - public void Unsubscribe(IIntegrationEventHandler handler) where T : IntegrationEvent + public void Unsubscribe() + where TH : IIntegrationEventHandler + where T : IntegrationEvent { - var eventName = typeof(T).Name; + _subsManager.RemoveSubscription(); + } - if (_handlers.ContainsKey(eventName) && _handlers[eventName].Contains(handler)) + private static Func FindHandlerByType(Type handlerType, IEnumerable> handlers) + { + foreach (var func in handlers) { - _handlers[eventName].Remove(handler); - - if (_handlers[eventName].Count == 0) + if (func.GetMethodInfo().ReturnType == handlerType) { - _handlers.Remove(eventName); - - var eventType = _eventTypes.SingleOrDefault(e => e.Name == eventName); - - if (eventType != null) - { - _eventTypes.Remove(eventType); - - if (!_persistentConnection.IsConnected) - { - _persistentConnection.TryConnect(); - } - - using (var channel = _persistentConnection.CreateModel()) - { - channel.QueueUnbind(queue: _queueName, - exchange: BROKER_NAME, - routingKey: eventName); - - if (_handlers.Keys.Count == 0) - { - _queueName = string.Empty; - - _consumerChannel.Close(); - } - } - } + return func; } } + + return null; } public void Dispose() @@ -153,8 +147,8 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ { _consumerChannel.Dispose(); } - - _handlers.Clear(); + + _subsManager.Clear(); } private IModel CreateConsumerChannel() @@ -195,15 +189,17 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ private async Task ProcessEvent(string eventName, string message) { - if (_handlers.ContainsKey(eventName)) - { - Type eventType = _eventTypes.Single(t => t.Name == eventName); + + if (_subsManager.HasSubscriptionsForEvent(eventName)) + { + var eventType = _subsManager.GetEventTypeByName(eventName); var integrationEvent = JsonConvert.DeserializeObject(message, eventType); - var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType); - var handlers = _handlers[eventName]; + var handlers = _subsManager.GetHandlersForEvent(eventName); - foreach (var handler in handlers) + foreach (var handlerfactory in handlers) { + var handler = handlerfactory.DynamicInvoke(); + var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType); await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent }); } } diff --git a/src/Services/Basket/Basket.API/Startup.cs b/src/Services/Basket/Basket.API/Startup.cs index da9baac48..855312a65 100644 --- a/src/Services/Basket/Basket.API/Startup.cs +++ b/src/Services/Basket/Basket.API/Startup.cs @@ -3,6 +3,7 @@ using Basket.API.IntegrationEvents.EventHandling; using Basket.API.IntegrationEvents.Events; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ; using Microsoft.eShopOnContainers.Services.Basket.API.Auth.Server; @@ -19,6 +20,7 @@ using StackExchange.Redis; using System.Linq; using System.Net; using System.Threading.Tasks; +using System; namespace Microsoft.eShopOnContainers.Services.Basket.API { @@ -80,8 +82,6 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API return new DefaultRabbitMQPersistentConnection(factory, logger); }); - services.AddSingleton(); - services.AddSwaggerGen(); services.ConfigureSwaggerGen(options => @@ -108,9 +108,16 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API }); services.AddTransient(); - services.AddTransient, ProductPriceChangedIntegrationEventHandler>(); - services.AddTransient, OrderStartedIntegrationEventHandler>(); + RegisterServiceBus(services); + } + private void RegisterServiceBus(IServiceCollection services) + { + services.AddSingleton(); + services.AddSingleton(); + + services.AddTransient(); + services.AddTransient(); } @@ -155,11 +162,13 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API var orderStartedHandler = app.ApplicationServices .GetService>(); - var eventBus = app.ApplicationServices - .GetRequiredService(); + var eventBus = app.ApplicationServices.GetRequiredService(); + + eventBus.Subscribe + (() => app.ApplicationServices.GetRequiredService()); - eventBus.Subscribe(catalogPriceHandler); - eventBus.Subscribe(orderStartedHandler); + eventBus.Subscribe + (() => app.ApplicationServices.GetRequiredService()); } } } diff --git a/src/Services/Catalog/Catalog.API/Startup.cs b/src/Services/Catalog/Catalog.API/Startup.cs index 1c1408b67..9eb195674 100644 --- a/src/Services/Catalog/Catalog.API/Startup.cs +++ b/src/Services/Catalog/Catalog.API/Startup.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; + using Microsoft.eShopOnContainers.BuildingBlocks.EventBus; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ; using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF; @@ -115,6 +116,7 @@ return new DefaultRabbitMQPersistentConnection(factory, logger); }); + services.AddSingleton(); services.AddSingleton(); } diff --git a/src/Services/Ordering/Ordering.API/Startup.cs b/src/Services/Ordering/Ordering.API/Startup.cs index 6665908a6..58d8f1cbe 100644 --- a/src/Services/Ordering/Ordering.API/Startup.cs +++ b/src/Services/Ordering/Ordering.API/Startup.cs @@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.EntityFrameworkCore; + using Microsoft.eShopOnContainers.BuildingBlocks.EventBus; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ; using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF; @@ -119,6 +120,7 @@ return new DefaultRabbitMQPersistentConnection(factory, logger); }); + services.AddSingleton(); services.AddSingleton(); services.AddOptions();