From 22cc8daa65eee3b59bdb5441309b68552ced0418 Mon Sep 17 00:00:00 2001 From: Eduard Tomas Date: Tue, 2 May 2017 10:29:24 +0200 Subject: [PATCH] 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 @@ -