diff --git a/.azure/pipelines/ci.yml b/.azure/pipelines/ci.yml
index 56ba9bcad7..16cad09b0f 100644
--- a/.azure/pipelines/ci.yml
+++ b/.azure/pipelines/ci.yml
@@ -71,6 +71,7 @@ variables:
# The following extra properties are not set when testing. Use with final build.[cmd,sh] of asset-producing jobs.
- name: _PublishArgs
value: /p:Publish=true
+ /p:GenerateChecksums=true
/p:DotNetPublishBlobFeedKey=$(dotnetfeed-storage-access-key-1)
/p:DotNetPublishBlobFeedUrl=https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json
/p:DotNetPublishToBlobFeed=$(_DotNetPublishToBlobFeed)
diff --git a/.vsconfig b/.vsconfig
new file mode 100644
index 0000000000..7a520fe61c
--- /dev/null
+++ b/.vsconfig
@@ -0,0 +1,12 @@
+{
+ "version": "1.0",
+ "components": [
+ "Microsoft.Net.Component.4.6.1.TargetingPack",
+ "Microsoft.Net.Component.4.7.2.SDK",
+ "Microsoft.Net.Component.4.7.2.TargetingPack",
+ "Microsoft.VisualStudio.Workload.ManagedDesktop",
+ "Microsoft.VisualStudio.Workload.NetCoreTools",
+ "Microsoft.VisualStudio.Workload.NetWeb",
+ "Microsoft.VisualStudio.Workload.VisualStudioExtension"
+ ]
+}
diff --git a/CODE-OF-CONDUCT.md b/CODE-OF-CONDUCT.md
new file mode 100644
index 0000000000..775f221c98
--- /dev/null
+++ b/CODE-OF-CONDUCT.md
@@ -0,0 +1,6 @@
+# Code of Conduct
+
+This project has adopted the code of conduct defined by the Contributor Covenant
+to clarify expected behavior in our community.
+
+For more information, see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/code-of-conduct).
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 238c74186d..a125150797 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -57,4 +57,4 @@ Your pull request will now go through extensive checks by the subject matter exp
## Code of conduct
-This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information, see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
+See [CODE-OF-CONDUCT.md](./CODE-OF-CONDUCT.md)
diff --git a/Directory.Build.props b/Directory.Build.props
index c40d203441..6e658ac596 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -13,7 +13,7 @@
true
$(MSBuildProjectName)-ref
- true
+ true
true
false
true
@@ -105,8 +105,18 @@
$(RuntimeInstallerBaseName)-internal
-
-
+
+
@@ -179,7 +189,6 @@
.tar.gz
.zip
- .sha512
diff --git a/Directory.Build.targets b/Directory.Build.targets
index ccef1aa25c..5c0c27b516 100644
--- a/Directory.Build.targets
+++ b/Directory.Build.targets
@@ -105,6 +105,14 @@
true
+
+ $(RepoRoot)THIRD-PARTY-NOTICES.TXT
+
+
+
+
+
+
diff --git a/README.md b/README.md
index 4c2e975c42..327c3e7208 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@ Follow the [Getting Started](https://docs.microsoft.com/aspnet/core/getting-star
Also check out the [.NET Homepage](https://www.microsoft.com/net) for released versions of .NET, getting started guides, and learning resources.
-See the [Issue Management Policies](https://github.com/dotnet/aspnetcore/blob/anurse/issue-policies/docs/IssueManagementPolicies.md) document for more information on how we handle incoming issues.
+See the [Issue Management Policies](https://github.com/dotnet/aspnetcore/blob/master/docs/IssueManagementPolicies.md) document for more information on how we handle incoming issues.
## How to Engage, Contribute, and Give Feedback
@@ -37,4 +37,4 @@ These are some other repos for related projects:
## Code of conduct
-This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
+See [CODE-OF-CONDUCT](./CODE-OF-CONDUCT.md)
diff --git a/eng/AfterSigning.targets b/eng/AfterSigning.targets
new file mode 100644
index 0000000000..4bbb0dcf03
--- /dev/null
+++ b/eng/AfterSigning.targets
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+ $(ArtifactsDir.Substring(0, $([MSBuild]::Subtract($(ArtifactsDir.Length), 1))))
+
+ $(ArtifactsDir)\installers\
+
+
+
+
+
+
+
+
+
+
+
+
+ %(FullPath).sha512
+
+
+
+
+
+
+
+
diff --git a/eng/Baseline.Designer.props b/eng/Baseline.Designer.props
index 057d2662c0..44b6452388 100644
--- a/eng/Baseline.Designer.props
+++ b/eng/Baseline.Designer.props
@@ -6,12 +6,12 @@
- 3.0.2
+ 3.0.3
- 3.0.2
+ 3.0.3
diff --git a/eng/Baseline.xml b/eng/Baseline.xml
index b4192011c4..df8a80748c 100644
--- a/eng/Baseline.xml
+++ b/eng/Baseline.xml
@@ -5,8 +5,8 @@ Update this list when preparing for a new patch.
-->
-
-
+
+
diff --git a/eng/Build.props b/eng/Build.props
index 6f1b3f1908..ef89408b47 100644
--- a/eng/Build.props
+++ b/eng/Build.props
@@ -152,6 +152,7 @@
$(RepoRoot)src\SiteExtensions\LoggingAggregate\test\**\*.csproj;
$(RepoRoot)src\Shared\**\*.*proj;
$(RepoRoot)src\Tools\**\*.*proj;
+ $(RepoRoot)src\Logging.AzureAppServices\**\src\*.csproj;
$(RepoRoot)src\Middleware\**\*.csproj;
$(RepoRoot)src\Razor\**\*.*proj;
$(RepoRoot)src\Mvc\**\*.*proj;
@@ -191,6 +192,7 @@
$(RepoRoot)src\Security\**\src\*.csproj;
$(RepoRoot)src\SiteExtensions\**\src\*.csproj;
$(RepoRoot)src\Tools\**\src\*.csproj;
+ $(RepoRoot)src\Logging.AzureAppServices\**\src\*.csproj;
$(RepoRoot)src\Middleware\**\src\*.csproj;
$(RepoRoot)src\Razor\**\src\*.csproj;
$(RepoRoot)src\Mvc\**\src\*.csproj;
diff --git a/eng/Dependencies.props b/eng/Dependencies.props
index d02e5158ae..acbfb7e1c9 100644
--- a/eng/Dependencies.props
+++ b/eng/Dependencies.props
@@ -29,7 +29,6 @@ and are generated based on the last package release.
-
@@ -105,7 +104,6 @@ and are generated based on the last package release.
-
@@ -120,13 +118,11 @@ and are generated based on the last package release.
-
-
-
+
diff --git a/eng/ProjectReferences.props b/eng/ProjectReferences.props
index dda9b06ce8..a7d42c3782 100644
--- a/eng/ProjectReferences.props
+++ b/eng/ProjectReferences.props
@@ -32,6 +32,7 @@
+
diff --git a/eng/Publishing.props b/eng/Publishing.props
index ab7456c178..2c13cb29bf 100644
--- a/eng/Publishing.props
+++ b/eng/Publishing.props
@@ -1,7 +1,7 @@
-
+
- $(ArtifactsDir.Substring(0, $([MSBuild]::Subtract($(ArtifactsDir.Length), 1))))
+ $(ArtifactsDir.Substring(0, $([MSBuild]::Subtract($(ArtifactsDir.Length), 1))))
$(PublishDependsOnTargets);_PublishInstallersAndChecksums
@@ -50,12 +50,10 @@
-
true
diff --git a/eng/SharedFramework.External.props b/eng/SharedFramework.External.props
index 9812152edd..59987b0a2c 100644
--- a/eng/SharedFramework.External.props
+++ b/eng/SharedFramework.External.props
@@ -18,9 +18,8 @@
$(SystemWindowsExtensionsPackageVersion.Split('.')[0]).$(SystemWindowsExtensionsPackageVersion.Split('.')[1]).0
-
-
+
@@ -54,8 +53,6 @@
-
-
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index e507920463..39a62cd7ee 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -13,320 +13,292 @@
https://github.com/dotnet/blazor
dd7fb4d3931d556458f62642c2edfc59f6295bfb
-
+
https://github.com/dotnet/aspnetcore-tooling
- 8587529296124ad1d7e2a9b75f536e2a2c301e48
+ 4ec71cb57e45db101bbd4ffcf64dafa1711de0af
-
+
https://github.com/dotnet/aspnetcore-tooling
- 8587529296124ad1d7e2a9b75f536e2a2c301e48
+ 4ec71cb57e45db101bbd4ffcf64dafa1711de0af
-
+
https://github.com/dotnet/aspnetcore-tooling
- 8587529296124ad1d7e2a9b75f536e2a2c301e48
+ 4ec71cb57e45db101bbd4ffcf64dafa1711de0af
-
+
https://github.com/dotnet/aspnetcore-tooling
- 8587529296124ad1d7e2a9b75f536e2a2c301e48
+ 4ec71cb57e45db101bbd4ffcf64dafa1711de0af
-
+
https://github.com/dotnet/efcore
- 7a6aa0a4f513c28b5a0501a2db8880885def2236
+ b0636ed8050797d0a9c16da8b98c2eea7d7e1f16
-
+
https://github.com/dotnet/efcore
- 7a6aa0a4f513c28b5a0501a2db8880885def2236
+ b0636ed8050797d0a9c16da8b98c2eea7d7e1f16
-
+
https://github.com/dotnet/efcore
- 7a6aa0a4f513c28b5a0501a2db8880885def2236
+ b0636ed8050797d0a9c16da8b98c2eea7d7e1f16
-
+
https://github.com/dotnet/efcore
- 7a6aa0a4f513c28b5a0501a2db8880885def2236
+ b0636ed8050797d0a9c16da8b98c2eea7d7e1f16
-
+
https://github.com/dotnet/efcore
- 7a6aa0a4f513c28b5a0501a2db8880885def2236
+ b0636ed8050797d0a9c16da8b98c2eea7d7e1f16
-
+
https://github.com/dotnet/efcore
- 7a6aa0a4f513c28b5a0501a2db8880885def2236
+ b0636ed8050797d0a9c16da8b98c2eea7d7e1f16
-
+
https://github.com/dotnet/efcore
- 7a6aa0a4f513c28b5a0501a2db8880885def2236
+ b0636ed8050797d0a9c16da8b98c2eea7d7e1f16
-
- https://github.com/dotnet/extensions
- bdc30606a08a10db8fb6909b75e9ebf6f9f482d4
-
-
- https://github.com/dotnet/extensions
- bdc30606a08a10db8fb6909b75e9ebf6f9f482d4
-
-
- https://github.com/dotnet/extensions
- bdc30606a08a10db8fb6909b75e9ebf6f9f482d4
-
-
- https://github.com/dotnet/extensions
- bdc30606a08a10db8fb6909b75e9ebf6f9f482d4
-
-
- https://github.com/dotnet/extensions
- bdc30606a08a10db8fb6909b75e9ebf6f9f482d4
-
-
- https://github.com/dotnet/extensions
- bdc30606a08a10db8fb6909b75e9ebf6f9f482d4
-
-
- https://github.com/dotnet/extensions
- bdc30606a08a10db8fb6909b75e9ebf6f9f482d4
-
-
- https://github.com/dotnet/extensions
- bdc30606a08a10db8fb6909b75e9ebf6f9f482d4
-
-
- https://github.com/dotnet/extensions
- bdc30606a08a10db8fb6909b75e9ebf6f9f482d4
-
-
- https://github.com/dotnet/extensions
- bdc30606a08a10db8fb6909b75e9ebf6f9f482d4
-
-
- https://github.com/dotnet/extensions
- bdc30606a08a10db8fb6909b75e9ebf6f9f482d4
-
-
- https://github.com/dotnet/extensions
- bdc30606a08a10db8fb6909b75e9ebf6f9f482d4
-
-
- https://github.com/dotnet/extensions
- bdc30606a08a10db8fb6909b75e9ebf6f9f482d4
-
-
- https://github.com/dotnet/extensions
- bdc30606a08a10db8fb6909b75e9ebf6f9f482d4
-
-
- https://github.com/dotnet/extensions
- bdc30606a08a10db8fb6909b75e9ebf6f9f482d4
-
-
- https://github.com/dotnet/extensions
- bdc30606a08a10db8fb6909b75e9ebf6f9f482d4
-
-
- https://github.com/dotnet/extensions
- bdc30606a08a10db8fb6909b75e9ebf6f9f482d4
-
-
- https://github.com/dotnet/extensions
- bdc30606a08a10db8fb6909b75e9ebf6f9f482d4
-
-
- https://github.com/dotnet/extensions
- bdc30606a08a10db8fb6909b75e9ebf6f9f482d4
-
-
- https://github.com/dotnet/extensions
- bdc30606a08a10db8fb6909b75e9ebf6f9f482d4
-
-
- https://github.com/dotnet/extensions
- bdc30606a08a10db8fb6909b75e9ebf6f9f482d4
-
-
- https://github.com/dotnet/extensions
- bdc30606a08a10db8fb6909b75e9ebf6f9f482d4
-
-
- https://github.com/dotnet/extensions
- bdc30606a08a10db8fb6909b75e9ebf6f9f482d4
-
-
- https://github.com/dotnet/extensions
- bdc30606a08a10db8fb6909b75e9ebf6f9f482d4
-
-
- https://github.com/dotnet/extensions
- bdc30606a08a10db8fb6909b75e9ebf6f9f482d4
-
-
- https://github.com/dotnet/extensions
- bdc30606a08a10db8fb6909b75e9ebf6f9f482d4
-
-
- https://github.com/dotnet/extensions
- bdc30606a08a10db8fb6909b75e9ebf6f9f482d4
-
-
- https://github.com/dotnet/extensions
- bdc30606a08a10db8fb6909b75e9ebf6f9f482d4
-
-
- https://github.com/dotnet/extensions
- bdc30606a08a10db8fb6909b75e9ebf6f9f482d4
-
-
- https://github.com/dotnet/extensions
- bdc30606a08a10db8fb6909b75e9ebf6f9f482d4
-
-
- https://github.com/dotnet/extensions
- bdc30606a08a10db8fb6909b75e9ebf6f9f482d4
-
-
- https://github.com/dotnet/extensions
- bdc30606a08a10db8fb6909b75e9ebf6f9f482d4
-
-
- https://github.com/dotnet/extensions
- bdc30606a08a10db8fb6909b75e9ebf6f9f482d4
-
-
- https://github.com/dotnet/extensions
- bdc30606a08a10db8fb6909b75e9ebf6f9f482d4
-
-
- https://github.com/dotnet/extensions
- bdc30606a08a10db8fb6909b75e9ebf6f9f482d4
-
-
- https://github.com/dotnet/extensions
- bdc30606a08a10db8fb6909b75e9ebf6f9f482d4
-
-
- https://github.com/dotnet/extensions
- bdc30606a08a10db8fb6909b75e9ebf6f9f482d4
-
-
- https://github.com/dotnet/extensions
- bdc30606a08a10db8fb6909b75e9ebf6f9f482d4
-
-
- https://github.com/dotnet/extensions
- bdc30606a08a10db8fb6909b75e9ebf6f9f482d4
-
-
- https://github.com/dotnet/extensions
- bdc30606a08a10db8fb6909b75e9ebf6f9f482d4
-
-
+
https://github.com/dotnet/runtime
- 2b487f31064fe07d3b3398a7432edd1fa5777796
+ e1fa5d7648d46f067e265211fc2c695d409fe788
-
+
https://github.com/dotnet/runtime
- 2b487f31064fe07d3b3398a7432edd1fa5777796
+ e1fa5d7648d46f067e265211fc2c695d409fe788
-
+
https://github.com/dotnet/runtime
- 2b487f31064fe07d3b3398a7432edd1fa5777796
+ e1fa5d7648d46f067e265211fc2c695d409fe788
-
+
https://github.com/dotnet/runtime
- 2b487f31064fe07d3b3398a7432edd1fa5777796
+ e1fa5d7648d46f067e265211fc2c695d409fe788
-
+
https://github.com/dotnet/runtime
- 2b487f31064fe07d3b3398a7432edd1fa5777796
+ e1fa5d7648d46f067e265211fc2c695d409fe788
-
+
https://github.com/dotnet/runtime
- 2b487f31064fe07d3b3398a7432edd1fa5777796
+ e1fa5d7648d46f067e265211fc2c695d409fe788
-
+
https://github.com/dotnet/runtime
- 2b487f31064fe07d3b3398a7432edd1fa5777796
+ e1fa5d7648d46f067e265211fc2c695d409fe788
-
+
https://github.com/dotnet/runtime
- 2b487f31064fe07d3b3398a7432edd1fa5777796
+ e1fa5d7648d46f067e265211fc2c695d409fe788
-
+
https://github.com/dotnet/runtime
- 2b487f31064fe07d3b3398a7432edd1fa5777796
+ e1fa5d7648d46f067e265211fc2c695d409fe788
-
+
https://github.com/dotnet/runtime
- 2b487f31064fe07d3b3398a7432edd1fa5777796
+ e1fa5d7648d46f067e265211fc2c695d409fe788
-
+
https://github.com/dotnet/runtime
- 2b487f31064fe07d3b3398a7432edd1fa5777796
+ e1fa5d7648d46f067e265211fc2c695d409fe788
-
+
https://github.com/dotnet/runtime
- 2b487f31064fe07d3b3398a7432edd1fa5777796
+ e1fa5d7648d46f067e265211fc2c695d409fe788
-
+
https://github.com/dotnet/runtime
- 2b487f31064fe07d3b3398a7432edd1fa5777796
+ e1fa5d7648d46f067e265211fc2c695d409fe788
-
+
https://github.com/dotnet/runtime
- 2b487f31064fe07d3b3398a7432edd1fa5777796
+ e1fa5d7648d46f067e265211fc2c695d409fe788
-
+
https://github.com/dotnet/runtime
- 2b487f31064fe07d3b3398a7432edd1fa5777796
+ e1fa5d7648d46f067e265211fc2c695d409fe788
-
+
https://github.com/dotnet/runtime
- 2b487f31064fe07d3b3398a7432edd1fa5777796
+ e1fa5d7648d46f067e265211fc2c695d409fe788
-
+
https://github.com/dotnet/runtime
- 2b487f31064fe07d3b3398a7432edd1fa5777796
+ e1fa5d7648d46f067e265211fc2c695d409fe788
-
+
https://github.com/dotnet/runtime
- 2b487f31064fe07d3b3398a7432edd1fa5777796
+ e1fa5d7648d46f067e265211fc2c695d409fe788
-
+
https://github.com/dotnet/runtime
- 2b487f31064fe07d3b3398a7432edd1fa5777796
+ e1fa5d7648d46f067e265211fc2c695d409fe788
-
+
https://github.com/dotnet/runtime
- 2b487f31064fe07d3b3398a7432edd1fa5777796
+ e1fa5d7648d46f067e265211fc2c695d409fe788
-
+
https://github.com/dotnet/runtime
- 2b487f31064fe07d3b3398a7432edd1fa5777796
+ e1fa5d7648d46f067e265211fc2c695d409fe788
-
+
https://github.com/dotnet/runtime
- 2b487f31064fe07d3b3398a7432edd1fa5777796
+ e1fa5d7648d46f067e265211fc2c695d409fe788
+
+
+ https://github.com/dotnet/runtime
+ e1fa5d7648d46f067e265211fc2c695d409fe788
+
+
+ https://github.com/dotnet/runtime
+ e1fa5d7648d46f067e265211fc2c695d409fe788
+
+
+ https://github.com/dotnet/runtime
+ e1fa5d7648d46f067e265211fc2c695d409fe788
+
+
+ https://github.com/dotnet/runtime
+ e1fa5d7648d46f067e265211fc2c695d409fe788
+
+
+ https://github.com/dotnet/runtime
+ e1fa5d7648d46f067e265211fc2c695d409fe788
+
+
+ https://github.com/dotnet/runtime
+ e1fa5d7648d46f067e265211fc2c695d409fe788
+
+
+ https://github.com/dotnet/runtime
+ e1fa5d7648d46f067e265211fc2c695d409fe788
+
+
+ https://github.com/dotnet/runtime
+ e1fa5d7648d46f067e265211fc2c695d409fe788
+
+
+ https://github.com/dotnet/runtime
+ e1fa5d7648d46f067e265211fc2c695d409fe788
+
+
+ https://github.com/dotnet/runtime
+ e1fa5d7648d46f067e265211fc2c695d409fe788
+
+
+ https://github.com/dotnet/runtime
+ e1fa5d7648d46f067e265211fc2c695d409fe788
+
+
+ https://github.com/dotnet/runtime
+ e1fa5d7648d46f067e265211fc2c695d409fe788
+
+
+ https://github.com/dotnet/runtime
+ e1fa5d7648d46f067e265211fc2c695d409fe788
+
+
+ https://github.com/dotnet/runtime
+ e1fa5d7648d46f067e265211fc2c695d409fe788
+
+
+ https://github.com/dotnet/runtime
+ e1fa5d7648d46f067e265211fc2c695d409fe788
+
+
+ https://github.com/dotnet/runtime
+ e1fa5d7648d46f067e265211fc2c695d409fe788
+
+
+ https://github.com/dotnet/runtime
+ e1fa5d7648d46f067e265211fc2c695d409fe788
+
+
+ https://github.com/dotnet/runtime
+ e1fa5d7648d46f067e265211fc2c695d409fe788
+
+
+ https://github.com/dotnet/runtime
+ e1fa5d7648d46f067e265211fc2c695d409fe788
+
+
+ https://github.com/dotnet/runtime
+ e1fa5d7648d46f067e265211fc2c695d409fe788
+
+
+ https://github.com/dotnet/runtime
+ e1fa5d7648d46f067e265211fc2c695d409fe788
+
+
+ https://github.com/dotnet/runtime
+ e1fa5d7648d46f067e265211fc2c695d409fe788
+
+
+ https://github.com/dotnet/runtime
+ e1fa5d7648d46f067e265211fc2c695d409fe788
+
+
+ https://github.com/dotnet/runtime
+ e1fa5d7648d46f067e265211fc2c695d409fe788
+
+
+ https://github.com/dotnet/runtime
+ e1fa5d7648d46f067e265211fc2c695d409fe788
+
+
+ https://github.com/dotnet/runtime
+ e1fa5d7648d46f067e265211fc2c695d409fe788
+
+
+ https://github.com/dotnet/runtime
+ e1fa5d7648d46f067e265211fc2c695d409fe788
+
+
+ https://github.com/dotnet/runtime
+ e1fa5d7648d46f067e265211fc2c695d409fe788
+
+
+ https://github.com/dotnet/runtime
+ e1fa5d7648d46f067e265211fc2c695d409fe788
+
+
+ https://github.com/dotnet/runtime
+ e1fa5d7648d46f067e265211fc2c695d409fe788
+
+
+ https://github.com/dotnet/runtime
+ e1fa5d7648d46f067e265211fc2c695d409fe788
+
+
+ https://github.com/dotnet/runtime
+ e1fa5d7648d46f067e265211fc2c695d409fe788
+
+
+ https://github.com/dotnet/runtime
+ e1fa5d7648d46f067e265211fc2c695d409fe788
+
+
+ https://github.com/dotnet/runtime
+ e1fa5d7648d46f067e265211fc2c695d409fe788
-
+
https://github.com/dotnet/runtime
- 2b487f31064fe07d3b3398a7432edd1fa5777796
+ e1fa5d7648d46f067e265211fc2c695d409fe788
-
+
https://github.com/dotnet/runtime
- 2b487f31064fe07d3b3398a7432edd1fa5777796
+ e1fa5d7648d46f067e265211fc2c695d409fe788
-
+
https://github.com/dotnet/runtime
- 2b487f31064fe07d3b3398a7432edd1fa5777796
-
-
- https://github.com/dotnet/extensions
- bdc30606a08a10db8fb6909b75e9ebf6f9f482d4
+ e1fa5d7648d46f067e265211fc2c695d409fe788
https://github.com/dotnet/arcade
@@ -340,9 +312,9 @@
https://github.com/dotnet/arcade
09bb9d929120b402348c9a0e9c8c951e824059aa
-
+
https://github.com/dotnet/roslyn
- c9f2423cb5a2ab1ee8de0ef10e536d7672b1a2ea
+ 8167e4880190407325d6cf7282f6bb62267abc56
diff --git a/eng/Versions.props b/eng/Versions.props
index c499646ad1..b1190c5562 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -9,7 +9,7 @@
5
0
0
- 3
+ 4
@@ -64,92 +64,83 @@
5.0.0-beta.20180.5
- 3.6.0-3.20177.6
-
- 5.0.0-preview.3-runtime.20202.4
- 5.0.0-preview.3.20202.4
- 5.0.0-preview.3.20202.4
- 5.0.0-preview.3.20202.4
-
- 5.0.0-preview.3.20202.4
- 5.0.0-preview.3.20202.4
- 5.0.0-preview.3.20202.4
- 5.0.0-preview.3.20202.4
- 5.0.0-preview.3.20202.4
- 5.0.0-preview.3.20202.4
- 5.0.0-preview.3.20202.4
- 5.0.0-preview.3.20202.4
- 5.0.0-preview.3.20202.4
- 5.0.0-preview.3.20202.4
- 5.0.0-preview.3.20202.4
- 5.0.0-preview.3.20202.4
- 5.0.0-preview.3.20202.4
- 5.0.0-preview.3.20202.4
- 5.0.0-preview.3.20202.4
- 5.0.0-preview.3.20202.4
- 5.0.0-preview.3.20202.4
- 5.0.0-preview.3.20202.4
- 5.0.0-preview.3.20202.4
- 5.0.0-preview.3.20202.4
+ 3.6.0-3.20201.6
+
+ 5.0.0-preview.4-runtime.20201.1
+ 5.0.0-preview.4.20201.1
+ 5.0.0-preview.4.20201.1
+ 5.0.0-preview.4.20201.1
+ 5.0.0-preview.4.20201.1
+ 5.0.0-preview.4.20201.1
+ 5.0.0-preview.4-runtime.20201.1
+ 5.0.0-preview.4-runtime.20201.1
+ 5.0.0-preview.4-runtime.20201.1
+ 5.0.0-preview.4-runtime.20201.1
+ 5.0.0-preview.4-runtime.20201.1
+ 5.0.0-preview.4-runtime.20201.1
+ 5.0.0-preview.4-runtime.20201.1
+ 5.0.0-preview.4-runtime.20201.1
+ 5.0.0-preview.4-runtime.20201.1
+ 5.0.0-preview.4-runtime.20201.1
+ 5.0.0-preview.4-runtime.20201.1
+ 5.0.0-preview.4-runtime.20201.1
+ 5.0.0-preview.4-runtime.20201.1
+ 5.0.0-preview.4-runtime.20201.1
+ 5.0.0-preview.4-runtime.20201.1
+ 5.0.0-preview.4-runtime.20201.1
+ 5.0.0-preview.4-runtime.20201.1
+ 5.0.0-preview.4-runtime.20201.1
+ 5.0.0-preview.4-runtime.20201.1
+ 5.0.0-preview.4-runtime.20201.1
+ 5.0.0-preview.4-runtime.20201.1
+ 5.0.0-preview.4-runtime.20201.1
+ 5.0.0-preview.4-runtime.20201.1
+ 5.0.0-preview.4-runtime.20201.1
+ 5.0.0-preview.4-runtime.20201.1
+ 5.0.0-preview.4-runtime.20201.1
+ 5.0.0-preview.4-runtime.20201.1
+ 5.0.0-preview.4-runtime.20201.1
+ 5.0.0-preview.4-runtime.20201.1
+ 5.0.0-preview.4-runtime.20201.1
+ 5.0.0-preview.4-runtime.20201.1
+ 5.0.0-preview.4-runtime.20201.1
+ 5.0.0-preview.4-runtime.20201.1
+ 5.0.0-preview.4-runtime.20201.1
+ 5.0.0-preview.4.20201.1
+ 5.0.0-preview.4.20201.1
+ 5.0.0-preview.4.20201.1
+ 5.0.0-preview.4.20201.1
+ 5.0.0-preview.4.20201.1
+ 5.0.0-preview.4.20201.1
+ 5.0.0-preview.4.20201.1
+ 5.0.0-preview.4.20201.1
+ 5.0.0-preview.4.20201.1
+ 5.0.0-preview.4.20201.1
+ 5.0.0-preview.4.20201.1
+ 5.0.0-preview.4.20201.1
+ 5.0.0-preview.4.20201.1
+ 5.0.0-preview.4.20201.1
+ 5.0.0-preview.4.20201.1
+ 5.0.0-preview.4.20201.1
+ 5.0.0-preview.4.20201.1
+ 5.0.0-preview.4.20201.1
- 5.0.0-preview.3.20202.4
+ 5.0.0-preview.4.20201.1
3.2.0-preview1.20067.1
-
- 5.0.0-preview.3.20204.4
- 5.0.0-preview.3.20204.4
- 5.0.0-preview.3.20204.4
- 5.0.0-preview.3.20204.4
- 5.0.0-preview.3.20204.4
- 5.0.0-preview.3.20204.4
- 5.0.0-preview.3.20204.4
- 5.0.0-preview.3.20204.4
- 5.0.0-preview.3.20204.4
- 5.0.0-preview.3.20204.4
- 5.0.0-preview.3.20204.4
- 5.0.0-preview.3.20204.4
- 5.0.0-preview.3.20204.4
- 5.0.0-preview.3.20204.4
- 5.0.0-preview.3.20204.4
- 5.0.0-preview.3.20204.4
- 5.0.0-preview.3.20204.4
- 5.0.0-preview.3.20204.4
- 5.0.0-preview.3.20204.4
- 5.0.0-preview.3.20204.4
- 5.0.0-preview.3.20204.4
- 5.0.0-preview.3.20204.4
- 5.0.0-preview.3.20204.4
- 5.0.0-preview.3.20204.4
- 5.0.0-preview.3.20204.4
- 5.0.0-preview.3.20204.4
- 5.0.0-preview.3.20204.4
- 5.0.0-preview.3.20204.4
- 5.0.0-preview.3.20204.4
- 5.0.0-preview.3.20204.4
- 5.0.0-preview.3.20204.4
- 5.0.0-preview.3.20204.4
- 5.0.0-preview.3.20204.4
- 5.0.0-preview.3.20204.4
- 5.0.0-preview.3.20204.4
- 5.0.0-preview.3.20204.4
- 5.0.0-preview.3.20204.4
- 5.0.0-preview.3.20204.4
- 5.0.0-preview.3.20204.4
- 5.0.0-preview.3.20204.4
- 5.0.0-preview.3.20204.4
- 5.0.0-preview.3.20181.2
- 5.0.0-preview.3.20181.2
- 5.0.0-preview.3.20181.2
- 5.0.0-preview.3.20181.2
- 5.0.0-preview.3.20181.2
- 5.0.0-preview.3.20181.2
- 5.0.0-preview.3.20181.2
+ 5.0.0-preview.4.20203.1
+ 5.0.0-preview.4.20203.1
+ 5.0.0-preview.4.20203.1
+ 5.0.0-preview.4.20203.1
+ 5.0.0-preview.4.20203.1
+ 5.0.0-preview.4.20203.1
+ 5.0.0-preview.4.20203.1
- 5.0.0-preview.3.20205.2
- 5.0.0-preview.3.20205.2
- 5.0.0-preview.3.20205.2
- 5.0.0-preview.3.20205.2
+ 5.0.0-preview.4.20201.4
+ 5.0.0-preview.4.20201.4
+ 5.0.0-preview.4.20201.4
+ 5.0.0-preview.4.20201.4
+ 5.0.0-preview.4.20180.4
3.0.0-build-20190530.3
1.0.0-beta-64023-03
diff --git a/eng/helix/content/RunTests/Directory.Build.props b/eng/helix/content/RunTests/Directory.Build.props
new file mode 100644
index 0000000000..c1df2220dd
--- /dev/null
+++ b/eng/helix/content/RunTests/Directory.Build.props
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/eng/helix/content/RunTests/Directory.Build.targets b/eng/helix/content/RunTests/Directory.Build.targets
new file mode 100644
index 0000000000..c1df2220dd
--- /dev/null
+++ b/eng/helix/content/RunTests/Directory.Build.targets
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/eng/helix/content/RunTests/ProcessResult.cs b/eng/helix/content/RunTests/ProcessResult.cs
new file mode 100644
index 0000000000..9e293c02c8
--- /dev/null
+++ b/eng/helix/content/RunTests/ProcessResult.cs
@@ -0,0 +1,20 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace RunTests
+{
+ public class ProcessResult
+ {
+ public ProcessResult(string standardOutput, string standardError, int exitCode)
+ {
+ StandardOutput = standardOutput;
+ StandardError = standardError;
+ ExitCode = exitCode;
+ }
+
+ public string StandardOutput { get; }
+ public string StandardError { get; }
+ public int ExitCode { get; }
+ }
+}
diff --git a/eng/helix/content/RunTests/ProcessUtil.cs b/eng/helix/content/RunTests/ProcessUtil.cs
new file mode 100644
index 0000000000..8c25f9fb5d
--- /dev/null
+++ b/eng/helix/content/RunTests/ProcessUtil.cs
@@ -0,0 +1,158 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+#nullable enable
+
+namespace RunTests
+{
+ public static class ProcessUtil
+ {
+ [DllImport("libc", SetLastError = true, EntryPoint = "kill")]
+ private static extern int sys_kill(int pid, int sig);
+
+ public static async Task RunAsync(
+ string filename,
+ string arguments,
+ string? workingDirectory = null,
+ bool throwOnError = true,
+ IDictionary? environmentVariables = null,
+ Action? outputDataReceived = null,
+ Action? errorDataReceived = null,
+ Action? onStart = null,
+ CancellationToken cancellationToken = default)
+ {
+ Console.WriteLine($"Running '{filename} {arguments}'");
+ using var process = new Process()
+ {
+ StartInfo =
+ {
+ FileName = filename,
+ Arguments = arguments,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ UseShellExecute = false,
+ CreateNoWindow = true,
+ },
+ EnableRaisingEvents = true
+ };
+
+
+ if (workingDirectory != null)
+ {
+ process.StartInfo.WorkingDirectory = workingDirectory;
+ }
+
+ if (environmentVariables != null)
+ {
+ foreach (var kvp in environmentVariables)
+ {
+ process.StartInfo.Environment.Add(kvp);
+ }
+ }
+
+ var outputBuilder = new StringBuilder();
+ process.OutputDataReceived += (_, e) =>
+ {
+ if (e.Data != null)
+ {
+ if (outputDataReceived != null)
+ {
+ outputDataReceived.Invoke(e.Data);
+ }
+ else
+ {
+ outputBuilder.AppendLine(e.Data);
+ }
+ }
+ };
+
+ var errorBuilder = new StringBuilder();
+ process.ErrorDataReceived += (_, e) =>
+ {
+ if (e.Data != null)
+ {
+ if (errorDataReceived != null)
+ {
+ errorDataReceived.Invoke(e.Data);
+ }
+ else
+ {
+ errorBuilder.AppendLine(e.Data);
+ }
+ }
+ };
+
+ var processLifetimeTask = new TaskCompletionSource();
+
+ process.Exited += (_, e) =>
+ {
+ Console.WriteLine($"'{process.StartInfo.FileName} {process.StartInfo.Arguments}' completed with exit code '{process.ExitCode}'");
+ if (throwOnError && process.ExitCode != 0)
+ {
+ processLifetimeTask.TrySetException(new InvalidOperationException($"Command {filename} {arguments} returned exit code {process.ExitCode}"));
+ }
+ else
+ {
+ processLifetimeTask.TrySetResult(new ProcessResult(outputBuilder.ToString(), errorBuilder.ToString(), process.ExitCode));
+ }
+ };
+
+ process.Start();
+ onStart?.Invoke(process.Id);
+
+ process.BeginOutputReadLine();
+ process.BeginErrorReadLine();
+
+ var cancelledTcs = new TaskCompletionSource
-
+
+
-
+ false
+ true
+ true
+ true
+
+ true
+
+
+
+
+
+
+
diff --git a/src/Analyzers/Internal.AspNetCore.Analyzers/src/PubternalityAnalyzer.cs b/src/Analyzers/Internal.AspNetCore.Analyzers/src/PubternalityAnalyzer.cs
new file mode 100644
index 0000000000..b66aa38c2e
--- /dev/null
+++ b/src/Analyzers/Internal.AspNetCore.Analyzers/src/PubternalityAnalyzer.cs
@@ -0,0 +1,279 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Immutable;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace Internal.AspNetCore.Analyzers
+{
+ [DiagnosticAnalyzer(LanguageNames.CSharp)]
+ public class PubternalityAnalyzer : DiagnosticAnalyzer
+ {
+ public PubternalityAnalyzer()
+ {
+ SupportedDiagnostics = ImmutableArray.Create(new[]
+ {
+ PubturnalityDescriptors.PUB0001,
+ PubturnalityDescriptors.PUB0002
+ });
+ }
+
+ public override ImmutableArray SupportedDiagnostics { get; }
+
+ public override void Initialize(AnalysisContext context)
+ {
+ context.EnableConcurrentExecution();
+
+ context.RegisterCompilationStartAction(analysisContext =>
+ {
+ analysisContext.RegisterSymbolAction(symbolAnalysisContext => AnalyzeTypeUsage(symbolAnalysisContext), SymbolKind.Namespace);
+ analysisContext.RegisterSyntaxNodeAction(syntaxContext => AnalyzeTypeUsage(syntaxContext), SyntaxKind.IdentifierName);
+ });
+ }
+
+ private void AnalyzeTypeUsage(SymbolAnalysisContext context)
+ {
+ var ns = (INamespaceSymbol)context.Symbol;
+ if (IsInternal(ns))
+ {
+ return;
+ }
+
+ foreach (var namespaceOrTypeSymbol in ns.GetMembers())
+ {
+ if (namespaceOrTypeSymbol.IsType)
+ {
+ CheckType((ITypeSymbol)namespaceOrTypeSymbol, context);
+ }
+ }
+ }
+
+ private void CheckType(ITypeSymbol typeSymbol, SymbolAnalysisContext context)
+ {
+ if (IsPrivate(typeSymbol) || IsPrivate(typeSymbol.ContainingType))
+ {
+ return;
+ }
+
+ if (typeSymbol.BaseType != null)
+ {
+ CheckType(context, typeSymbol.BaseType, typeSymbol.DeclaringSyntaxReferences);
+ }
+
+ foreach (var member in typeSymbol.GetMembers())
+ {
+ CheckMember(context, member);
+ }
+
+ foreach (var innerType in typeSymbol.GetTypeMembers())
+ {
+ CheckType(innerType, context);
+ }
+
+ if (typeSymbol is INamedTypeSymbol namedTypeSymbol)
+ {
+ // Check delegate signatures
+ if (namedTypeSymbol.DelegateInvokeMethod != null)
+ {
+ CheckMethod(context, namedTypeSymbol.DelegateInvokeMethod);
+ }
+ }
+ }
+
+ private void CheckMember(SymbolAnalysisContext context, ISymbol symbol)
+ {
+ if (IsPrivate(symbol))
+ {
+ return;
+ }
+
+ switch (symbol)
+ {
+ case IFieldSymbol fieldSymbol:
+ {
+ CheckType(context, fieldSymbol.Type, fieldSymbol.DeclaringSyntaxReferences);
+ break;
+ }
+ case IPropertySymbol propertySymbol:
+ {
+ CheckType(context, propertySymbol.Type, propertySymbol.DeclaringSyntaxReferences);
+ break;
+ }
+ case IMethodSymbol methodSymbol:
+ {
+ // Skip compiler generated members that we already explicitly check
+ switch (methodSymbol.MethodKind)
+ {
+ case MethodKind.EventAdd:
+ case MethodKind.EventRaise:
+ case MethodKind.EventRemove:
+ case MethodKind.PropertyGet:
+ case MethodKind.PropertySet:
+ case MethodKind.DelegateInvoke:
+ case MethodKind.Ordinary when methodSymbol.ContainingType.TypeKind == TypeKind.Delegate:
+ return;
+ }
+
+ CheckMethod(context, methodSymbol);
+ break;
+ }
+ case IEventSymbol eventSymbol:
+ CheckType(context, eventSymbol.Type, eventSymbol.DeclaringSyntaxReferences);
+ break;
+ }
+ }
+
+ private void CheckMethod(SymbolAnalysisContext context, IMethodSymbol methodSymbol)
+ {
+ if (IsPrivate(methodSymbol))
+ {
+ return;
+ }
+
+ foreach (var parameter in methodSymbol.Parameters)
+ {
+ CheckType(context, parameter.Type, parameter.DeclaringSyntaxReferences);
+ }
+
+ CheckType(context, methodSymbol.ReturnType, methodSymbol.DeclaringSyntaxReferences);
+ }
+
+ private static bool IsPrivate(ISymbol symbol)
+ {
+ return symbol != null &&
+ (symbol.DeclaredAccessibility == Accessibility.Private ||
+ symbol.DeclaredAccessibility == Accessibility.Internal ||
+ IsInternal(symbol.ContainingNamespace));
+ }
+
+ private void CheckAttributes(SymbolAnalysisContext context, ImmutableArray attributes)
+ {
+ foreach (var attributeData in attributes)
+ {
+ CheckType(context, attributeData.AttributeClass, attributeData.ApplicationSyntaxReference);
+ }
+ }
+
+ private void CheckType(SymbolAnalysisContext context, ITypeSymbol symbol, SyntaxReference syntax)
+ {
+ var pubternalType = GetPubternalType(symbol);
+ if (pubternalType != null)
+ {
+ ReportPUB0001(context, pubternalType, syntax);
+ }
+ }
+ private void CheckType(SymbolAnalysisContext context, ITypeSymbol symbol, ImmutableArray syntaxReferences)
+ {
+ var pubternalType = GetPubternalType(symbol);
+ if (pubternalType != null)
+ {
+ foreach (var syntaxReference in syntaxReferences)
+ {
+ ReportPUB0001(context, pubternalType, syntaxReference);
+ }
+ }
+ }
+
+ private static void ReportPUB0001(SymbolAnalysisContext context, ITypeSymbol pubternalType, SyntaxReference syntax)
+ {
+ var syntaxNode = syntax.GetSyntax();
+ var location = syntaxNode.GetLocation();
+
+ if (syntaxNode is BaseTypeDeclarationSyntax baseTypeDeclarationSyntax)
+ {
+ location = baseTypeDeclarationSyntax.Identifier.GetLocation();
+ }
+
+ if (syntaxNode is DelegateDeclarationSyntax delegateDeclarationSyntax)
+ {
+ location = delegateDeclarationSyntax.ReturnType.GetLocation();
+ }
+
+ if (syntaxNode is BasePropertyDeclarationSyntax propertyDeclaration)
+ {
+ location = propertyDeclaration.Type.GetLocation();
+ }
+
+ if (syntaxNode is MethodDeclarationSyntax method)
+ {
+ location = method.ReturnType.GetLocation();
+ }
+
+ if (syntaxNode is VariableDeclaratorSyntax variableDeclarator)
+ {
+ if (variableDeclarator.Parent is VariableDeclarationSyntax fieldDeclaration)
+ {
+ location = fieldDeclaration.Type.GetLocation();
+ }
+ }
+
+ context.ReportDiagnostic(Diagnostic.Create(PubturnalityDescriptors.PUB0001, location, pubternalType.ToDisplayString()));
+ }
+
+ private ITypeSymbol GetPubternalType(ITypeSymbol symbol)
+ {
+ if (IsInternal(symbol.ContainingNamespace))
+ {
+ return symbol;
+ }
+ else
+ {
+ if (symbol is INamedTypeSymbol namedTypeSymbol && namedTypeSymbol.IsGenericType)
+ {
+ foreach (var argument in namedTypeSymbol.TypeArguments)
+ {
+ var argumentSymbol = GetPubternalType(argument);
+ if (argumentSymbol != null)
+ {
+ return argumentSymbol;
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private void AnalyzeTypeUsage(SyntaxNodeAnalysisContext syntaxContext)
+ {
+ var identifier = (IdentifierNameSyntax)syntaxContext.Node;
+
+ var symbolInfo = ModelExtensions.GetTypeInfo(syntaxContext.SemanticModel, identifier, syntaxContext.CancellationToken);
+ if (symbolInfo.Type == null)
+ {
+ return;
+ }
+
+ var type = symbolInfo.Type;
+ if (!IsInternal(type.ContainingNamespace))
+ {
+ // don't care about non-pubternal type references
+ return;
+ }
+
+ if (!syntaxContext.ContainingSymbol.ContainingAssembly.Equals(type.ContainingAssembly))
+ {
+ syntaxContext.ReportDiagnostic(Diagnostic.Create(PubturnalityDescriptors.PUB0002, identifier.GetLocation(), type.ToDisplayString()));
+ }
+ }
+
+ private static bool IsInternal(INamespaceSymbol ns)
+ {
+ while (ns != null)
+ {
+ if (ns.Name == "Internal")
+ {
+ return true;
+ }
+
+ ns = ns.ContainingNamespace;
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/Analyzers/Internal.AspNetCore.Analyzers/src/PubturnalityDescriptors.cs b/src/Analyzers/Internal.AspNetCore.Analyzers/src/PubturnalityDescriptors.cs
new file mode 100644
index 0000000000..6064ebaf34
--- /dev/null
+++ b/src/Analyzers/Internal.AspNetCore.Analyzers/src/PubturnalityDescriptors.cs
@@ -0,0 +1,25 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.CodeAnalysis;
+
+namespace Internal.AspNetCore.Analyzers
+{
+ internal class PubturnalityDescriptors
+ {
+ public static DiagnosticDescriptor PUB0001 = new DiagnosticDescriptor(
+ "PUB0001",
+ "Pubternal type in public API",
+ "Pubternal type ('{0}') usage in public API",
+ "Usage",
+ DiagnosticSeverity.Warning, true);
+
+ public static DiagnosticDescriptor PUB0002 = new DiagnosticDescriptor(
+ "PUB0002",
+ "Cross assembly pubternal reference",
+ "Cross assembly pubternal type ('{0}') reference",
+ "Usage",
+ DiagnosticSeverity.Error, false);
+ }
+}
diff --git a/src/Analyzers/Internal.AspNetCore.Analyzers/test/Internal.AspNetCore.Analyzers.Tests.csproj b/src/Analyzers/Internal.AspNetCore.Analyzers/test/Internal.AspNetCore.Analyzers.Tests.csproj
new file mode 100644
index 0000000000..99dcca9dc1
--- /dev/null
+++ b/src/Analyzers/Internal.AspNetCore.Analyzers/test/Internal.AspNetCore.Analyzers.Tests.csproj
@@ -0,0 +1,15 @@
+
+
+
+ $(DefaultNetCoreTargetFramework);net472
+ true
+
+ false
+
+
+
+
+
+
+
+
diff --git a/src/Analyzers/Internal.AspNetCore.Analyzers/test/PubternabilityAnalyzerTests.cs b/src/Analyzers/Internal.AspNetCore.Analyzers/test/PubternabilityAnalyzerTests.cs
new file mode 100644
index 0000000000..ebcd5fa1e8
--- /dev/null
+++ b/src/Analyzers/Internal.AspNetCore.Analyzers/test/PubternabilityAnalyzerTests.cs
@@ -0,0 +1,260 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis;
+using Microsoft.AspNetCore.Analyzer.Testing;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Internal.AspNetCore.Analyzers.Tests
+{
+ public class PubternabilityAnalyzerTests : DiagnosticVerifier
+ {
+
+ private const string InternalDefinitions = @"
+namespace A.Internal.Namespace
+{
+ public class C {}
+ public delegate C CD ();
+ public class CAAttribute: System.Attribute {}
+
+ public class Program
+ {
+ public static void Main() {}
+ }
+}";
+ public PubternabilityAnalyzerTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper)
+ {
+ }
+
+ [Theory]
+ [MemberData(nameof(PublicMemberDefinitions))]
+ public async Task PublicExposureOfPubternalTypeProducesPUB0001(string member)
+ {
+ var code = GetSourceFromNamespaceDeclaration($@"
+namespace A
+{{
+ public class T
+ {{
+ {member}
+ }}
+}}");
+ var diagnostic = Assert.Single(await GetDiagnostics(code.Source));
+ Assert.Equal("PUB0001", diagnostic.Id);
+ AnalyzerAssert.DiagnosticLocation(code.DefaultMarkerLocation, diagnostic.Location);
+ }
+
+ [Theory]
+ [MemberData(nameof(PublicMemberWithAllowedDefinitions))]
+ public async Task PublicExposureOfPubternalMembersSometimesAllowed(string member)
+ {
+ var code = GetSourceFromNamespaceDeclaration($@"
+namespace A
+{{
+ public class T
+ {{
+ {member}
+ }}
+}}");
+ Assert.Empty(await GetDiagnostics(code.Source));
+ }
+
+
+ [Theory]
+ [MemberData(nameof(PublicTypeDefinitions))]
+ public async Task PublicExposureOfPubternalTypeProducesInTypeDefinitionPUB0001(string member)
+ {
+ var code = GetSourceFromNamespaceDeclaration($@"
+namespace A
+{{
+ {member}
+}}");
+ var diagnostic = Assert.Single(await GetDiagnostics(code.Source));
+ Assert.Equal("PUB0001", diagnostic.Id);
+ AnalyzerAssert.DiagnosticLocation(code.DefaultMarkerLocation, diagnostic.Location);
+ }
+
+ [Theory]
+ [MemberData(nameof(PublicMemberDefinitions))]
+ public async Task PrivateUsageOfPubternalTypeDoesNotProduce(string member)
+ {
+ var code = GetSourceFromNamespaceDeclaration($@"
+namespace A
+{{
+ internal class T
+ {{
+ {member}
+ }}
+}}");
+ var diagnostics = await GetDiagnostics(code.Source);
+ Assert.Empty(diagnostics);
+ }
+
+ [Theory]
+ [MemberData(nameof(PrivateMemberDefinitions))]
+ public async Task PrivateUsageOfPubternalTypeDoesNotProduceInPublicClasses(string member)
+ {
+ var code = GetSourceFromNamespaceDeclaration($@"
+namespace A
+{{
+ public class T
+ {{
+ {member}
+ }}
+}}");
+ var diagnostics = await GetDiagnostics(code.Source);
+ Assert.Empty(diagnostics);
+ }
+
+
+ [Theory]
+ [MemberData(nameof(PublicTypeWithAllowedDefinitions))]
+ public async Task PublicExposureOfPubternalTypeSometimesAllowed(string member)
+ {
+ var code = GetSourceFromNamespaceDeclaration($@"
+namespace A
+{{
+ {member}
+}}");
+ var diagnostics = await GetDiagnostics(code.Source);
+ Assert.Empty(diagnostics);
+ }
+
+ [Theory]
+ [MemberData(nameof(PrivateMemberDefinitions))]
+ [MemberData(nameof(PublicMemberDefinitions))]
+ public async Task DefinitionOfPubternalCrossAssemblyProducesPUB0002(string member)
+ {
+ var code = TestSource.Read($@"
+using A.Internal.Namespace;
+namespace A
+{{
+ internal class T
+ {{
+ {member}
+ }}
+}}");
+
+ var diagnostic = Assert.Single(await GetDiagnosticWithProjectReference(code.Source));
+ Assert.Equal("PUB0002", diagnostic.Id);
+ AnalyzerAssert.DiagnosticLocation(code.DefaultMarkerLocation, diagnostic.Location);
+ }
+
+ [Theory]
+ [MemberData(nameof(TypeUsages))]
+ public async Task UsageOfPubternalCrossAssemblyProducesPUB0002(string usage)
+ {
+ var code = TestSource.Read($@"
+using A.Internal.Namespace;
+namespace A
+{{
+ public class T
+ {{
+ private void M()
+ {{
+ {usage}
+ }}
+ }}
+}}");
+ var diagnostic = Assert.Single(await GetDiagnosticWithProjectReference(code.Source));
+ Assert.Equal("PUB0002", diagnostic.Id);
+ AnalyzerAssert.DiagnosticLocation(code.DefaultMarkerLocation, diagnostic.Location);
+ }
+
+ public static IEnumerable
diff --git a/src/Components/Components/src/Microsoft.AspNetCore.Components.multitarget.nuspec b/src/Components/Components/src/Microsoft.AspNetCore.Components.multitarget.nuspec
index 7017ce828e..e2190e2be5 100644
--- a/src/Components/Components/src/Microsoft.AspNetCore.Components.multitarget.nuspec
+++ b/src/Components/Components/src/Microsoft.AspNetCore.Components.multitarget.nuspec
@@ -21,6 +21,6 @@
-
+
diff --git a/src/Components/Components/src/Microsoft.AspNetCore.Components.netcoreapp.nuspec b/src/Components/Components/src/Microsoft.AspNetCore.Components.netcoreapp.nuspec
index 9ea33e53dc..69d234bc08 100644
--- a/src/Components/Components/src/Microsoft.AspNetCore.Components.netcoreapp.nuspec
+++ b/src/Components/Components/src/Microsoft.AspNetCore.Components.netcoreapp.nuspec
@@ -15,6 +15,6 @@
-
+
diff --git a/src/Components/Components/test/RendererTest.cs b/src/Components/Components/test/RendererTest.cs
index 77e0b06faa..5ecddb3ed6 100644
--- a/src/Components/Components/test/RendererTest.cs
+++ b/src/Components/Components/test/RendererTest.cs
@@ -2810,8 +2810,7 @@ namespace Microsoft.AspNetCore.Components.Test
Assert.Equal(10, component.OnAfterRenderCallCount);
}
- [ConditionalFact]
- [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/7487")]
+ [Fact]
public async Task CanTriggerEventHandlerDisposedInEarlierPendingBatchAsync()
{
// This represents the scenario where the same event handler is being triggered
diff --git a/src/Components/Directory.Build.props b/src/Components/Directory.Build.props
index b614476c4c..65e48e29f5 100644
--- a/src/Components/Directory.Build.props
+++ b/src/Components/Directory.Build.props
@@ -20,6 +20,8 @@
$(MSBuildThisFileDirectory)Blazor\Build\src\bin\$(Configuration)\$(DefaultNetCoreTargetFramework)\
+
+ $(MSBuildThisFileDirectory)THIRD-PARTY-NOTICES.txt
diff --git a/src/Components/Directory.Build.targets b/src/Components/Directory.Build.targets
index b6b1f773d9..d6569c4088 100644
--- a/src/Components/Directory.Build.targets
+++ b/src/Components/Directory.Build.targets
@@ -24,8 +24,6 @@
-
-
+
-
diff --git a/src/DataProtection/.vsconfig b/src/DataProtection/.vsconfig
new file mode 100644
index 0000000000..7a520fe61c
--- /dev/null
+++ b/src/DataProtection/.vsconfig
@@ -0,0 +1,12 @@
+{
+ "version": "1.0",
+ "components": [
+ "Microsoft.Net.Component.4.6.1.TargetingPack",
+ "Microsoft.Net.Component.4.7.2.SDK",
+ "Microsoft.Net.Component.4.7.2.TargetingPack",
+ "Microsoft.VisualStudio.Workload.ManagedDesktop",
+ "Microsoft.VisualStudio.Workload.NetCoreTools",
+ "Microsoft.VisualStudio.Workload.NetWeb",
+ "Microsoft.VisualStudio.Workload.VisualStudioExtension"
+ ]
+}
diff --git a/src/DataProtection/Extensions/test/DataProtectionProviderTests.cs b/src/DataProtection/Extensions/test/DataProtectionProviderTests.cs
index 5caee24b12..951b003063 100644
--- a/src/DataProtection/Extensions/test/DataProtectionProviderTests.cs
+++ b/src/DataProtection/Extensions/test/DataProtectionProviderTests.cs
@@ -115,7 +115,7 @@ namespace Microsoft.AspNetCore.DataProtection
[ConditionalFact]
[X509StoreIsAvailable(StoreName.My, StoreLocation.CurrentUser)]
- [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/6720")]
+ [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/6720", Queues = "OSX.1014.Amd64;OSX.1014.Amd64.Open")]
public void System_UsesProvidedDirectoryAndCertificate()
{
var filePath = Path.Combine(GetTestFilesPath(), "TestCert.pfx");
@@ -165,7 +165,6 @@ namespace Microsoft.AspNetCore.DataProtection
[ConditionalFact]
[X509StoreIsAvailable(StoreName.My, StoreLocation.CurrentUser)]
- [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/6720")]
public void System_UsesProvidedCertificateNotFromStore()
{
using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
diff --git a/src/DefaultBuilder/.vsconfig b/src/DefaultBuilder/.vsconfig
new file mode 100644
index 0000000000..7a520fe61c
--- /dev/null
+++ b/src/DefaultBuilder/.vsconfig
@@ -0,0 +1,12 @@
+{
+ "version": "1.0",
+ "components": [
+ "Microsoft.Net.Component.4.6.1.TargetingPack",
+ "Microsoft.Net.Component.4.7.2.SDK",
+ "Microsoft.Net.Component.4.7.2.TargetingPack",
+ "Microsoft.VisualStudio.Workload.ManagedDesktop",
+ "Microsoft.VisualStudio.Workload.NetCoreTools",
+ "Microsoft.VisualStudio.Workload.NetWeb",
+ "Microsoft.VisualStudio.Workload.VisualStudioExtension"
+ ]
+}
diff --git a/src/Features/JsonPatch/.vsconfig b/src/Features/JsonPatch/.vsconfig
new file mode 100644
index 0000000000..7a520fe61c
--- /dev/null
+++ b/src/Features/JsonPatch/.vsconfig
@@ -0,0 +1,12 @@
+{
+ "version": "1.0",
+ "components": [
+ "Microsoft.Net.Component.4.6.1.TargetingPack",
+ "Microsoft.Net.Component.4.7.2.SDK",
+ "Microsoft.Net.Component.4.7.2.TargetingPack",
+ "Microsoft.VisualStudio.Workload.ManagedDesktop",
+ "Microsoft.VisualStudio.Workload.NetCoreTools",
+ "Microsoft.VisualStudio.Workload.NetWeb",
+ "Microsoft.VisualStudio.Workload.VisualStudioExtension"
+ ]
+}
diff --git a/src/Framework/Directory.Build.props b/src/Framework/Directory.Build.props
index 2a8b3bf3c0..97b1b157ff 100644
--- a/src/Framework/Directory.Build.props
+++ b/src/Framework/Directory.Build.props
@@ -6,6 +6,7 @@
PlatformManifest.txt
$(ArtifactsObjDir)$(PlatformManifestFileName)
+ true
diff --git a/src/Framework/ref/Microsoft.AspNetCore.App.Ref.csproj b/src/Framework/ref/Microsoft.AspNetCore.App.Ref.csproj
index f695e4d3ee..4f60448f35 100644
--- a/src/Framework/ref/Microsoft.AspNetCore.App.Ref.csproj
+++ b/src/Framework/ref/Microsoft.AspNetCore.App.Ref.csproj
@@ -53,6 +53,10 @@ This package is an internal implementation of the .NET Core SDK and is not meant
$(AspNetCoreMajorVersion).$(AspNetCoreMinorVersion).0
$(ReferencePackSharedFxVersion)-$(VersionSuffix)
+
+
+ $(PkgMicrosoft_Extensions_Internal_Transport)\ref\$(TargetFramework)\
+
@@ -109,14 +113,14 @@ This package is an internal implementation of the .NET Core SDK and is not meant
BeforeTargets="_GetPackageFiles"
DependsOnTargets="ResolveReferences;FindReferenceAssembliesForReferences">
- <_AvailableExtensionsRefAssemblies Include="$(MicrosoftInternalExtensionsRefsPath)\*.dll" />
+ <_AvailableExtensionsRefAssemblies Include="$(RuntimeExtensionsReferenceDirectory)*.dll" />
-
+
@@ -131,7 +135,7 @@ This package is an internal implementation of the .NET Core SDK and is not meant
-
+
<_ReferencedExtensionsRefAssembliesToExclude Include="@(_ReferencedExtensionsRefAssemblies)" Exclude="@(_DuplicatedExtensionsRefAssemblies)" />
@@ -147,10 +151,10 @@ This package is an internal implementation of the .NET Core SDK and is not meant
@(ReferencePathWithRefAssemblies->WithMetadataValue('ReferenceGrouping', 'Microsoft.NETCore.App'));" />
+ Include="@(_SelectedExtensionsRefAssemblies->'$(RuntimeExtensionsReferenceDirectory)%(FileName)%(Extension)')" />
-
+
@@ -165,7 +169,7 @@ This package is an internal implementation of the .NET Core SDK and is not meant
Outputs="$(TargetDir)$(PackageConflictManifestFileName)">
- <_AspNetCoreAppPackageOverrides Include="@(AspNetCoreReferenceAssemblyPath->'%(NuGetPackageId)|%(NuGetPackageVersion)')" Condition="!Exists('$(MicrosoftInternalExtensionsRefsPath)%(AspNetCoreReferenceAssemblyPath.NuGetPackageId).dll') AND '%(AspNetCoreReferenceAssemblyPath.NuGetPackageId)' != 'Microsoft.NETCore.App' AND '%(AspNetCoreReferenceAssemblyPath.NuGetPackageId)' != 'Microsoft.Internal.Extensions.Refs' AND '%(AspNetCoreReferenceAssemblyPath.NuGetSourceType)' == 'Package' " />
+ <_AspNetCoreAppPackageOverrides Include="@(AspNetCoreReferenceAssemblyPath->'%(NuGetPackageId)|%(NuGetPackageVersion)')" Condition="!Exists('$(RuntimeExtensionsReferenceDirectory)%(AspNetCoreReferenceAssemblyPath.NuGetPackageId).dll') AND '%(AspNetCoreReferenceAssemblyPath.NuGetPackageId)' != 'Microsoft.NETCore.App' AND '%(AspNetCoreReferenceAssemblyPath.NuGetPackageId)' != 'Microsoft.Extensions.Internal.Transport' AND '%(AspNetCoreReferenceAssemblyPath.NuGetSourceType)' == 'Package' " />
<_AspNetCoreAppPackageOverrides Include="@(_SelectedExtensionsRefAssemblies->'%(FileName)|$(MicrosoftInternalExtensionsRefsPackageOverrideVersion)')" />
diff --git a/src/Framework/src/Microsoft.AspNetCore.App.Runtime.csproj b/src/Framework/src/Microsoft.AspNetCore.App.Runtime.csproj
index e7b3fd2014..7397e945bf 100644
--- a/src/Framework/src/Microsoft.AspNetCore.App.Runtime.csproj
+++ b/src/Framework/src/Microsoft.AspNetCore.App.Runtime.csproj
@@ -67,7 +67,7 @@ This package is an internal implementation of the .NET Core SDK and is not meant
$(IntermediateOutputPath)ignoreme.dev.runtimeconfig.json
- $(IntermediateOutputPath).version
+ $(IntermediateOutputPath)$(SharedFxName).versions.txt
none
@@ -156,12 +156,6 @@ This package is an internal implementation of the .NET Core SDK and is not meant
$(InstallersOutputPath)$(RedistArchiveOutputFileName)
-
-
- $(RedistArchiveOutputPath)$(ChecksumExtension)
-
-
-
@@ -501,4 +495,13 @@ This package is an internal implementation of the .NET Core SDK and is not meant
+
+
+
+
+
+
+
diff --git a/src/Framework/test/Microsoft.AspNetCore.App.UnitTests.csproj b/src/Framework/test/Microsoft.AspNetCore.App.UnitTests.csproj
index 9abf901de4..8d9b26e7d0 100644
--- a/src/Framework/test/Microsoft.AspNetCore.App.UnitTests.csproj
+++ b/src/Framework/test/Microsoft.AspNetCore.App.UnitTests.csproj
@@ -36,6 +36,10 @@
<_Parameter1>TargetingPackLayoutRoot
<_Parameter2>$(TargetingPackLayoutRoot)
+
+ <_Parameter1>IsTargetingPackBuilding
+ <_Parameter2>$(IsTargetingPackBuilding)
+
<_Parameter1>VerifyAncmBinary
<_Parameter2>$(VerifyAncmBinary)
diff --git a/src/Framework/test/SharedFxTests.cs b/src/Framework/test/SharedFxTests.cs
index 2fc09b96b0..558fc34439 100644
--- a/src/Framework/test/SharedFxTests.cs
+++ b/src/Framework/test/SharedFxTests.cs
@@ -131,7 +131,7 @@ namespace Microsoft.AspNetCore
[Fact]
public void ItContainsVersionFile()
{
- var versionFile = Path.Combine(_sharedFxRoot, ".version");
+ var versionFile = Path.Combine(_sharedFxRoot, "Microsoft.AspNetCore.App.versions.txt");
AssertEx.FileExists(versionFile);
var lines = File.ReadAllLines(versionFile);
Assert.Equal(2, lines.Length);
diff --git a/src/Framework/test/TargetingPackTests.cs b/src/Framework/test/TargetingPackTests.cs
index 82b43cb831..54da276513 100644
--- a/src/Framework/test/TargetingPackTests.cs
+++ b/src/Framework/test/TargetingPackTests.cs
@@ -20,17 +20,24 @@ namespace Microsoft.AspNetCore
private readonly string _expectedRid;
private readonly string _targetingPackRoot;
private readonly ITestOutputHelper _output;
+ private readonly bool _isTargetingPackBuilding;
public TargetingPackTests(ITestOutputHelper output)
{
_output = output;
_expectedRid = TestData.GetSharedFxRuntimeIdentifier();
_targetingPackRoot = Path.Combine(TestData.GetTestDataValue("TargetingPackLayoutRoot"), "packs", "Microsoft.AspNetCore.App.Ref", TestData.GetTestDataValue("TargetingPackVersion"));
+ _isTargetingPackBuilding = bool.Parse(TestData.GetTestDataValue("IsTargetingPackBuilding"));
}
[Fact]
public void AssembliesAreReferenceAssemblies()
{
+ if (!_isTargetingPackBuilding)
+ {
+ return;
+ }
+
IEnumerable dlls = Directory.GetFiles(_targetingPackRoot, "*.dll", SearchOption.AllDirectories);
Assert.NotEmpty(dlls);
@@ -58,6 +65,11 @@ namespace Microsoft.AspNetCore
[Fact]
public void PlatformManifestListsAllFiles()
{
+ if (!_isTargetingPackBuilding)
+ {
+ return;
+ }
+
var platformManifestPath = Path.Combine(_targetingPackRoot, "data", "PlatformManifest.txt");
var expectedAssemblies = TestData.GetSharedFxDependencies()
.Split(';', StringSplitOptions.RemoveEmptyEntries)
diff --git a/src/Grpc/.vsconfig b/src/Grpc/.vsconfig
new file mode 100644
index 0000000000..7a520fe61c
--- /dev/null
+++ b/src/Grpc/.vsconfig
@@ -0,0 +1,12 @@
+{
+ "version": "1.0",
+ "components": [
+ "Microsoft.Net.Component.4.6.1.TargetingPack",
+ "Microsoft.Net.Component.4.7.2.SDK",
+ "Microsoft.Net.Component.4.7.2.TargetingPack",
+ "Microsoft.VisualStudio.Workload.ManagedDesktop",
+ "Microsoft.VisualStudio.Workload.NetCoreTools",
+ "Microsoft.VisualStudio.Workload.NetWeb",
+ "Microsoft.VisualStudio.Workload.VisualStudioExtension"
+ ]
+}
diff --git a/src/HealthChecks/HealthChecks/ref/Microsoft.Extensions.Diagnostics.HealthChecks.csproj b/src/HealthChecks/HealthChecks/ref/Microsoft.Extensions.Diagnostics.HealthChecks.csproj
index d6b79361e7..4f83bbda83 100644
--- a/src/HealthChecks/HealthChecks/ref/Microsoft.Extensions.Diagnostics.HealthChecks.csproj
+++ b/src/HealthChecks/HealthChecks/ref/Microsoft.Extensions.Diagnostics.HealthChecks.csproj
@@ -9,6 +9,7 @@
+
@@ -16,6 +17,7 @@
+
diff --git a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs
index d1d6374cba..f2dcc4f5b7 100644
--- a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs
+++ b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs
@@ -125,7 +125,8 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
description: "A timeout occurred while running check.",
duration: duration,
exception: ex,
- data: null);
+ data: null,
+ tags: registration.Tags);
Log.HealthCheckError(_logger, registration, ex, duration);
}
@@ -139,7 +140,8 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
description: ex.Message,
duration: duration,
exception: ex,
- data: null);
+ data: null,
+ tags: registration.Tags);
Log.HealthCheckError(_logger, registration, ex, duration);
}
diff --git a/src/HealthChecks/HealthChecks/src/Microsoft.Extensions.Diagnostics.HealthChecks.csproj b/src/HealthChecks/HealthChecks/src/Microsoft.Extensions.Diagnostics.HealthChecks.csproj
index a737e01d45..44e1d8d95f 100644
--- a/src/HealthChecks/HealthChecks/src/Microsoft.Extensions.Diagnostics.HealthChecks.csproj
+++ b/src/HealthChecks/HealthChecks/src/Microsoft.Extensions.Diagnostics.HealthChecks.csproj
@@ -28,6 +28,7 @@ Microsoft.Extensions.Diagnostics.HealthChecks.IHealthChecksBuilder
+
diff --git a/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs b/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs
index 50cf7ebeae..1cb5b7420b 100644
--- a/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs
+++ b/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs
@@ -113,6 +113,47 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
});
}
+ [Fact]
+ public async Task CheckAsync_TagsArePresentInHealthReportEntryIfExceptionOccurs()
+ {
+ const string ExceptionMessage = "exception-message";
+ const string OperationCancelledMessage = "operation-cancelled-message";
+ var exceptionTags = new[] { "unhealthy-check-tag" };
+ var operationExceptionTags = new[] { "degraded-check-tag" };
+
+ // Arrange
+ var service = CreateHealthChecksService(b =>
+ {
+ b.AddAsyncCheck("ExceptionCheck", _ => throw new Exception(ExceptionMessage), exceptionTags);
+ b.AddAsyncCheck("OperationExceptionCheck", _ => throw new OperationCanceledException(OperationCancelledMessage), operationExceptionTags);
+ });
+
+ // Act
+ var results = await service.CheckHealthAsync();
+
+ // Assert
+ Assert.Collection(
+ results.Entries.OrderBy(kvp => kvp.Key),
+ actual =>
+ {
+ Assert.Equal("ExceptionCheck", actual.Key);
+ Assert.Equal(ExceptionMessage, actual.Value.Description);
+ Assert.Equal(HealthStatus.Unhealthy, actual.Value.Status);
+ Assert.Equal(ExceptionMessage, actual.Value.Exception.Message);
+ Assert.Empty(actual.Value.Data);
+ Assert.Equal(actual.Value.Tags, exceptionTags);
+ },
+ actual =>
+ {
+ Assert.Equal("OperationExceptionCheck", actual.Key);
+ Assert.Equal("A timeout occurred while running check.", actual.Value.Description);
+ Assert.Equal(HealthStatus.Unhealthy, actual.Value.Status);
+ Assert.Equal(OperationCancelledMessage, actual.Value.Exception.Message);
+ Assert.Empty(actual.Value.Data);
+ Assert.Equal(actual.Value.Tags, operationExceptionTags);
+ });
+ }
+
[Fact]
public async Task CheckAsync_RunsFilteredChecksAndAggregatesResultsAsync()
{
diff --git a/src/Hosting/.vsconfig b/src/Hosting/.vsconfig
new file mode 100644
index 0000000000..7a520fe61c
--- /dev/null
+++ b/src/Hosting/.vsconfig
@@ -0,0 +1,12 @@
+{
+ "version": "1.0",
+ "components": [
+ "Microsoft.Net.Component.4.6.1.TargetingPack",
+ "Microsoft.Net.Component.4.7.2.SDK",
+ "Microsoft.Net.Component.4.7.2.TargetingPack",
+ "Microsoft.VisualStudio.Workload.ManagedDesktop",
+ "Microsoft.VisualStudio.Workload.NetCoreTools",
+ "Microsoft.VisualStudio.Workload.NetWeb",
+ "Microsoft.VisualStudio.Workload.VisualStudioExtension"
+ ]
+}
diff --git a/src/Hosting/Server.IntegrationTesting/src/Microsoft.AspNetCore.Server.IntegrationTesting.csproj b/src/Hosting/Server.IntegrationTesting/src/Microsoft.AspNetCore.Server.IntegrationTesting.csproj
index ba625f4332..939dfcf376 100644
--- a/src/Hosting/Server.IntegrationTesting/src/Microsoft.AspNetCore.Server.IntegrationTesting.csproj
+++ b/src/Hosting/Server.IntegrationTesting/src/Microsoft.AspNetCore.Server.IntegrationTesting.csproj
@@ -23,7 +23,6 @@
-
diff --git a/src/Hosting/TestHost/src/HttpContextBuilder.cs b/src/Hosting/TestHost/src/HttpContextBuilder.cs
index f425a55b2d..736b0458a6 100644
--- a/src/Hosting/TestHost/src/HttpContextBuilder.cs
+++ b/src/Hosting/TestHost/src/HttpContextBuilder.cs
@@ -115,10 +115,25 @@ namespace Microsoft.AspNetCore.TestHost
// This could throw an error if there was a pending server read. Needs to
// happen before completing the response so the response returns the error.
var requestBodyInProgress = RequestBodyReadInProgress();
+ if (requestBodyInProgress)
+ {
+ // If request is still in progress then abort it.
+ CancelRequestBody();
+ }
// Matches Kestrel server: response is completed before request is drained
await CompleteResponseAsync();
- await CompleteRequestAsync(requestBodyInProgress);
+
+ if (!requestBodyInProgress)
+ {
+ // Writer was already completed in send request callback.
+ await _requestPipe.Reader.CompleteAsync();
+
+ // Don't wait for request to drain. It could block indefinitely. In a real server
+ // we would wait for a timeout and then kill the socket.
+ // Potential future improvement: add logging that the request timed out
+ }
+
_application.DisposeContext(_testContext, exception: null);
}
catch (Exception ex)
@@ -165,24 +180,6 @@ namespace Microsoft.AspNetCore.TestHost
CancelRequestBody();
}
- private async Task CompleteRequestAsync(bool requestBodyInProgress)
- {
- if (requestBodyInProgress)
- {
- // If request is still in progress then abort it.
- CancelRequestBody();
- }
- else
- {
- // Writer was already completed in send request callback.
- await _requestPipe.Reader.CompleteAsync();
- }
-
- // Don't wait for request to drain. It could block indefinitely. In a real server
- // we would wait for a timeout and then kill the socket.
- // Potential future improvement: add logging that the request timed out
- }
-
private bool RequestBodyReadInProgress()
{
try
diff --git a/src/Hosting/test/FunctionalTests/ShutdownTests.cs b/src/Hosting/test/FunctionalTests/ShutdownTests.cs
index 96ba91bcaa..58abe91b33 100644
--- a/src/Hosting/test/FunctionalTests/ShutdownTests.cs
+++ b/src/Hosting/test/FunctionalTests/ShutdownTests.cs
@@ -32,6 +32,7 @@ namespace Microsoft.AspNetCore.Hosting.FunctionalTests
await ExecuteShutdownTest(nameof(ShutdownTestRun), "Run");
}
+ [QuarantinedTest]
[ConditionalFact]
[OSSkipCondition(OperatingSystems.Windows)]
[OSSkipCondition(OperatingSystems.MacOSX)]
@@ -133,7 +134,7 @@ namespace Microsoft.AspNetCore.Hosting.FunctionalTests
private static void WaitForExitOrKill(Process process)
{
- process.WaitForExit(1000);
+ process.WaitForExit(5 * 1000);
if (!process.HasExited)
{
process.Kill();
diff --git a/src/Http/.vsconfig b/src/Http/.vsconfig
new file mode 100644
index 0000000000..7a520fe61c
--- /dev/null
+++ b/src/Http/.vsconfig
@@ -0,0 +1,12 @@
+{
+ "version": "1.0",
+ "components": [
+ "Microsoft.Net.Component.4.6.1.TargetingPack",
+ "Microsoft.Net.Component.4.7.2.SDK",
+ "Microsoft.Net.Component.4.7.2.TargetingPack",
+ "Microsoft.VisualStudio.Workload.ManagedDesktop",
+ "Microsoft.VisualStudio.Workload.NetCoreTools",
+ "Microsoft.VisualStudio.Workload.NetWeb",
+ "Microsoft.VisualStudio.Workload.VisualStudioExtension"
+ ]
+}
diff --git a/src/Http/Http.Abstractions/ref/Microsoft.AspNetCore.Http.Abstractions.netcoreapp.cs b/src/Http/Http.Abstractions/ref/Microsoft.AspNetCore.Http.Abstractions.netcoreapp.cs
index 850f4c3569..1ece9f3acd 100644
--- a/src/Http/Http.Abstractions/ref/Microsoft.AspNetCore.Http.Abstractions.netcoreapp.cs
+++ b/src/Http/Http.Abstractions/ref/Microsoft.AspNetCore.Http.Abstractions.netcoreapp.cs
@@ -97,6 +97,14 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
}
namespace Microsoft.AspNetCore.Http
{
+ public partial class BadHttpRequestException : System.IO.IOException
+ {
+ public BadHttpRequestException(string message) { }
+ public BadHttpRequestException(string message, System.Exception innerException) { }
+ public BadHttpRequestException(string message, int statusCode) { }
+ public BadHttpRequestException(string message, int statusCode, System.Exception innerException) { }
+ public int StatusCode { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } }
+ }
public abstract partial class ConnectionInfo
{
protected ConnectionInfo() { }
diff --git a/src/Http/Http.Abstractions/src/BadHttpRequestException.cs b/src/Http/Http.Abstractions/src/BadHttpRequestException.cs
new file mode 100644
index 0000000000..1e8a0bf096
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/BadHttpRequestException.cs
@@ -0,0 +1,60 @@
+using System;
+using System.IO;
+
+namespace Microsoft.AspNetCore.Http
+{
+ ///
+ /// Represents an HTTP request error
+ ///
+ public class BadHttpRequestException : IOException
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The message to associate with this exception.
+ /// The HTTP status code to associate with this exception.
+ public BadHttpRequestException(string message, int statusCode)
+ : base(message)
+ {
+ StatusCode = statusCode;
+ }
+
+ ///
+ /// Initializes a new instance of the class with the set to 400 Bad Request.
+ ///
+ /// The message to associate with this exception
+ public BadHttpRequestException(string message)
+ : base(message)
+ {
+ StatusCode = StatusCodes.Status400BadRequest;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The message to associate with this exception.
+ /// The HTTP status code to associate with this exception.
+ /// The inner exception to associate with this exception
+ public BadHttpRequestException(string message, int statusCode, Exception innerException)
+ : base(message, innerException)
+ {
+ StatusCode = statusCode;
+ }
+
+ ///
+ /// Initializes a new instance of the class with the set to 400 Bad Request.
+ ///
+ /// The message to associate with this exception
+ /// The inner exception to associate with this exception
+ public BadHttpRequestException(string message, Exception innerException)
+ : base(message, innerException)
+ {
+ StatusCode = StatusCodes.Status400BadRequest;
+ }
+
+ ///
+ /// Gets the HTTP status code for this exception.
+ ///
+ public int StatusCode { get; }
+ }
+}
diff --git a/src/Http/Http.Extensions/ref/Microsoft.AspNetCore.Http.Extensions.netcoreapp.cs b/src/Http/Http.Extensions/ref/Microsoft.AspNetCore.Http.Extensions.netcoreapp.cs
index 14f3400e95..7fa291f591 100644
--- a/src/Http/Http.Extensions/ref/Microsoft.AspNetCore.Http.Extensions.netcoreapp.cs
+++ b/src/Http/Http.Extensions/ref/Microsoft.AspNetCore.Http.Extensions.netcoreapp.cs
@@ -43,6 +43,7 @@ namespace Microsoft.AspNetCore.Http.Extensions
public partial class QueryBuilder : System.Collections.Generic.IEnumerable>, System.Collections.IEnumerable
{
public QueryBuilder() { }
+ public QueryBuilder(System.Collections.Generic.IEnumerable> parameters) { }
public QueryBuilder(System.Collections.Generic.IEnumerable> parameters) { }
public void Add(string key, System.Collections.Generic.IEnumerable values) { }
public void Add(string key, string value) { }
diff --git a/src/Http/Http.Extensions/src/QueryBuilder.cs b/src/Http/Http.Extensions/src/QueryBuilder.cs
index e9feb391b1..ab2d95b79d 100644
--- a/src/Http/Http.Extensions/src/QueryBuilder.cs
+++ b/src/Http/Http.Extensions/src/QueryBuilder.cs
@@ -3,8 +3,10 @@
using System.Collections;
using System.Collections.Generic;
+using System.Linq;
using System.Text;
using System.Text.Encodings.Web;
+using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNetCore.Http.Extensions
{
@@ -23,6 +25,12 @@ namespace Microsoft.AspNetCore.Http.Extensions
_params = new List>(parameters);
}
+ public QueryBuilder(IEnumerable> parameters)
+ : this(parameters.SelectMany(kvp => kvp.Value, (kvp, v) => KeyValuePair.Create(kvp.Key, v)))
+ {
+
+ }
+
public void Add(string key, IEnumerable values)
{
foreach (var value in values)
@@ -78,4 +86,4 @@ namespace Microsoft.AspNetCore.Http.Extensions
return _params.GetEnumerator();
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Http/Http.Extensions/test/QueryBuilderTests.cs b/src/Http/Http.Extensions/test/QueryBuilderTests.cs
index 7d15dd87bf..c2517c45d4 100644
--- a/src/Http/Http.Extensions/test/QueryBuilderTests.cs
+++ b/src/Http/Http.Extensions/test/QueryBuilderTests.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using Microsoft.Extensions.Primitives;
using Xunit;
namespace Microsoft.AspNetCore.Http.Extensions
@@ -70,6 +71,18 @@ namespace Microsoft.AspNetCore.Http.Extensions
Assert.Equal("?key1=value1&key2=value2&key3=value3", builder.ToString());
}
+ [Fact]
+ public void AddMultipleValuesViaConstructor_WithStringValues()
+ {
+ var builder = new QueryBuilder(new[]
+ {
+ new KeyValuePair("key1", new StringValues(new [] { "value1", string.Empty, "value3" })),
+ new KeyValuePair("key2", string.Empty),
+ new KeyValuePair("key3", StringValues.Empty)
+ });
+ Assert.Equal("?key1=value1&key1=&key1=value3&key2=", builder.ToString());
+ }
+
[Fact]
public void AddMultipleValuesViaInitializer_AddedInOrder()
{
@@ -95,4 +108,4 @@ namespace Microsoft.AspNetCore.Http.Extensions
Assert.Equal("?key1=value1&key2=value2&key3=value3", builder1.ToString());
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Http/WebUtilities/ref/Microsoft.AspNetCore.WebUtilities.netcoreapp.cs b/src/Http/WebUtilities/ref/Microsoft.AspNetCore.WebUtilities.netcoreapp.cs
index 886f5c0052..3aa6ce55dc 100644
--- a/src/Http/WebUtilities/ref/Microsoft.AspNetCore.WebUtilities.netcoreapp.cs
+++ b/src/Http/WebUtilities/ref/Microsoft.AspNetCore.WebUtilities.netcoreapp.cs
@@ -216,6 +216,8 @@ namespace Microsoft.AspNetCore.WebUtilities
public static partial class QueryHelpers
{
public static string AddQueryString(string uri, System.Collections.Generic.IDictionary queryString) { throw null; }
+ public static string AddQueryString(string uri, System.Collections.Generic.IEnumerable> queryString) { throw null; }
+ public static string AddQueryString(string uri, System.Collections.Generic.IEnumerable> queryString) { throw null; }
public static string AddQueryString(string uri, string name, string value) { throw null; }
public static System.Collections.Generic.Dictionary ParseNullableQuery(string queryString) { throw null; }
public static System.Collections.Generic.Dictionary ParseQuery(string queryString) { throw null; }
diff --git a/src/Http/WebUtilities/src/QueryHelpers.cs b/src/Http/WebUtilities/src/QueryHelpers.cs
index ca71329f03..9c28f4ab2b 100644
--- a/src/Http/WebUtilities/src/QueryHelpers.cs
+++ b/src/Http/WebUtilities/src/QueryHelpers.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Text;
using System.Text.Encodings.Web;
using Microsoft.Extensions.Primitives;
@@ -46,10 +47,10 @@ namespace Microsoft.AspNetCore.WebUtilities
}
///
- /// Append the given query keys and values to the uri.
+ /// Append the given query keys and values to the URI.
///
- /// The base uri.
- /// A collection of name value query pairs to append.
+ /// The base URI.
+ /// A dictionary of query keys and values to append.
/// The combined result.
/// is null.
/// is null.
@@ -68,7 +69,38 @@ namespace Microsoft.AspNetCore.WebUtilities
return AddQueryString(uri, (IEnumerable>)queryString);
}
- private static string AddQueryString(
+ ///
+ /// Append the given query keys and values to the URI.
+ ///
+ /// The base URI.
+ /// A collection of query names and values to append.
+ /// The combined result.
+ /// is null.
+ /// is null.
+ public static string AddQueryString(string uri, IEnumerable> queryString)
+ {
+ if (uri == null)
+ {
+ throw new ArgumentNullException(nameof(uri));
+ }
+
+ if (queryString == null)
+ {
+ throw new ArgumentNullException(nameof(queryString));
+ }
+
+ return AddQueryString(uri, queryString.SelectMany(kvp => kvp.Value, (kvp, v) => KeyValuePair.Create(kvp.Key, v)));
+ }
+
+ ///
+ /// Append the given query keys and values to the URI.
+ ///
+ /// The base URI.
+ /// A collection of name value query pairs to append.
+ /// The combined result.
+ /// is null.
+ /// is null.
+ public static string AddQueryString(
string uri,
IEnumerable> queryString)
{
diff --git a/src/Http/WebUtilities/test/QueryHelpersTests.cs b/src/Http/WebUtilities/test/QueryHelpersTests.cs
index a64bcbf03b..204813e5b6 100644
--- a/src/Http/WebUtilities/test/QueryHelpersTests.cs
+++ b/src/Http/WebUtilities/test/QueryHelpersTests.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using Microsoft.Extensions.Primitives;
using Xunit;
namespace Microsoft.AspNetCore.WebUtilities
@@ -119,5 +120,37 @@ namespace Microsoft.AspNetCore.WebUtilities
var result = QueryHelpers.AddQueryString(uri, queryStrings);
Assert.Equal(expectedUri, result);
}
+
+ [Theory]
+ [InlineData("http://contoso.com/", "http://contoso.com/?param1=value1¶m1=¶m1=value3¶m2=")]
+ [InlineData("http://contoso.com/someaction", "http://contoso.com/someaction?param1=value1¶m1=¶m1=value3¶m2=")]
+ [InlineData("http://contoso.com/someaction?param2=1", "http://contoso.com/someaction?param2=1¶m1=value1¶m1=¶m1=value3¶m2=")]
+ [InlineData("http://contoso.com/some#action", "http://contoso.com/some?param1=value1¶m1=¶m1=value3¶m2=#action")]
+ [InlineData("http://contoso.com/some?param2=1#action", "http://contoso.com/some?param2=1¶m1=value1¶m1=¶m1=value3¶m2=#action")]
+ [InlineData("http://contoso.com/#action", "http://contoso.com/?param1=value1¶m1=¶m1=value3¶m2=#action")]
+ [InlineData(
+ "http://contoso.com/someaction?q=test#anchor?value",
+ "http://contoso.com/someaction?q=test¶m1=value1¶m1=¶m1=value3¶m2=#anchor?value")]
+ [InlineData(
+ "http://contoso.com/someaction#anchor?stuff",
+ "http://contoso.com/someaction?param1=value1¶m1=¶m1=value3¶m2=#anchor?stuff")]
+ [InlineData(
+ "http://contoso.com/someaction?name?something",
+ "http://contoso.com/someaction?name?something¶m1=value1¶m1=¶m1=value3¶m2=")]
+ [InlineData(
+ "http://contoso.com/someaction#name#something",
+ "http://contoso.com/someaction?param1=value1¶m1=¶m1=value3¶m2=#name#something")]
+ public void AddQueryStringWithEnumerableOfKeysAndStringValues(string uri, string expectedUri)
+ {
+ var queryStrings = new Dictionary()
+ {
+ { "param1", new StringValues(new [] { "value1", string.Empty, "value3" }) },
+ { "param2", string.Empty },
+ { "param3", StringValues.Empty }
+ };
+
+ var result = QueryHelpers.AddQueryString(uri, queryStrings);
+ Assert.Equal(expectedUri, result);
+ }
}
}
diff --git a/src/Identity/.vsconfig b/src/Identity/.vsconfig
new file mode 100644
index 0000000000..7a520fe61c
--- /dev/null
+++ b/src/Identity/.vsconfig
@@ -0,0 +1,12 @@
+{
+ "version": "1.0",
+ "components": [
+ "Microsoft.Net.Component.4.6.1.TargetingPack",
+ "Microsoft.Net.Component.4.7.2.SDK",
+ "Microsoft.Net.Component.4.7.2.TargetingPack",
+ "Microsoft.VisualStudio.Workload.ManagedDesktop",
+ "Microsoft.VisualStudio.Workload.NetCoreTools",
+ "Microsoft.VisualStudio.Workload.NetWeb",
+ "Microsoft.VisualStudio.Workload.VisualStudioExtension"
+ ]
+}
diff --git a/src/Identity/ApiAuthorization.IdentityServer/test/Configuration/ConfigureSigningCredentialsTests.cs b/src/Identity/ApiAuthorization.IdentityServer/test/Configuration/ConfigureSigningCredentialsTests.cs
index 099fdf73e5..dda0233d77 100644
--- a/src/Identity/ApiAuthorization.IdentityServer/test/Configuration/ConfigureSigningCredentialsTests.cs
+++ b/src/Identity/ApiAuthorization.IdentityServer/test/Configuration/ConfigureSigningCredentialsTests.cs
@@ -23,7 +23,6 @@ namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
X509KeyStorageFlags.DefaultKeySet);
[ConditionalFact]
- [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/6720")]
[FrameworkSkipCondition(RuntimeFrameworks.CLR)]
public void Configure_AddsDevelopmentKeyFromConfiguration()
{
@@ -64,7 +63,7 @@ namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
}
[ConditionalFact]
- [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/6720")]
+ [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/6720", Queues = "OSX.1014.Amd64;OSX.1014.Amd64.Open")]
public void Configure_LoadsPfxCertificateCredentialFromConfiguration()
{
// Arrange
@@ -94,7 +93,7 @@ namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
}
[ConditionalFact]
- [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/6720")]
+ [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/6720", Queues = "OSX.1014.Amd64;OSX.1014.Amd64.Open")]
public void Configure_LoadsCertificateStoreCertificateCredentialFromConfiguration()
{
try
diff --git a/src/Identity/ApiAuthorization.IdentityServer/test/Configuration/SigningKeysLoaderTests.cs b/src/Identity/ApiAuthorization.IdentityServer/test/Configuration/SigningKeysLoaderTests.cs
index c5d543dfe9..893be873ab 100644
--- a/src/Identity/ApiAuthorization.IdentityServer/test/Configuration/SigningKeysLoaderTests.cs
+++ b/src/Identity/ApiAuthorization.IdentityServer/test/Configuration/SigningKeysLoaderTests.cs
@@ -59,7 +59,7 @@ namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer.Configuration
}
[ConditionalFact]
- [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/6720")]
+ [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/6720", Queues = "OSX.1014.Amd64;OSX.1014.Amd64.Open")]
public static void LoadFromStoreCert_SkipsCertificatesNotYetValid()
{
try
@@ -82,7 +82,7 @@ namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer.Configuration
}
[ConditionalFact]
- [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/6720")]
+ [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/6720", Queues = "OSX.1014.Amd64;OSX.1014.Amd64.Open")]
public static void LoadFromStoreCert_PrefersCertificatesCloserToExpirationDate()
{
try
@@ -105,7 +105,7 @@ namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer.Configuration
}
[ConditionalFact]
- [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/6720")]
+ [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/6720", Queues = "OSX.1014.Amd64;OSX.1014.Amd64.Open")]
public static void LoadFromStoreCert_SkipsExpiredCertificates()
{
try
diff --git a/src/Identity/Core/ref/Microsoft.AspNetCore.Identity.netcoreapp.cs b/src/Identity/Core/ref/Microsoft.AspNetCore.Identity.netcoreapp.cs
index 0ccafd325b..61b1891d26 100644
--- a/src/Identity/Core/ref/Microsoft.AspNetCore.Identity.netcoreapp.cs
+++ b/src/Identity/Core/ref/Microsoft.AspNetCore.Identity.netcoreapp.cs
@@ -188,6 +188,8 @@ namespace Microsoft.Extensions.DependencyInjection
public static Microsoft.AspNetCore.Identity.IdentityBuilder AddIdentity(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) where TUser : class where TRole : class { throw null; }
public static Microsoft.AspNetCore.Identity.IdentityBuilder AddIdentity(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action setupAction) where TUser : class where TRole : class { throw null; }
public static Microsoft.Extensions.DependencyInjection.IServiceCollection ConfigureApplicationCookie(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action configure) { throw null; }
+ public static Microsoft.Extensions.DependencyInjection.IServiceCollection ConfigureApplicationCookie(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action configure) where TService : class { throw null; }
public static Microsoft.Extensions.DependencyInjection.IServiceCollection ConfigureExternalCookie(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action configure) { throw null; }
+ public static Microsoft.Extensions.DependencyInjection.IServiceCollection ConfigureExternalCookie(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action configure) where TService : class { throw null; }
}
}
diff --git a/src/Identity/Core/src/IdentityServiceCollectionExtensions.cs b/src/Identity/Core/src/IdentityServiceCollectionExtensions.cs
index 45e3d567eb..ffbc9f563e 100644
--- a/src/Identity/Core/src/IdentityServiceCollectionExtensions.cs
+++ b/src/Identity/Core/src/IdentityServiceCollectionExtensions.cs
@@ -110,6 +110,21 @@ namespace Microsoft.Extensions.DependencyInjection
public static IServiceCollection ConfigureApplicationCookie(this IServiceCollection services, Action configure)
=> services.Configure(IdentityConstants.ApplicationScheme, configure);
+ ///
+ /// Configures the application cookie.
+ ///
+ /// TService: A service resolved from the IServiceProvider for use when configuring this authentication provider. If you need multiple services then specify IServiceProvider and resolve them directly.
+ /// The services available in the application.
+ /// An action to configure the .
+ /// The services.
+ public static IServiceCollection ConfigureApplicationCookie(this IServiceCollection services, Action configure) where TService : class
+ {
+ services.AddOptions(IdentityConstants.ApplicationScheme)
+ .Configure(configure);
+
+ return services;
+ }
+
///
/// Configure the external cookie.
///
@@ -118,5 +133,20 @@ namespace Microsoft.Extensions.DependencyInjection
/// The services.
public static IServiceCollection ConfigureExternalCookie(this IServiceCollection services, Action configure)
=> services.Configure(IdentityConstants.ExternalScheme, configure);
+
+ ///
+ /// Configure the external cookie.
+ ///
+ /// TService: A service resolved from the IServiceProvider for use when configuring this authentication provider. If you need multiple services then specify IServiceProvider and resolve them directly.
+ /// The services available in the application.
+ /// An action to configure the .
+ /// The services.
+ public static IServiceCollection ConfigureExternalCookie(this IServiceCollection services, Action configure) where TService : class
+ {
+ services.AddOptions(IdentityConstants.ExternalScheme)
+ .Configure(configure);
+
+ return services;
+ }
}
}
diff --git a/src/Identity/Extensions.Core/ref/Microsoft.Extensions.Identity.Core.csproj b/src/Identity/Extensions.Core/ref/Microsoft.Extensions.Identity.Core.csproj
index 1ddecfe522..846f73feee 100644
--- a/src/Identity/Extensions.Core/ref/Microsoft.Extensions.Identity.Core.csproj
+++ b/src/Identity/Extensions.Core/ref/Microsoft.Extensions.Identity.Core.csproj
@@ -11,6 +11,7 @@
+
diff --git a/src/Identity/Extensions.Core/src/Microsoft.Extensions.Identity.Core.csproj b/src/Identity/Extensions.Core/src/Microsoft.Extensions.Identity.Core.csproj
index 7555dda1fe..5f6681e261 100644
--- a/src/Identity/Extensions.Core/src/Microsoft.Extensions.Identity.Core.csproj
+++ b/src/Identity/Extensions.Core/src/Microsoft.Extensions.Identity.Core.csproj
@@ -13,7 +13,8 @@
-
+
+
diff --git a/src/Identity/UI/src/Microsoft.AspNetCore.Identity.UI.csproj b/src/Identity/UI/src/Microsoft.AspNetCore.Identity.UI.csproj
index 6da54c6869..23f9d4bad5 100644
--- a/src/Identity/UI/src/Microsoft.AspNetCore.Identity.UI.csproj
+++ b/src/Identity/UI/src/Microsoft.AspNetCore.Identity.UI.csproj
@@ -23,6 +23,7 @@
Bootstrap4
+ $(MSBuildThisFileDirectory)THIRD-PARTY-NOTICES.TXT
@@ -32,7 +33,6 @@
-
diff --git a/src/Identity/test/Identity.Test/IdentityUIScriptsTest.cs b/src/Identity/test/Identity.Test/IdentityUIScriptsTest.cs
index 005895f9c0..6bfa6efa5e 100644
--- a/src/Identity/test/Identity.Test/IdentityUIScriptsTest.cs
+++ b/src/Identity/test/Identity.Test/IdentityUIScriptsTest.cs
@@ -40,6 +40,7 @@ namespace Microsoft.AspNetCore.Identity.Test
[Theory]
[MemberData(nameof(ScriptWithIntegrityData))]
+ [QuarantinedTest]
public async Task IdentityUI_ScriptTags_SubresourceIntegrityCheck(ScriptTag scriptTag)
{
var integrity = await GetShaIntegrity(scriptTag);
diff --git a/src/Installers/Windows/.vsconfig b/src/Installers/Windows/.vsconfig
new file mode 100644
index 0000000000..8f411e8f86
--- /dev/null
+++ b/src/Installers/Windows/.vsconfig
@@ -0,0 +1,16 @@
+{
+ "version": "1.0",
+ "components": [
+ "Microsoft.Net.Component.4.6.1.TargetingPack",
+ "Microsoft.Net.Component.4.7.2.SDK",
+ "Microsoft.Net.Component.4.7.2.TargetingPack",
+ "Microsoft.VisualStudio.Component.VC.ATL",
+ "Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
+ "Microsoft.VisualStudio.Component.Windows10SDK.17134",
+ "Microsoft.VisualStudio.Workload.ManagedDesktop",
+ "Microsoft.VisualStudio.Workload.NativeDesktop",
+ "Microsoft.VisualStudio.Workload.NetCoreTools",
+ "Microsoft.VisualStudio.Workload.NetWeb",
+ "Microsoft.VisualStudio.Workload.VisualStudioExtension"
+ ]
+}
diff --git a/src/Installers/Windows/AspNetCoreModule-Setup/IIS-Setup/.vsconfig b/src/Installers/Windows/AspNetCoreModule-Setup/IIS-Setup/.vsconfig
new file mode 100644
index 0000000000..8f411e8f86
--- /dev/null
+++ b/src/Installers/Windows/AspNetCoreModule-Setup/IIS-Setup/.vsconfig
@@ -0,0 +1,16 @@
+{
+ "version": "1.0",
+ "components": [
+ "Microsoft.Net.Component.4.6.1.TargetingPack",
+ "Microsoft.Net.Component.4.7.2.SDK",
+ "Microsoft.Net.Component.4.7.2.TargetingPack",
+ "Microsoft.VisualStudio.Component.VC.ATL",
+ "Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
+ "Microsoft.VisualStudio.Component.Windows10SDK.17134",
+ "Microsoft.VisualStudio.Workload.ManagedDesktop",
+ "Microsoft.VisualStudio.Workload.NativeDesktop",
+ "Microsoft.VisualStudio.Workload.NetCoreTools",
+ "Microsoft.VisualStudio.Workload.NetWeb",
+ "Microsoft.VisualStudio.Workload.VisualStudioExtension"
+ ]
+}
diff --git a/src/Installers/Windows/AspNetCoreModule-Setup/IIS-Setup/IIS-Common/.vsconfig b/src/Installers/Windows/AspNetCoreModule-Setup/IIS-Setup/IIS-Common/.vsconfig
new file mode 100644
index 0000000000..8f411e8f86
--- /dev/null
+++ b/src/Installers/Windows/AspNetCoreModule-Setup/IIS-Setup/IIS-Common/.vsconfig
@@ -0,0 +1,16 @@
+{
+ "version": "1.0",
+ "components": [
+ "Microsoft.Net.Component.4.6.1.TargetingPack",
+ "Microsoft.Net.Component.4.7.2.SDK",
+ "Microsoft.Net.Component.4.7.2.TargetingPack",
+ "Microsoft.VisualStudio.Component.VC.ATL",
+ "Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
+ "Microsoft.VisualStudio.Component.Windows10SDK.17134",
+ "Microsoft.VisualStudio.Workload.ManagedDesktop",
+ "Microsoft.VisualStudio.Workload.NativeDesktop",
+ "Microsoft.VisualStudio.Workload.NetCoreTools",
+ "Microsoft.VisualStudio.Workload.NetWeb",
+ "Microsoft.VisualStudio.Workload.VisualStudioExtension"
+ ]
+}
diff --git a/src/Installers/Windows/AspNetCoreModule-Setup/IIS-Setup/IIS-Common/README.md b/src/Installers/Windows/AspNetCoreModule-Setup/IIS-Setup/IIS-Common/README.md
index 77fc8404c4..511b1088fc 100644
--- a/src/Installers/Windows/AspNetCoreModule-Setup/IIS-Setup/IIS-Common/README.md
+++ b/src/Installers/Windows/AspNetCoreModule-Setup/IIS-Setup/IIS-Common/README.md
@@ -2,17 +2,3 @@ Microsoft IIS Common
--------------------------------
The repository contains common resources shared by IIS Out-Of-Band (OOB) products.
-
-### Contributing
-
-This project welcomes contributions and suggestions. Most contributions require you to agree to a
-Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
-the rights to use your contribution. For details, visit https://cla.microsoft.com.
-
-When you submit a pull request, a CLA-bot will automatically determine whether you need to provide
-a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions
-provided by the bot. You will only need to do this once across all repos using our CLA.
-
-This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
-For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
-contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
diff --git a/src/Installers/Windows/AspNetCoreModule-Setup/IIS-Setup/README.md b/src/Installers/Windows/AspNetCoreModule-Setup/IIS-Setup/README.md
index 22e594aa6c..5a4135df83 100644
--- a/src/Installers/Windows/AspNetCoreModule-Setup/IIS-Setup/README.md
+++ b/src/Installers/Windows/AspNetCoreModule-Setup/IIS-Setup/README.md
@@ -2,17 +2,3 @@ Microsoft IIS Setup
--------------------------------
The repository contains setup resources shared by IIS Out-Of-Band (OOB) products.
-
-### Contributing
-
-This project welcomes contributions and suggestions. Most contributions require you to agree to a
-Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
-the rights to use your contribution. For details, visit https://cla.microsoft.com.
-
-When you submit a pull request, a CLA-bot will automatically determine whether you need to provide
-a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions
-provided by the bot. You will only need to do this once across all repos using our CLA.
-
-This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
-For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
-contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
diff --git a/src/Installers/Windows/Wix.targets b/src/Installers/Windows/Wix.targets
index 2e4b8dca03..de6636d51d 100644
--- a/src/Installers/Windows/Wix.targets
+++ b/src/Installers/Windows/Wix.targets
@@ -64,7 +64,7 @@
+ BeforeTargets="Build">
<_cabs Include="$(TargetDir)**/*.cab" />
@@ -72,10 +72,4 @@
-
-
- $(InstallersOutputPath)$(PackageFileName)$(ChecksumExtension)
-
-
-
diff --git a/src/Logging.AzureAppServices/Directory.Build.props b/src/Logging.AzureAppServices/Directory.Build.props
new file mode 100644
index 0000000000..68f87d4f24
--- /dev/null
+++ b/src/Logging.AzureAppServices/Directory.Build.props
@@ -0,0 +1,8 @@
+
+
+
+
+
+ true
+
+
diff --git a/src/Logging.AzureAppServices/src/AzureAppServicesLoggerFactoryExtensions.cs b/src/Logging.AzureAppServices/src/AzureAppServicesLoggerFactoryExtensions.cs
new file mode 100644
index 0000000000..9b680e9138
--- /dev/null
+++ b/src/Logging.AzureAppServices/src/AzureAppServicesLoggerFactoryExtensions.cs
@@ -0,0 +1,98 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.Logging.AzureAppServices;
+using Microsoft.Extensions.Logging.Configuration;
+using Microsoft.Extensions.Options;
+using static Microsoft.Extensions.DependencyInjection.ServiceDescriptor;
+
+namespace Microsoft.Extensions.Logging
+{
+ ///
+ /// Extension methods for adding Azure diagnostics logger.
+ ///
+ public static class AzureAppServicesLoggerFactoryExtensions
+ {
+ ///
+ /// Adds an Azure Web Apps diagnostics logger.
+ ///
+ /// The extension method argument
+ public static ILoggingBuilder AddAzureWebAppDiagnostics(this ILoggingBuilder builder)
+ {
+ var context = WebAppContext.Default;
+
+ // Only add the provider if we're in Azure WebApp. That cannot change once the apps started
+ return AddAzureWebAppDiagnostics(builder, context);
+ }
+
+ internal static ILoggingBuilder AddAzureWebAppDiagnostics(this ILoggingBuilder builder, IWebAppContext context)
+ {
+ if (!context.IsRunningInAzureWebApp)
+ {
+ return builder;
+ }
+
+ builder.AddConfiguration();
+
+ var config = SiteConfigurationProvider.GetAzureLoggingConfiguration(context);
+ var services = builder.Services;
+
+ var addedFileLogger = TryAddEnumerable(services, Singleton());
+ var addedBlobLogger = TryAddEnumerable(services, Singleton());
+
+ if (addedFileLogger || addedBlobLogger)
+ {
+ services.AddSingleton(context);
+ services.AddSingleton>(
+ new ConfigurationChangeTokenSource(config));
+ }
+
+ if (addedFileLogger)
+ {
+ services.AddSingleton>(CreateFileFilterConfigureOptions(config));
+ services.AddSingleton>(new FileLoggerConfigureOptions(config, context));
+ services.AddSingleton>(
+ new ConfigurationChangeTokenSource(config));
+ LoggerProviderOptions.RegisterProviderOptions(builder.Services);
+ }
+
+ if (addedBlobLogger)
+ {
+ services.AddSingleton>(CreateBlobFilterConfigureOptions(config));
+ services.AddSingleton>(new BlobLoggerConfigureOptions(config, context));
+ services.AddSingleton>(
+ new ConfigurationChangeTokenSource(config));
+ LoggerProviderOptions.RegisterProviderOptions(builder.Services);
+ }
+
+ return builder;
+ }
+
+ private static bool TryAddEnumerable(IServiceCollection collection, ServiceDescriptor descriptor)
+ {
+ var beforeCount = collection.Count;
+ collection.TryAddEnumerable(descriptor);
+ return beforeCount != collection.Count;
+ }
+
+ private static ConfigurationBasedLevelSwitcher CreateBlobFilterConfigureOptions(IConfiguration config)
+ {
+ return new ConfigurationBasedLevelSwitcher(
+ configuration: config,
+ provider: typeof(BlobLoggerProvider),
+ levelKey: "AzureBlobTraceLevel");
+ }
+
+ private static ConfigurationBasedLevelSwitcher CreateFileFilterConfigureOptions(IConfiguration config)
+ {
+ return new ConfigurationBasedLevelSwitcher(
+ configuration: config,
+ provider: typeof(FileLoggerProvider),
+ levelKey: "AzureDriveTraceLevel");
+ }
+ }
+}
diff --git a/src/Logging.AzureAppServices/src/AzureBlobLoggerOptions.cs b/src/Logging.AzureAppServices/src/AzureBlobLoggerOptions.cs
new file mode 100644
index 0000000000..1e1285b358
--- /dev/null
+++ b/src/Logging.AzureAppServices/src/AzureBlobLoggerOptions.cs
@@ -0,0 +1,39 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace Microsoft.Extensions.Logging.AzureAppServices
+{
+ ///
+ /// Options for Azure diagnostics blob logging.
+ ///
+ public class AzureBlobLoggerOptions: BatchingLoggerOptions
+ {
+ private string _blobName = "applicationLog.txt";
+
+ ///
+ /// Gets or sets the last section of log blob name.
+ /// Defaults to "applicationLog.txt".
+ ///
+ public string BlobName
+ {
+ get { return _blobName; }
+ set
+ {
+ if (string.IsNullOrEmpty(value))
+ {
+ throw new ArgumentException(nameof(value), $"{nameof(BlobName)} must be non-empty string.");
+ }
+ _blobName = value;
+ }
+ }
+
+ internal string ContainerUrl { get; set; }
+
+ internal string ApplicationName { get; set; }
+
+ internal string ApplicationInstanceId { get; set; }
+ }
+}
diff --git a/src/Logging.AzureAppServices/src/AzureFileLoggerOptions.cs b/src/Logging.AzureAppServices/src/AzureFileLoggerOptions.cs
new file mode 100644
index 0000000000..af8b5a112e
--- /dev/null
+++ b/src/Logging.AzureAppServices/src/AzureFileLoggerOptions.cs
@@ -0,0 +1,73 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace Microsoft.Extensions.Logging.AzureAppServices
+{
+ ///
+ /// Options for Azure diagnostics file logging.
+ ///
+ public class AzureFileLoggerOptions: BatchingLoggerOptions
+ {
+ private int? _fileSizeLimit = 10 * 1024 * 1024;
+ private int? _retainedFileCountLimit = 2;
+ private string _fileName = "diagnostics-";
+
+ ///
+ /// Gets or sets a strictly positive value representing the maximum log size in bytes or null for no limit.
+ /// Once the log is full, no more messages will be appended.
+ /// Defaults to 10MB.
+ ///
+ public int? FileSizeLimit
+ {
+ get { return _fileSizeLimit; }
+ set
+ {
+ if (value <= 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(FileSizeLimit)} must be positive.");
+ }
+ _fileSizeLimit = value;
+ }
+ }
+
+ ///
+ /// Gets or sets a strictly positive value representing the maximum retained file count or null for no limit.
+ /// Defaults to 2.
+ ///
+ public int? RetainedFileCountLimit
+ {
+ get { return _retainedFileCountLimit; }
+ set
+ {
+ if (value <= 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(RetainedFileCountLimit)} must be positive.");
+ }
+ _retainedFileCountLimit = value;
+ }
+ }
+
+ ///
+ /// Gets or sets a string representing the prefix of the file name used to store the logging information.
+ /// The current date, in the format YYYYMMDD will be added after the given value.
+ /// Defaults to diagnostics-.
+ ///
+ public string FileName
+ {
+ get { return _fileName; }
+ set
+ {
+ if (string.IsNullOrEmpty(value))
+ {
+ throw new ArgumentException(nameof(value));
+ }
+ _fileName = value;
+ }
+ }
+
+ internal string LogDirectory { get; set; }
+ }
+}
diff --git a/src/Logging.AzureAppServices/src/BatchLoggerConfigureOptions.cs b/src/Logging.AzureAppServices/src/BatchLoggerConfigureOptions.cs
new file mode 100644
index 0000000000..8dc8727b3a
--- /dev/null
+++ b/src/Logging.AzureAppServices/src/BatchLoggerConfigureOptions.cs
@@ -0,0 +1,37 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.Extensions.Logging.AzureAppServices
+{
+ internal class BatchLoggerConfigureOptions : IConfigureOptions
+ {
+ private readonly IConfiguration _configuration;
+ private readonly string _isEnabledKey;
+
+ public BatchLoggerConfigureOptions(IConfiguration configuration, string isEnabledKey)
+ {
+ _configuration = configuration;
+ _isEnabledKey = isEnabledKey;
+ }
+
+ public void Configure(BatchingLoggerOptions options)
+ {
+ options.IsEnabled = TextToBoolean(_configuration.GetSection(_isEnabledKey)?.Value);
+ }
+
+ private static bool TextToBoolean(string text)
+ {
+ if (string.IsNullOrEmpty(text) ||
+ !bool.TryParse(text, out var result))
+ {
+ result = false;
+ }
+
+ return result;
+ }
+ }
+}
diff --git a/src/Logging.AzureAppServices/src/BatchingLogger.cs b/src/Logging.AzureAppServices/src/BatchingLogger.cs
new file mode 100644
index 0000000000..bd192169f3
--- /dev/null
+++ b/src/Logging.AzureAppServices/src/BatchingLogger.cs
@@ -0,0 +1,75 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Text;
+
+namespace Microsoft.Extensions.Logging.AzureAppServices
+{
+ internal class BatchingLogger : ILogger
+ {
+ private readonly BatchingLoggerProvider _provider;
+ private readonly string _category;
+
+ public BatchingLogger(BatchingLoggerProvider loggerProvider, string categoryName)
+ {
+ _provider = loggerProvider;
+ _category = categoryName;
+ }
+
+ public IDisposable BeginScope(TState state)
+ {
+ return null;
+ }
+
+ public bool IsEnabled(LogLevel logLevel)
+ {
+ return _provider.IsEnabled;
+ }
+
+ public void Log(DateTimeOffset timestamp, LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter)
+ {
+ if (!IsEnabled(logLevel))
+ {
+ return;
+ }
+
+ var builder = new StringBuilder();
+ builder.Append(timestamp.ToString("yyyy-MM-dd HH:mm:ss.fff zzz"));
+ builder.Append(" [");
+ builder.Append(logLevel.ToString());
+ builder.Append("] ");
+ builder.Append(_category);
+
+ var scopeProvider = _provider.ScopeProvider;
+ if (scopeProvider != null)
+ {
+ scopeProvider.ForEachScope((scope, stringBuilder) =>
+ {
+ stringBuilder.Append(" => ").Append(scope);
+ }, builder);
+
+ builder.AppendLine(":");
+ }
+ else
+ {
+ builder.Append(": ");
+ }
+
+ builder.AppendLine(formatter(state, exception));
+
+ if (exception != null)
+ {
+ builder.AppendLine(exception.ToString());
+ }
+
+ _provider.AddMessage(timestamp, builder.ToString());
+ }
+
+ public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter)
+ {
+ Log(DateTimeOffset.Now, logLevel, eventId, state, exception, formatter);
+ }
+ }
+}
diff --git a/src/Logging.AzureAppServices/src/BatchingLoggerOptions.cs b/src/Logging.AzureAppServices/src/BatchingLoggerOptions.cs
new file mode 100644
index 0000000000..9fbd964800
--- /dev/null
+++ b/src/Logging.AzureAppServices/src/BatchingLoggerOptions.cs
@@ -0,0 +1,80 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace Microsoft.Extensions.Logging.AzureAppServices
+{
+ ///
+ /// Options for a logger which batches up log messages.
+ ///
+ public class BatchingLoggerOptions
+ {
+ private int? _batchSize;
+ private int? _backgroundQueueSize = 1000;
+ private TimeSpan _flushPeriod = TimeSpan.FromSeconds(1);
+
+ ///
+ /// Gets or sets the period after which logs will be flushed to the store.
+ ///
+ public TimeSpan FlushPeriod
+ {
+ get { return _flushPeriod; }
+ set
+ {
+ if (value <= TimeSpan.Zero)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(FlushPeriod)} must be positive.");
+ }
+ _flushPeriod = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the maximum size of the background log message queue or null for no limit.
+ /// After maximum queue size is reached log event sink would start blocking.
+ /// Defaults to 1000.
+ ///
+ public int? BackgroundQueueSize
+ {
+ get { return _backgroundQueueSize; }
+ set
+ {
+ if (value < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(BackgroundQueueSize)} must be non-negative.");
+ }
+ _backgroundQueueSize = value;
+ }
+ }
+
+ ///
+ /// Gets or sets a maximum number of events to include in a single batch or null for no limit.
+ ///
+ /// Defaults to null.
+ public int? BatchSize
+ {
+ get { return _batchSize; }
+ set
+ {
+ if (value <= 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(BatchSize)} must be positive.");
+ }
+ _batchSize = value;
+ }
+ }
+
+ ///
+ /// Gets or sets value indicating if logger accepts and queues writes.
+ ///
+ public bool IsEnabled { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether scopes should be included in the message.
+ /// Defaults to false.
+ ///
+ public bool IncludeScopes { get; set; } = false;
+ }
+}
diff --git a/src/Logging.AzureAppServices/src/BatchingLoggerProvider.cs b/src/Logging.AzureAppServices/src/BatchingLoggerProvider.cs
new file mode 100644
index 0000000000..227a616f3b
--- /dev/null
+++ b/src/Logging.AzureAppServices/src/BatchingLoggerProvider.cs
@@ -0,0 +1,208 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.Extensions.Logging.AzureAppServices
+{
+ ///
+ /// A provider of instances.
+ ///
+ public abstract class BatchingLoggerProvider : ILoggerProvider, ISupportExternalScope
+ {
+ private readonly List _currentBatch = new List();
+ private readonly TimeSpan _interval;
+ private readonly int? _queueSize;
+ private readonly int? _batchSize;
+ private readonly IDisposable _optionsChangeToken;
+
+ private int _messagesDropped;
+
+ private BlockingCollection _messageQueue;
+ private Task _outputTask;
+ private CancellationTokenSource _cancellationTokenSource;
+
+ private bool _includeScopes;
+ private IExternalScopeProvider _scopeProvider;
+
+ internal IExternalScopeProvider ScopeProvider => _includeScopes ? _scopeProvider : null;
+
+ internal BatchingLoggerProvider(IOptionsMonitor options)
+ {
+ // NOTE: Only IsEnabled is monitored
+
+ var loggerOptions = options.CurrentValue;
+ if (loggerOptions.BatchSize <= 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(loggerOptions.BatchSize), $"{nameof(loggerOptions.BatchSize)} must be a positive number.");
+ }
+ if (loggerOptions.FlushPeriod <= TimeSpan.Zero)
+ {
+ throw new ArgumentOutOfRangeException(nameof(loggerOptions.FlushPeriod), $"{nameof(loggerOptions.FlushPeriod)} must be longer than zero.");
+ }
+
+ _interval = loggerOptions.FlushPeriod;
+ _batchSize = loggerOptions.BatchSize;
+ _queueSize = loggerOptions.BackgroundQueueSize;
+
+ _optionsChangeToken = options.OnChange(UpdateOptions);
+ UpdateOptions(options.CurrentValue);
+ }
+
+ ///
+ /// Checks if the queue is enabled.
+ ///
+ public bool IsEnabled { get; private set; }
+
+ private void UpdateOptions(BatchingLoggerOptions options)
+ {
+ var oldIsEnabled = IsEnabled;
+ IsEnabled = options.IsEnabled;
+ _includeScopes = options.IncludeScopes;
+
+ if (oldIsEnabled != IsEnabled)
+ {
+ if (IsEnabled)
+ {
+ Start();
+ }
+ else
+ {
+ Stop();
+ }
+ }
+
+ }
+
+ internal abstract Task WriteMessagesAsync(IEnumerable messages, CancellationToken token);
+
+ private async Task ProcessLogQueue()
+ {
+ while (!_cancellationTokenSource.IsCancellationRequested)
+ {
+ var limit = _batchSize ?? int.MaxValue;
+
+ while (limit > 0 && _messageQueue.TryTake(out var message))
+ {
+ _currentBatch.Add(message);
+ limit--;
+ }
+
+ var messagesDropped = Interlocked.Exchange(ref _messagesDropped, 0);
+ if (messagesDropped != 0)
+ {
+ _currentBatch.Add(new LogMessage(DateTimeOffset.Now, $"{messagesDropped} message(s) dropped because of queue size limit. Increase the queue size or decrease logging verbosity to avoid this.{Environment.NewLine}"));
+ }
+
+ if (_currentBatch.Count > 0)
+ {
+ try
+ {
+ await WriteMessagesAsync(_currentBatch, _cancellationTokenSource.Token);
+ }
+ catch
+ {
+ // ignored
+ }
+
+ _currentBatch.Clear();
+ }
+ else
+ {
+ await IntervalAsync(_interval, _cancellationTokenSource.Token);
+ }
+ }
+ }
+
+ ///
+ /// Wait for the given .
+ ///
+ /// The amount of time to wait.
+ /// A that can be used to cancel the delay.
+ /// A which completes when the has passed or the has been canceled.
+ protected virtual Task IntervalAsync(TimeSpan interval, CancellationToken cancellationToken)
+ {
+ return Task.Delay(interval, cancellationToken);
+ }
+
+ internal void AddMessage(DateTimeOffset timestamp, string message)
+ {
+ if (!_messageQueue.IsAddingCompleted)
+ {
+ try
+ {
+ if (!_messageQueue.TryAdd(new LogMessage(timestamp, message), millisecondsTimeout: 0, cancellationToken: _cancellationTokenSource.Token))
+ {
+ Interlocked.Increment(ref _messagesDropped);
+ }
+ }
+ catch
+ {
+ //cancellation token canceled or CompleteAdding called
+ }
+ }
+ }
+
+ private void Start()
+ {
+ _messageQueue = _queueSize == null ?
+ new BlockingCollection(new ConcurrentQueue()) :
+ new BlockingCollection(new ConcurrentQueue(), _queueSize.Value);
+
+ _cancellationTokenSource = new CancellationTokenSource();
+ _outputTask = Task.Run(ProcessLogQueue);
+ }
+
+ private void Stop()
+ {
+ _cancellationTokenSource.Cancel();
+ _messageQueue.CompleteAdding();
+
+ try
+ {
+ _outputTask.Wait(_interval);
+ }
+ catch (TaskCanceledException)
+ {
+ }
+ catch (AggregateException ex) when (ex.InnerExceptions.Count == 1 && ex.InnerExceptions[0] is TaskCanceledException)
+ {
+ }
+ }
+
+ ///
+ public void Dispose()
+ {
+ _optionsChangeToken?.Dispose();
+ if (IsEnabled)
+ {
+ Stop();
+ }
+ }
+
+ ///
+ /// Creates a with the given .
+ ///
+ /// The name of the category to create this logger with.
+ /// The that was created.
+ public ILogger CreateLogger(string categoryName)
+ {
+ return new BatchingLogger(this, categoryName);
+ }
+
+ ///
+ /// Sets the scope on this provider.
+ ///
+ /// Provides the scope.
+ void ISupportExternalScope.SetScopeProvider(IExternalScopeProvider scopeProvider)
+ {
+ _scopeProvider = scopeProvider;
+ }
+ }
+}
diff --git a/src/Logging.AzureAppServices/src/BlobAppendReferenceWrapper.cs b/src/Logging.AzureAppServices/src/BlobAppendReferenceWrapper.cs
new file mode 100644
index 0000000000..e9805128b7
--- /dev/null
+++ b/src/Logging.AzureAppServices/src/BlobAppendReferenceWrapper.cs
@@ -0,0 +1,97 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Net;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.Extensions.Logging.AzureAppServices
+{
+ ///
+ internal class BlobAppendReferenceWrapper : ICloudAppendBlob
+ {
+ private readonly Uri _fullUri;
+ private readonly HttpClient _client;
+ private readonly Uri _appendUri;
+
+ public BlobAppendReferenceWrapper(string containerUrl, string name, HttpClient client)
+ {
+ var uriBuilder = new UriBuilder(containerUrl);
+ uriBuilder.Path += "/" + name;
+ _fullUri = uriBuilder.Uri;
+
+ AppendBlockQuery(uriBuilder);
+ _appendUri = uriBuilder.Uri;
+ _client = client;
+ }
+
+ ///
+ public async Task AppendAsync(ArraySegment data, CancellationToken cancellationToken)
+ {
+ Task AppendDataAsync()
+ {
+ var message = new HttpRequestMessage(HttpMethod.Put, _appendUri)
+ {
+ Content = new ByteArrayContent(data.Array, data.Offset, data.Count)
+ };
+ AddCommonHeaders(message);
+
+ return _client.SendAsync(message, cancellationToken);
+ }
+
+ var response = await AppendDataAsync();
+
+ if (response.StatusCode == HttpStatusCode.NotFound)
+ {
+ // If no blob exists try creating it
+ var message = new HttpRequestMessage(HttpMethod.Put, _fullUri)
+ {
+ // Set Content-Length to 0 to create "Append Blob"
+ Content = new ByteArrayContent(Array.Empty()),
+ Headers =
+ {
+ { "If-None-Match", "*" }
+ }
+ };
+
+ AddCommonHeaders(message);
+
+ response = await _client.SendAsync(message, cancellationToken);
+
+ // If result is 2** or 412 try to append again
+ if (response.IsSuccessStatusCode ||
+ response.StatusCode == HttpStatusCode.PreconditionFailed)
+ {
+ // Retry sending data after blob creation
+ response = await AppendDataAsync();
+ }
+ }
+
+ response.EnsureSuccessStatusCode();
+ }
+
+ private static void AddCommonHeaders(HttpRequestMessage message)
+ {
+ message.Headers.Add("x-ms-blob-type", "AppendBlob");
+ message.Headers.Add("x-ms-version", "2016-05-31");
+ message.Headers.Date = DateTimeOffset.UtcNow;
+ }
+
+ private static void AppendBlockQuery(UriBuilder uriBuilder)
+ {
+ // See https://msdn.microsoft.com/en-us/library/system.uribuilder.query.aspx for:
+ // Note: Do not append a string directly to Query property.
+ // If the length of Query is greater than 1, retrieve the property value
+ // as a string, remove the leading question mark, append the new query string,
+ // and set the property with the combined string.
+ var queryToAppend = "comp=appendblock";
+ if (uriBuilder.Query != null && uriBuilder.Query.Length > 1)
+ uriBuilder.Query = uriBuilder.Query.Substring(1) + "&" + queryToAppend;
+ else
+ uriBuilder.Query = queryToAppend;
+ }
+ }
+}
diff --git a/src/Logging.AzureAppServices/src/BlobLoggerConfigureOptions.cs b/src/Logging.AzureAppServices/src/BlobLoggerConfigureOptions.cs
new file mode 100644
index 0000000000..f9a186872b
--- /dev/null
+++ b/src/Logging.AzureAppServices/src/BlobLoggerConfigureOptions.cs
@@ -0,0 +1,30 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.Extensions.Logging.AzureAppServices
+{
+ internal class BlobLoggerConfigureOptions : BatchLoggerConfigureOptions, IConfigureOptions
+ {
+ private readonly IConfiguration _configuration;
+ private readonly IWebAppContext _context;
+
+ public BlobLoggerConfigureOptions(IConfiguration configuration, IWebAppContext context)
+ : base(configuration, "AzureBlobEnabled")
+ {
+ _configuration = configuration;
+ _context = context;
+ }
+
+ public void Configure(AzureBlobLoggerOptions options)
+ {
+ base.Configure(options);
+ options.ContainerUrl = _configuration.GetSection("APPSETTING_DIAGNOSTICS_AZUREBLOBCONTAINERSASURL")?.Value;
+ options.ApplicationName = _context.SiteName;
+ options.ApplicationInstanceId = _context.SiteInstanceId;
+ }
+ }
+}
diff --git a/src/Logging.AzureAppServices/src/BlobLoggerProvider.cs b/src/Logging.AzureAppServices/src/BlobLoggerProvider.cs
new file mode 100644
index 0000000000..3d62ea2ac6
--- /dev/null
+++ b/src/Logging.AzureAppServices/src/BlobLoggerProvider.cs
@@ -0,0 +1,92 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.Extensions.Logging.AzureAppServices
+{
+ ///
+ /// The implementation that stores messages by appending them to Azure Blob in batches.
+ ///
+ [ProviderAlias("AzureAppServicesBlob")]
+ public class BlobLoggerProvider : BatchingLoggerProvider
+ {
+ private readonly string _appName;
+ private readonly string _fileName;
+ private readonly Func _blobReferenceFactory;
+ private readonly HttpClient _httpClient;
+
+ ///
+ /// Creates a new instance of
+ ///
+ /// The options to use when creating a provider.
+ public BlobLoggerProvider(IOptionsMonitor options)
+ : this(options, null)
+ {
+ _blobReferenceFactory = name => new BlobAppendReferenceWrapper(
+ options.CurrentValue.ContainerUrl,
+ name,
+ _httpClient);
+ }
+
+ ///
+ /// Creates a new instance of
+ ///
+ /// The container to store logs to.
+ /// Options to be used in creating a logger.
+ internal BlobLoggerProvider(
+ IOptionsMonitor options,
+ Func blobReferenceFactory) :
+ base(options)
+ {
+ var value = options.CurrentValue;
+ _appName = value.ApplicationName;
+ _fileName = value.ApplicationInstanceId + "_" + value.BlobName;
+ _blobReferenceFactory = blobReferenceFactory;
+ _httpClient = new HttpClient();
+ }
+
+ internal override async Task WriteMessagesAsync(IEnumerable messages, CancellationToken cancellationToken)
+ {
+ var eventGroups = messages.GroupBy(GetBlobKey);
+ foreach (var eventGroup in eventGroups)
+ {
+ var key = eventGroup.Key;
+ var blobName = $"{_appName}/{key.Year}/{key.Month:00}/{key.Day:00}/{key.Hour:00}/{_fileName}";
+
+ var blob = _blobReferenceFactory(blobName);
+
+ using (var stream = new MemoryStream())
+ using (var writer = new StreamWriter(stream))
+ {
+ foreach (var logEvent in eventGroup)
+ {
+ writer.Write(logEvent.Message);
+ }
+
+ await writer.FlushAsync();
+ var tryGetBuffer = stream.TryGetBuffer(out var buffer);
+ Debug.Assert(tryGetBuffer);
+ await blob.AppendAsync(buffer, cancellationToken);
+ }
+ }
+ }
+
+ private (int Year, int Month, int Day, int Hour) GetBlobKey(LogMessage e)
+ {
+ return (e.Timestamp.Year,
+ e.Timestamp.Month,
+ e.Timestamp.Day,
+ e.Timestamp.Hour);
+ }
+ }
+}
diff --git a/src/Logging.AzureAppServices/src/ConfigurationBasedLevelSwitcher.cs b/src/Logging.AzureAppServices/src/ConfigurationBasedLevelSwitcher.cs
new file mode 100644
index 0000000000..c62ccb2331
--- /dev/null
+++ b/src/Logging.AzureAppServices/src/ConfigurationBasedLevelSwitcher.cs
@@ -0,0 +1,51 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.Extensions.Logging.AzureAppServices
+{
+ internal class ConfigurationBasedLevelSwitcher: IConfigureOptions
+ {
+ private readonly IConfiguration _configuration;
+ private readonly Type _provider;
+ private readonly string _levelKey;
+
+ public ConfigurationBasedLevelSwitcher(IConfiguration configuration, Type provider, string levelKey)
+ {
+ _configuration = configuration;
+ _provider = provider;
+ _levelKey = levelKey;
+ }
+
+ public void Configure(LoggerFilterOptions options)
+ {
+ options.Rules.Add(new LoggerFilterRule(_provider.FullName, null, GetLogLevel(), null));
+ }
+
+ private LogLevel GetLogLevel()
+ {
+ return TextToLogLevel(_configuration.GetSection(_levelKey)?.Value);
+ }
+
+ private static LogLevel TextToLogLevel(string text)
+ {
+ switch (text?.ToUpperInvariant())
+ {
+ case "ERROR":
+ return LogLevel.Error;
+ case "WARNING":
+ return LogLevel.Warning;
+ case "INFORMATION":
+ return LogLevel.Information;
+ case "VERBOSE":
+ return LogLevel.Trace;
+ default:
+ return LogLevel.None;
+ }
+ }
+ }
+}
diff --git a/src/Logging.AzureAppServices/src/FileLoggerConfigureOptions.cs b/src/Logging.AzureAppServices/src/FileLoggerConfigureOptions.cs
new file mode 100644
index 0000000000..8cd1f5eb91
--- /dev/null
+++ b/src/Logging.AzureAppServices/src/FileLoggerConfigureOptions.cs
@@ -0,0 +1,27 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.IO;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.Extensions.Logging.AzureAppServices
+{
+ internal class FileLoggerConfigureOptions : BatchLoggerConfigureOptions, IConfigureOptions
+ {
+ private readonly IWebAppContext _context;
+
+ public FileLoggerConfigureOptions(IConfiguration configuration, IWebAppContext context)
+ : base(configuration, "AzureDriveEnabled")
+ {
+ _context = context;
+ }
+
+ public void Configure(AzureFileLoggerOptions options)
+ {
+ base.Configure(options);
+ options.LogDirectory = Path.Combine(_context.HomeFolder, "LogFiles", "Application");
+ }
+ }
+}
diff --git a/src/Logging.AzureAppServices/src/FileLoggerProvider.cs b/src/Logging.AzureAppServices/src/FileLoggerProvider.cs
new file mode 100644
index 0000000000..1143d38c07
--- /dev/null
+++ b/src/Logging.AzureAppServices/src/FileLoggerProvider.cs
@@ -0,0 +1,89 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.Extensions.Logging.AzureAppServices
+{
+ ///
+ /// A which writes out to a file.
+ ///
+ [ProviderAlias("AzureAppServicesFile")]
+ public class FileLoggerProvider : BatchingLoggerProvider
+ {
+ private readonly string _path;
+ private readonly string _fileName;
+ private readonly int? _maxFileSize;
+ private readonly int? _maxRetainedFiles;
+
+ ///
+ /// Creates a new instance of .
+ ///
+ /// The options to use when creating a provider.
+ public FileLoggerProvider(IOptionsMonitor options) : base(options)
+ {
+ var loggerOptions = options.CurrentValue;
+ _path = loggerOptions.LogDirectory;
+ _fileName = loggerOptions.FileName;
+ _maxFileSize = loggerOptions.FileSizeLimit;
+ _maxRetainedFiles = loggerOptions.RetainedFileCountLimit;
+ }
+
+ internal override async Task WriteMessagesAsync(IEnumerable messages, CancellationToken cancellationToken)
+ {
+ Directory.CreateDirectory(_path);
+
+ foreach (var group in messages.GroupBy(GetGrouping))
+ {
+ var fullName = GetFullName(group.Key);
+ var fileInfo = new FileInfo(fullName);
+ if (_maxFileSize > 0 && fileInfo.Exists && fileInfo.Length > _maxFileSize)
+ {
+ return;
+ }
+
+ using (var streamWriter = File.AppendText(fullName))
+ {
+ foreach (var item in group)
+ {
+ await streamWriter.WriteAsync(item.Message);
+ }
+ }
+ }
+
+ RollFiles();
+ }
+
+ private string GetFullName((int Year, int Month, int Day) group)
+ {
+ return Path.Combine(_path, $"{_fileName}{group.Year:0000}{group.Month:00}{group.Day:00}.txt");
+ }
+
+ private (int Year, int Month, int Day) GetGrouping(LogMessage message)
+ {
+ return (message.Timestamp.Year, message.Timestamp.Month, message.Timestamp.Day);
+ }
+
+ private void RollFiles()
+ {
+ if (_maxRetainedFiles > 0)
+ {
+ var files = new DirectoryInfo(_path)
+ .GetFiles(_fileName + "*")
+ .OrderByDescending(f => f.Name)
+ .Skip(_maxRetainedFiles.Value);
+
+ foreach (var item in files)
+ {
+ item.Delete();
+ }
+ }
+ }
+ }
+}
diff --git a/src/Logging.AzureAppServices/src/ICloudAppendBlob.cs b/src/Logging.AzureAppServices/src/ICloudAppendBlob.cs
new file mode 100644
index 0000000000..2f55bbb0d1
--- /dev/null
+++ b/src/Logging.AzureAppServices/src/ICloudAppendBlob.cs
@@ -0,0 +1,23 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.Extensions.Logging.AzureAppServices
+{
+ ///
+ /// Represents an append blob, a type of blob where blocks of data are always committed to the end of the blob.
+ ///
+ internal interface ICloudAppendBlob
+ {
+ ///
+ /// Initiates an asynchronous operation to open a stream for writing to the blob.
+ ///
+ /// A object of type that represents the asynchronous operation.
+ Task AppendAsync(ArraySegment data, CancellationToken cancellationToken);
+ }
+}
diff --git a/src/Logging.AzureAppServices/src/IWebAppContext.cs b/src/Logging.AzureAppServices/src/IWebAppContext.cs
new file mode 100644
index 0000000000..f8c826ceb8
--- /dev/null
+++ b/src/Logging.AzureAppServices/src/IWebAppContext.cs
@@ -0,0 +1,32 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace Microsoft.Extensions.Logging.AzureAppServices
+{
+ ///
+ /// Represents an Azure WebApp context
+ ///
+ internal interface IWebAppContext
+ {
+ ///
+ /// Gets the path to the home folder if running in Azure WebApp
+ ///
+ string HomeFolder { get; }
+
+ ///
+ /// Gets the name of site if running in Azure WebApp
+ ///
+ string SiteName { get; }
+
+ ///
+ /// Gets the id of site if running in Azure WebApp
+ ///
+ string SiteInstanceId { get; }
+
+ ///
+ /// Gets a value indicating whether or new we're in an Azure WebApp
+ ///
+ bool IsRunningInAzureWebApp { get; }
+ }
+}
diff --git a/src/Logging.AzureAppServices/src/LogMessage.cs b/src/Logging.AzureAppServices/src/LogMessage.cs
new file mode 100644
index 0000000000..4a1179ceb3
--- /dev/null
+++ b/src/Logging.AzureAppServices/src/LogMessage.cs
@@ -0,0 +1,20 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace Microsoft.Extensions.Logging.AzureAppServices
+{
+ internal readonly struct LogMessage
+ {
+ public LogMessage(DateTimeOffset timestamp, string message)
+ {
+ Timestamp = timestamp;
+ Message = message;
+ }
+
+ public DateTimeOffset Timestamp { get; }
+ public string Message { get; }
+ }
+}
diff --git a/src/Logging.AzureAppServices/src/Microsoft.Extensions.Logging.AzureAppServices.csproj b/src/Logging.AzureAppServices/src/Microsoft.Extensions.Logging.AzureAppServices.csproj
new file mode 100644
index 0000000000..5bedde8c6d
--- /dev/null
+++ b/src/Logging.AzureAppServices/src/Microsoft.Extensions.Logging.AzureAppServices.csproj
@@ -0,0 +1,24 @@
+
+
+
+ Logger implementation to support Azure App Services 'Diagnostics logs' and 'Log stream' features.
+ netstandard2.0
+ $(NoWarn);CS1591
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Logging.AzureAppServices/src/Properties/AssemblyInfo.cs b/src/Logging.AzureAppServices/src/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..7c7d332545
--- /dev/null
+++ b/src/Logging.AzureAppServices/src/Properties/AssemblyInfo.cs
@@ -0,0 +1,8 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.CompilerServices;
+
+
+[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
diff --git a/src/Logging.AzureAppServices/src/SiteConfigurationProvider.cs b/src/Logging.AzureAppServices/src/SiteConfigurationProvider.cs
new file mode 100644
index 0000000000..452c936f93
--- /dev/null
+++ b/src/Logging.AzureAppServices/src/SiteConfigurationProvider.cs
@@ -0,0 +1,23 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.IO;
+using Microsoft.Extensions.Configuration;
+
+namespace Microsoft.Extensions.Logging.AzureAppServices
+{
+ internal class SiteConfigurationProvider
+ {
+ public static IConfiguration GetAzureLoggingConfiguration(IWebAppContext context)
+ {
+ var settingsFolder = Path.Combine(context.HomeFolder, "site", "diagnostics");
+ var settingsFile = Path.Combine(settingsFolder, "settings.json");
+
+ return new ConfigurationBuilder()
+ .AddEnvironmentVariables()
+ .AddJsonFile(settingsFile, optional: true, reloadOnChange: true)
+ .Build();
+ }
+ }
+}
diff --git a/src/Logging.AzureAppServices/src/WebAppContext.cs b/src/Logging.AzureAppServices/src/WebAppContext.cs
new file mode 100644
index 0000000000..8bdd3f1c76
--- /dev/null
+++ b/src/Logging.AzureAppServices/src/WebAppContext.cs
@@ -0,0 +1,34 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace Microsoft.Extensions.Logging.AzureAppServices
+{
+ ///
+ /// Represents the default implementation of .
+ ///
+ internal class WebAppContext : IWebAppContext
+ {
+ ///
+ /// Gets the default instance of the WebApp context.
+ ///
+ public static WebAppContext Default { get; } = new WebAppContext();
+
+ private WebAppContext() { }
+
+ ///
+ public string HomeFolder { get; } = Environment.GetEnvironmentVariable("HOME");
+
+ ///
+ public string SiteName { get; } = Environment.GetEnvironmentVariable("WEBSITE_SITE_NAME");
+
+ ///
+ public string SiteInstanceId { get; } = Environment.GetEnvironmentVariable("WEBSITE_INSTANCE_ID");
+
+ ///
+ public bool IsRunningInAzureWebApp => !string.IsNullOrEmpty(HomeFolder) &&
+ !string.IsNullOrEmpty(SiteName);
+ }
+}
diff --git a/src/Logging.AzureAppServices/test/AzureAppendBlobTests.cs b/src/Logging.AzureAppServices/test/AzureAppendBlobTests.cs
new file mode 100644
index 0000000000..2fd5955e86
--- /dev/null
+++ b/src/Logging.AzureAppServices/test/AzureAppendBlobTests.cs
@@ -0,0 +1,186 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Net;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Microsoft.Extensions.Logging.AzureAppServices.Test
+{
+ public class AzureAppendBlobTests
+ {
+ public string _containerUrl = "https://host/container?query=1";
+ public string _blobName = "blob/path";
+
+ [Fact]
+ public async Task SendsDataAsStream()
+ {
+ var testMessageHandler = new TestMessageHandler(async message =>
+ {
+ Assert.Equal(HttpMethod.Put, message.Method);
+ Assert.Equal("https://host/container/blob/path?query=1&comp=appendblock", message.RequestUri.ToString());
+ Assert.Equal(new byte[] { 0, 2, 3 }, await message.Content.ReadAsByteArrayAsync());
+ AssertDefaultHeaders(message);
+
+ return new HttpResponseMessage(HttpStatusCode.OK);
+ });
+
+ var blob = new BlobAppendReferenceWrapper(_containerUrl, _blobName, new HttpClient(testMessageHandler));
+ await blob.AppendAsync(new ArraySegment(new byte[] { 0, 2, 3 }), CancellationToken.None);
+ }
+
+ private static void AssertDefaultHeaders(HttpRequestMessage message)
+ {
+ Assert.Equal(new[] {"AppendBlob"}, message.Headers.GetValues("x-ms-blob-type"));
+ Assert.Equal(new[] {"2016-05-31"}, message.Headers.GetValues("x-ms-version"));
+ Assert.NotNull(message.Headers.Date);
+ }
+
+ [Theory]
+ [InlineData(HttpStatusCode.Created)]
+ [InlineData(HttpStatusCode.PreconditionFailed)]
+ public async Task CreatesBlobIfNotExist(HttpStatusCode createStatusCode)
+ {
+ var stage = 0;
+ var testMessageHandler = new TestMessageHandler(async message =>
+ {
+ // First PUT request
+ if (stage == 0)
+ {
+ Assert.Equal(HttpMethod.Put, message.Method);
+ Assert.Equal("https://host/container/blob/path?query=1&comp=appendblock", message.RequestUri.ToString());
+ Assert.Equal(new byte[] { 0, 2, 3 }, await message.Content.ReadAsByteArrayAsync());
+ Assert.Equal(3, message.Content.Headers.ContentLength);
+
+ AssertDefaultHeaders(message);
+
+ stage++;
+ return new HttpResponseMessage(HttpStatusCode.NotFound);
+ }
+ // Create request
+ if (stage == 1)
+ {
+ Assert.Equal(HttpMethod.Put, message.Method);
+ Assert.Equal("https://host/container/blob/path?query=1", message.RequestUri.ToString());
+ Assert.Equal(0, message.Content.Headers.ContentLength);
+ Assert.Equal(new[] { "*" }, message.Headers.GetValues("If-None-Match"));
+
+ AssertDefaultHeaders(message);
+
+ stage++;
+ return new HttpResponseMessage(createStatusCode);
+ }
+ // First PUT request
+ if (stage == 2)
+ {
+ Assert.Equal(HttpMethod.Put, message.Method);
+ Assert.Equal("https://host/container/blob/path?query=1&comp=appendblock", message.RequestUri.ToString());
+ Assert.Equal(new byte[] { 0, 2, 3 }, await message.Content.ReadAsByteArrayAsync());
+ Assert.Equal(3, message.Content.Headers.ContentLength);
+
+ AssertDefaultHeaders(message);
+
+ stage++;
+ return new HttpResponseMessage(HttpStatusCode.Created);
+ }
+ throw new NotImplementedException();
+ });
+
+ var blob = new BlobAppendReferenceWrapper(_containerUrl, _blobName, new HttpClient(testMessageHandler));
+ await blob.AppendAsync(new ArraySegment(new byte[] { 0, 2, 3 }), CancellationToken.None);
+
+ Assert.Equal(3, stage);
+ }
+
+ [Fact]
+ public async Task ThrowsForUnknownStatus()
+ {
+ var stage = 0;
+ var testMessageHandler = new TestMessageHandler(async message =>
+ {
+ // First PUT request
+ if (stage == 0)
+ {
+ Assert.Equal(HttpMethod.Put, message.Method);
+ Assert.Equal("https://host/container/blob/path?query=1&comp=appendblock", message.RequestUri.ToString());
+ Assert.Equal(new byte[] { 0, 2, 3 }, await message.Content.ReadAsByteArrayAsync());
+ Assert.Equal(3, message.Content.Headers.ContentLength);
+
+ AssertDefaultHeaders(message);
+
+ stage++;
+ return new HttpResponseMessage(HttpStatusCode.InternalServerError);
+ }
+
+ throw new NotImplementedException();
+ });
+
+ var blob = new BlobAppendReferenceWrapper(_containerUrl, _blobName, new HttpClient(testMessageHandler));
+ await Assert.ThrowsAsync(() => blob.AppendAsync(new ArraySegment(new byte[] { 0, 2, 3 }), CancellationToken.None));
+
+ Assert.Equal(1, stage);
+ }
+
+ [Fact]
+ public async Task ThrowsForUnknownStatusDuringCreation()
+ {
+ var stage = 0;
+ var testMessageHandler = new TestMessageHandler(async message =>
+ {
+ // First PUT request
+ if (stage == 0)
+ {
+ Assert.Equal(HttpMethod.Put, message.Method);
+ Assert.Equal("https://host/container/blob/path?query=1&comp=appendblock", message.RequestUri.ToString());
+ Assert.Equal(new byte[] { 0, 2, 3 }, await message.Content.ReadAsByteArrayAsync());
+ Assert.Equal(3, message.Content.Headers.ContentLength);
+
+ AssertDefaultHeaders(message);
+
+ stage++;
+ return new HttpResponseMessage(HttpStatusCode.NotFound);
+ }
+ // Create request
+ if (stage == 1)
+ {
+ Assert.Equal(HttpMethod.Put, message.Method);
+ Assert.Equal("https://host/container/blob/path?query=1", message.RequestUri.ToString());
+ Assert.Equal(0, message.Content.Headers.ContentLength);
+ Assert.Equal(new[] { "*" }, message.Headers.GetValues("If-None-Match"));
+
+ AssertDefaultHeaders(message);
+
+ stage++;
+ return new HttpResponseMessage(HttpStatusCode.InternalServerError);
+ }
+
+ throw new NotImplementedException();
+ });
+
+ var blob = new BlobAppendReferenceWrapper(_containerUrl, _blobName, new HttpClient(testMessageHandler));
+ await Assert.ThrowsAsync(() => blob.AppendAsync(new ArraySegment(new byte[] { 0, 2, 3 }), CancellationToken.None));
+
+ Assert.Equal(2, stage);
+ }
+
+
+ private class TestMessageHandler : HttpMessageHandler
+ {
+ private readonly Func> _callback;
+
+ public TestMessageHandler(Func> callback)
+ {
+ _callback = callback;
+ }
+
+ protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ return await _callback(request);
+ }
+ }
+ }
+}
diff --git a/src/Logging.AzureAppServices/test/AzureBlobSinkTests.cs b/src/Logging.AzureAppServices/test/AzureBlobSinkTests.cs
new file mode 100644
index 0000000000..4d9125335a
--- /dev/null
+++ b/src/Logging.AzureAppServices/test/AzureBlobSinkTests.cs
@@ -0,0 +1,98 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Moq;
+using Xunit;
+
+namespace Microsoft.Extensions.Logging.AzureAppServices.Test
+{
+ public class AzureBlobSinkTests
+ {
+ DateTimeOffset _timestampOne = new DateTimeOffset(2016, 05, 04, 03, 02, 01, TimeSpan.Zero);
+
+ [Fact]
+ public async Task WritesMessagesInBatches()
+ {
+ var blob = new Mock();
+ var buffers = new List();
+ blob.Setup(b => b.AppendAsync(It.IsAny>(), It.IsAny()))
+ .Callback((ArraySegment s, CancellationToken ct) => buffers.Add(ToArray(s)))
+ .Returns(Task.CompletedTask);
+
+ var sink = new TestBlobSink(name => blob.Object);
+ var logger = (BatchingLogger)sink.CreateLogger("Cat");
+
+ await sink.IntervalControl.Pause;
+
+ for (int i = 0; i < 5; i++)
+ {
+ logger.Log(_timestampOne, LogLevel.Information, 0, "Text " + i, null, (state, ex) => state);
+ }
+
+ sink.IntervalControl.Resume();
+ await sink.IntervalControl.Pause;
+
+ Assert.Single(buffers);
+ Assert.Equal(
+ "2016-05-04 03:02:01.000 +00:00 [Information] Cat: Text 0" + Environment.NewLine +
+ "2016-05-04 03:02:01.000 +00:00 [Information] Cat: Text 1" + Environment.NewLine +
+ "2016-05-04 03:02:01.000 +00:00 [Information] Cat: Text 2" + Environment.NewLine +
+ "2016-05-04 03:02:01.000 +00:00 [Information] Cat: Text 3" + Environment.NewLine +
+ "2016-05-04 03:02:01.000 +00:00 [Information] Cat: Text 4" + Environment.NewLine,
+ Encoding.UTF8.GetString(buffers[0]));
+ }
+
+ [Fact]
+ public async Task GroupsByHour()
+ {
+ var blob = new Mock();
+ var buffers = new List();
+ var names = new List();
+
+ blob.Setup(b => b.AppendAsync(It.IsAny>(), It.IsAny()))
+ .Callback((ArraySegment s, CancellationToken ct) => buffers.Add(ToArray(s)))
+ .Returns(Task.CompletedTask);
+
+ var sink = new TestBlobSink(name =>
+ {
+ names.Add(name);
+ return blob.Object;
+ });
+ var logger = (BatchingLogger)sink.CreateLogger("Cat");
+
+ await sink.IntervalControl.Pause;
+
+ var startDate = _timestampOne;
+ for (int i = 0; i < 3; i++)
+ {
+ logger.Log(startDate, LogLevel.Information, 0, "Text " + i, null, (state, ex) => state);
+
+ startDate = startDate.AddHours(1);
+ }
+
+ sink.IntervalControl.Resume();
+ await sink.IntervalControl.Pause;
+
+ Assert.Equal(3, buffers.Count);
+
+ Assert.Equal("appname/2016/05/04/03/42_filename", names[0]);
+ Assert.Equal("appname/2016/05/04/04/42_filename", names[1]);
+ Assert.Equal("appname/2016/05/04/05/42_filename", names[2]);
+ }
+
+ private byte[] ToArray(ArraySegment inputStream)
+ {
+ return inputStream.Array
+ .Skip(inputStream.Offset)
+ .Take(inputStream.Count)
+ .ToArray();
+ }
+ }
+}
diff --git a/src/Logging.AzureAppServices/test/AzureDiagnosticsConfigurationProviderTests.cs b/src/Logging.AzureAppServices/test/AzureDiagnosticsConfigurationProviderTests.cs
new file mode 100644
index 0000000000..00d7dcd58d
--- /dev/null
+++ b/src/Logging.AzureAppServices/test/AzureDiagnosticsConfigurationProviderTests.cs
@@ -0,0 +1,70 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.IO;
+using Moq;
+using Xunit;
+
+namespace Microsoft.Extensions.Logging.AzureAppServices.Test
+{
+ public class AzureDiagnosticsConfigurationProviderTests
+ {
+ [Fact]
+ public void NoConfigFile()
+ {
+ var tempFolder = Path.Combine(Path.GetTempPath(), "AzureWebAppLoggerThisFolderShouldNotExist");
+
+ var contextMock = new Mock();
+ contextMock.SetupGet(c => c.HomeFolder)
+ .Returns(tempFolder);
+
+ var config = SiteConfigurationProvider.GetAzureLoggingConfiguration(contextMock.Object);
+
+ Assert.NotNull(config);
+ }
+
+ [Fact]
+ public void ReadsSettingsFileAndEnvironment()
+ {
+ var tempFolder = Path.Combine(Path.GetTempPath(), "WebAppLoggerConfigurationDisabledInSettingsFile");
+
+ try
+ {
+ var settingsFolder = Path.Combine(tempFolder, "site", "diagnostics");
+ var settingsFile = Path.Combine(settingsFolder, "settings.json");
+
+ if (!Directory.Exists(settingsFolder))
+ {
+ Directory.CreateDirectory(settingsFolder);
+ }
+ Environment.SetEnvironmentVariable("RANDOM_ENVIRONMENT_VARIABLE", "USEFUL_VALUE");
+ File.WriteAllText(settingsFile, @"{ ""key"":""test value"" }");
+
+ var contextMock = new Mock();
+ contextMock.SetupGet(c => c.HomeFolder)
+ .Returns(tempFolder);
+
+ var config = SiteConfigurationProvider.GetAzureLoggingConfiguration(contextMock.Object);
+
+ Assert.Equal("test value", config["key"]);
+ Assert.Equal("USEFUL_VALUE", config["RANDOM_ENVIRONMENT_VARIABLE"]);
+ }
+ finally
+ {
+ if (Directory.Exists(tempFolder))
+ {
+ try
+ {
+ Directory.Delete(tempFolder, recursive: true);
+ }
+ catch
+ {
+ // Don't break the test if temp folder deletion fails.
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Logging.AzureAppServices/test/BatchingLoggerProviderTests.cs b/src/Logging.AzureAppServices/test/BatchingLoggerProviderTests.cs
new file mode 100644
index 0000000000..9ab0c0cb45
--- /dev/null
+++ b/src/Logging.AzureAppServices/test/BatchingLoggerProviderTests.cs
@@ -0,0 +1,136 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.RegularExpressions;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Microsoft.Extensions.Logging.AzureAppServices.Test
+{
+ public class BatchingLoggerProviderTests
+ {
+ private DateTimeOffset _timestampOne = new DateTimeOffset(2016, 05, 04, 03, 02, 01, TimeSpan.Zero);
+ private string _nl = Environment.NewLine;
+ private Regex _timeStampRegex = new Regex(@"^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}.\d{3} .\d{2}:\d{2} ");
+
+ [Fact]
+ public async Task LogsInIntervals()
+ {
+ var provider = new TestBatchingLoggingProvider();
+ var logger = (BatchingLogger)provider.CreateLogger("Cat");
+
+ await provider.IntervalControl.Pause;
+
+ logger.Log(_timestampOne, LogLevel.Information, 0, "Info message", null, (state, ex) => state);
+ logger.Log(_timestampOne.AddHours(1), LogLevel.Error, 0, "Error message", null, (state, ex) => state);
+
+ provider.IntervalControl.Resume();
+ await provider.IntervalControl.Pause;
+
+ Assert.Equal("2016-05-04 03:02:01.000 +00:00 [Information] Cat: Info message" + _nl, provider.Batches[0][0].Message);
+ Assert.Equal("2016-05-04 04:02:01.000 +00:00 [Error] Cat: Error message" + _nl, provider.Batches[0][1].Message);
+ }
+
+ [Fact]
+ public async Task IncludesScopes()
+ {
+ var provider = new TestBatchingLoggingProvider(includeScopes: true);
+ var factory = new LoggerFactory(new [] { provider });
+ var logger = factory.CreateLogger("Cat");
+
+ await provider.IntervalControl.Pause;
+
+ using (logger.BeginScope("Scope"))
+ {
+ using (logger.BeginScope("Scope2"))
+ {
+ logger.Log(LogLevel.Information, 0, "Info message", null, (state, ex) => state);
+ }
+ }
+
+ provider.IntervalControl.Resume();
+ await provider.IntervalControl.Pause;
+
+ Assert.Matches(_timeStampRegex, provider.Batches[0][0].Message);
+ Assert.EndsWith(
+ " [Information] Cat => Scope => Scope2:" + _nl +
+ "Info message" + _nl,
+ provider.Batches[0][0].Message);
+ }
+
+ [Fact]
+ public async Task RespectsBatchSize()
+ {
+ var provider = new TestBatchingLoggingProvider(maxBatchSize: 1);
+ var logger = (BatchingLogger)provider.CreateLogger("Cat");
+
+ await provider.IntervalControl.Pause;
+
+ logger.Log(_timestampOne, LogLevel.Information, 0, "Info message", null, (state, ex) => state);
+ logger.Log(_timestampOne.AddHours(1), LogLevel.Error, 0, "Error message", null, (state, ex) => state);
+
+ provider.IntervalControl.Resume();
+ await provider.IntervalControl.Pause;
+
+ Assert.Equal(2, provider.Batches.Count);
+ Assert.Single(provider.Batches[0]);
+ Assert.Equal("2016-05-04 03:02:01.000 +00:00 [Information] Cat: Info message" + _nl, provider.Batches[0][0].Message);
+
+ Assert.Single(provider.Batches[1]);
+ Assert.Equal("2016-05-04 04:02:01.000 +00:00 [Error] Cat: Error message" + _nl, provider.Batches[1][0].Message);
+ }
+
+ [Fact]
+ public async Task DropsMessagesWhenReachingMaxQueue()
+ {
+ var provider = new TestBatchingLoggingProvider(maxQueueSize: 1);
+ var logger = (BatchingLogger)provider.CreateLogger("Cat");
+
+ await provider.IntervalControl.Pause;
+
+ logger.Log(_timestampOne, LogLevel.Information, 0, "Info message", null, (state, ex) => state);
+ logger.Log(_timestampOne.AddHours(1), LogLevel.Error, 0, "Error message", null, (state, ex) => state);
+
+ provider.IntervalControl.Resume();
+ await provider.IntervalControl.Pause;
+
+ Assert.Equal(2, provider.Batches[0].Length);
+ Assert.Equal("2016-05-04 03:02:01.000 +00:00 [Information] Cat: Info message" + _nl, provider.Batches[0][0].Message);
+ Assert.Equal("1 message(s) dropped because of queue size limit. Increase the queue size or decrease logging verbosity to avoid this." + _nl, provider.Batches[0][1].Message);
+ }
+
+ private class TestBatchingLoggingProvider: BatchingLoggerProvider
+ {
+ public List Batches { get; } = new List();
+ public ManualIntervalControl IntervalControl { get; } = new ManualIntervalControl();
+
+ public TestBatchingLoggingProvider(TimeSpan? interval = null, int? maxBatchSize = null, int? maxQueueSize = null, bool includeScopes = false)
+ : base(new OptionsWrapperMonitor(new BatchingLoggerOptions
+ {
+ FlushPeriod = interval ?? TimeSpan.FromSeconds(1),
+ BatchSize = maxBatchSize,
+ BackgroundQueueSize = maxQueueSize,
+ IsEnabled = true,
+ IncludeScopes = includeScopes
+ }))
+ {
+ }
+
+ internal override Task WriteMessagesAsync(IEnumerable messages, CancellationToken token)
+ {
+ Batches.Add(messages.ToArray());
+ return Task.CompletedTask;
+ }
+
+ protected override Task IntervalAsync(TimeSpan interval, CancellationToken cancellationToken)
+ {
+ return IntervalControl.IntervalAsync();
+ }
+ }
+ }
+}
diff --git a/src/Logging.AzureAppServices/test/ConfigureOptionsTests.cs b/src/Logging.AzureAppServices/test/ConfigureOptionsTests.cs
new file mode 100644
index 0000000000..46b72c7a0d
--- /dev/null
+++ b/src/Logging.AzureAppServices/test/ConfigureOptionsTests.cs
@@ -0,0 +1,71 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using Microsoft.Extensions.Configuration;
+using Moq;
+using Xunit;
+
+namespace Microsoft.Extensions.Logging.AzureAppServices.Test
+{
+ public class ConfigureOptionsTests
+ {
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ [InlineData(null)]
+ public void InitializesIsEnabled(bool? enabled)
+ {
+ var configuration = new ConfigurationBuilder().AddInMemoryCollection(new[]
+ {
+ new KeyValuePair("IsEnabledKey", Convert.ToString(enabled))
+ }).Build();
+
+ var options = new BatchingLoggerOptions();
+ new BatchLoggerConfigureOptions(configuration, "IsEnabledKey").Configure(options);
+
+ Assert.Equal(enabled ?? false, options.IsEnabled);
+ }
+
+ [Fact]
+ public void InitializesLogDirectory()
+ {
+ var configuration = new ConfigurationBuilder().AddInMemoryCollection(new[]
+ {
+ new KeyValuePair("APPSETTING_DIAGNOSTICS_AZUREBLOBCONTAINERSASURL", "http://container/url")
+ }).Build();
+
+ var contextMock = new Mock();
+ contextMock.SetupGet(c => c.HomeFolder).Returns("Home");
+
+ var options = new AzureFileLoggerOptions();
+ new FileLoggerConfigureOptions(configuration, contextMock.Object).Configure(options);
+
+ Assert.Equal(Path.Combine("Home", "LogFiles", "Application"), options.LogDirectory);
+ }
+
+ [Fact]
+ public void InitializesBlobUriSiteInstanceAndName()
+ {
+ var configuration = new ConfigurationBuilder().AddInMemoryCollection(new []
+ {
+ new KeyValuePair("APPSETTING_DIAGNOSTICS_AZUREBLOBCONTAINERSASURL", "http://container/url")
+ }).Build();
+
+ var contextMock = new Mock();
+ contextMock.SetupGet(c => c.HomeFolder).Returns("Home");
+ contextMock.SetupGet(c => c.SiteInstanceId).Returns("InstanceId");
+ contextMock.SetupGet(c => c.SiteName).Returns("Name");
+
+ var options = new AzureBlobLoggerOptions();
+ new BlobLoggerConfigureOptions(configuration, contextMock.Object).Configure(options);
+
+ Assert.Equal("http://container/url", options.ContainerUrl);
+ Assert.Equal("InstanceId", options.ApplicationInstanceId);
+ Assert.Equal("Name", options.ApplicationName);
+ }
+ }
+}
diff --git a/src/Logging.AzureAppServices/test/FileLoggerTests.cs b/src/Logging.AzureAppServices/test/FileLoggerTests.cs
new file mode 100644
index 0000000000..a3fcd2587d
--- /dev/null
+++ b/src/Logging.AzureAppServices/test/FileLoggerTests.cs
@@ -0,0 +1,122 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Microsoft.Extensions.Logging.AzureAppServices.Test
+{
+ public class FileLoggerTests: IDisposable
+ {
+ DateTimeOffset _timestampOne = new DateTimeOffset(2016, 05, 04, 03, 02, 01, TimeSpan.Zero);
+
+ public FileLoggerTests()
+ {
+ TempPath = Path.GetTempFileName() + "_";
+ }
+
+ public string TempPath { get; }
+
+ public void Dispose()
+ {
+ try
+ {
+ if (Directory.Exists(TempPath))
+ {
+ Directory.Delete(TempPath, true);
+ }
+ }
+ catch
+ {
+ // ignored
+ }
+ }
+
+ [Fact]
+ public async Task WritesToTextFile()
+ {
+ var provider = new TestFileLoggerProvider(TempPath);
+ var logger = (BatchingLogger)provider.CreateLogger("Cat");
+
+ await provider.IntervalControl.Pause;
+
+ logger.Log(_timestampOne, LogLevel.Information, 0, "Info message", null, (state, ex) => state);
+ logger.Log(_timestampOne.AddHours(1), LogLevel.Error, 0, "Error message", null, (state, ex) => state);
+
+ provider.IntervalControl.Resume();
+ await provider.IntervalControl.Pause;
+
+ Assert.Equal(
+ "2016-05-04 03:02:01.000 +00:00 [Information] Cat: Info message" + Environment.NewLine +
+ "2016-05-04 04:02:01.000 +00:00 [Error] Cat: Error message" + Environment.NewLine,
+ File.ReadAllText(Path.Combine(TempPath, "LogFile.20160504.txt")));
+ }
+
+ [Fact]
+ public async Task RollsTextFile()
+ {
+ var provider = new TestFileLoggerProvider(TempPath);
+ var logger = (BatchingLogger)provider.CreateLogger("Cat");
+
+ await provider.IntervalControl.Pause;
+
+ logger.Log(_timestampOne, LogLevel.Information, 0, "Info message", null, (state, ex) => state);
+ logger.Log(_timestampOne.AddDays(1), LogLevel.Error, 0, "Error message", null, (state, ex) => state);
+
+ provider.IntervalControl.Resume();
+ await provider.IntervalControl.Pause;
+
+ Assert.Equal(
+ "2016-05-04 03:02:01.000 +00:00 [Information] Cat: Info message" + Environment.NewLine,
+ File.ReadAllText(Path.Combine(TempPath, "LogFile.20160504.txt")));
+
+ Assert.Equal(
+ "2016-05-05 03:02:01.000 +00:00 [Error] Cat: Error message" + Environment.NewLine,
+ File.ReadAllText(Path.Combine(TempPath, "LogFile.20160505.txt")));
+ }
+
+ [Fact]
+ public async Task RespectsMaxFileCount()
+ {
+ Directory.CreateDirectory(TempPath);
+ File.WriteAllText(Path.Combine(TempPath, "randomFile.txt"), "Text");
+
+ var provider = new TestFileLoggerProvider(TempPath, maxRetainedFiles: 5);
+ var logger = (BatchingLogger)provider.CreateLogger("Cat");
+
+ await provider.IntervalControl.Pause;
+ var timestamp = _timestampOne;
+
+ for (int i = 0; i < 10; i++)
+ {
+ logger.Log(timestamp, LogLevel.Information, 0, "Info message", null, (state, ex) => state);
+ logger.Log(timestamp.AddHours(1), LogLevel.Error, 0, "Error message", null, (state, ex) => state);
+
+ timestamp = timestamp.AddDays(1);
+ }
+
+ provider.IntervalControl.Resume();
+ await provider.IntervalControl.Pause;
+
+ var actualFiles = new DirectoryInfo(TempPath)
+ .GetFiles()
+ .Select(f => f.Name)
+ .OrderBy(f => f)
+ .ToArray();
+
+ Assert.Equal(6, actualFiles.Length);
+ Assert.Equal(new[] {
+ "LogFile.20160509.txt",
+ "LogFile.20160510.txt",
+ "LogFile.20160511.txt",
+ "LogFile.20160512.txt",
+ "LogFile.20160513.txt",
+ "randomFile.txt"
+ }, actualFiles);
+ }
+ }
+}
diff --git a/src/Logging.AzureAppServices/test/LoggerBuilderExtensionsTests.cs b/src/Logging.AzureAppServices/test/LoggerBuilderExtensionsTests.cs
new file mode 100644
index 0000000000..cf8bede1a5
--- /dev/null
+++ b/src/Logging.AzureAppServices/test/LoggerBuilderExtensionsTests.cs
@@ -0,0 +1,79 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Linq;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+using Moq;
+using Xunit;
+
+namespace Microsoft.Extensions.Logging.AzureAppServices.Test
+{
+ public class LoggerBuilderExtensionsTests
+ {
+ private IWebAppContext _appContext;
+
+ public LoggerBuilderExtensionsTests()
+ {
+ var contextMock = new Mock();
+ contextMock.SetupGet(c => c.IsRunningInAzureWebApp).Returns(true);
+ contextMock.SetupGet(c => c.HomeFolder).Returns(".");
+ _appContext = contextMock.Object;
+ }
+
+ [Fact]
+ public void BuilderExtensionAddsSingleSetOfServicesWhenCalledTwice()
+ {
+ var serviceCollection = new ServiceCollection();
+ serviceCollection.AddLogging(builder => builder.AddAzureWebAppDiagnostics(_appContext));
+ var count = serviceCollection.Count;
+
+ Assert.NotEqual(0, count);
+
+ serviceCollection.AddLogging(builder => builder.AddAzureWebAppDiagnostics(_appContext));
+
+ Assert.Equal(count, serviceCollection.Count);
+ }
+
+ [Fact]
+ public void BuilderExtensionAddsConfigurationChangeTokenSource()
+ {
+ var serviceCollection = new ServiceCollection();
+ serviceCollection.AddLogging(builder => builder.AddConfiguration(new ConfigurationBuilder().Build()));
+
+ // Tracking for main configuration
+ Assert.Equal(1, serviceCollection.Count(d => d.ServiceType == typeof(IOptionsChangeTokenSource)));
+
+ serviceCollection.AddLogging(builder => builder.AddAzureWebAppDiagnostics(_appContext));
+
+ // Make sure we add another config change token for azure diagnostic configuration
+ Assert.Equal(2, serviceCollection.Count(d => d.ServiceType == typeof(IOptionsChangeTokenSource)));
+ }
+
+ [Fact]
+ public void BuilderExtensionAddsIConfigureOptions()
+ {
+ var serviceCollection = new ServiceCollection();
+ serviceCollection.AddLogging(builder => builder.AddConfiguration(new ConfigurationBuilder().Build()));
+
+ // Tracking for main configuration
+ Assert.Equal(2, serviceCollection.Count(d => d.ServiceType == typeof(IConfigureOptions)));
+
+ serviceCollection.AddLogging(builder => builder.AddAzureWebAppDiagnostics(_appContext));
+
+ Assert.Equal(4, serviceCollection.Count(d => d.ServiceType == typeof(IConfigureOptions)));
+ }
+
+ [Fact]
+ public void LoggerProviderIsResolvable()
+ {
+ var serviceCollection = new ServiceCollection();
+ serviceCollection.AddLogging(builder => builder.AddAzureWebAppDiagnostics(_appContext));
+
+ var serviceProvider = serviceCollection.BuildServiceProvider();
+ var loggerFactory = serviceProvider.GetService();
+ }
+ }
+}
diff --git a/src/Logging.AzureAppServices/test/ManualIntervalControl.cs b/src/Logging.AzureAppServices/test/ManualIntervalControl.cs
new file mode 100644
index 0000000000..29cc883a28
--- /dev/null
+++ b/src/Logging.AzureAppServices/test/ManualIntervalControl.cs
@@ -0,0 +1,31 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Threading.Tasks;
+
+namespace Microsoft.Extensions.Logging.AzureAppServices.Test
+{
+ internal class ManualIntervalControl
+ {
+
+ private TaskCompletionSource _pauseCompletionSource = new TaskCompletionSource();
+ private TaskCompletionSource _resumeCompletionSource;
+
+ public Task Pause => _pauseCompletionSource.Task;
+
+ public void Resume()
+ {
+ _pauseCompletionSource = new TaskCompletionSource();
+ _resumeCompletionSource.SetResult(null);
+ }
+
+ public async Task IntervalAsync()
+ {
+ _resumeCompletionSource = new TaskCompletionSource();
+ _pauseCompletionSource.SetResult(null);
+
+ await _resumeCompletionSource.Task;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Logging.AzureAppServices/test/Microsoft.Extensions.Logging.AzureAppServices.Tests.csproj b/src/Logging.AzureAppServices/test/Microsoft.Extensions.Logging.AzureAppServices.Tests.csproj
new file mode 100644
index 0000000000..7365c79076
--- /dev/null
+++ b/src/Logging.AzureAppServices/test/Microsoft.Extensions.Logging.AzureAppServices.Tests.csproj
@@ -0,0 +1,28 @@
+
+
+
+ $(DefaultNetCoreTargetFramework);net472
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Logging.AzureAppServices/test/OptionsWrapperMonitor.cs b/src/Logging.AzureAppServices/test/OptionsWrapperMonitor.cs
new file mode 100644
index 0000000000..fbc531c26d
--- /dev/null
+++ b/src/Logging.AzureAppServices/test/OptionsWrapperMonitor.cs
@@ -0,0 +1,26 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.Extensions.Logging.AzureAppServices.Test
+{
+ internal class OptionsWrapperMonitor : IOptionsMonitor
+ {
+ public OptionsWrapperMonitor(T currentValue)
+ {
+ CurrentValue = currentValue;
+ }
+
+ public IDisposable OnChange(Action listener)
+ {
+ return null;
+ }
+
+ public T Get(string name) => CurrentValue;
+
+ public T CurrentValue { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Logging.AzureAppServices/test/TestBlobSink.cs b/src/Logging.AzureAppServices/test/TestBlobSink.cs
new file mode 100644
index 0000000000..4b9ec445be
--- /dev/null
+++ b/src/Logging.AzureAppServices/test/TestBlobSink.cs
@@ -0,0 +1,32 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.Extensions.Logging.AzureAppServices.Test
+{
+ internal class TestBlobSink : BlobLoggerProvider
+ {
+ internal ManualIntervalControl IntervalControl { get; } = new ManualIntervalControl();
+
+ public TestBlobSink(Func blobReferenceFactory) : base(
+ new OptionsWrapperMonitor(new AzureBlobLoggerOptions()
+ {
+ ApplicationInstanceId = "42",
+ ApplicationName = "appname",
+ BlobName = "filename",
+ IsEnabled = true
+ }),
+ blobReferenceFactory)
+ {
+ }
+
+ protected override Task IntervalAsync(TimeSpan interval, CancellationToken cancellationToken)
+ {
+ return IntervalControl.IntervalAsync();
+ }
+ }
+}
diff --git a/src/Logging.AzureAppServices/test/TestFileLoggerProvider.cs b/src/Logging.AzureAppServices/test/TestFileLoggerProvider.cs
new file mode 100644
index 0000000000..60fbb88bd8
--- /dev/null
+++ b/src/Logging.AzureAppServices/test/TestFileLoggerProvider.cs
@@ -0,0 +1,36 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.Extensions.Logging.AzureAppServices.Test
+{
+ internal class TestFileLoggerProvider : FileLoggerProvider
+ {
+ internal ManualIntervalControl IntervalControl { get; } = new ManualIntervalControl();
+
+ public TestFileLoggerProvider(
+ string path,
+ string fileName = "LogFile.",
+ int maxFileSize = 32_000,
+ int maxRetainedFiles = 100)
+ : base(new OptionsWrapperMonitor(new AzureFileLoggerOptions()
+ {
+ LogDirectory = path,
+ FileName = fileName,
+ FileSizeLimit = maxFileSize,
+ RetainedFileCountLimit = maxRetainedFiles,
+ IsEnabled = true
+ }))
+ {
+ }
+
+ protected override Task IntervalAsync(TimeSpan interval, CancellationToken cancellationToken)
+ {
+ return IntervalControl.IntervalAsync();
+ }
+ }
+}
diff --git a/src/Logging.AzureAppServices/test/WebConfigurationLevelSwitchTests.cs b/src/Logging.AzureAppServices/test/WebConfigurationLevelSwitchTests.cs
new file mode 100644
index 0000000000..f933b9f2fa
--- /dev/null
+++ b/src/Logging.AzureAppServices/test/WebConfigurationLevelSwitchTests.cs
@@ -0,0 +1,40 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using Microsoft.Extensions.Configuration;
+using Xunit;
+
+namespace Microsoft.Extensions.Logging.AzureAppServices.Test
+{
+ public class WebConfigurationLevelSwitchTests
+ {
+ [Theory]
+ [InlineData("Error", LogLevel.Error)]
+ [InlineData("Warning", LogLevel.Warning)]
+ [InlineData("Information", LogLevel.Information)]
+ [InlineData("Verbose", LogLevel.Trace)]
+ [InlineData("ABCD", LogLevel.None)]
+ public void AddsRuleWithCorrectLevel(string levelValue, LogLevel expectedLevel)
+ {
+ var configuration = new ConfigurationBuilder().AddInMemoryCollection(
+ new[]
+ {
+ new KeyValuePair("levelKey", levelValue),
+ })
+ .Build();
+
+ var levelSwitcher = new ConfigurationBasedLevelSwitcher(configuration, typeof(TestFileLoggerProvider), "levelKey");
+
+ var filterConfiguration = new LoggerFilterOptions();
+ levelSwitcher.Configure(filterConfiguration);
+
+ Assert.Equal(1, filterConfiguration.Rules.Count);
+
+ var rule = filterConfiguration.Rules[0];
+ Assert.Equal(typeof(TestFileLoggerProvider).FullName, rule.ProviderName);
+ Assert.Equal(expectedLevel, rule.LogLevel);
+ }
+ }
+}
diff --git a/src/Middleware/.vsconfig b/src/Middleware/.vsconfig
new file mode 100644
index 0000000000..7a520fe61c
--- /dev/null
+++ b/src/Middleware/.vsconfig
@@ -0,0 +1,12 @@
+{
+ "version": "1.0",
+ "components": [
+ "Microsoft.Net.Component.4.6.1.TargetingPack",
+ "Microsoft.Net.Component.4.7.2.SDK",
+ "Microsoft.Net.Component.4.7.2.TargetingPack",
+ "Microsoft.VisualStudio.Workload.ManagedDesktop",
+ "Microsoft.VisualStudio.Workload.NetCoreTools",
+ "Microsoft.VisualStudio.Workload.NetWeb",
+ "Microsoft.VisualStudio.Workload.VisualStudioExtension"
+ ]
+}
diff --git a/src/Middleware/Session/samples/SessionSample.csproj b/src/Middleware/Session/samples/SessionSample.csproj
index c1d8559a47..3beef6fa7b 100644
--- a/src/Middleware/Session/samples/SessionSample.csproj
+++ b/src/Middleware/Session/samples/SessionSample.csproj
@@ -9,8 +9,6 @@
-
-
diff --git a/src/Middleware/Session/samples/Startup.cs b/src/Middleware/Session/samples/Startup.cs
index c776d8e2ff..9bc4265daf 100644
--- a/src/Middleware/Session/samples/Startup.cs
+++ b/src/Middleware/Session/samples/Startup.cs
@@ -21,7 +21,8 @@ namespace SessionSample
// Uncomment the following line to use the in-memory implementation of IDistributedCache
services.AddDistributedMemoryCache();
- // Uncomment the following line to use the Microsoft SQL Server implementation of IDistributedCache.
+ // Uncomment the following line to use the Microsoft SQL Server implementation of IDistributedCache
+ // and add a PackageReference to Microsoft.Extensions.Caching.SqlServer in the .csrpoj.
// Note that this would require setting up the session state database.
//services.AddDistributedSqlServerCache(o =>
//{
@@ -30,7 +31,8 @@ namespace SessionSample
// o.TableName = "Sessions";
//});
- // Uncomment the following line to use the Redis implementation of IDistributedCache.
+ // Uncomment the following line to use the Redis implementation of IDistributedCache
+ // and add a PackageReference to Microsoft.Extensions.Caching.StackExchangeRedis in the .csrpoj.
// This will override any previously registered IDistributedCache service.
//services.AddStackExchangeRedisCache(o =>
//{
diff --git a/src/MusicStore/.vsconfig b/src/MusicStore/.vsconfig
new file mode 100644
index 0000000000..7a520fe61c
--- /dev/null
+++ b/src/MusicStore/.vsconfig
@@ -0,0 +1,12 @@
+{
+ "version": "1.0",
+ "components": [
+ "Microsoft.Net.Component.4.6.1.TargetingPack",
+ "Microsoft.Net.Component.4.7.2.SDK",
+ "Microsoft.Net.Component.4.7.2.TargetingPack",
+ "Microsoft.VisualStudio.Workload.ManagedDesktop",
+ "Microsoft.VisualStudio.Workload.NetCoreTools",
+ "Microsoft.VisualStudio.Workload.NetWeb",
+ "Microsoft.VisualStudio.Workload.VisualStudioExtension"
+ ]
+}
diff --git a/src/Mvc/.vsconfig b/src/Mvc/.vsconfig
new file mode 100644
index 0000000000..7a520fe61c
--- /dev/null
+++ b/src/Mvc/.vsconfig
@@ -0,0 +1,12 @@
+{
+ "version": "1.0",
+ "components": [
+ "Microsoft.Net.Component.4.6.1.TargetingPack",
+ "Microsoft.Net.Component.4.7.2.SDK",
+ "Microsoft.Net.Component.4.7.2.TargetingPack",
+ "Microsoft.VisualStudio.Workload.ManagedDesktop",
+ "Microsoft.VisualStudio.Workload.NetCoreTools",
+ "Microsoft.VisualStudio.Workload.NetWeb",
+ "Microsoft.VisualStudio.Workload.VisualStudioExtension"
+ ]
+}
diff --git a/src/Mvc/Mvc.Analyzers/test/Mvc.Analyzers.Test.csproj b/src/Mvc/Mvc.Analyzers/test/Mvc.Analyzers.Test.csproj
index 4335457466..bcbc066c95 100644
--- a/src/Mvc/Mvc.Analyzers/test/Mvc.Analyzers.Test.csproj
+++ b/src/Mvc/Mvc.Analyzers/test/Mvc.Analyzers.Test.csproj
@@ -15,8 +15,8 @@
+
-
diff --git a/src/Mvc/Mvc.Api.Analyzers/test/Mvc.Api.Analyzers.Test.csproj b/src/Mvc/Mvc.Api.Analyzers/test/Mvc.Api.Analyzers.Test.csproj
index 179e80e92b..a37838a6bf 100644
--- a/src/Mvc/Mvc.Api.Analyzers/test/Mvc.Api.Analyzers.Test.csproj
+++ b/src/Mvc/Mvc.Api.Analyzers/test/Mvc.Api.Analyzers.Test.csproj
@@ -3,6 +3,7 @@
$(DefaultNetCoreTargetFramework)
Microsoft.AspNetCore.Mvc.Api.Analyzers
+ true
@@ -13,8 +14,8 @@
+
-
diff --git a/src/ProjectTemplates/.vsconfig b/src/ProjectTemplates/.vsconfig
new file mode 100644
index 0000000000..7a520fe61c
--- /dev/null
+++ b/src/ProjectTemplates/.vsconfig
@@ -0,0 +1,12 @@
+{
+ "version": "1.0",
+ "components": [
+ "Microsoft.Net.Component.4.6.1.TargetingPack",
+ "Microsoft.Net.Component.4.7.2.SDK",
+ "Microsoft.Net.Component.4.7.2.TargetingPack",
+ "Microsoft.VisualStudio.Workload.ManagedDesktop",
+ "Microsoft.VisualStudio.Workload.NetCoreTools",
+ "Microsoft.VisualStudio.Workload.NetWeb",
+ "Microsoft.VisualStudio.Workload.VisualStudioExtension"
+ ]
+}
diff --git a/src/ProjectTemplates/test/BlazorServerTemplateTest.cs b/src/ProjectTemplates/test/BlazorServerTemplateTest.cs
index 6d7a5ac7f8..a198f9b799 100644
--- a/src/ProjectTemplates/test/BlazorServerTemplateTest.cs
+++ b/src/ProjectTemplates/test/BlazorServerTemplateTest.cs
@@ -81,10 +81,11 @@ namespace Templates.Test
}
}
- [ConditionalTheory]
+ [ConditionalTheory(Skip = "See: https://github.com/dotnet/aspnetcore/issues/20520")]
[InlineData(true)]
[InlineData(false)]
[SkipOnHelix("ef restore no worky")]
+ [QuarantinedTest]
public async Task BlazorServerTemplateWorks_IndividualAuth(bool useLocalDB)
{
Project = await ProjectFactory.GetOrCreateProject("blazorserverindividual" + (useLocalDB ? "uld" : ""), Output);
diff --git a/src/ProjectTemplates/test/GrpcTemplateTest.cs b/src/ProjectTemplates/test/GrpcTemplateTest.cs
index 707ffb9034..371c0022d3 100644
--- a/src/ProjectTemplates/test/GrpcTemplateTest.cs
+++ b/src/ProjectTemplates/test/GrpcTemplateTest.cs
@@ -25,7 +25,7 @@ namespace Templates.Test
public ProjectFactoryFixture ProjectFactory { get; }
public ITestOutputHelper Output { get; }
- [ConditionalFact(Skip = "This test run for over an hour")]
+ [ConditionalFact]
[SkipOnHelix("Not supported queues", Queues = "Windows.7.Amd64;Windows.7.Amd64.Open;OSX.1014.Amd64;OSX.1014.Amd64.Open")]
[QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/19716")]
public async Task GrpcTemplate()
diff --git a/src/ProjectTemplates/test/Helpers/Project.cs b/src/ProjectTemplates/test/Helpers/Project.cs
index 68642c48f4..64f21104c4 100644
--- a/src/ProjectTemplates/test/Helpers/Project.cs
+++ b/src/ProjectTemplates/test/Helpers/Project.cs
@@ -27,11 +27,11 @@ namespace Templates.Test.Helpers
public static bool IsCIEnvironment => typeof(Project).Assembly.GetCustomAttributes()
.Any(a => a.Key == "ContinuousIntegrationBuild");
- public static string ArtifactsLogDir => (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("HELIX_DIR")))
+ public static string ArtifactsLogDir => (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("HELIX_WORKITEM_UPLOAD_ROOT")))
? GetAssemblyMetadata("ArtifactsLogDir")
- : Path.Combine(Environment.GetEnvironmentVariable("HELIX_DIR"), "logs");
-
- public static string DotNetEfFullPath => (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("DotNetEfFullPath")))
+ : Environment.GetEnvironmentVariable("HELIX_WORKITEM_UPLOAD_ROOT");
+
+ public static string DotNetEfFullPath => (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("DotNetEfFullPath")))
? typeof(ProjectFactoryFixture).Assembly.GetCustomAttributes()
.First(attribute => attribute.Key == "DotNetEfFullPath")
.Value
@@ -309,7 +309,7 @@ namespace Templates.Test.Helpers
internal async Task RunDotNetEfCreateMigrationAsync(string migrationName)
{
var args = $"--verbose --no-build migrations add {migrationName}";
-
+
// Only run one instance of 'dotnet new' at once, as a workaround for
// https://github.com/aspnet/templating/issues/63
await DotNetNewLock.WaitAsync();
@@ -324,7 +324,7 @@ namespace Templates.Test.Helpers
{
command = "dotnet-ef";
}
-
+
var result = ProcessEx.Run(Output, TemplateOutputDir, command, args);
await result.Exited;
return result;
@@ -353,7 +353,7 @@ namespace Templates.Test.Helpers
{
command = "dotnet-ef";
}
-
+
var result = ProcessEx.Run(Output, TemplateOutputDir, command, args);
await result.Exited;
return result;
diff --git a/src/ProjectTemplates/test/IdentityUIPackageTest.cs b/src/ProjectTemplates/test/IdentityUIPackageTest.cs
index da766229a6..b89c19b61a 100644
--- a/src/ProjectTemplates/test/IdentityUIPackageTest.cs
+++ b/src/ProjectTemplates/test/IdentityUIPackageTest.cs
@@ -118,7 +118,7 @@ namespace Templates.Test
"Identity/lib/jquery-validation-unobtrusive/LICENSE.txt",
};
- [ConditionalTheory(Skip = "This test run for over an hour")]
+ [ConditionalTheory]
[MemberData(nameof(MSBuildIdentityUIPackageOptions))]
[SkipOnHelix("cert failure", Queues = "OSX.1014.Amd64;OSX.1014.Amd64.Open")]
[QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/19716")]
diff --git a/src/ProjectTemplates/test/MvcTemplateTest.cs b/src/ProjectTemplates/test/MvcTemplateTest.cs
index d881ffcdd0..ff60a16bd8 100644
--- a/src/ProjectTemplates/test/MvcTemplateTest.cs
+++ b/src/ProjectTemplates/test/MvcTemplateTest.cs
@@ -104,7 +104,7 @@ namespace Templates.Test
}
}
- [ConditionalTheory(Skip = "This test run for over an hour")]
+ [ConditionalTheory]
[InlineData(true)]
[InlineData(false)]
[SkipOnHelix("cert failure", Queues = "OSX.1014.Amd64;OSX.1014.Amd64.Open")]
diff --git a/src/ProjectTemplates/test/RazorPagesTemplateTest.cs b/src/ProjectTemplates/test/RazorPagesTemplateTest.cs
index f7727fbb5a..10370a3573 100644
--- a/src/ProjectTemplates/test/RazorPagesTemplateTest.cs
+++ b/src/ProjectTemplates/test/RazorPagesTemplateTest.cs
@@ -94,7 +94,7 @@ namespace Templates.Test
}
}
- [ConditionalTheory(Skip = "This test run for over an hour")]
+ [ConditionalTheory]
[InlineData(false)]
[InlineData(true)]
[SkipOnHelix("cert failure", Queues = "OSX.1014.Amd64;OSX.1014.Amd64.Open")]
diff --git a/src/ProjectTemplates/test/SpaTemplateTest/ReactTemplateTest.cs b/src/ProjectTemplates/test/SpaTemplateTest/ReactTemplateTest.cs
index d4a1ff1756..9e591ff67d 100644
--- a/src/ProjectTemplates/test/SpaTemplateTest/ReactTemplateTest.cs
+++ b/src/ProjectTemplates/test/SpaTemplateTest/ReactTemplateTest.cs
@@ -23,7 +23,7 @@ namespace Templates.Test.SpaTemplateTest
=> SpaTemplateImplAsync("reactnoauth", "react", useLocalDb: false, usesAuth: false);
[QuarantinedTest]
- [ConditionalFact(Skip="This test run for over an hour")]
+ [ConditionalFact]
[SkipOnHelix("selenium")]
public Task ReactTemplate_IndividualAuth_NetCore()
=> SpaTemplateImplAsync("reactindividual", "react", useLocalDb: false, usesAuth: true);
diff --git a/src/ProjectTemplates/test/WorkerTemplateTest.cs b/src/ProjectTemplates/test/WorkerTemplateTest.cs
index 09f444811c..1bc2d8d5b3 100644
--- a/src/ProjectTemplates/test/WorkerTemplateTest.cs
+++ b/src/ProjectTemplates/test/WorkerTemplateTest.cs
@@ -21,7 +21,7 @@ namespace Templates.Test
public ProjectFactoryFixture ProjectFactory { get; }
public ITestOutputHelper Output { get; }
- [Fact(Skip = "This test run for over an hour")]
+ [Fact]
[QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/19716")]
public async Task WorkerTemplateAsync()
{
diff --git a/src/Razor/.vsconfig b/src/Razor/.vsconfig
new file mode 100644
index 0000000000..7a520fe61c
--- /dev/null
+++ b/src/Razor/.vsconfig
@@ -0,0 +1,12 @@
+{
+ "version": "1.0",
+ "components": [
+ "Microsoft.Net.Component.4.6.1.TargetingPack",
+ "Microsoft.Net.Component.4.7.2.SDK",
+ "Microsoft.Net.Component.4.7.2.TargetingPack",
+ "Microsoft.VisualStudio.Workload.ManagedDesktop",
+ "Microsoft.VisualStudio.Workload.NetCoreTools",
+ "Microsoft.VisualStudio.Workload.NetWeb",
+ "Microsoft.VisualStudio.Workload.VisualStudioExtension"
+ ]
+}
diff --git a/src/Security/.vsconfig b/src/Security/.vsconfig
new file mode 100644
index 0000000000..7a520fe61c
--- /dev/null
+++ b/src/Security/.vsconfig
@@ -0,0 +1,12 @@
+{
+ "version": "1.0",
+ "components": [
+ "Microsoft.Net.Component.4.6.1.TargetingPack",
+ "Microsoft.Net.Component.4.7.2.SDK",
+ "Microsoft.Net.Component.4.7.2.TargetingPack",
+ "Microsoft.VisualStudio.Workload.ManagedDesktop",
+ "Microsoft.VisualStudio.Workload.NetCoreTools",
+ "Microsoft.VisualStudio.Workload.NetWeb",
+ "Microsoft.VisualStudio.Workload.VisualStudioExtension"
+ ]
+}
diff --git a/src/Security/Authentication/Certificate/src/CertificateAuthenticationExtensions.cs b/src/Security/Authentication/Certificate/src/CertificateAuthenticationExtensions.cs
index d49f2c274b..4926a21382 100644
--- a/src/Security/Authentication/Certificate/src/CertificateAuthenticationExtensions.cs
+++ b/src/Security/Authentication/Certificate/src/CertificateAuthenticationExtensions.cs
@@ -28,7 +28,7 @@ namespace Microsoft.Extensions.DependencyInjection
///
/// The .
public static AuthenticationBuilder AddCertificate(this AuthenticationBuilder builder, string authenticationScheme)
- => builder.AddCertificate(authenticationScheme, configureOptions: null);
+ => builder.AddCertificate(authenticationScheme, configureOptions: (Action)null);
///
/// Adds certificate authentication.
@@ -39,6 +39,16 @@ namespace Microsoft.Extensions.DependencyInjection
public static AuthenticationBuilder AddCertificate(this AuthenticationBuilder builder, Action configureOptions)
=> builder.AddCertificate(CertificateAuthenticationDefaults.AuthenticationScheme, configureOptions);
+ ///
+ /// Adds certificate authentication.
+ ///
+ /// TService: A service resolved from the IServiceProvider for use when configuring this authentication provider. If you need multiple services then specify IServiceProvider and resolve them directly.
+ /// The .
+ ///
+ /// The .
+ public static AuthenticationBuilder AddCertificate(this AuthenticationBuilder builder, Action configureOptions) where TService : class
+ => builder.AddCertificate(CertificateAuthenticationDefaults.AuthenticationScheme, configureOptions);
+
///
/// Adds certificate authentication.
///
@@ -50,6 +60,32 @@ namespace Microsoft.Extensions.DependencyInjection
this AuthenticationBuilder builder,
string authenticationScheme,
Action configureOptions)
- => builder.AddScheme(authenticationScheme, configureOptions);
+ {
+ Action configureOptionsWithServices;
+ if (configureOptions == null)
+ {
+ configureOptionsWithServices = null;
+ }
+ else
+ {
+ configureOptionsWithServices = (options, _) => configureOptions(options);
+ }
+
+ return builder.AddCertificate(authenticationScheme, configureOptionsWithServices);
+ }
+
+ ///
+ /// Adds certificate authentication.
+ ///
+ /// TService: A service resolved from the IServiceProvider for use when configuring this authentication provider. If you need multiple services then specify IServiceProvider and resolve them directly.
+ /// The .
+ ///
+ ///
+ /// The .
+ public static AuthenticationBuilder AddCertificate(
+ this AuthenticationBuilder builder,
+ string authenticationScheme,
+ Action configureOptions) where TService : class
+ => builder.AddScheme(authenticationScheme, configureOptions);
}
}
diff --git a/src/Security/Authentication/Cookies/ref/Microsoft.AspNetCore.Authentication.Cookies.netcoreapp.cs b/src/Security/Authentication/Cookies/ref/Microsoft.AspNetCore.Authentication.Cookies.netcoreapp.cs
index f3d730a1f9..714bdd2ebd 100644
--- a/src/Security/Authentication/Cookies/ref/Microsoft.AspNetCore.Authentication.Cookies.netcoreapp.cs
+++ b/src/Security/Authentication/Cookies/ref/Microsoft.AspNetCore.Authentication.Cookies.netcoreapp.cs
@@ -126,5 +126,8 @@ namespace Microsoft.Extensions.DependencyInjection
public static Microsoft.AspNetCore.Authentication.AuthenticationBuilder AddCookie(this Microsoft.AspNetCore.Authentication.AuthenticationBuilder builder, string authenticationScheme) { throw null; }
public static Microsoft.AspNetCore.Authentication.AuthenticationBuilder AddCookie(this Microsoft.AspNetCore.Authentication.AuthenticationBuilder builder, string authenticationScheme, System.Action configureOptions) { throw null; }
public static Microsoft.AspNetCore.Authentication.AuthenticationBuilder AddCookie(this Microsoft.AspNetCore.Authentication.AuthenticationBuilder builder, string authenticationScheme, string displayName, System.Action configureOptions) { throw null; }
+ public static Microsoft.AspNetCore.Authentication.AuthenticationBuilder AddCookie(this Microsoft.AspNetCore.Authentication.AuthenticationBuilder builder, System.Action configureOptions) where TService : class { throw null; }
+ public static Microsoft.AspNetCore.Authentication.AuthenticationBuilder AddCookie(this Microsoft.AspNetCore.Authentication.AuthenticationBuilder builder, string authenticationScheme, System.Action configureOptions) where TService : class { throw null; }
+ public static Microsoft.AspNetCore.Authentication.AuthenticationBuilder AddCookie(this Microsoft.AspNetCore.Authentication.AuthenticationBuilder builder, string authenticationScheme, string displayName, System.Action configureOptions) where TService : class { throw null; }
}
}
diff --git a/src/Security/Authentication/Cookies/samples/CookieSessionSample/Startup.cs b/src/Security/Authentication/Cookies/samples/CookieSessionSample/Startup.cs
index f7b8f2bb88..c538866d7e 100644
--- a/src/Security/Authentication/Cookies/samples/CookieSessionSample/Startup.cs
+++ b/src/Security/Authentication/Cookies/samples/CookieSessionSample/Startup.cs
@@ -14,12 +14,14 @@ namespace CookieSessionSample
{
public void ConfigureServices(IServiceCollection services)
{
+ services.AddSingleton();
+
// This can be removed after https://github.com/aspnet/IISIntegration/issues/371
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
- }).AddCookie(o => o.SessionStore = new MemoryCacheTicketStore());
+ }).AddCookie((o, ticketStore) => o.SessionStore = ticketStore);
}
public void Configure(IApplicationBuilder app)
diff --git a/src/Security/Authentication/Cookies/src/CookieExtensions.cs b/src/Security/Authentication/Cookies/src/CookieExtensions.cs
index 7763e6a624..a67a708149 100644
--- a/src/Security/Authentication/Cookies/src/CookieExtensions.cs
+++ b/src/Security/Authentication/Cookies/src/CookieExtensions.cs
@@ -15,19 +15,40 @@ namespace Microsoft.Extensions.DependencyInjection
=> builder.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme);
public static AuthenticationBuilder AddCookie(this AuthenticationBuilder builder, string authenticationScheme)
- => builder.AddCookie(authenticationScheme, configureOptions: null);
+ => builder.AddCookie(authenticationScheme, configureOptions: (Action)null);
- public static AuthenticationBuilder AddCookie(this AuthenticationBuilder builder, Action configureOptions)
+ public static AuthenticationBuilder AddCookie(this AuthenticationBuilder builder, Action configureOptions)
+ => builder.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, configureOptions);
+
+ public static AuthenticationBuilder AddCookie(this AuthenticationBuilder builder, Action configureOptions) where TService : class
=> builder.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, configureOptions);
public static AuthenticationBuilder AddCookie(this AuthenticationBuilder builder, string authenticationScheme, Action configureOptions)
=> builder.AddCookie(authenticationScheme, displayName: null, configureOptions: configureOptions);
+ public static AuthenticationBuilder AddCookie(this AuthenticationBuilder builder, string authenticationScheme, Action configureOptions) where TService : class
+ => builder.AddCookie(authenticationScheme, displayName: null, configureOptions: configureOptions);
+
public static AuthenticationBuilder AddCookie(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action configureOptions)
+ {
+ Action configureOptionsWithServices;
+ if (configureOptions == null)
+ {
+ configureOptionsWithServices = null;
+ }
+ else
+ {
+ configureOptionsWithServices = (options, _) => configureOptions(options);
+ }
+
+ return builder.AddCookie(authenticationScheme, displayName, configureOptionsWithServices);
+ }
+
+ public static AuthenticationBuilder AddCookie(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action configureOptions) where TService : class
{
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton, PostConfigureCookieAuthenticationOptions>());
builder.Services.AddOptions(authenticationScheme).Validate(o => o.Cookie.Expiration == null, "Cookie.Expiration is ignored, use ExpireTimeSpan instead.");
- return builder.AddScheme(authenticationScheme, displayName, configureOptions);
+ return builder.AddScheme(authenticationScheme, displayName, configureOptions);
}
}
}
diff --git a/src/Security/Authentication/Core/ref/Microsoft.AspNetCore.Authentication.netcoreapp.cs b/src/Security/Authentication/Core/ref/Microsoft.AspNetCore.Authentication.netcoreapp.cs
index 1a1d21a774..1cf17c4f5f 100644
--- a/src/Security/Authentication/Core/ref/Microsoft.AspNetCore.Authentication.netcoreapp.cs
+++ b/src/Security/Authentication/Core/ref/Microsoft.AspNetCore.Authentication.netcoreapp.cs
@@ -16,9 +16,13 @@ namespace Microsoft.AspNetCore.Authentication
public AuthenticationBuilder(Microsoft.Extensions.DependencyInjection.IServiceCollection services) { }
public virtual Microsoft.Extensions.DependencyInjection.IServiceCollection Services { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } }
public virtual Microsoft.AspNetCore.Authentication.AuthenticationBuilder AddPolicyScheme(string authenticationScheme, string displayName, System.Action configureOptions) { throw null; }
+ public virtual Microsoft.AspNetCore.Authentication.AuthenticationBuilder AddPolicyScheme(string authenticationScheme, string displayName, System.Action configureOptions) where TService : class { throw null; }
public virtual Microsoft.AspNetCore.Authentication.AuthenticationBuilder AddRemoteScheme(string authenticationScheme, string displayName, System.Action configureOptions) where TOptions : Microsoft.AspNetCore.Authentication.RemoteAuthenticationOptions, new() where THandler : Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler { throw null; }
+ public virtual Microsoft.AspNetCore.Authentication.AuthenticationBuilder AddRemoteScheme(string authenticationScheme, string displayName, System.Action configureOptions) where TOptions : Microsoft.AspNetCore.Authentication.RemoteAuthenticationOptions, new() where THandler : Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler where TService : class { throw null; }
public virtual Microsoft.AspNetCore.Authentication.AuthenticationBuilder AddScheme(string authenticationScheme, System.Action configureOptions) where TOptions : Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions, new() where THandler : Microsoft.AspNetCore.Authentication.AuthenticationHandler { throw null; }
public virtual Microsoft.AspNetCore.Authentication.AuthenticationBuilder AddScheme(string authenticationScheme, string displayName, System.Action configureOptions) where TOptions : Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions, new() where THandler : Microsoft.AspNetCore.Authentication.AuthenticationHandler { throw null; }
+ public virtual Microsoft.AspNetCore.Authentication.AuthenticationBuilder AddScheme(string authenticationScheme, System.Action configureOptions) where TOptions : Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions, new() where THandler : Microsoft.AspNetCore.Authentication.AuthenticationHandler where TService : class { throw null; }
+ public virtual Microsoft.AspNetCore.Authentication.AuthenticationBuilder AddScheme(string authenticationScheme, string displayName, System.Action configureOptions) where TOptions : Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions, new() where THandler : Microsoft.AspNetCore.Authentication.AuthenticationHandler where TService : class { throw null; }
}
public abstract partial class AuthenticationHandler : Microsoft.AspNetCore.Authentication.IAuthenticationHandler where TOptions : Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions, new()
{
diff --git a/src/Security/Authentication/Core/src/AuthenticationBuilder.cs b/src/Security/Authentication/Core/src/AuthenticationBuilder.cs
index d4efd0c847..829fe007d7 100644
--- a/src/Security/Authentication/Core/src/AuthenticationBuilder.cs
+++ b/src/Security/Authentication/Core/src/AuthenticationBuilder.cs
@@ -25,25 +25,31 @@ namespace Microsoft.AspNetCore.Authentication
///
public virtual IServiceCollection Services { get; }
- private AuthenticationBuilder AddSchemeHelper(string authenticationScheme, string displayName, Action configureOptions)
+ private AuthenticationBuilder AddSchemeHelper(string authenticationScheme, string displayName, Action configureOptions) where TService : class
where TOptions : AuthenticationSchemeOptions, new()
where THandler : class, IAuthenticationHandler
{
Services.Configure(o =>
{
- o.AddScheme(authenticationScheme, scheme => {
+ o.AddScheme(authenticationScheme, scheme =>
+ {
scheme.HandlerType = typeof(THandler);
scheme.DisplayName = displayName;
});
});
+
+ var optionsBuilder = Services.AddOptions(authenticationScheme)
+ .Validate(o =>
+ {
+ o.Validate(authenticationScheme);
+ return true;
+ });
+
if (configureOptions != null)
{
- Services.Configure(authenticationScheme, configureOptions);
+ optionsBuilder.Configure(configureOptions);
}
- Services.AddOptions(authenticationScheme).Validate(o => {
- o.Validate(authenticationScheme);
- return true;
- });
+
Services.AddTransient();
return this;
}
@@ -60,7 +66,22 @@ namespace Microsoft.AspNetCore.Authentication
public virtual AuthenticationBuilder AddScheme(string authenticationScheme, string displayName, Action configureOptions)
where TOptions : AuthenticationSchemeOptions, new()
where THandler : AuthenticationHandler
- => AddSchemeHelper(authenticationScheme, displayName, configureOptions);
+ => AddSchemeHelper(authenticationScheme, displayName, MapConfiguration(configureOptions));
+
+ ///
+ /// Adds a which can be used by .
+ ///
+ /// The type to configure the handler."/>.
+ /// The used to handle this scheme.
+ /// TService: A service resolved from the IServiceProvider for use when configuring this authentication provider. If you need multiple services then specify IServiceProvider and resolve them directly.
+ /// The name of this scheme.
+ /// The display name of this scheme.
+ /// Used to configure the scheme options.
+ /// The builder.
+ public virtual AuthenticationBuilder AddScheme(string authenticationScheme, string displayName, Action configureOptions) where TService : class
+ where TOptions : AuthenticationSchemeOptions, new()
+ where THandler : AuthenticationHandler
+ => AddSchemeHelper(authenticationScheme, displayName, configureOptions);
///
/// Adds a which can be used by .
@@ -75,6 +96,20 @@ namespace Microsoft.AspNetCore.Authentication
where THandler : AuthenticationHandler
=> AddScheme(authenticationScheme, displayName: null, configureOptions: configureOptions);
+ ///
+ /// Adds a which can be used by .
+ ///
+ /// The type to configure the handler."/>.
+ /// The used to handle this scheme.
+ /// TService: A service resolved from the IServiceProvider for use when configuring this authentication provider. If you need multiple services then specify IServiceProvider and resolve them directly.
+ /// The name of this scheme.
+ /// Used to configure the scheme options.
+ /// The builder.
+ public virtual AuthenticationBuilder AddScheme(string authenticationScheme, Action configureOptions) where TService : class
+ where TOptions : AuthenticationSchemeOptions, new()
+ where THandler : AuthenticationHandler
+ => AddScheme(authenticationScheme, displayName: null, configureOptions: configureOptions);
+
///
/// Adds a based that supports remote authentication
/// which can be used by .
@@ -93,6 +128,25 @@ namespace Microsoft.AspNetCore.Authentication
return AddScheme(authenticationScheme, displayName, configureOptions: configureOptions);
}
+ ///
+ /// Adds a based that supports remote authentication
+ /// which can be used by .
+ ///
+ /// The type to configure the handler."/>.
+ /// The used to handle this scheme.
+ /// TService: A service resolved from the IServiceProvider for use when configuring this authentication provider. If you need multiple services then specify IServiceProvider and resolve them directly.
+ /// The name of this scheme.
+ /// The display name of this scheme.
+ /// Used to configure the scheme options.
+ /// The builder.
+ public virtual AuthenticationBuilder AddRemoteScheme(string authenticationScheme, string displayName, Action configureOptions) where TService : class
+ where TOptions : RemoteAuthenticationOptions, new()
+ where THandler : RemoteAuthenticationHandler
+ {
+ Services.TryAddEnumerable(ServiceDescriptor.Singleton, EnsureSignInScheme>());
+ return AddScheme(authenticationScheme, displayName, configureOptions: configureOptions);
+ }
+
///
/// Adds a based authentication handler which can be used to
/// redirect to other authentication schemes.
@@ -102,7 +156,30 @@ namespace Microsoft.AspNetCore.Authentication
/// Used to configure the scheme options.
/// The builder.
public virtual AuthenticationBuilder AddPolicyScheme(string authenticationScheme, string displayName, Action configureOptions)
- => AddSchemeHelper(authenticationScheme, displayName, configureOptions);
+ => AddSchemeHelper(authenticationScheme, displayName, MapConfiguration(configureOptions));
+
+ ///
+ /// Adds a based authentication handler which can be used to
+ /// redirect to other authentication schemes.
+ ///
+ /// The name of this scheme.
+ /// The display name of this scheme.
+ /// Used to configure the scheme options.
+ /// The builder.
+ public virtual AuthenticationBuilder AddPolicyScheme(string authenticationScheme, string displayName, Action configureOptions) where TService : class
+ => AddSchemeHelper(authenticationScheme, displayName, configureOptions);
+
+ private Action MapConfiguration(Action configureOptions)
+ {
+ if (configureOptions == null)
+ {
+ return null;
+ }
+ else
+ {
+ return (options, _) => configureOptions(options);
+ }
+ }
// Used to ensure that there's always a default sign in scheme that's not itself
private class EnsureSignInScheme : IPostConfigureOptions where TOptions : RemoteAuthenticationOptions
diff --git a/src/Security/Authentication/Facebook/src/FacebookExtensions.cs b/src/Security/Authentication/Facebook/src/FacebookExtensions.cs
index 2273724a42..b32b35c6c1 100644
--- a/src/Security/Authentication/Facebook/src/FacebookExtensions.cs
+++ b/src/Security/Authentication/Facebook/src/FacebookExtensions.cs
@@ -15,10 +15,31 @@ namespace Microsoft.Extensions.DependencyInjection
public static AuthenticationBuilder AddFacebook(this AuthenticationBuilder builder, Action configureOptions)
=> builder.AddFacebook(FacebookDefaults.AuthenticationScheme, configureOptions);
+ public static AuthenticationBuilder AddFacebook(this AuthenticationBuilder builder, Action configureOptions) where TService : class
+ => builder.AddFacebook(FacebookDefaults.AuthenticationScheme, configureOptions);
+
public static AuthenticationBuilder AddFacebook(this AuthenticationBuilder builder, string authenticationScheme, Action configureOptions)
=> builder.AddFacebook(authenticationScheme, FacebookDefaults.DisplayName, configureOptions);
+ public static AuthenticationBuilder AddFacebook(this AuthenticationBuilder builder, string authenticationScheme, Action configureOptions) where TService : class
+ => builder.AddFacebook(authenticationScheme, FacebookDefaults.DisplayName, configureOptions);
+
public static AuthenticationBuilder AddFacebook(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action configureOptions)
- => builder.AddOAuth(authenticationScheme, displayName, configureOptions);
+ {
+ Action configureOptionsWithServices;
+ if (configureOptions == null)
+ {
+ configureOptionsWithServices = null;
+ }
+ else
+ {
+ configureOptionsWithServices = (options, _) => configureOptions(options);
+ }
+
+ return builder.AddFacebook(authenticationScheme, displayName, configureOptionsWithServices);
+ }
+
+ public static AuthenticationBuilder AddFacebook(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action configureOptions) where TService : class
+ => builder.AddOAuth(authenticationScheme, displayName, configureOptions);
}
}
diff --git a/src/Security/Authentication/Google/src/GoogleExtensions.cs b/src/Security/Authentication/Google/src/GoogleExtensions.cs
index 95547014ca..88add9610d 100644
--- a/src/Security/Authentication/Google/src/GoogleExtensions.cs
+++ b/src/Security/Authentication/Google/src/GoogleExtensions.cs
@@ -15,10 +15,31 @@ namespace Microsoft.Extensions.DependencyInjection
public static AuthenticationBuilder AddGoogle(this AuthenticationBuilder builder, Action configureOptions)
=> builder.AddGoogle(GoogleDefaults.AuthenticationScheme, configureOptions);
+ public static AuthenticationBuilder AddGoogle(this AuthenticationBuilder builder, Action configureOptions) where TService : class
+ => builder.AddGoogle(GoogleDefaults.AuthenticationScheme, configureOptions);
+
public static AuthenticationBuilder AddGoogle(this AuthenticationBuilder builder, string authenticationScheme, Action configureOptions)
=> builder.AddGoogle(authenticationScheme, GoogleDefaults.DisplayName, configureOptions);
+ public static AuthenticationBuilder AddGoogle(this AuthenticationBuilder builder, string authenticationScheme, Action configureOptions) where TService : class
+ => builder.AddGoogle(authenticationScheme, GoogleDefaults.DisplayName, configureOptions);
+
public static AuthenticationBuilder AddGoogle(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action configureOptions)
- => builder.AddOAuth(authenticationScheme, displayName, configureOptions);
+ {
+ Action configureOptionsWithServices;
+ if (configureOptions == null)
+ {
+ configureOptionsWithServices = null;
+ }
+ else
+ {
+ configureOptionsWithServices = (options, _) => configureOptions(options);
+ }
+
+ return builder.AddGoogle(authenticationScheme, displayName, configureOptionsWithServices);
+ }
+
+ public static AuthenticationBuilder AddGoogle(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action configureOptions) where TService : class
+ => builder.AddOAuth(authenticationScheme, displayName, configureOptions);
}
}
diff --git a/src/Security/Authentication/JwtBearer/src/JwtBearerExtensions.cs b/src/Security/Authentication/JwtBearer/src/JwtBearerExtensions.cs
index 334407c0da..a5aa46a451 100644
--- a/src/Security/Authentication/JwtBearer/src/JwtBearerExtensions.cs
+++ b/src/Security/Authentication/JwtBearer/src/JwtBearerExtensions.cs
@@ -17,13 +17,34 @@ namespace Microsoft.Extensions.DependencyInjection
public static AuthenticationBuilder AddJwtBearer(this AuthenticationBuilder builder, Action configureOptions)
=> builder.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, configureOptions);
+ public static AuthenticationBuilder AddJwtBearer(this AuthenticationBuilder builder, Action configureOptions) where TService : class
+ => builder.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, configureOptions);
+
public static AuthenticationBuilder AddJwtBearer(this AuthenticationBuilder builder, string authenticationScheme, Action configureOptions)
=> builder.AddJwtBearer(authenticationScheme, displayName: null, configureOptions: configureOptions);
+ public static AuthenticationBuilder AddJwtBearer(this AuthenticationBuilder builder, string authenticationScheme, Action configureOptions) where TService : class
+ => builder.AddJwtBearer(authenticationScheme, displayName: null, configureOptions: configureOptions);
+
public static AuthenticationBuilder AddJwtBearer(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action configureOptions)
+ {
+ Action configureOptionsWithServices;
+ if (configureOptions == null)
+ {
+ configureOptionsWithServices = null;
+ }
+ else
+ {
+ configureOptionsWithServices = (options, _) => configureOptions(options);
+ }
+
+ return builder.AddJwtBearer(authenticationScheme, displayName, configureOptionsWithServices);
+ }
+
+ public static AuthenticationBuilder AddJwtBearer(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action configureOptions) where TService : class
{
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton, JwtBearerPostConfigureOptions>());
- return builder.AddScheme(authenticationScheme, displayName, configureOptions);
+ return builder.AddScheme(authenticationScheme, displayName, configureOptions);
}
}
}
diff --git a/src/Security/Authentication/MicrosoftAccount/src/MicrosoftAccountExtensions.cs b/src/Security/Authentication/MicrosoftAccount/src/MicrosoftAccountExtensions.cs
index 7f24e5af77..0c59ce3504 100644
--- a/src/Security/Authentication/MicrosoftAccount/src/MicrosoftAccountExtensions.cs
+++ b/src/Security/Authentication/MicrosoftAccount/src/MicrosoftAccountExtensions.cs
@@ -15,10 +15,31 @@ namespace Microsoft.Extensions.DependencyInjection
public static AuthenticationBuilder AddMicrosoftAccount(this AuthenticationBuilder builder, Action configureOptions)
=> builder.AddMicrosoftAccount(MicrosoftAccountDefaults.AuthenticationScheme, configureOptions);
+ public static AuthenticationBuilder AddMicrosoftAccount(this AuthenticationBuilder builder, Action configureOptions) where TService : class
+ => builder.AddMicrosoftAccount(MicrosoftAccountDefaults.AuthenticationScheme, configureOptions);
+
public static AuthenticationBuilder AddMicrosoftAccount(this AuthenticationBuilder builder, string authenticationScheme, Action configureOptions)
=> builder.AddMicrosoftAccount(authenticationScheme, MicrosoftAccountDefaults.DisplayName, configureOptions);
+ public static AuthenticationBuilder AddMicrosoftAccount(this AuthenticationBuilder builder, string authenticationScheme, Action configureOptions) where TService : class
+ => builder.AddMicrosoftAccount(authenticationScheme, MicrosoftAccountDefaults.DisplayName, configureOptions);
+
public static AuthenticationBuilder AddMicrosoftAccount(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action configureOptions)
- => builder.AddOAuth(authenticationScheme, displayName, configureOptions);
+ {
+ Action configureOptionsWithServices;
+ if (configureOptions == null)
+ {
+ configureOptionsWithServices = null;
+ }
+ else
+ {
+ configureOptionsWithServices = (options, _) => configureOptions(options);
+ }
+
+ return builder.AddMicrosoftAccount(authenticationScheme, displayName, configureOptionsWithServices);
+ }
+
+ public static AuthenticationBuilder AddMicrosoftAccount(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action configureOptions) where TService : class
+ => builder.AddOAuth(authenticationScheme, displayName, configureOptions);
}
-}
\ No newline at end of file
+}
diff --git a/src/Security/Authentication/Negotiate/src/NegotiateExtensions.cs b/src/Security/Authentication/Negotiate/src/NegotiateExtensions.cs
index f5bbf8cbc8..401c3dc839 100644
--- a/src/Security/Authentication/Negotiate/src/NegotiateExtensions.cs
+++ b/src/Security/Authentication/Negotiate/src/NegotiateExtensions.cs
@@ -31,6 +31,16 @@ namespace Microsoft.Extensions.DependencyInjection
public static AuthenticationBuilder AddNegotiate(this AuthenticationBuilder builder, Action configureOptions)
=> builder.AddNegotiate(NegotiateDefaults.AuthenticationScheme, configureOptions);
+ ///
+ /// Adds and configures Negotiate authentication.
+ ///
+ /// TService: A service resolved from the IServiceProvider for use when configuring this authentication provider. If you need multiple services then specify IServiceProvider and resolve them directly.
+ /// The .
+ /// Allows for configuring the authentication handler.
+ /// The original builder.
+ public static AuthenticationBuilder AddNegotiate(this AuthenticationBuilder builder, Action configureOptions) where TService : class
+ => builder.AddNegotiate(NegotiateDefaults.AuthenticationScheme, configureOptions);
+
///
/// Adds and configures Negotiate authentication.
///
@@ -41,6 +51,17 @@ namespace Microsoft.Extensions.DependencyInjection
public static AuthenticationBuilder AddNegotiate(this AuthenticationBuilder builder, string authenticationScheme, Action configureOptions)
=> builder.AddNegotiate(authenticationScheme, displayName: null, configureOptions: configureOptions);
+ ///
+ /// Adds and configures Negotiate authentication.
+ ///
+ /// TService: A service resolved from the IServiceProvider for use when configuring this authentication provider. If you need multiple services then specify IServiceProvider and resolve them directly.
+ /// The .
+ /// The scheme name used to identify the authentication handler internally.
+ /// Allows for configuring the authentication handler.
+ /// The original builder.
+ public static AuthenticationBuilder AddNegotiate(this AuthenticationBuilder builder, string authenticationScheme, Action configureOptions) where TService : class
+ => builder.AddNegotiate(authenticationScheme, displayName: null, configureOptions: configureOptions);
+
///
/// Adds and configures Negotiate authentication.
///
@@ -50,9 +71,33 @@ namespace Microsoft.Extensions.DependencyInjection
/// Allows for configuring the authentication handler.
/// The original builder.
public static AuthenticationBuilder AddNegotiate(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action configureOptions)
+ {
+ Action configureOptionsWithServices;
+ if (configureOptions == null)
+ {
+ configureOptionsWithServices = null;
+ }
+ else
+ {
+ configureOptionsWithServices = (options, _) => configureOptions(options);
+ }
+
+ return builder.AddNegotiate(authenticationScheme, displayName, configureOptionsWithServices);
+ }
+
+ ///
+ /// Adds and configures Negotiate authentication.
+ ///
+ /// TService: A service resolved from the IServiceProvider for use when configuring this authentication provider. If you need multiple services then specify IServiceProvider and resolve them directly.
+ /// The .
+ /// The scheme name used to identify the authentication handler internally.
+ /// The name displayed to users when selecting an authentication handler. The default is null to prevent this from displaying.
+ /// Allows for configuring the authentication handler.
+ /// The original builder.
+ public static AuthenticationBuilder AddNegotiate(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action configureOptions) where TService : class
{
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton, PostConfigureNegotiateOptions>());
- return builder.AddScheme(authenticationScheme, displayName, configureOptions);
+ return builder.AddScheme(authenticationScheme, displayName, configureOptions);
}
}
}
diff --git a/src/Security/Authentication/Negotiate/test/Negotiate.Test/EventTests.cs b/src/Security/Authentication/Negotiate/test/Negotiate.Test/EventTests.cs
index bc18f861b5..0979640207 100644
--- a/src/Security/Authentication/Negotiate/test/Negotiate.Test/EventTests.cs
+++ b/src/Security/Authentication/Negotiate/test/Negotiate.Test/EventTests.cs
@@ -21,7 +21,6 @@ using Xunit;
namespace Microsoft.AspNetCore.Authentication.Negotiate
{
- [QuarantinedTest]
public class EventTests
{
[Fact]
diff --git a/src/Security/Authentication/Negotiate/test/Negotiate.Test/NegotiateHandlerTests.cs b/src/Security/Authentication/Negotiate/test/Negotiate.Test/NegotiateHandlerTests.cs
index 89685b286b..0b0b7b14cd 100644
--- a/src/Security/Authentication/Negotiate/test/Negotiate.Test/NegotiateHandlerTests.cs
+++ b/src/Security/Authentication/Negotiate/test/Negotiate.Test/NegotiateHandlerTests.cs
@@ -22,7 +22,6 @@ using Xunit.Sdk;
namespace Microsoft.AspNetCore.Authentication.Negotiate
{
- [QuarantinedTest]
public class NegotiateHandlerTests
{
[Fact]
diff --git a/src/Security/Authentication/Negotiate/test/Negotiate.Test/ServerDeferralTests.cs b/src/Security/Authentication/Negotiate/test/Negotiate.Test/ServerDeferralTests.cs
index e5cf8ca7b6..efd513b829 100644
--- a/src/Security/Authentication/Negotiate/test/Negotiate.Test/ServerDeferralTests.cs
+++ b/src/Security/Authentication/Negotiate/test/Negotiate.Test/ServerDeferralTests.cs
@@ -15,7 +15,6 @@ using Xunit;
namespace Microsoft.AspNetCore.Authentication.Negotiate
{
- [QuarantinedTest]
public class ServerDeferralTests
{
[Fact]
diff --git a/src/Security/Authentication/OAuth/ref/Microsoft.AspNetCore.Authentication.OAuth.netcoreapp.cs b/src/Security/Authentication/OAuth/ref/Microsoft.AspNetCore.Authentication.OAuth.netcoreapp.cs
index 2301daf1ad..94d2fc4d37 100644
--- a/src/Security/Authentication/OAuth/ref/Microsoft.AspNetCore.Authentication.OAuth.netcoreapp.cs
+++ b/src/Security/Authentication/OAuth/ref/Microsoft.AspNetCore.Authentication.OAuth.netcoreapp.cs
@@ -168,8 +168,12 @@ namespace Microsoft.Extensions.DependencyInjection
{
public static Microsoft.AspNetCore.Authentication.AuthenticationBuilder AddOAuth(this Microsoft.AspNetCore.Authentication.AuthenticationBuilder builder, string authenticationScheme, System.Action configureOptions) { throw null; }
public static Microsoft.AspNetCore.Authentication.AuthenticationBuilder AddOAuth(this Microsoft.AspNetCore.Authentication.AuthenticationBuilder builder, string authenticationScheme, string displayName, System.Action configureOptions) { throw null; }
+ public static Microsoft.AspNetCore.Authentication.AuthenticationBuilder AddOAuth(this Microsoft.AspNetCore.Authentication.AuthenticationBuilder builder, string authenticationScheme, System.Action configureOptions) where TService : class { throw null; }
+ public static Microsoft.AspNetCore.Authentication.AuthenticationBuilder AddOAuth(this Microsoft.AspNetCore.Authentication.AuthenticationBuilder builder, string authenticationScheme, string displayName, System.Action configureOptions) where TService : class { throw null; }
public static Microsoft.AspNetCore.Authentication.AuthenticationBuilder AddOAuth(this Microsoft.AspNetCore.Authentication.AuthenticationBuilder builder, string authenticationScheme, System.Action configureOptions) where TOptions : Microsoft.AspNetCore.Authentication.OAuth.OAuthOptions, new() where THandler : Microsoft.AspNetCore.Authentication.OAuth.OAuthHandler { throw null; }
public static Microsoft.AspNetCore.Authentication.AuthenticationBuilder AddOAuth(this Microsoft.AspNetCore.Authentication.AuthenticationBuilder builder, string authenticationScheme, string displayName, System.Action configureOptions) where TOptions : Microsoft.AspNetCore.Authentication.OAuth.OAuthOptions, new() where THandler : Microsoft.AspNetCore.Authentication.OAuth.OAuthHandler { throw null; }
+ public static Microsoft.AspNetCore.Authentication.AuthenticationBuilder AddOAuth(this Microsoft.AspNetCore.Authentication.AuthenticationBuilder builder, string authenticationScheme, System.Action configureOptions) where TOptions : Microsoft.AspNetCore.Authentication.OAuth.OAuthOptions, new() where THandler : Microsoft.AspNetCore.Authentication.OAuth.OAuthHandler where TService : class { throw null; }
+ public static Microsoft.AspNetCore.Authentication.AuthenticationBuilder AddOAuth(this Microsoft.AspNetCore.Authentication.AuthenticationBuilder builder, string authenticationScheme, string displayName, System.Action configureOptions) where TOptions : Microsoft.AspNetCore.Authentication.OAuth.OAuthOptions, new() where THandler : Microsoft.AspNetCore.Authentication.OAuth.OAuthHandler where TService : class { throw null; }
}
public partial class OAuthPostConfigureOptions : Microsoft.Extensions.Options.IPostConfigureOptions where TOptions : Microsoft.AspNetCore.Authentication.OAuth.OAuthOptions, new() where THandler : Microsoft.AspNetCore.Authentication.OAuth.OAuthHandler
{
diff --git a/src/Security/Authentication/OAuth/src/OAuthExtensions.cs b/src/Security/Authentication/OAuth/src/OAuthExtensions.cs
index 22c541a0ac..69bb2d73d3 100644
--- a/src/Security/Authentication/OAuth/src/OAuthExtensions.cs
+++ b/src/Security/Authentication/OAuth/src/OAuthExtensions.cs
@@ -14,20 +14,50 @@ namespace Microsoft.Extensions.DependencyInjection
public static AuthenticationBuilder AddOAuth(this AuthenticationBuilder builder, string authenticationScheme, Action configureOptions)
=> builder.AddOAuth>(authenticationScheme, configureOptions);
+ public static AuthenticationBuilder AddOAuth(this AuthenticationBuilder builder, string authenticationScheme, Action configureOptions) where TService : class
+ => builder.AddOAuth, TService>(authenticationScheme, configureOptions);
+
public static AuthenticationBuilder AddOAuth(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action configureOptions)
=> builder.AddOAuth>(authenticationScheme, displayName, configureOptions);
+ public static AuthenticationBuilder AddOAuth(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action configureOptions) where TService : class
+ => builder.AddOAuth, TService>(authenticationScheme, displayName, configureOptions);
+
public static AuthenticationBuilder AddOAuth(this AuthenticationBuilder builder, string authenticationScheme, Action configureOptions)
where TOptions : OAuthOptions, new()
where THandler : OAuthHandler
=> builder.AddOAuth(authenticationScheme, OAuthDefaults.DisplayName, configureOptions);
+ public static AuthenticationBuilder AddOAuth(this AuthenticationBuilder builder, string authenticationScheme, Action configureOptions)
+ where TOptions : OAuthOptions, new()
+ where THandler : OAuthHandler
+ where TService : class
+ => builder.AddOAuth(authenticationScheme, OAuthDefaults.DisplayName, configureOptions);
+
public static AuthenticationBuilder AddOAuth(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action configureOptions)
where TOptions : OAuthOptions, new()
where THandler : OAuthHandler
+ {
+ Action configureOptionsWithServices;
+ if (configureOptions == null)
+ {
+ configureOptionsWithServices = null;
+ }
+ else
+ {
+ configureOptionsWithServices = (options, _) => configureOptions(options);
+ }
+
+ return builder.AddOAuth(authenticationScheme, displayName, configureOptionsWithServices);
+ }
+
+ public static AuthenticationBuilder AddOAuth(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action configureOptions)
+ where TOptions : OAuthOptions, new()
+ where THandler : OAuthHandler
+ where TService : class
{
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton, OAuthPostConfigureOptions>());
- return builder.AddRemoteScheme(authenticationScheme, displayName, configureOptions);
+ return builder.AddRemoteScheme(authenticationScheme, displayName, configureOptions);
}
}
}
diff --git a/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectExtensions.cs b/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectExtensions.cs
index f427bebaff..482452bca2 100644
--- a/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectExtensions.cs
+++ b/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectExtensions.cs
@@ -17,13 +17,34 @@ namespace Microsoft.Extensions.DependencyInjection
public static AuthenticationBuilder AddOpenIdConnect(this AuthenticationBuilder builder, Action configureOptions)
=> builder.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, configureOptions);
+ public static AuthenticationBuilder AddOpenIdConnect(this AuthenticationBuilder builder, Action configureOptions) where TService : class
+ => builder.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, configureOptions);
+
public static AuthenticationBuilder AddOpenIdConnect(this AuthenticationBuilder builder, string authenticationScheme, Action configureOptions)
=> builder.AddOpenIdConnect(authenticationScheme, OpenIdConnectDefaults.DisplayName, configureOptions);
+ public static AuthenticationBuilder AddOpenIdConnect(this AuthenticationBuilder builder, string authenticationScheme, Action configureOptions) where TService : class
+ => builder.AddOpenIdConnect(authenticationScheme, OpenIdConnectDefaults.DisplayName, configureOptions);
+
public static AuthenticationBuilder AddOpenIdConnect(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action configureOptions)
+ {
+ Action configureOptionsWithServices;
+ if (configureOptions == null)
+ {
+ configureOptionsWithServices = null;
+ }
+ else
+ {
+ configureOptionsWithServices = (options, _) => configureOptions(options);
+ }
+
+ return builder.AddOpenIdConnect(authenticationScheme, displayName, configureOptionsWithServices);
+ }
+
+ public static AuthenticationBuilder AddOpenIdConnect(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action configureOptions) where TService : class
{
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton, OpenIdConnectPostConfigureOptions>());
- return builder.AddRemoteScheme(authenticationScheme, displayName, configureOptions);
+ return builder.AddRemoteScheme(authenticationScheme, displayName, configureOptions);
}
}
}
diff --git a/src/Security/Authentication/Twitter/src/TwitterExtensions.cs b/src/Security/Authentication/Twitter/src/TwitterExtensions.cs
index 7243805692..f6f6b93a9e 100644
--- a/src/Security/Authentication/Twitter/src/TwitterExtensions.cs
+++ b/src/Security/Authentication/Twitter/src/TwitterExtensions.cs
@@ -17,13 +17,34 @@ namespace Microsoft.Extensions.DependencyInjection
public static AuthenticationBuilder AddTwitter(this AuthenticationBuilder builder, Action configureOptions)
=> builder.AddTwitter(TwitterDefaults.AuthenticationScheme, configureOptions);
+ public static AuthenticationBuilder AddTwitter(this AuthenticationBuilder builder, Action configureOptions) where TService : class
+ => builder.AddTwitter(TwitterDefaults.AuthenticationScheme, configureOptions);
+
public static AuthenticationBuilder AddTwitter(this AuthenticationBuilder builder, string authenticationScheme, Action configureOptions)
=> builder.AddTwitter(authenticationScheme, TwitterDefaults.DisplayName, configureOptions);
+ public static AuthenticationBuilder AddTwitter(this AuthenticationBuilder builder, string authenticationScheme, Action configureOptions) where TService : class
+ => builder.AddTwitter(authenticationScheme, TwitterDefaults.DisplayName, configureOptions);
+
public static AuthenticationBuilder AddTwitter(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action configureOptions)
+ {
+ Action configureOptionsWithServices;
+ if (configureOptions == null)
+ {
+ configureOptionsWithServices = null;
+ }
+ else
+ {
+ configureOptionsWithServices = (options, _) => configureOptions(options);
+ }
+
+ return builder.AddTwitter(authenticationScheme, displayName, configureOptionsWithServices);
+ }
+
+ public static AuthenticationBuilder AddTwitter(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action configureOptions) where TService : class
{
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton, TwitterPostConfigureOptions>());
- return builder.AddRemoteScheme(authenticationScheme, displayName, configureOptions);
+ return builder.AddRemoteScheme(authenticationScheme, displayName, configureOptions);
}
}
}
diff --git a/src/Security/Authentication/WsFederation/src/WsFederationExtensions.cs b/src/Security/Authentication/WsFederation/src/WsFederationExtensions.cs
index 47091d58d5..6a9ffad239 100644
--- a/src/Security/Authentication/WsFederation/src/WsFederationExtensions.cs
+++ b/src/Security/Authentication/WsFederation/src/WsFederationExtensions.cs
@@ -31,6 +31,16 @@ namespace Microsoft.Extensions.DependencyInjection
public static AuthenticationBuilder AddWsFederation(this AuthenticationBuilder builder, Action configureOptions)
=> builder.AddWsFederation(WsFederationDefaults.AuthenticationScheme, configureOptions);
+ ///
+ /// Registers the using the default authentication scheme, display name, and the given options configuration.
+ ///
+ /// TService: A service resolved from the IServiceProvider for use when configuring this authentication provider. If you need multiple services then specify IServiceProvider and resolve them directly.
+ ///
+ /// A delegate that configures the .
+ ///
+ public static AuthenticationBuilder AddWsFederation(this AuthenticationBuilder builder, Action configureOptions) where TService : class
+ => builder.AddWsFederation(WsFederationDefaults.AuthenticationScheme, configureOptions);
+
///
/// Registers the using the given authentication scheme, default display name, and the given options configuration.
///
@@ -41,6 +51,17 @@ namespace Microsoft.Extensions.DependencyInjection
public static AuthenticationBuilder AddWsFederation(this AuthenticationBuilder builder, string authenticationScheme, Action configureOptions)
=> builder.AddWsFederation(authenticationScheme, WsFederationDefaults.DisplayName, configureOptions);
+ ///
+ /// Registers the using the given authentication scheme, default display name, and the given options configuration.
+ ///
+ /// TService: A service resolved from the IServiceProvider for use when configuring this authentication provider. If you need multiple services then specify IServiceProvider and resolve them directly.
+ ///
+ ///
+ /// A delegate that configures the .
+ ///
+ public static AuthenticationBuilder AddWsFederation(this AuthenticationBuilder builder, string authenticationScheme, Action configureOptions) where TService : class
+ => builder.AddWsFederation(authenticationScheme, WsFederationDefaults.DisplayName, configureOptions);
+
///
/// Registers the using the given authentication scheme, display name, and options configuration.
///
@@ -50,9 +71,33 @@ namespace Microsoft.Extensions.DependencyInjection
/// A delegate that configures the .
///
public static AuthenticationBuilder AddWsFederation(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action configureOptions)
+ {
+ Action configureOptionsWithServices;
+ if (configureOptions == null)
+ {
+ configureOptionsWithServices = null;
+ }
+ else
+ {
+ configureOptionsWithServices = (options, _) => configureOptions(options);
+ }
+
+ return builder.AddWsFederation(authenticationScheme, displayName, configureOptionsWithServices);
+ }
+
+ ///
+ /// Registers the using the given authentication scheme, display name, and options configuration.
+ ///
+ /// TService: A service resolved from the IServiceProvider for use when configuring this authentication provider. If you need multiple services then specify IServiceProvider and resolve them directly.
+ ///
+ ///
+ ///
+ /// A delegate that configures the .
+ ///
+ public static AuthenticationBuilder AddWsFederation(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action configureOptions) where TService : class
{
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton, WsFederationPostConfigureOptions>());
- return builder.AddRemoteScheme(authenticationScheme, displayName, configureOptions);
+ return builder.AddRemoteScheme(authenticationScheme, displayName, configureOptions);
}
}
}
diff --git a/src/Servers/HttpSys/.vsconfig b/src/Servers/HttpSys/.vsconfig
new file mode 100644
index 0000000000..7a520fe61c
--- /dev/null
+++ b/src/Servers/HttpSys/.vsconfig
@@ -0,0 +1,12 @@
+{
+ "version": "1.0",
+ "components": [
+ "Microsoft.Net.Component.4.6.1.TargetingPack",
+ "Microsoft.Net.Component.4.7.2.SDK",
+ "Microsoft.Net.Component.4.7.2.TargetingPack",
+ "Microsoft.VisualStudio.Workload.ManagedDesktop",
+ "Microsoft.VisualStudio.Workload.NetCoreTools",
+ "Microsoft.VisualStudio.Workload.NetWeb",
+ "Microsoft.VisualStudio.Workload.VisualStudioExtension"
+ ]
+}
diff --git a/src/Servers/IIS/.vsconfig b/src/Servers/IIS/.vsconfig
new file mode 100644
index 0000000000..8f411e8f86
--- /dev/null
+++ b/src/Servers/IIS/.vsconfig
@@ -0,0 +1,16 @@
+{
+ "version": "1.0",
+ "components": [
+ "Microsoft.Net.Component.4.6.1.TargetingPack",
+ "Microsoft.Net.Component.4.7.2.SDK",
+ "Microsoft.Net.Component.4.7.2.TargetingPack",
+ "Microsoft.VisualStudio.Component.VC.ATL",
+ "Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
+ "Microsoft.VisualStudio.Component.Windows10SDK.17134",
+ "Microsoft.VisualStudio.Workload.ManagedDesktop",
+ "Microsoft.VisualStudio.Workload.NativeDesktop",
+ "Microsoft.VisualStudio.Workload.NetCoreTools",
+ "Microsoft.VisualStudio.Workload.NetWeb",
+ "Microsoft.VisualStudio.Workload.VisualStudioExtension"
+ ]
+}
diff --git a/src/Servers/IIS/IIS/ref/Microsoft.AspNetCore.Server.IIS.netcoreapp.cs b/src/Servers/IIS/IIS/ref/Microsoft.AspNetCore.Server.IIS.netcoreapp.cs
index 479bfe8e6c..a32e60734b 100644
--- a/src/Servers/IIS/IIS/ref/Microsoft.AspNetCore.Server.IIS.netcoreapp.cs
+++ b/src/Servers/IIS/IIS/ref/Microsoft.AspNetCore.Server.IIS.netcoreapp.cs
@@ -21,10 +21,11 @@ namespace Microsoft.AspNetCore.Hosting
}
namespace Microsoft.AspNetCore.Server.IIS
{
- public sealed partial class BadHttpRequestException : System.IO.IOException
+ [System.ObsoleteAttribute("Moved to Microsoft.AspNetCore.Http.BadHttpRequestException")]
+ public sealed partial class BadHttpRequestException : Microsoft.AspNetCore.Http.BadHttpRequestException
{
- internal BadHttpRequestException() { }
- public int StatusCode { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } }
+ internal BadHttpRequestException() : base (default(string), default(int)) { }
+ public new int StatusCode { get { throw null; } }
}
public static partial class HttpContextExtensions
{
diff --git a/src/Servers/IIS/IIS/src/BadHttpRequestException.cs b/src/Servers/IIS/IIS/src/BadHttpRequestException.cs
index 527692f89c..2abefae9e9 100644
--- a/src/Servers/IIS/IIS/src/BadHttpRequestException.cs
+++ b/src/Servers/IIS/IIS/src/BadHttpRequestException.cs
@@ -1,44 +1,24 @@
// 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.IO;
using System.Runtime.CompilerServices;
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Server.IIS
{
- public sealed class BadHttpRequestException : IOException
+ [Obsolete("Moved to Microsoft.AspNetCore.Http.BadHttpRequestException")]
+ public sealed class BadHttpRequestException : Microsoft.AspNetCore.Http.BadHttpRequestException
{
- private BadHttpRequestException(string message, int statusCode, RequestRejectionReason reason)
- : base(message)
+ internal BadHttpRequestException(string message, int statusCode, RequestRejectionReason reason)
+ : base(message, statusCode)
{
- StatusCode = statusCode;
Reason = reason;
}
- public int StatusCode { get; }
+ public new int StatusCode { get => base.StatusCode; }
internal RequestRejectionReason Reason { get; }
-
- internal static void Throw(RequestRejectionReason reason)
- {
- throw GetException(reason);
- }
-
- [MethodImpl(MethodImplOptions.NoInlining)]
- internal static BadHttpRequestException GetException(RequestRejectionReason reason)
- {
- BadHttpRequestException ex;
- switch (reason)
- {
- case RequestRejectionReason.RequestBodyTooLarge:
- ex = new BadHttpRequestException(CoreStrings.BadRequest_RequestBodyTooLarge, StatusCodes.Status413PayloadTooLarge, reason);
- break;
- default:
- ex = new BadHttpRequestException(CoreStrings.BadRequest, StatusCodes.Status400BadRequest, reason);
- break;
- }
- return ex;
- }
}
}
diff --git a/src/Servers/IIS/IIS/src/Core/IISHttpContext.IO.cs b/src/Servers/IIS/IIS/src/Core/IISHttpContext.IO.cs
index 5f831eb01a..60d098bc11 100644
--- a/src/Servers/IIS/IIS/src/Core/IISHttpContext.IO.cs
+++ b/src/Servers/IIS/IIS/src/Core/IISHttpContext.IO.cs
@@ -122,7 +122,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
if (_consumedBytes > MaxRequestBodySize)
{
- BadHttpRequestException.Throw(RequestRejectionReason.RequestBodyTooLarge);
+ IISBadHttpRequestException.Throw(RequestRejectionReason.RequestBodyTooLarge);
}
var result = await _bodyInputPipe.Writer.FlushAsync();
diff --git a/src/Servers/IIS/IIS/src/Core/IISHttpContext.Log.cs b/src/Servers/IIS/IIS/src/Core/IISHttpContext.Log.cs
index 4f84786cf0..8a28ecc08a 100644
--- a/src/Servers/IIS/IIS/src/Core/IISHttpContext.Log.cs
+++ b/src/Servers/IIS/IIS/src/Core/IISHttpContext.Log.cs
@@ -38,7 +38,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
_unexpectedError(logger, className, methodName, ex);
}
- public static void ConnectionBadRequest(ILogger logger, string connectionId, BadHttpRequestException ex)
+ public static void ConnectionBadRequest(ILogger logger, string connectionId, Microsoft.AspNetCore.Http.BadHttpRequestException ex)
{
_connectionBadRequest(logger, connectionId, ex.Message, ex);
}
diff --git a/src/Servers/IIS/IIS/src/Core/IISHttpContext.cs b/src/Servers/IIS/IIS/src/Core/IISHttpContext.cs
index 74f0cbae6b..5103ae871d 100644
--- a/src/Servers/IIS/IIS/src/Core/IISHttpContext.cs
+++ b/src/Servers/IIS/IIS/src/Core/IISHttpContext.cs
@@ -26,6 +26,8 @@ using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.IIS.Core
{
+ using BadHttpRequestException = Microsoft.AspNetCore.Http.BadHttpRequestException;
+
internal abstract partial class IISHttpContext : NativeRequestContext, IThreadPoolWorkItem, IDisposable
{
private const int MinAllocBufferSize = 2048;
@@ -293,7 +295,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
if (RequestHeaders.ContentLength > MaxRequestBodySize)
{
- BadHttpRequestException.Throw(RequestRejectionReason.RequestBodyTooLarge);
+ IISBadHttpRequestException.Throw(RequestRejectionReason.RequestBodyTooLarge);
}
HasStartedConsumingRequestBody = true;
diff --git a/src/Servers/IIS/IIS/src/Core/IISHttpContextOfT.cs b/src/Servers/IIS/IIS/src/Core/IISHttpContextOfT.cs
index 060f105233..cfdc20b225 100644
--- a/src/Servers/IIS/IIS/src/Core/IISHttpContextOfT.cs
+++ b/src/Servers/IIS/IIS/src/Core/IISHttpContextOfT.cs
@@ -11,6 +11,8 @@ using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.IIS.Core
{
+ using BadHttpRequestException = Microsoft.AspNetCore.Http.BadHttpRequestException;
+
internal class IISHttpContextOfT : IISHttpContext
{
private readonly IHttpApplication _application;
diff --git a/src/Servers/IIS/IIS/src/IISBadHttpRequestException.cs b/src/Servers/IIS/IIS/src/IISBadHttpRequestException.cs
new file mode 100644
index 0000000000..3ee8ee42aa
--- /dev/null
+++ b/src/Servers/IIS/IIS/src/IISBadHttpRequestException.cs
@@ -0,0 +1,36 @@
+// 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.IO;
+using System.Runtime.CompilerServices;
+using Microsoft.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Server.IIS
+{
+ internal static class IISBadHttpRequestException
+ {
+ internal static void Throw(RequestRejectionReason reason)
+ {
+ throw GetException(reason);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+#pragma warning disable CS0618 // Type or member is obsolete
+ internal static BadHttpRequestException GetException(RequestRejectionReason reason)
+ {
+ BadHttpRequestException ex;
+ switch (reason)
+ {
+ case RequestRejectionReason.RequestBodyTooLarge:
+ ex = new BadHttpRequestException(CoreStrings.BadRequest_RequestBodyTooLarge, StatusCodes.Status413PayloadTooLarge, reason);
+ break;
+ default:
+ ex = new BadHttpRequestException(CoreStrings.BadRequest, StatusCodes.Status400BadRequest, reason);
+ break;
+ }
+ return ex;
+ }
+#pragma warning restore CS0618 // Type or member is obsolete
+ }
+}
diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/ClientCertificateTests.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/ClientCertificateTests.cs
index b0847f84a1..5503c2b4fa 100644
--- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/ClientCertificateTests.cs
+++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/ClientCertificateTests.cs
@@ -89,7 +89,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests
}
catch (Exception ex)
{
- Logger.LogError($"Certificate is invalid. Issuer name: {cert.Issuer}");
+ Logger.LogError($"Certificate is invalid. Issuer name: {cert?.Issuer}");
using (var store = new X509Store(StoreName.Root, StoreLocation.LocalMachine))
{
Logger.LogError($"List of current certificates in root store:");
diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/StartupTests.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/StartupTests.cs
index 5f6fed86e9..c030a87161 100644
--- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/StartupTests.cs
+++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/StartupTests.cs
@@ -212,7 +212,6 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.InProcess
}
[ConditionalFact]
- [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/20153")]
public async Task DetectsOverriddenServer()
{
var deploymentParameters = Fixture.GetBaseDeploymentParameters(Fixture.InProcessTestSite);
@@ -230,7 +229,6 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.InProcess
}
[ConditionalFact]
- [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/20153")]
public async Task LogsStartupExceptionExitError()
{
var deploymentParameters = Fixture.GetBaseDeploymentParameters(Fixture.InProcessTestSite);
@@ -709,7 +707,6 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.InProcess
[InlineData("DOTNET_ENVIRONMENT", "deVelopment")]
[InlineData("ASPNETCORE_DETAILEDERRORS", "1")]
[InlineData("ASPNETCORE_DETAILEDERRORS", "TRUE")]
- [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/20153")]
public async Task ExceptionIsLoggedToEventLogAndPutInResponseWhenDeveloperExceptionPageIsEnabled(string environmentVariable, string value)
{
var deploymentParameters = Fixture.GetBaseDeploymentParameters();
@@ -734,7 +731,6 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.InProcess
[ConditionalFact]
[RequiresNewHandler]
- [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/20153")]
public async Task ExceptionIsLoggedToEventLogAndPutInResponseWhenDeveloperExceptionPageIsEnabledViaWebConfig()
{
var deploymentParameters = Fixture.GetBaseDeploymentParameters();
@@ -762,7 +758,6 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.InProcess
[RequiresNewHandler]
[InlineData("ThrowInStartup")]
[InlineData("ThrowInStartupGenericHost")]
- [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/20153")]
public async Task ExceptionIsLoggedToEventLogAndPutInResponseDuringHostingStartupProcess(string startupType)
{
var deploymentParameters = Fixture.GetBaseDeploymentParameters();
@@ -785,7 +780,6 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.InProcess
[ConditionalFact]
[RequiresIIS(IISCapability.PoolEnvironmentVariables)]
[RequiresNewHandler]
- [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/20153")]
public async Task ExceptionIsNotLoggedToResponseWhenStartupHookIsDisabled()
{
var deploymentParameters = Fixture.GetBaseDeploymentParameters();
@@ -808,7 +802,6 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.InProcess
[ConditionalFact]
[RequiresNewHandler]
- [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/20153")]
public async Task ExceptionIsLoggedToEventLogDoesNotWriteToResponse()
{
var deploymentParameters = Fixture.GetBaseDeploymentParameters();
diff --git a/src/Servers/IIS/IIS/test/IIS.Tests/MaxRequestBodySizeTests.cs b/src/Servers/IIS/IIS/test/IIS.Tests/MaxRequestBodySizeTests.cs
index 1f46ed1b4b..0eb92e4ec7 100644
--- a/src/Servers/IIS/IIS/test/IIS.Tests/MaxRequestBodySizeTests.cs
+++ b/src/Servers/IIS/IIS/test/IIS.Tests/MaxRequestBodySizeTests.cs
@@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Server.IIS.FunctionalTests;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Logging.Testing;
using Xunit;
+using BadHttpRequestException = Microsoft.AspNetCore.Http.BadHttpRequestException;
namespace IIS.Tests
{
@@ -24,6 +25,7 @@ namespace IIS.Tests
var globalMaxRequestBodySize = 0x100000000;
BadHttpRequestException exception = null;
+
using (var testServer = await TestServer.Create(
async ctx =>
{
@@ -60,6 +62,7 @@ namespace IIS.Tests
var perRequestMaxRequestBodySize = 0x100;
BadHttpRequestException exception = null;
+
using (var testServer = await TestServer.Create(
async ctx =>
{
@@ -71,6 +74,7 @@ namespace IIS.Tests
await ctx.Request.Body.ReadAsync(new byte[2000]);
}
+
catch (BadHttpRequestException ex)
{
exception = ex;
@@ -266,6 +270,7 @@ namespace IIS.Tests
var maxRequestSize = 0x1000;
BadHttpRequestException exception = null;
+
using (var testServer = await TestServer.Create(
async ctx =>
{
@@ -307,13 +312,14 @@ namespace IIS.Tests
{
BadHttpRequestException requestRejectedEx1 = null;
BadHttpRequestException requestRejectedEx2 = null;
+
using (var testServer = await TestServer.Create(
async ctx =>
{
var buffer = new byte[1];
- requestRejectedEx1 = await Assert.ThrowsAsync(
+ requestRejectedEx1 = await Assert.ThrowsAnyAsync(
async () => await ctx.Request.Body.ReadAsync(buffer, 0, 1));
- requestRejectedEx2 = await Assert.ThrowsAsync(
+ requestRejectedEx2 = await Assert.ThrowsAnyAsync(
async () => await ctx.Request.Body.ReadAsync(buffer, 0, 1));
throw requestRejectedEx2;
}, LoggerFactory, new IISServerOptions { MaxRequestBodySize = 0 }))
diff --git a/src/Servers/IIS/IIS/test/testassets/IIS.Common.TestLib/IIS.Common.TestLib.csproj b/src/Servers/IIS/IIS/test/testassets/IIS.Common.TestLib/IIS.Common.TestLib.csproj
index ced51607d8..e4ff5df0cc 100644
--- a/src/Servers/IIS/IIS/test/testassets/IIS.Common.TestLib/IIS.Common.TestLib.csproj
+++ b/src/Servers/IIS/IIS/test/testassets/IIS.Common.TestLib/IIS.Common.TestLib.csproj
@@ -7,8 +7,8 @@
+
-
diff --git a/src/Servers/IIS/IIS/test/testassets/InProcessNewShimWebSite/InProcessNewShimWebSite.csproj b/src/Servers/IIS/IIS/test/testassets/InProcessNewShimWebSite/InProcessNewShimWebSite.csproj
index fabcb2938a..7e319b26df 100644
--- a/src/Servers/IIS/IIS/test/testassets/InProcessNewShimWebSite/InProcessNewShimWebSite.csproj
+++ b/src/Servers/IIS/IIS/test/testassets/InProcessNewShimWebSite/InProcessNewShimWebSite.csproj
@@ -7,6 +7,7 @@
InProcessWebSite
InProcessNewShimWebSite
FORWARDCOMPAT
+ false
diff --git a/src/Servers/Kestrel/.vsconfig b/src/Servers/Kestrel/.vsconfig
new file mode 100644
index 0000000000..7a520fe61c
--- /dev/null
+++ b/src/Servers/Kestrel/.vsconfig
@@ -0,0 +1,12 @@
+{
+ "version": "1.0",
+ "components": [
+ "Microsoft.Net.Component.4.6.1.TargetingPack",
+ "Microsoft.Net.Component.4.7.2.SDK",
+ "Microsoft.Net.Component.4.7.2.TargetingPack",
+ "Microsoft.VisualStudio.Workload.ManagedDesktop",
+ "Microsoft.VisualStudio.Workload.NetCoreTools",
+ "Microsoft.VisualStudio.Workload.NetWeb",
+ "Microsoft.VisualStudio.Workload.VisualStudioExtension"
+ ]
+}
diff --git a/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp.cs b/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp.cs
index 6673afaa1c..7de1a0907e 100644
--- a/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp.cs
+++ b/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp.cs
@@ -62,10 +62,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel
}
namespace Microsoft.AspNetCore.Server.Kestrel.Core
{
- public sealed partial class BadHttpRequestException : System.IO.IOException
+ [System.ObsoleteAttribute("Moved to Microsoft.AspNetCore.Http.BadHttpRequestException")]
+ public sealed partial class BadHttpRequestException : Microsoft.AspNetCore.Http.BadHttpRequestException
{
- internal BadHttpRequestException() { }
- public int StatusCode { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } }
+ internal BadHttpRequestException() : base (default(string), default(int)) { }
+ public new int StatusCode { get { throw null; } }
}
public partial class Http2Limits
{
@@ -127,6 +128,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
{
public KestrelServerOptions() { }
public bool AddServerHeader { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
+ public bool AllowResponseHeaderCompression { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
public bool AllowSynchronousIO { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
public System.IServiceProvider ApplicationServices { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
public Microsoft.AspNetCore.Server.Kestrel.KestrelConfigurationLoader ConfigurationLoader { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
diff --git a/src/Servers/Kestrel/Core/src/BadHttpRequestException.cs b/src/Servers/Kestrel/Core/src/BadHttpRequestException.cs
index 16f7ab0fce..d68eff9a45 100644
--- a/src/Servers/Kestrel/Core/src/BadHttpRequestException.cs
+++ b/src/Servers/Kestrel/Core/src/BadHttpRequestException.cs
@@ -1,26 +1,23 @@
// 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.Diagnostics;
-using System.IO;
-using System.Runtime.CompilerServices;
-using Microsoft.AspNetCore.Http;
+using System;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNetCore.Server.Kestrel.Core
{
- public sealed class BadHttpRequestException : IOException
+ [Obsolete("Moved to Microsoft.AspNetCore.Http.BadHttpRequestException")]
+ public sealed class BadHttpRequestException : Microsoft.AspNetCore.Http.BadHttpRequestException
{
- private BadHttpRequestException(string message, int statusCode, RequestRejectionReason reason)
+ internal BadHttpRequestException(string message, int statusCode, RequestRejectionReason reason)
: this(message, statusCode, reason, null)
{ }
- private BadHttpRequestException(string message, int statusCode, RequestRejectionReason reason, HttpMethod? requiredMethod)
- : base(message)
+ internal BadHttpRequestException(string message, int statusCode, RequestRejectionReason reason, HttpMethod? requiredMethod)
+ : base(message, statusCode)
{
- StatusCode = statusCode;
Reason = reason;
if (requiredMethod.HasValue)
@@ -29,151 +26,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
}
}
- public int StatusCode { get; }
+ public new int StatusCode { get => base.StatusCode; }
internal StringValues AllowedHeader { get; }
internal RequestRejectionReason Reason { get; }
-
- [StackTraceHidden]
- internal static void Throw(RequestRejectionReason reason)
- {
- throw GetException(reason);
- }
-
- [StackTraceHidden]
- internal static void Throw(RequestRejectionReason reason, HttpMethod method)
- => throw GetException(reason, method.ToString().ToUpperInvariant());
-
- [MethodImpl(MethodImplOptions.NoInlining)]
- internal static BadHttpRequestException GetException(RequestRejectionReason reason)
- {
- BadHttpRequestException ex;
- switch (reason)
- {
- case RequestRejectionReason.InvalidRequestHeadersNoCRLF:
- ex = new BadHttpRequestException(CoreStrings.BadRequest_InvalidRequestHeadersNoCRLF, StatusCodes.Status400BadRequest, reason);
- break;
- case RequestRejectionReason.InvalidRequestLine:
- ex = new BadHttpRequestException(CoreStrings.BadRequest_InvalidRequestLine, StatusCodes.Status400BadRequest, reason);
- break;
- case RequestRejectionReason.MalformedRequestInvalidHeaders:
- ex = new BadHttpRequestException(CoreStrings.BadRequest_MalformedRequestInvalidHeaders, StatusCodes.Status400BadRequest, reason);
- break;
- case RequestRejectionReason.MultipleContentLengths:
- ex = new BadHttpRequestException(CoreStrings.BadRequest_MultipleContentLengths, StatusCodes.Status400BadRequest, reason);
- break;
- case RequestRejectionReason.UnexpectedEndOfRequestContent:
- ex = new BadHttpRequestException(CoreStrings.BadRequest_UnexpectedEndOfRequestContent, StatusCodes.Status400BadRequest, reason);
- break;
- case RequestRejectionReason.BadChunkSuffix:
- ex = new BadHttpRequestException(CoreStrings.BadRequest_BadChunkSuffix, StatusCodes.Status400BadRequest, reason);
- break;
- case RequestRejectionReason.BadChunkSizeData:
- ex = new BadHttpRequestException(CoreStrings.BadRequest_BadChunkSizeData, StatusCodes.Status400BadRequest, reason);
- break;
- case RequestRejectionReason.ChunkedRequestIncomplete:
- ex = new BadHttpRequestException(CoreStrings.BadRequest_ChunkedRequestIncomplete, StatusCodes.Status400BadRequest, reason);
- break;
- case RequestRejectionReason.InvalidCharactersInHeaderName:
- ex = new BadHttpRequestException(CoreStrings.BadRequest_InvalidCharactersInHeaderName, StatusCodes.Status400BadRequest, reason);
- break;
- case RequestRejectionReason.RequestLineTooLong:
- ex = new BadHttpRequestException(CoreStrings.BadRequest_RequestLineTooLong, StatusCodes.Status414UriTooLong, reason);
- break;
- case RequestRejectionReason.HeadersExceedMaxTotalSize:
- ex = new BadHttpRequestException(CoreStrings.BadRequest_HeadersExceedMaxTotalSize, StatusCodes.Status431RequestHeaderFieldsTooLarge, reason);
- break;
- case RequestRejectionReason.TooManyHeaders:
- ex = new BadHttpRequestException(CoreStrings.BadRequest_TooManyHeaders, StatusCodes.Status431RequestHeaderFieldsTooLarge, reason);
- break;
- case RequestRejectionReason.RequestBodyTooLarge:
- ex = new BadHttpRequestException(CoreStrings.BadRequest_RequestBodyTooLarge, StatusCodes.Status413PayloadTooLarge, reason);
- break;
- case RequestRejectionReason.RequestHeadersTimeout:
- ex = new BadHttpRequestException(CoreStrings.BadRequest_RequestHeadersTimeout, StatusCodes.Status408RequestTimeout, reason);
- break;
- case RequestRejectionReason.RequestBodyTimeout:
- ex = new BadHttpRequestException(CoreStrings.BadRequest_RequestBodyTimeout, StatusCodes.Status408RequestTimeout, reason);
- break;
- case RequestRejectionReason.OptionsMethodRequired:
- ex = new BadHttpRequestException(CoreStrings.BadRequest_MethodNotAllowed, StatusCodes.Status405MethodNotAllowed, reason, HttpMethod.Options);
- break;
- case RequestRejectionReason.ConnectMethodRequired:
- ex = new BadHttpRequestException(CoreStrings.BadRequest_MethodNotAllowed, StatusCodes.Status405MethodNotAllowed, reason, HttpMethod.Connect);
- break;
- case RequestRejectionReason.MissingHostHeader:
- ex = new BadHttpRequestException(CoreStrings.BadRequest_MissingHostHeader, StatusCodes.Status400BadRequest, reason);
- break;
- case RequestRejectionReason.MultipleHostHeaders:
- ex = new BadHttpRequestException(CoreStrings.BadRequest_MultipleHostHeaders, StatusCodes.Status400BadRequest, reason);
- break;
- case RequestRejectionReason.InvalidHostHeader:
- ex = new BadHttpRequestException(CoreStrings.BadRequest_InvalidHostHeader, StatusCodes.Status400BadRequest, reason);
- break;
- case RequestRejectionReason.UpgradeRequestCannotHavePayload:
- ex = new BadHttpRequestException(CoreStrings.BadRequest_UpgradeRequestCannotHavePayload, StatusCodes.Status400BadRequest, reason);
- break;
- default:
- ex = new BadHttpRequestException(CoreStrings.BadRequest, StatusCodes.Status400BadRequest, reason);
- break;
- }
- return ex;
- }
-
- [StackTraceHidden]
- internal static void Throw(RequestRejectionReason reason, string detail)
- {
- throw GetException(reason, detail);
- }
-
- [StackTraceHidden]
- internal static void Throw(RequestRejectionReason reason, StringValues detail)
- {
- throw GetException(reason, detail.ToString());
- }
-
- [MethodImpl(MethodImplOptions.NoInlining)]
- internal static BadHttpRequestException GetException(RequestRejectionReason reason, string detail)
- {
- BadHttpRequestException ex;
- switch (reason)
- {
- case RequestRejectionReason.TlsOverHttpError:
- ex = new BadHttpRequestException(CoreStrings.HttpParserTlsOverHttpError, StatusCodes.Status400BadRequest, reason);
- break;
- case RequestRejectionReason.InvalidRequestLine:
- ex = new BadHttpRequestException(CoreStrings.FormatBadRequest_InvalidRequestLine_Detail(detail), StatusCodes.Status400BadRequest, reason);
- break;
- case RequestRejectionReason.InvalidRequestTarget:
- ex = new BadHttpRequestException(CoreStrings.FormatBadRequest_InvalidRequestTarget_Detail(detail), StatusCodes.Status400BadRequest, reason);
- break;
- case RequestRejectionReason.InvalidRequestHeader:
- ex = new BadHttpRequestException(CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(detail), StatusCodes.Status400BadRequest, reason);
- break;
- case RequestRejectionReason.InvalidContentLength:
- ex = new BadHttpRequestException(CoreStrings.FormatBadRequest_InvalidContentLength_Detail(detail), StatusCodes.Status400BadRequest, reason);
- break;
- case RequestRejectionReason.UnrecognizedHTTPVersion:
- ex = new BadHttpRequestException(CoreStrings.FormatBadRequest_UnrecognizedHTTPVersion(detail), StatusCodes.Status505HttpVersionNotsupported, reason);
- break;
- case RequestRejectionReason.FinalTransferCodingNotChunked:
- ex = new BadHttpRequestException(CoreStrings.FormatBadRequest_FinalTransferCodingNotChunked(detail), StatusCodes.Status400BadRequest, reason);
- break;
- case RequestRejectionReason.LengthRequired:
- ex = new BadHttpRequestException(CoreStrings.FormatBadRequest_LengthRequired(detail), StatusCodes.Status411LengthRequired, reason);
- break;
- case RequestRejectionReason.LengthRequiredHttp10:
- ex = new BadHttpRequestException(CoreStrings.FormatBadRequest_LengthRequiredHttp10(detail), StatusCodes.Status400BadRequest, reason);
- break;
- case RequestRejectionReason.InvalidHostHeader:
- ex = new BadHttpRequestException(CoreStrings.FormatBadRequest_InvalidHostHeader_Detail(detail), StatusCodes.Status400BadRequest, reason);
- break;
- default:
- ex = new BadHttpRequestException(CoreStrings.BadRequest, StatusCodes.Status400BadRequest, reason);
- break;
- }
- return ex;
- }
}
}
diff --git a/src/Servers/Kestrel/Core/src/CoreStrings.resx b/src/Servers/Kestrel/Core/src/CoreStrings.resx
index f84ed1d2ce..1d270be8ee 100644
--- a/src/Servers/Kestrel/Core/src/CoreStrings.resx
+++ b/src/Servers/Kestrel/Core/src/CoreStrings.resx
@@ -599,4 +599,7 @@ For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?l
Unable to resolve service for type 'Microsoft.AspNetCore.Connections.IConnectionListenerFactory' while attempting to activate 'Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServer'.
+
+ A value greater than or equal to zero is required.
+
\ No newline at end of file
diff --git a/src/Servers/Kestrel/Core/src/Http2Limits.cs b/src/Servers/Kestrel/Core/src/Http2Limits.cs
index 68d101f076..713159f66a 100644
--- a/src/Servers/Kestrel/Core/src/Http2Limits.cs
+++ b/src/Servers/Kestrel/Core/src/Http2Limits.cs
@@ -39,9 +39,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
}
///
- /// Limits the size of the header compression table, in octets, the HPACK decoder on the server can use.
+ /// Limits the size of the header compression tables, in octets, the HPACK encoder and decoder on the server can use.
///
- /// Value must be greater than 0, defaults to 4096
+ /// Value must be greater than or equal to 0, defaults to 4096
///
///
public int HeaderTableSize
@@ -49,9 +49,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
get => _headerTableSize;
set
{
- if (value <= 0)
+ if (value < 0)
{
- throw new ArgumentOutOfRangeException(nameof(value), value, CoreStrings.GreaterThanZeroRequired);
+ throw new ArgumentOutOfRangeException(nameof(value), value, CoreStrings.GreaterThanOrEqualToZeroRequired);
}
_headerTableSize = value;
diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1ChunkedEncodingMessageBody.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1ChunkedEncodingMessageBody.cs
index 9c87b3697e..2a2c01051d 100644
--- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1ChunkedEncodingMessageBody.cs
+++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1ChunkedEncodingMessageBody.cs
@@ -135,7 +135,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
if (_context.RequestTimedOut)
{
- BadHttpRequestException.Throw(RequestRejectionReason.RequestBodyTimeout);
+ KestrelBadHttpRequestException.Throw(RequestRejectionReason.RequestBodyTimeout);
}
var readableBuffer = result.Buffer;
@@ -373,7 +373,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
}
// At this point, 10 bytes have been consumed which is enough to parse the max value "7FFFFFFF\r\n".
- BadHttpRequestException.Throw(RequestRejectionReason.BadChunkSizeData);
+ KestrelBadHttpRequestException.Throw(RequestRejectionReason.BadChunkSizeData);
}
private void ParseExtension(ReadOnlySequence buffer, out SequencePosition consumed, out SequencePosition examined)
@@ -469,7 +469,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
}
else
{
- BadHttpRequestException.Throw(RequestRejectionReason.BadChunkSuffix);
+ KestrelBadHttpRequestException.Throw(RequestRejectionReason.BadChunkSuffix);
}
}
@@ -528,7 +528,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
throw new IOException(CoreStrings.BadRequest_BadChunkSizeData, ex);
}
- BadHttpRequestException.Throw(RequestRejectionReason.BadChunkSizeData);
+ KestrelBadHttpRequestException.Throw(RequestRejectionReason.BadChunkSizeData);
+
return -1; // can't happen, but compiler complains
}
diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs
index 832d48cdce..ef77a1f396 100644
--- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs
+++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs
@@ -204,7 +204,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
if (!_parser.ParseRequestLine(new Http1ParsingHandler(this), trimmedBuffer, out consumed, out examined))
{
// We read the maximum allowed but didn't complete the start line.
- BadHttpRequestException.Throw(RequestRejectionReason.RequestLineTooLong);
+ KestrelBadHttpRequestException.Throw(RequestRejectionReason.RequestLineTooLong);
}
return true;
@@ -261,7 +261,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
if (!result)
{
// We read the maximum allowed but didn't complete the headers.
- BadHttpRequestException.Throw(RequestRejectionReason.HeadersExceedMaxTotalSize);
+ KestrelBadHttpRequestException.Throw(RequestRejectionReason.HeadersExceedMaxTotalSize);
}
TimeoutControl.CancelTimeout();
@@ -424,7 +424,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
// requests (https://tools.ietf.org/html/rfc7231#section-4.3.6).
if (method != HttpMethod.Connect)
{
- BadHttpRequestException.Throw(RequestRejectionReason.ConnectMethodRequired);
+ KestrelBadHttpRequestException.Throw(RequestRejectionReason.ConnectMethodRequired);
}
// When making a CONNECT request to establish a tunnel through one or
@@ -468,7 +468,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
// OPTIONS request (https://tools.ietf.org/html/rfc7231#section-4.3.7).
if (method != HttpMethod.Options)
{
- BadHttpRequestException.Throw(RequestRejectionReason.OptionsMethodRequired);
+ KestrelBadHttpRequestException.Throw(RequestRejectionReason.OptionsMethodRequired);
}
RawTarget = Asterisk;
@@ -555,11 +555,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
return;
}
- BadHttpRequestException.Throw(RequestRejectionReason.MissingHostHeader);
+
+ KestrelBadHttpRequestException.Throw(RequestRejectionReason.MissingHostHeader);
}
else if (hostCount > 1)
{
- BadHttpRequestException.Throw(RequestRejectionReason.MultipleHostHeaders);
+ KestrelBadHttpRequestException.Throw(RequestRejectionReason.MultipleHostHeaders);
}
else if (_requestTargetForm != HttpRequestTarget.OriginForm)
{
@@ -568,7 +569,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
}
else if (!HttpUtilities.IsHostHeaderValid(hostText))
{
- BadHttpRequestException.Throw(RequestRejectionReason.InvalidHostHeader, hostText);
+ KestrelBadHttpRequestException.Throw(RequestRejectionReason.InvalidHostHeader, hostText);
}
}
@@ -578,7 +579,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
if (hostText != RawTarget)
{
- BadHttpRequestException.Throw(RequestRejectionReason.InvalidHostHeader, hostText);
+ KestrelBadHttpRequestException.Throw(RequestRejectionReason.InvalidHostHeader, hostText);
}
}
else if (_requestTargetForm == HttpRequestTarget.AbsoluteForm)
@@ -595,14 +596,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
if (!_absoluteRequestTarget.IsDefaultPort
|| hostText != _absoluteRequestTarget.Authority + ":" + _absoluteRequestTarget.Port.ToString(CultureInfo.InvariantCulture))
{
- BadHttpRequestException.Throw(RequestRejectionReason.InvalidHostHeader, hostText);
+ KestrelBadHttpRequestException.Throw(RequestRejectionReason.InvalidHostHeader, hostText);
}
}
}
if (!HttpUtilities.IsHostHeaderValid(hostText))
{
- BadHttpRequestException.Throw(RequestRejectionReason.InvalidHostHeader, hostText);
+ KestrelBadHttpRequestException.Throw(RequestRejectionReason.InvalidHostHeader, hostText);
}
}
@@ -655,7 +656,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
if (_requestProcessingStatus == RequestProcessingStatus.ParsingHeaders)
{
- BadHttpRequestException.Throw(RequestRejectionReason.MalformedRequestInvalidHeaders);
+ KestrelBadHttpRequestException.Throw(RequestRejectionReason.MalformedRequestInvalidHeaders);
}
throw;
}
@@ -672,10 +673,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
endConnection = true;
return true;
case RequestProcessingStatus.ParsingRequestLine:
- BadHttpRequestException.Throw(RequestRejectionReason.InvalidRequestLine);
+ KestrelBadHttpRequestException.Throw(RequestRejectionReason.InvalidRequestLine);
break;
case RequestProcessingStatus.ParsingHeaders:
- BadHttpRequestException.Throw(RequestRejectionReason.MalformedRequestInvalidHeaders);
+ KestrelBadHttpRequestException.Throw(RequestRejectionReason.MalformedRequestInvalidHeaders);
break;
}
}
@@ -690,7 +691,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
// In this case, there is an ongoing request but the start line/header parsing has timed out, so send
// a 408 response.
- BadHttpRequestException.Throw(RequestRejectionReason.RequestHeadersTimeout);
+ KestrelBadHttpRequestException.Throw(RequestRejectionReason.RequestHeadersTimeout);
}
endConnection = false;
diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1ContentLengthMessageBody.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1ContentLengthMessageBody.cs
index 7ef9a167a4..b0d1bf7846 100644
--- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1ContentLengthMessageBody.cs
+++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1ContentLengthMessageBody.cs
@@ -58,7 +58,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
// which is unknown to StartTimingReadAsync.
if (_context.RequestTimedOut)
{
- BadHttpRequestException.Throw(RequestRejectionReason.RequestBodyTimeout);
+ KestrelBadHttpRequestException.Throw(RequestRejectionReason.RequestBodyTimeout);
}
try
@@ -85,7 +85,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
if (_context.RequestTimedOut)
{
ResetReadingState();
- BadHttpRequestException.Throw(RequestRejectionReason.RequestBodyTimeout);
+ KestrelBadHttpRequestException.Throw(RequestRejectionReason.RequestBodyTimeout);
}
if (_readResult.IsCompleted)
@@ -234,7 +234,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
if (_contentLength > _context.MaxRequestBodySize)
{
- BadHttpRequestException.Throw(RequestRejectionReason.RequestBodyTooLarge);
+ KestrelBadHttpRequestException.Throw(RequestRejectionReason.RequestBodyTooLarge);
}
}
diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs
index c5993cd7cc..282e665db8 100644
--- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs
+++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs
@@ -11,6 +11,8 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
+ using BadHttpRequestException = Microsoft.AspNetCore.Http.BadHttpRequestException;
+
internal abstract class Http1MessageBody : MessageBody
{
protected readonly Http1Connection _context;
@@ -31,7 +33,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
// closing the connection without a response as expected.
_context.OnInputOrOutputCompleted();
- BadHttpRequestException.Throw(RequestRejectionReason.UnexpectedEndOfRequestContent);
+ KestrelBadHttpRequestException.Throw(RequestRejectionReason.UnexpectedEndOfRequestContent);
}
public abstract bool TryReadInternal(out ReadResult readResult);
@@ -87,7 +89,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
AdvanceTo(result.Buffer.End);
} while (!result.IsCompleted);
}
- catch (BadHttpRequestException ex)
+ catch (Microsoft.AspNetCore.Http.BadHttpRequestException ex)
{
_context.SetBadRequestState(ex);
}
@@ -130,7 +132,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
if (headers.HeaderTransferEncoding.Count > 0 || (headers.ContentLength.HasValue && headers.ContentLength.Value != 0))
{
- BadHttpRequestException.Throw(RequestRejectionReason.UpgradeRequestCannotHavePayload);
+ KestrelBadHttpRequestException.Throw(RequestRejectionReason.UpgradeRequestCannotHavePayload);
}
context.OnTrailersComplete(); // No trailers for these.
@@ -150,7 +152,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
// status code and then close the connection.
if (transferCoding != TransferCoding.Chunked)
{
- BadHttpRequestException.Throw(RequestRejectionReason.FinalTransferCodingNotChunked, transferEncoding);
+ KestrelBadHttpRequestException.Throw(RequestRejectionReason.FinalTransferCodingNotChunked, transferEncoding);
}
// TODO may push more into the wrapper rather than just calling into the message body
@@ -175,7 +177,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
if (context.Method == HttpMethod.Post || context.Method == HttpMethod.Put)
{
var requestRejectionReason = httpVersion == HttpVersion.Http11 ? RequestRejectionReason.LengthRequired : RequestRejectionReason.LengthRequiredHttp10;
- BadHttpRequestException.Throw(requestRejectionReason, context.Method);
+ KestrelBadHttpRequestException.Throw(requestRejectionReason, context.Method);
}
context.OnTrailersComplete(); // No trailers for these.
diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpParser.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpParser.cs
index ff905ca079..d34dc047c2 100644
--- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpParser.cs
+++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpParser.cs
@@ -11,6 +11,8 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
+ using BadHttpRequestException = Microsoft.AspNetCore.Http.BadHttpRequestException;
+
public class HttpParser : IHttpParser where TRequestHandler : IHttpHeadersHandler, IHttpRequestLineHandler
{
private readonly bool _showErrorDetails;
@@ -225,7 +227,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
Debug.Assert(readAhead == 0 || readAhead == 2);
// Headers don't end in CRLF line.
- BadHttpRequestException.Throw(RequestRejectionReason.InvalidRequestHeadersNoCRLF);
+
+ KestrelBadHttpRequestException.Throw(RequestRejectionReason.InvalidRequestHeadersNoCRLF);
}
var length = 0;
@@ -451,7 +454,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
[MethodImpl(MethodImplOptions.NoInlining)]
private unsafe BadHttpRequestException GetInvalidRequestException(RequestRejectionReason reason, byte* detail, int length)
- => BadHttpRequestException.GetException(
+ => KestrelBadHttpRequestException.GetException(
reason,
_showErrorDetails
? new Span(detail, length).GetAsciiStringEscaped(Constants.MaxExceptionDetailSize)
diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs
index 539c386efa..cfe97f0f1e 100644
--- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs
+++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs
@@ -26,6 +26,8 @@ using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
+ using BadHttpRequestException = Microsoft.AspNetCore.Http.BadHttpRequestException;
+
internal abstract partial class HttpProtocol : IHttpResponseControl
{
private static readonly byte[] _bytesConnectionClose = Encoding.ASCII.GetBytes("\r\nConnection: close");
@@ -513,7 +515,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
_requestHeadersParsed++;
if (_requestHeadersParsed > ServerOptions.Limits.MaxRequestHeaderCount)
{
- BadHttpRequestException.Throw(RequestRejectionReason.TooManyHeaders);
+ KestrelBadHttpRequestException.Throw(RequestRejectionReason.TooManyHeaders);
}
HttpRequestHeaders.Append(name, value);
@@ -525,7 +527,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
_requestHeadersParsed++;
if (_requestHeadersParsed > ServerOptions.Limits.MaxRequestHeaderCount)
{
- BadHttpRequestException.Throw(RequestRejectionReason.TooManyHeaders);
+ KestrelBadHttpRequestException.Throw(RequestRejectionReason.TooManyHeaders);
}
string key = name.GetHeaderName();
@@ -547,6 +549,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
try
{
+ // We run the request processing loop in a seperate async method so per connection
+ // exception handling doesn't complicate the generated asm for the loop.
await ProcessRequests(application);
}
catch (BadHttpRequestException ex)
@@ -624,91 +628,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
InitializeBodyControl(messageBody);
- var context = application.CreateContext(this);
-
- try
- {
- KestrelEventSource.Log.RequestStart(this);
-
- // Run the application code for this request
- await application.ProcessRequestAsync(context);
-
- // Trigger OnStarting if it hasn't been called yet and the app hasn't
- // already failed. If an OnStarting callback throws we can go through
- // our normal error handling in ProduceEnd.
- // https://github.com/aspnet/KestrelHttpServer/issues/43
- if (!HasResponseStarted && _applicationException == null && _onStarting?.Count > 0)
- {
- await FireOnStarting();
- }
-
- if (!_connectionAborted && !VerifyResponseContentLength(out var lengthException))
- {
- ReportApplicationError(lengthException);
- }
- }
- catch (BadHttpRequestException ex)
- {
- // Capture BadHttpRequestException for further processing
- // This has to be caught here so StatusCode is set properly before disposing the HttpContext
- // (DisposeContext logs StatusCode).
- SetBadRequestState(ex);
- ReportApplicationError(ex);
- }
- catch (Exception ex)
- {
- ReportApplicationError(ex);
- }
-
- KestrelEventSource.Log.RequestStop(this);
-
- // At this point all user code that needs use to the request or response streams has completed.
- // Using these streams in the OnCompleted callback is not allowed.
- try
- {
- await _bodyControl.StopAsync();
- }
- catch (Exception ex)
- {
- // BodyControl.StopAsync() can throw if the PipeWriter was completed prior to the application writing
- // enough bytes to satisfy the specified Content-Length. This risks double-logging the exception,
- // but this scenario generally indicates an app bug, so I don't want to risk not logging it.
- ReportApplicationError(ex);
- }
-
- // 4XX responses are written by TryProduceInvalidRequestResponse during connection tear down.
- if (_requestRejectedException == null)
- {
- if (!_connectionAborted)
- {
- // Call ProduceEnd() before consuming the rest of the request body to prevent
- // delaying clients waiting for the chunk terminator:
- //
- // https://github.com/dotnet/corefx/issues/17330#issuecomment-288248663
- //
- // This also prevents the 100 Continue response from being sent if the app
- // never tried to read the body.
- // https://github.com/aspnet/KestrelHttpServer/issues/2102
- //
- // ProduceEnd() must be called before _application.DisposeContext(), to ensure
- // HttpContext.Response.StatusCode is correctly set when
- // IHttpContextFactory.Dispose(HttpContext) is called.
- await ProduceEnd();
- }
- else if (!HasResponseStarted)
- {
- // If the request was aborted and no response was sent, there's no
- // meaningful status code to log.
- StatusCode = 0;
- }
- }
-
- if (_onCompleted?.Count > 0)
- {
- await FireOnCompleted();
- }
-
- application.DisposeContext(context, _applicationException);
+ // We run user controlled request processing in a seperate async method
+ // so any changes made to ExecutionContext are undone when it returns and
+ // each request starts with a fresh ExecutionContext state.
+ await ProcessRequest(application);
// Even for non-keep-alive requests, try to consume the entire body to avoid RSTs.
if (!_connectionAborted && _requestRejectedException == null && !messageBody.IsEmpty)
@@ -723,6 +646,95 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
}
}
+ private async ValueTask ProcessRequest(IHttpApplication application)
+ {
+ var context = application.CreateContext(this);
+
+ try
+ {
+ KestrelEventSource.Log.RequestStart(this);
+
+ // Run the application code for this request
+ await application.ProcessRequestAsync(context);
+
+ // Trigger OnStarting if it hasn't been called yet and the app hasn't
+ // already failed. If an OnStarting callback throws we can go through
+ // our normal error handling in ProduceEnd.
+ // https://github.com/aspnet/KestrelHttpServer/issues/43
+ if (!HasResponseStarted && _applicationException == null && _onStarting?.Count > 0)
+ {
+ await FireOnStarting();
+ }
+
+ if (!_connectionAborted && !VerifyResponseContentLength(out var lengthException))
+ {
+ ReportApplicationError(lengthException);
+ }
+ }
+ catch (BadHttpRequestException ex)
+ {
+ // Capture BadHttpRequestException for further processing
+ // This has to be caught here so StatusCode is set properly before disposing the HttpContext
+ // (DisposeContext logs StatusCode).
+ SetBadRequestState(ex);
+ ReportApplicationError(ex);
+ }
+ catch (Exception ex)
+ {
+ ReportApplicationError(ex);
+ }
+
+ KestrelEventSource.Log.RequestStop(this);
+
+ // At this point all user code that needs use to the request or response streams has completed.
+ // Using these streams in the OnCompleted callback is not allowed.
+ try
+ {
+ await _bodyControl.StopAsync();
+ }
+ catch (Exception ex)
+ {
+ // BodyControl.StopAsync() can throw if the PipeWriter was completed prior to the application writing
+ // enough bytes to satisfy the specified Content-Length. This risks double-logging the exception,
+ // but this scenario generally indicates an app bug, so I don't want to risk not logging it.
+ ReportApplicationError(ex);
+ }
+
+ // 4XX responses are written by TryProduceInvalidRequestResponse during connection tear down.
+ if (_requestRejectedException == null)
+ {
+ if (!_connectionAborted)
+ {
+ // Call ProduceEnd() before consuming the rest of the request body to prevent
+ // delaying clients waiting for the chunk terminator:
+ //
+ // https://github.com/dotnet/corefx/issues/17330#issuecomment-288248663
+ //
+ // This also prevents the 100 Continue response from being sent if the app
+ // never tried to read the body.
+ // https://github.com/aspnet/KestrelHttpServer/issues/2102
+ //
+ // ProduceEnd() must be called before _application.DisposeContext(), to ensure
+ // HttpContext.Response.StatusCode is correctly set when
+ // IHttpContextFactory.Dispose(HttpContext) is called.
+ await ProduceEnd();
+ }
+ else if (!HasResponseStarted)
+ {
+ // If the request was aborted and no response was sent, there's no
+ // meaningful status code to log.
+ StatusCode = 0;
+ }
+ }
+
+ if (_onCompleted?.Count > 0)
+ {
+ await FireOnCompleted();
+ }
+
+ application.DisposeContext(context, _applicationException);
+ }
+
public void OnStarting(Func callback, object state)
{
if (HasResponseStarted)
@@ -749,108 +761,55 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
protected Task FireOnStarting()
{
var onStarting = _onStarting;
-
- if (onStarting == null || onStarting.Count == 0)
+ if (onStarting?.Count > 0)
{
- return Task.CompletedTask;
- }
- else
- {
- return FireOnStartingMayAwait(onStarting);
- }
- }
-
- private Task FireOnStartingMayAwait(Stack, object>> onStarting)
- {
- try
- {
- while (onStarting.TryPop(out var entry))
- {
- var task = entry.Key.Invoke(entry.Value);
- if (!task.IsCompletedSuccessfully)
- {
- return FireOnStartingAwaited(task, onStarting);
- }
- }
- }
- catch (Exception ex)
- {
- ReportApplicationError(ex);
+ return ProcessEvents(this, onStarting);
}
return Task.CompletedTask;
- }
- private async Task FireOnStartingAwaited(Task currentTask, Stack, object>> onStarting)
- {
- try
+ static async Task ProcessEvents(HttpProtocol protocol, Stack, object>> events)
{
- await currentTask;
-
- while (onStarting.TryPop(out var entry))
+ // Try/Catch is outside the loop as any error that occurs is before the request starts.
+ // So we want to report it as an ApplicationError to fail the request and not process more events.
+ try
{
- await entry.Key.Invoke(entry.Value);
+ while (events.TryPop(out var entry))
+ {
+ await entry.Key.Invoke(entry.Value);
+ }
+ }
+ catch (Exception ex)
+ {
+ protocol.ReportApplicationError(ex);
}
- }
- catch (Exception ex)
- {
- ReportApplicationError(ex);
}
}
protected Task FireOnCompleted()
{
var onCompleted = _onCompleted;
-
- if (onCompleted == null || onCompleted.Count == 0)
+ if (onCompleted?.Count > 0)
{
- return Task.CompletedTask;
- }
-
- return FireOnCompletedMayAwait(onCompleted);
- }
-
- private Task FireOnCompletedMayAwait(Stack, object>> onCompleted)
- {
- while (onCompleted.TryPop(out var entry))
- {
- try
- {
- var task = entry.Key.Invoke(entry.Value);
- if (!task.IsCompletedSuccessfully)
- {
- return FireOnCompletedAwaited(task, onCompleted);
- }
- }
- catch (Exception ex)
- {
- ReportApplicationError(ex);
- }
+ return ProcessEvents(this, onCompleted);
}
return Task.CompletedTask;
- }
- private async Task FireOnCompletedAwaited(Task currentTask, Stack, object>> onCompleted)
- {
- try
+ static async Task ProcessEvents(HttpProtocol protocol, Stack, object>> events)
{
- await currentTask;
- }
- catch (Exception ex)
- {
- Log.ApplicationError(ConnectionId, TraceIdentifier, ex);
- }
-
- while (onCompleted.TryPop(out var entry))
- {
- try
+ // Try/Catch is inside the loop as any error that occurs is after the request has finished.
+ // So we will just log it and keep processing the events, as the completion has already happened.
+ while (events.TryPop(out var entry))
{
- await entry.Key.Invoke(entry.Value);
- }
- catch (Exception ex)
- {
- Log.ApplicationError(ConnectionId, TraceIdentifier, ex);
+ try
+ {
+ await entry.Key.Invoke(entry.Value);
+ }
+ catch (Exception ex)
+ {
+ protocol.Log.ApplicationError(protocol.ConnectionId, protocol.TraceIdentifier, ex);
+ }
}
}
}
@@ -1258,9 +1217,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
SetErrorResponseHeaders(ex.StatusCode);
- if (!StringValues.IsNullOrEmpty(ex.AllowedHeader))
+#pragma warning disable CS0618 // Type or member is obsolete
+ if (ex is Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException kestrelEx && !StringValues.IsNullOrEmpty(kestrelEx.AllowedHeader))
+#pragma warning restore CS0618 // Type or member is obsolete
{
- HttpResponseHeaders.HeaderAllow = ex.AllowedHeader;
+ HttpResponseHeaders.HeaderAllow = kestrelEx.AllowedHeader;
}
}
@@ -1314,7 +1275,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
[MethodImpl(MethodImplOptions.NoInlining)]
private BadHttpRequestException GetInvalidRequestTargetException(Span target)
- => BadHttpRequestException.GetException(
+ => KestrelBadHttpRequestException.GetException(
RequestRejectionReason.InvalidRequestTarget,
Log.IsEnabled(LogLevel.Information)
? target.GetAsciiStringEscaped(Constants.MaxExceptionDetailSize)
diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpRequestHeaders.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpRequestHeaders.cs
index a5e78a4442..95f5c69e79 100644
--- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpRequestHeaders.cs
+++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpRequestHeaders.cs
@@ -65,7 +65,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
if (!HeaderUtilities.TryParseNonNegativeInt64(value, out var parsed))
{
- BadHttpRequestException.Throw(RequestRejectionReason.InvalidContentLength, value);
+ KestrelBadHttpRequestException.Throw(RequestRejectionReason.InvalidContentLength, value);
}
return parsed;
@@ -76,14 +76,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
if (_contentLength.HasValue)
{
- BadHttpRequestException.Throw(RequestRejectionReason.MultipleContentLengths);
+ KestrelBadHttpRequestException.Throw(RequestRejectionReason.MultipleContentLengths);
}
if (!Utf8Parser.TryParse(value, out long parsed, out var consumed) ||
parsed < 0 ||
consumed != value.Length)
{
- BadHttpRequestException.Throw(RequestRejectionReason.InvalidContentLength, value.GetRequestHeaderStringNonNullCharacters(UseLatin1));
+ KestrelBadHttpRequestException.Throw(RequestRejectionReason.InvalidContentLength, value.GetRequestHeaderStringNonNullCharacters(UseLatin1));
}
_contentLength = parsed;
diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/MessageBody.cs b/src/Servers/Kestrel/Core/src/Internal/Http/MessageBody.cs
index e97712f0ae..4f55f3de74 100644
--- a/src/Servers/Kestrel/Core/src/Internal/Http/MessageBody.cs
+++ b/src/Servers/Kestrel/Core/src/Internal/Http/MessageBody.cs
@@ -170,7 +170,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
if (_consumedBytes > _context.MaxRequestBodySize)
{
- BadHttpRequestException.Throw(RequestRejectionReason.RequestBodyTooLarge);
+ KestrelBadHttpRequestException.Throw(RequestRejectionReason.RequestBodyTooLarge);
}
}
diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/HPackHeaderWriter.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/HPackHeaderWriter.cs
index 1598a18c7f..33c7b920f3 100644
--- a/src/Servers/Kestrel/Core/src/Internal/Http2/HPackHeaderWriter.cs
+++ b/src/Servers/Kestrel/Core/src/Internal/Http2/HPackHeaderWriter.cs
@@ -4,7 +4,6 @@
using System;
using System.Net.Http;
using System.Net.Http.HPack;
-using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{
@@ -13,57 +12,105 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
///
/// Begin encoding headers in the first HEADERS frame.
///
- public static bool BeginEncodeHeaders(int statusCode, Http2HeadersEnumerator headersEnumerator, Span buffer, out int length)
+ public static bool BeginEncodeHeaders(int statusCode, HPackEncoder hpackEncoder, Http2HeadersEnumerator headersEnumerator, Span buffer, out int length)
{
- if (!HPackEncoder.EncodeStatusHeader(statusCode, buffer, out var statusCodeLength))
+ length = 0;
+
+ if (!hpackEncoder.EnsureDynamicTableSizeUpdate(buffer, out var sizeUpdateLength))
{
throw new HPackEncodingException(SR.net_http_hpack_encode_failure);
}
+ length += sizeUpdateLength;
+
+ if (!EncodeStatusHeader(statusCode, hpackEncoder, buffer.Slice(length), out var statusCodeLength))
+ {
+ throw new HPackEncodingException(SR.net_http_hpack_encode_failure);
+ }
+ length += statusCodeLength;
if (!headersEnumerator.MoveNext())
{
- length = statusCodeLength;
return true;
}
// We're ok with not throwing if no headers were encoded because we've already encoded the status.
// There is a small chance that the header will encode if there is no other content in the next HEADERS frame.
- var done = EncodeHeaders(headersEnumerator, buffer.Slice(statusCodeLength), throwIfNoneEncoded: false, out var headersLength);
- length = statusCodeLength + headersLength;
-
+ var done = EncodeHeadersCore(hpackEncoder, headersEnumerator, buffer.Slice(length), throwIfNoneEncoded: false, out var headersLength);
+ length += headersLength;
return done;
}
///
/// Begin encoding headers in the first HEADERS frame.
///
- public static bool BeginEncodeHeaders(Http2HeadersEnumerator headersEnumerator, Span buffer, out int length)
+ public static bool BeginEncodeHeaders(HPackEncoder hpackEncoder, Http2HeadersEnumerator headersEnumerator, Span buffer, out int length)
{
+ length = 0;
+
+ if (!hpackEncoder.EnsureDynamicTableSizeUpdate(buffer, out var sizeUpdateLength))
+ {
+ throw new HPackEncodingException(SR.net_http_hpack_encode_failure);
+ }
+ length += sizeUpdateLength;
+
if (!headersEnumerator.MoveNext())
{
- length = 0;
return true;
}
- return EncodeHeaders(headersEnumerator, buffer, throwIfNoneEncoded: true, out length);
+ var done = EncodeHeadersCore(hpackEncoder, headersEnumerator, buffer.Slice(length), throwIfNoneEncoded: true, out var headersLength);
+ length += headersLength;
+ return done;
}
///
/// Continue encoding headers in the next HEADERS frame. The enumerator should already have a current value.
///
- public static bool ContinueEncodeHeaders(Http2HeadersEnumerator headersEnumerator, Span buffer, out int length)
+ public static bool ContinueEncodeHeaders(HPackEncoder hpackEncoder, Http2HeadersEnumerator headersEnumerator, Span buffer, out int length)
{
- return EncodeHeaders(headersEnumerator, buffer, throwIfNoneEncoded: true, out length);
+ return EncodeHeadersCore(hpackEncoder, headersEnumerator, buffer, throwIfNoneEncoded: true, out length);
}
- private static bool EncodeHeaders(Http2HeadersEnumerator headersEnumerator, Span buffer, bool throwIfNoneEncoded, out int length)
+ private static bool EncodeStatusHeader(int statusCode, HPackEncoder hpackEncoder, Span buffer, out int length)
+ {
+ switch (statusCode)
+ {
+ case 200:
+ case 204:
+ case 206:
+ case 304:
+ case 400:
+ case 404:
+ case 500:
+ // Status codes which exist in the HTTP/2 StaticTable.
+ return HPackEncoder.EncodeIndexedHeaderField(H2StaticTable.StatusIndex[statusCode], buffer, out length);
+ default:
+ const string name = ":status";
+ var value = StatusCodes.ToStatusString(statusCode);
+ return hpackEncoder.EncodeHeader(buffer, H2StaticTable.Status200, HeaderEncodingHint.Index, name, value, out length);
+ }
+ }
+
+ private static bool EncodeHeadersCore(HPackEncoder hpackEncoder, Http2HeadersEnumerator headersEnumerator, Span buffer, bool throwIfNoneEncoded, out int length)
{
var currentLength = 0;
do
{
- if (!EncodeHeader(headersEnumerator.KnownHeaderType, headersEnumerator.Current.Key, headersEnumerator.Current.Value, buffer.Slice(currentLength), out int headerLength))
+ var staticTableId = headersEnumerator.HPackStaticTableId;
+ var name = headersEnumerator.Current.Key;
+ var value = headersEnumerator.Current.Value;
+
+ var hint = ResolveHeaderEncodingHint(staticTableId, name);
+
+ if (!hpackEncoder.EncodeHeader(
+ buffer.Slice(currentLength),
+ staticTableId,
+ hint,
+ name,
+ value,
+ out var headerLength))
{
- // The the header wasn't written and no headers have been written then the header is too large.
+ // If the header wasn't written, and no headers have been written, then the header is too large.
// Throw an error to avoid an infinite loop of attempting to write large header.
if (currentLength == 0 && throwIfNoneEncoded)
{
@@ -79,79 +126,48 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
while (headersEnumerator.MoveNext());
length = currentLength;
-
return true;
}
- private static bool EncodeHeader(KnownHeaderType knownHeaderType, string name, string value, Span buffer, out int length)
+ private static HeaderEncodingHint ResolveHeaderEncodingHint(int staticTableId, string name)
{
- var hPackStaticTableId = GetResponseHeaderStaticTableId(knownHeaderType);
-
- if (hPackStaticTableId == -1)
+ HeaderEncodingHint hint;
+ if (IsSensitive(staticTableId, name))
{
- return HPackEncoder.EncodeLiteralHeaderFieldWithoutIndexingNewName(name, value, buffer, out length);
+ hint = HeaderEncodingHint.NeverIndex;
+ }
+ else if (IsNotDynamicallyIndexed(staticTableId))
+ {
+ hint = HeaderEncodingHint.IgnoreIndex;
}
else
{
- return HPackEncoder.EncodeLiteralHeaderFieldWithoutIndexing(hPackStaticTableId, value, buffer, out length);
+ hint = HeaderEncodingHint.Index;
}
+
+ return hint;
}
- private static int GetResponseHeaderStaticTableId(KnownHeaderType responseHeaderType)
+ private static bool IsSensitive(int staticTableIndex, string name)
{
- switch (responseHeaderType)
+ // Set-Cookie could contain sensitive data.
+ if (staticTableIndex == H2StaticTable.SetCookie)
{
- case KnownHeaderType.CacheControl:
- return H2StaticTable.CacheControl;
- case KnownHeaderType.Date:
- return H2StaticTable.Date;
- case KnownHeaderType.TransferEncoding:
- return H2StaticTable.TransferEncoding;
- case KnownHeaderType.Via:
- return H2StaticTable.Via;
- case KnownHeaderType.Allow:
- return H2StaticTable.Allow;
- case KnownHeaderType.ContentType:
- return H2StaticTable.ContentType;
- case KnownHeaderType.ContentEncoding:
- return H2StaticTable.ContentEncoding;
- case KnownHeaderType.ContentLanguage:
- return H2StaticTable.ContentLanguage;
- case KnownHeaderType.ContentLocation:
- return H2StaticTable.ContentLocation;
- case KnownHeaderType.ContentRange:
- return H2StaticTable.ContentRange;
- case KnownHeaderType.Expires:
- return H2StaticTable.Expires;
- case KnownHeaderType.LastModified:
- return H2StaticTable.LastModified;
- case KnownHeaderType.AcceptRanges:
- return H2StaticTable.AcceptRanges;
- case KnownHeaderType.Age:
- return H2StaticTable.Age;
- case KnownHeaderType.ETag:
- return H2StaticTable.ETag;
- case KnownHeaderType.Location:
- return H2StaticTable.Location;
- case KnownHeaderType.ProxyAuthenticate:
- return H2StaticTable.ProxyAuthenticate;
- case KnownHeaderType.RetryAfter:
- return H2StaticTable.RetryAfter;
- case KnownHeaderType.Server:
- return H2StaticTable.Server;
- case KnownHeaderType.SetCookie:
- return H2StaticTable.SetCookie;
- case KnownHeaderType.Vary:
- return H2StaticTable.Vary;
- case KnownHeaderType.WWWAuthenticate:
- return H2StaticTable.WwwAuthenticate;
- case KnownHeaderType.AccessControlAllowOrigin:
- return H2StaticTable.AccessControlAllowOrigin;
- case KnownHeaderType.ContentLength:
- return H2StaticTable.ContentLength;
- default:
- return -1;
+ return true;
}
+ if (string.Equals(name, "Content-Disposition", StringComparison.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ private static bool IsNotDynamicallyIndexed(int staticTableIndex)
+ {
+ // Content-Length is added to static content. Content length is different for each
+ // file, and is unlikely to be reused because of browser caching.
+ return staticTableIndex == H2StaticTable.ContentLength;
}
}
}
diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs
index 8666bebc64..3bc267e8c7 100644
--- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs
+++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs
@@ -87,7 +87,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
httpLimits.MinResponseDataRate,
context.ConnectionId,
context.MemoryPool,
- context.ServiceContext.Log);
+ context.ServiceContext);
var inputOptions = new PipeOptions(pool: context.MemoryPool,
readerScheduler: context.ServiceContext.Scheduler,
@@ -150,7 +150,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
public void HandleRequestHeadersTimeout()
{
- Log.ConnectionBadRequest(ConnectionId, BadHttpRequestException.GetException(RequestRejectionReason.RequestHeadersTimeout));
+ Log.ConnectionBadRequest(ConnectionId, KestrelBadHttpRequestException.GetException(RequestRejectionReason.RequestHeadersTimeout));
Abort(new ConnectionAbortedException(CoreStrings.BadRequest_RequestHeadersTimeout));
}
@@ -743,6 +743,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
}
}
+ // Maximum HPack encoder size is limited by Http2Limits.HeaderTableSize, configured max the server.
+ //
+ // Note that the client HPack decoder doesn't care about the ACK so we don't need to lock sending the
+ // ACK and updating the table size on the server together.
+ // The client will wait until a size agreed upon by it (sent in SETTINGS_HEADER_TABLE_SIZE) and the
+ // server (sent as a dynamic table size update in the next HEADERS frame) is received before applying
+ // the new size.
+ _frameWriter.UpdateMaxHeaderTableSize(Math.Min(_clientSettings.HeaderTableSize, (uint)Limits.Http2.HeaderTableSize));
+
return ackTask.AsTask();
}
catch (Http2SettingsParameterOutOfRangeException ex)
@@ -1188,7 +1197,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
_currentHeadersStream.OnHeader(name, value);
}
}
- catch (BadHttpRequestException bre)
+ catch (Microsoft.AspNetCore.Http.BadHttpRequestException bre)
{
throw new Http2ConnectionErrorException(bre.Message, Http2ErrorCode.PROTOCOL_ERROR);
}
diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs
index fd8d553067..ca428eae8a 100644
--- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs
+++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs
@@ -38,6 +38,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
private readonly ITimeoutControl _timeoutControl;
private readonly MinDataRate _minResponseDataRate;
private readonly TimingPipeFlusher _flusher;
+ private readonly HPackEncoder _hpackEncoder;
private uint _maxFrameSize = Http2PeerSettings.MinAllowedMaxFrameSize;
private byte[] _headerEncodingBuffer;
@@ -55,7 +56,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
MinDataRate minResponseDataRate,
string connectionId,
MemoryPool memoryPool,
- IKestrelTrace log)
+ ServiceContext serviceContext)
{
// Allow appending more data to the PipeWriter when a flush is pending.
_outputWriter = new ConcurrentPipeWriter(outputPipeWriter, memoryPool, _writeLock);
@@ -63,12 +64,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
_http2Connection = http2Connection;
_connectionOutputFlowControl = connectionOutputFlowControl;
_connectionId = connectionId;
- _log = log;
+ _log = serviceContext.Log;
_timeoutControl = timeoutControl;
_minResponseDataRate = minResponseDataRate;
- _flusher = new TimingPipeFlusher(_outputWriter, timeoutControl, log);
+ _flusher = new TimingPipeFlusher(_outputWriter, timeoutControl, serviceContext.Log);
_outgoingFrame = new Http2Frame();
_headerEncodingBuffer = new byte[_maxFrameSize];
+
+ _hpackEncoder = new HPackEncoder(serviceContext.ServerOptions.AllowResponseHeaderCompression);
+ }
+
+ public void UpdateMaxHeaderTableSize(uint maxHeaderTableSize)
+ {
+ lock (_writeLock)
+ {
+ _hpackEncoder.UpdateMaxHeaderTableSize(maxHeaderTableSize);
+ }
}
public void UpdateMaxFrameSize(uint maxFrameSize)
@@ -175,7 +186,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
_headersEnumerator.Initialize(headers);
_outgoingFrame.PrepareHeaders(headerFrameFlags, streamId);
var buffer = _headerEncodingBuffer.AsSpan();
- var done = HPackHeaderWriter.BeginEncodeHeaders(statusCode, _headersEnumerator, buffer, out var payloadLength);
+ var done = HPackHeaderWriter.BeginEncodeHeaders(statusCode, _hpackEncoder, _headersEnumerator, buffer, out var payloadLength);
FinishWritingHeaders(streamId, payloadLength, done);
}
catch (HPackEncodingException hex)
@@ -201,7 +212,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
_headersEnumerator.Initialize(headers);
_outgoingFrame.PrepareHeaders(Http2HeadersFrameFlags.END_STREAM, streamId);
var buffer = _headerEncodingBuffer.AsSpan();
- var done = HPackHeaderWriter.BeginEncodeHeaders(_headersEnumerator, buffer, out var payloadLength);
+ var done = HPackHeaderWriter.BeginEncodeHeaders(_hpackEncoder, _headersEnumerator, buffer, out var payloadLength);
FinishWritingHeaders(streamId, payloadLength, done);
}
catch (HPackEncodingException hex)
@@ -230,7 +241,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{
_outgoingFrame.PrepareContinuation(Http2ContinuationFrameFlags.NONE, streamId);
- done = HPackHeaderWriter.ContinueEncodeHeaders(_headersEnumerator, buffer, out payloadLength);
+ done = HPackHeaderWriter.ContinueEncodeHeaders(_hpackEncoder, _headersEnumerator, buffer, out payloadLength);
_outgoingFrame.PayloadLength = payloadLength;
if (done)
diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2HeaderEnumerator.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2HeaderEnumerator.cs
index 421650b9fd..db21bfb0fd 100644
--- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2HeaderEnumerator.cs
+++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2HeaderEnumerator.cs
@@ -3,6 +3,7 @@
using System.Collections;
using System.Collections.Generic;
+using System.Net.Http.HPack;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.Extensions.Primitives;
@@ -15,8 +16,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
private HttpResponseTrailers.Enumerator _trailersEnumerator;
private IEnumerator> _genericEnumerator;
private StringValues.Enumerator _stringValuesEnumerator;
+ private KnownHeaderType _knownHeaderType;
- public KnownHeaderType KnownHeaderType { get; private set; }
+ public int HPackStaticTableId => GetResponseHeaderStaticTableId(_knownHeaderType);
public KeyValuePair Current { get; private set; }
object IEnumerator.Current => Current;
@@ -33,7 +35,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
_stringValuesEnumerator = default;
Current = default;
- KnownHeaderType = default;
+ _knownHeaderType = default;
}
public void Initialize(HttpResponseTrailers headers)
@@ -45,7 +47,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
_stringValuesEnumerator = default;
Current = default;
- KnownHeaderType = default;
+ _knownHeaderType = default;
}
public void Initialize(IDictionary headers)
@@ -57,7 +59,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
_stringValuesEnumerator = default;
Current = default;
- KnownHeaderType = default;
+ _knownHeaderType = default;
}
public bool MoveNext()
@@ -110,7 +112,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
else
{
enumerator = _genericEnumerator.Current.Value.GetEnumerator();
- KnownHeaderType = default;
+ _knownHeaderType = default;
return true;
}
}
@@ -124,7 +126,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
else
{
enumerator = _trailersEnumerator.Current.Value.GetEnumerator();
- KnownHeaderType = _trailersEnumerator.CurrentKnownType;
+ _knownHeaderType = _trailersEnumerator.CurrentKnownType;
return true;
}
}
@@ -138,7 +140,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
else
{
enumerator = _headersEnumerator.Current.Value.GetEnumerator();
- KnownHeaderType = _headersEnumerator.CurrentKnownType;
+ _knownHeaderType = _headersEnumerator.CurrentKnownType;
return true;
}
}
@@ -159,11 +161,68 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
_headersEnumerator.Reset();
}
_stringValuesEnumerator = default;
- KnownHeaderType = default;
+ _knownHeaderType = default;
}
public void Dispose()
{
}
+
+ internal static int GetResponseHeaderStaticTableId(KnownHeaderType responseHeaderType)
+ {
+ switch (responseHeaderType)
+ {
+ case KnownHeaderType.CacheControl:
+ return H2StaticTable.CacheControl;
+ case KnownHeaderType.Date:
+ return H2StaticTable.Date;
+ case KnownHeaderType.TransferEncoding:
+ return H2StaticTable.TransferEncoding;
+ case KnownHeaderType.Via:
+ return H2StaticTable.Via;
+ case KnownHeaderType.Allow:
+ return H2StaticTable.Allow;
+ case KnownHeaderType.ContentType:
+ return H2StaticTable.ContentType;
+ case KnownHeaderType.ContentEncoding:
+ return H2StaticTable.ContentEncoding;
+ case KnownHeaderType.ContentLanguage:
+ return H2StaticTable.ContentLanguage;
+ case KnownHeaderType.ContentLocation:
+ return H2StaticTable.ContentLocation;
+ case KnownHeaderType.ContentRange:
+ return H2StaticTable.ContentRange;
+ case KnownHeaderType.Expires:
+ return H2StaticTable.Expires;
+ case KnownHeaderType.LastModified:
+ return H2StaticTable.LastModified;
+ case KnownHeaderType.AcceptRanges:
+ return H2StaticTable.AcceptRanges;
+ case KnownHeaderType.Age:
+ return H2StaticTable.Age;
+ case KnownHeaderType.ETag:
+ return H2StaticTable.ETag;
+ case KnownHeaderType.Location:
+ return H2StaticTable.Location;
+ case KnownHeaderType.ProxyAuthenticate:
+ return H2StaticTable.ProxyAuthenticate;
+ case KnownHeaderType.RetryAfter:
+ return H2StaticTable.RetryAfter;
+ case KnownHeaderType.Server:
+ return H2StaticTable.Server;
+ case KnownHeaderType.SetCookie:
+ return H2StaticTable.SetCookie;
+ case KnownHeaderType.Vary:
+ return H2StaticTable.Vary;
+ case KnownHeaderType.WWWAuthenticate:
+ return H2StaticTable.WwwAuthenticate;
+ case KnownHeaderType.AccessControlAllowOrigin:
+ return H2StaticTable.AccessControlAllowOrigin;
+ case KnownHeaderType.ContentLength:
+ return H2StaticTable.ContentLength;
+ default:
+ return -1;
+ }
+ }
}
}
diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2MessageBody.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2MessageBody.cs
index dd62d69cbb..3d1dead1b9 100644
--- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2MessageBody.cs
+++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2MessageBody.cs
@@ -26,7 +26,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
// Note ContentLength or MaxRequestBodySize may be null
if (_context.RequestHeaders.ContentLength > _context.MaxRequestBodySize)
{
- BadHttpRequestException.Throw(RequestRejectionReason.RequestBodyTooLarge);
+ KestrelBadHttpRequestException.Throw(RequestRejectionReason.RequestBodyTooLarge);
}
}
diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2OutputProducer.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2OutputProducer.cs
index 0f95f70ef7..cc30a8ee66 100644
--- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2OutputProducer.cs
+++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2OutputProducer.cs
@@ -41,10 +41,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
private bool _suffixSent;
private bool _streamEnded;
private bool _writerComplete;
- private bool _disposed;
// Internal for testing
internal ValueTask _dataWriteProcessingTask;
+ internal bool _disposed;
/// The core logic for the IValueTaskSource implementation.
private ManualResetValueTaskSourceCore _responseCompleteTaskSource = new ManualResetValueTaskSourceCore { RunContinuationsAsynchronously = true }; // mutable struct, do not make this readonly
diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2StreamStack.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2StreamStack.cs
index 1c141dd236..88512a19df 100644
--- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2StreamStack.cs
+++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2StreamStack.cs
@@ -37,6 +37,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
return true;
}
+ public bool TryPeek(out Http2Stream result)
+ {
+ int size = _size - 1;
+ Http2StreamAsValueType[] array = _array;
+
+ if ((uint)size >= (uint)array.Length)
+ {
+ result = default;
+ return false;
+ }
+
+ result = array[size];
+ return true;
+ }
+
// Pushes an item to the top of the stack.
public void Push(Http2Stream item)
{
diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ControlStream.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ControlStream.cs
index a75d8bc69a..0c89d21c0d 100644
--- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ControlStream.cs
+++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ControlStream.cs
@@ -67,7 +67,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
public void HandleRequestHeadersTimeout()
{
- //Log.ConnectionBadRequest(ConnectionId, BadHttpRequestException.GetException(RequestRejectionReason.RequestHeadersTimeout));
+ //Log.ConnectionBadRequest(ConnectionId, KestrelBadHttpRequestException.GetException(RequestRejectionReason.RequestHeadersTimeout));
Abort(new ConnectionAbortedException(CoreStrings.BadRequest_RequestHeadersTimeout));
}
diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3MessageBody.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3MessageBody.cs
index 5af5f8df47..77efd60e7b 100644
--- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3MessageBody.cs
+++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3MessageBody.cs
@@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
// Note ContentLength or MaxRequestBodySize may be null
if (_context.RequestHeaders.ContentLength > _context.MaxRequestBodySize)
{
- BadHttpRequestException.Throw(RequestRejectionReason.RequestBodyTooLarge);
+ KestrelBadHttpRequestException.Throw(RequestRejectionReason.RequestBodyTooLarge);
}
}
diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs
index 6518d00afd..576443150c 100644
--- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs
+++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs
@@ -143,7 +143,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
base.OnHeader(name, value);
}
}
- catch (BadHttpRequestException bre)
+ catch (Microsoft.AspNetCore.Http.BadHttpRequestException bre)
{
throw new Http3StreamErrorException(bre.Message, Http3ErrorCode.ProtocolError);
}
@@ -293,7 +293,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
public void HandleRequestHeadersTimeout()
{
- Log.ConnectionBadRequest(ConnectionId, BadHttpRequestException.GetException(RequestRejectionReason.RequestHeadersTimeout));
+ Log.ConnectionBadRequest(ConnectionId, KestrelBadHttpRequestException.GetException(RequestRejectionReason.RequestHeadersTimeout));
Abort(new ConnectionAbortedException(CoreStrings.BadRequest_RequestHeadersTimeout), Http3ErrorCode.RequestRejected);
}
diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpUtilities.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpUtilities.cs
index 70fa7f00b3..9e21228c6d 100644
--- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpUtilities.cs
+++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpUtilities.cs
@@ -107,7 +107,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
// in the string
if (!StringUtilities.TryGetAsciiString((byte*)state.ToPointer(), output, buffer.Length))
{
- BadHttpRequestException.Throw(RequestRejectionReason.InvalidCharactersInHeaderName);
+ KestrelBadHttpRequestException.Throw(RequestRejectionReason.InvalidCharactersInHeaderName);
}
}
}
diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/IKestrelTrace.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/IKestrelTrace.cs
index cd441ae3a2..54a8392fae 100644
--- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/IKestrelTrace.cs
+++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/IKestrelTrace.cs
@@ -33,7 +33,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
void NotAllConnectionsClosedGracefully();
- void ConnectionBadRequest(string connectionId, BadHttpRequestException ex);
+ void ConnectionBadRequest(string connectionId, Microsoft.AspNetCore.Http.BadHttpRequestException ex);
void ApplicationError(string connectionId, string traceIdentifier, Exception ex);
diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelTrace.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelTrace.cs
index f89732a704..6edaf7b599 100644
--- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelTrace.cs
+++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelTrace.cs
@@ -175,7 +175,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
_notAllConnectionsClosedGracefully(_logger, null);
}
- public virtual void ConnectionBadRequest(string connectionId, BadHttpRequestException ex)
+ public virtual void ConnectionBadRequest(string connectionId, Microsoft.AspNetCore.Http.BadHttpRequestException ex)
{
_connectionBadRequest(_logger, connectionId, ex.Message, ex);
}
diff --git a/src/Servers/Kestrel/Core/src/KestralBadHttpRequestException.cs b/src/Servers/Kestrel/Core/src/KestralBadHttpRequestException.cs
new file mode 100644
index 0000000000..0921e41e77
--- /dev/null
+++ b/src/Servers/Kestrel/Core/src/KestralBadHttpRequestException.cs
@@ -0,0 +1,159 @@
+// 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.Diagnostics;
+using System.Runtime.CompilerServices;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.AspNetCore.Server.Kestrel.Core
+{
+ internal static class KestrelBadHttpRequestException
+ {
+ [StackTraceHidden]
+ internal static void Throw(RequestRejectionReason reason)
+ {
+ throw GetException(reason);
+ }
+
+ [StackTraceHidden]
+ internal static void Throw(RequestRejectionReason reason, HttpMethod method)
+ => throw GetException(reason, method.ToString().ToUpperInvariant());
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+#pragma warning disable CS0618 // Type or member is obsolete
+ internal static BadHttpRequestException GetException(RequestRejectionReason reason)
+ {
+ BadHttpRequestException ex;
+ switch (reason)
+ {
+ case RequestRejectionReason.InvalidRequestHeadersNoCRLF:
+ ex = new BadHttpRequestException(CoreStrings.BadRequest_InvalidRequestHeadersNoCRLF, StatusCodes.Status400BadRequest, reason);
+ break;
+ case RequestRejectionReason.InvalidRequestLine:
+ ex = new BadHttpRequestException(CoreStrings.BadRequest_InvalidRequestLine, StatusCodes.Status400BadRequest, reason);
+ break;
+ case RequestRejectionReason.MalformedRequestInvalidHeaders:
+ ex = new BadHttpRequestException(CoreStrings.BadRequest_MalformedRequestInvalidHeaders, StatusCodes.Status400BadRequest, reason);
+ break;
+ case RequestRejectionReason.MultipleContentLengths:
+ ex = new BadHttpRequestException(CoreStrings.BadRequest_MultipleContentLengths, StatusCodes.Status400BadRequest, reason);
+ break;
+ case RequestRejectionReason.UnexpectedEndOfRequestContent:
+ ex = new BadHttpRequestException(CoreStrings.BadRequest_UnexpectedEndOfRequestContent, StatusCodes.Status400BadRequest, reason);
+ break;
+ case RequestRejectionReason.BadChunkSuffix:
+ ex = new BadHttpRequestException(CoreStrings.BadRequest_BadChunkSuffix, StatusCodes.Status400BadRequest, reason);
+ break;
+ case RequestRejectionReason.BadChunkSizeData:
+ ex = new BadHttpRequestException(CoreStrings.BadRequest_BadChunkSizeData, StatusCodes.Status400BadRequest, reason);
+ break;
+ case RequestRejectionReason.ChunkedRequestIncomplete:
+ ex = new BadHttpRequestException(CoreStrings.BadRequest_ChunkedRequestIncomplete, StatusCodes.Status400BadRequest, reason);
+ break;
+ case RequestRejectionReason.InvalidCharactersInHeaderName:
+ ex = new BadHttpRequestException(CoreStrings.BadRequest_InvalidCharactersInHeaderName, StatusCodes.Status400BadRequest, reason);
+ break;
+ case RequestRejectionReason.RequestLineTooLong:
+ ex = new BadHttpRequestException(CoreStrings.BadRequest_RequestLineTooLong, StatusCodes.Status414UriTooLong, reason);
+ break;
+ case RequestRejectionReason.HeadersExceedMaxTotalSize:
+ ex = new BadHttpRequestException(CoreStrings.BadRequest_HeadersExceedMaxTotalSize, StatusCodes.Status431RequestHeaderFieldsTooLarge, reason);
+ break;
+ case RequestRejectionReason.TooManyHeaders:
+ ex = new BadHttpRequestException(CoreStrings.BadRequest_TooManyHeaders, StatusCodes.Status431RequestHeaderFieldsTooLarge, reason);
+ break;
+ case RequestRejectionReason.RequestBodyTooLarge:
+ ex = new BadHttpRequestException(CoreStrings.BadRequest_RequestBodyTooLarge, StatusCodes.Status413PayloadTooLarge, reason);
+ break;
+ case RequestRejectionReason.RequestHeadersTimeout:
+ ex = new BadHttpRequestException(CoreStrings.BadRequest_RequestHeadersTimeout, StatusCodes.Status408RequestTimeout, reason);
+ break;
+ case RequestRejectionReason.RequestBodyTimeout:
+ ex = new BadHttpRequestException(CoreStrings.BadRequest_RequestBodyTimeout, StatusCodes.Status408RequestTimeout, reason);
+ break;
+ case RequestRejectionReason.OptionsMethodRequired:
+ ex = new BadHttpRequestException(CoreStrings.BadRequest_MethodNotAllowed, StatusCodes.Status405MethodNotAllowed, reason, HttpMethod.Options);
+ break;
+ case RequestRejectionReason.ConnectMethodRequired:
+ ex = new BadHttpRequestException(CoreStrings.BadRequest_MethodNotAllowed, StatusCodes.Status405MethodNotAllowed, reason, HttpMethod.Connect);
+ break;
+ case RequestRejectionReason.MissingHostHeader:
+ ex = new BadHttpRequestException(CoreStrings.BadRequest_MissingHostHeader, StatusCodes.Status400BadRequest, reason);
+ break;
+ case RequestRejectionReason.MultipleHostHeaders:
+ ex = new BadHttpRequestException(CoreStrings.BadRequest_MultipleHostHeaders, StatusCodes.Status400BadRequest, reason);
+ break;
+ case RequestRejectionReason.InvalidHostHeader:
+ ex = new BadHttpRequestException(CoreStrings.BadRequest_InvalidHostHeader, StatusCodes.Status400BadRequest, reason);
+ break;
+ case RequestRejectionReason.UpgradeRequestCannotHavePayload:
+ ex = new BadHttpRequestException(CoreStrings.BadRequest_UpgradeRequestCannotHavePayload, StatusCodes.Status400BadRequest, reason);
+ break;
+ default:
+ ex = new BadHttpRequestException(CoreStrings.BadRequest, StatusCodes.Status400BadRequest, reason);
+ break;
+ }
+ return ex;
+ }
+#pragma warning restore CS0618 // Type or member is obsolete
+
+ [StackTraceHidden]
+ internal static void Throw(RequestRejectionReason reason, string detail)
+ {
+ throw GetException(reason, detail);
+ }
+
+ [StackTraceHidden]
+ internal static void Throw(RequestRejectionReason reason, StringValues detail)
+ {
+ throw GetException(reason, detail.ToString());
+ }
+
+#pragma warning disable CS0618 // Type or member is obsolete
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ internal static BadHttpRequestException GetException(RequestRejectionReason reason, string detail)
+ {
+ BadHttpRequestException ex;
+ switch (reason)
+ {
+ case RequestRejectionReason.TlsOverHttpError:
+ ex = new BadHttpRequestException(CoreStrings.HttpParserTlsOverHttpError, StatusCodes.Status400BadRequest, reason);
+ break;
+ case RequestRejectionReason.InvalidRequestLine:
+ ex = new BadHttpRequestException(CoreStrings.FormatBadRequest_InvalidRequestLine_Detail(detail), StatusCodes.Status400BadRequest, reason);
+ break;
+ case RequestRejectionReason.InvalidRequestTarget:
+ ex = new BadHttpRequestException(CoreStrings.FormatBadRequest_InvalidRequestTarget_Detail(detail), StatusCodes.Status400BadRequest, reason);
+ break;
+ case RequestRejectionReason.InvalidRequestHeader:
+ ex = new BadHttpRequestException(CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(detail), StatusCodes.Status400BadRequest, reason);
+ break;
+ case RequestRejectionReason.InvalidContentLength:
+ ex = new BadHttpRequestException(CoreStrings.FormatBadRequest_InvalidContentLength_Detail(detail), StatusCodes.Status400BadRequest, reason);
+ break;
+ case RequestRejectionReason.UnrecognizedHTTPVersion:
+ ex = new BadHttpRequestException(CoreStrings.FormatBadRequest_UnrecognizedHTTPVersion(detail), StatusCodes.Status505HttpVersionNotsupported, reason);
+ break;
+ case RequestRejectionReason.FinalTransferCodingNotChunked:
+ ex = new BadHttpRequestException(CoreStrings.FormatBadRequest_FinalTransferCodingNotChunked(detail), StatusCodes.Status400BadRequest, reason);
+ break;
+ case RequestRejectionReason.LengthRequired:
+ ex = new BadHttpRequestException(CoreStrings.FormatBadRequest_LengthRequired(detail), StatusCodes.Status411LengthRequired, reason);
+ break;
+ case RequestRejectionReason.LengthRequiredHttp10:
+ ex = new BadHttpRequestException(CoreStrings.FormatBadRequest_LengthRequiredHttp10(detail), StatusCodes.Status400BadRequest, reason);
+ break;
+ case RequestRejectionReason.InvalidHostHeader:
+ ex = new BadHttpRequestException(CoreStrings.FormatBadRequest_InvalidHostHeader_Detail(detail), StatusCodes.Status400BadRequest, reason);
+ break;
+ default:
+ ex = new BadHttpRequestException(CoreStrings.BadRequest, StatusCodes.Status400BadRequest, reason);
+ break;
+ }
+ return ex;
+ }
+#pragma warning restore CS0618 // Type or member is obsolete
+ }
+}
diff --git a/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs b/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs
index 71def30d73..c849b84caf 100644
--- a/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs
+++ b/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs
@@ -38,6 +38,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
///
public bool AddServerHeader { get; set; } = true;
+ ///
+ /// Gets or sets a value that controls whether dynamic compression of response headers is allowed.
+ /// For more information about the security considerations of HPack dynamic header compression, visit
+ /// https://tools.ietf.org/html/rfc7541#section-7.
+ ///
+ ///
+ /// Defaults to true.
+ ///
+ public bool AllowResponseHeaderCompression { get; set; } = true;
+
///
/// Gets or sets a value that controls whether synchronous IO is allowed for the and
///
diff --git a/src/Servers/Kestrel/Core/src/Microsoft.AspNetCore.Server.Kestrel.Core.csproj b/src/Servers/Kestrel/Core/src/Microsoft.AspNetCore.Server.Kestrel.Core.csproj
index 72a38463da..73f77bb773 100644
--- a/src/Servers/Kestrel/Core/src/Microsoft.AspNetCore.Server.Kestrel.Core.csproj
+++ b/src/Servers/Kestrel/Core/src/Microsoft.AspNetCore.Server.Kestrel.Core.csproj
@@ -1,4 +1,4 @@
-
+
Core components of ASP.NET Core Kestrel cross-platform web server.
@@ -16,9 +16,10 @@
-
-
-
+
+
+
+
diff --git a/src/Servers/Kestrel/Core/test/HPackHeaderWriterTests.cs b/src/Servers/Kestrel/Core/test/HPackHeaderWriterTests.cs
index 3b290d712b..bab3515211 100644
--- a/src/Servers/Kestrel/Core/test/HPackHeaderWriterTests.cs
+++ b/src/Servers/Kestrel/Core/test/HPackHeaderWriterTests.cs
@@ -1,199 +1,199 @@
-// 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.
+//// 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.Linq;
-using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2;
-using Microsoft.Extensions.Primitives;
-using Xunit;
+//using System;
+//using System.Collections.Generic;
+//using System.Linq;
+//using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2;
+//using Microsoft.Extensions.Primitives;
+//using Xunit;
-namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
-{
- public class HPackHeaderWriterTests
- {
- public static TheoryData[], byte[], int?> SinglePayloadData
- {
- get
- {
- var data = new TheoryData[], byte[], int?>();
+//namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
+//{
+// public class HPackHeaderWriterTests
+// {
+// public static TheoryData[], byte[], int?> SinglePayloadData
+// {
+// get
+// {
+// var data = new TheoryData[], byte[], int?>();
- // Lowercase header name letters only
- data.Add(
- new[]
- {
- new KeyValuePair("CustomHeader", "CustomValue"),
- },
- new byte[]
- {
- // 0 12 c u s t o m
- 0x00, 0x0c, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d,
- // h e a d e r 11 C
- 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x0b, 0x43,
- // u s t o m V a l
- 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x56, 0x61, 0x6c,
- // u e
- 0x75, 0x65
- },
- null);
- // Lowercase header name letters only
- data.Add(
- new[]
- {
- new KeyValuePair("CustomHeader!#$%&'*+-.^_`|~", "CustomValue"),
- },
- new byte[]
- {
- // 0 27 c u s t o m
- 0x00, 0x1b, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d,
- // h e a d e r ! #
- 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x21, 0x23,
- // $ % & ' * + - .
- 0x24, 0x25, 0x26, 0x27, 0x2a, 0x2b, 0x2d, 0x2e,
- // ^ _ ` | ~ 11 C u
- 0x5e, 0x5f, 0x60, 0x7c, 0x7e, 0x0b, 0x43, 0x75,
- // s t o m V a l u
- 0x73, 0x74, 0x6f, 0x6d, 0x56, 0x61, 0x6c, 0x75,
- // e
- 0x65
- },
- null);
- // Single Payload
- data.Add(
- new[]
- {
- new KeyValuePair("date", "Mon, 24 Jul 2017 19:22:30 GMT"),
- new KeyValuePair("content-type", "text/html; charset=utf-8"),
- new KeyValuePair("server", "Kestrel")
- },
- new byte[]
- {
- 0x88, 0x00, 0x04, 0x64, 0x61, 0x74, 0x65, 0x1d,
- 0x4d, 0x6f, 0x6e, 0x2c, 0x20, 0x32, 0x34, 0x20,
- 0x4a, 0x75, 0x6c, 0x20, 0x32, 0x30, 0x31, 0x37,
- 0x20, 0x31, 0x39, 0x3a, 0x32, 0x32, 0x3a, 0x33,
- 0x30, 0x20, 0x47, 0x4d, 0x54, 0x00, 0x0c, 0x63,
- 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x74,
- 0x79, 0x70, 0x65, 0x18, 0x74, 0x65, 0x78, 0x74,
- 0x2f, 0x68, 0x74, 0x6d, 0x6c, 0x3b, 0x20, 0x63,
- 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x75,
- 0x74, 0x66, 0x2d, 0x38, 0x00, 0x06, 0x73, 0x65,
- 0x72, 0x76, 0x65, 0x72, 0x07, 0x4b, 0x65, 0x73,
- 0x74, 0x72, 0x65, 0x6c
- },
- 200);
+// // Lowercase header name letters only
+// data.Add(
+// new[]
+// {
+// new KeyValuePair("CustomHeader", "CustomValue"),
+// },
+// new byte[]
+// {
+// // 0 12 c u s t o m
+// 0x00, 0x0c, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d,
+// // h e a d e r 11 C
+// 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x0b, 0x43,
+// // u s t o m V a l
+// 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x56, 0x61, 0x6c,
+// // u e
+// 0x75, 0x65
+// },
+// null);
+// // Lowercase header name letters only
+// data.Add(
+// new[]
+// {
+// new KeyValuePair("CustomHeader!#$%&'*+-.^_`|~", "CustomValue"),
+// },
+// new byte[]
+// {
+// // 0 27 c u s t o m
+// 0x00, 0x1b, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d,
+// // h e a d e r ! #
+// 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x21, 0x23,
+// // $ % & ' * + - .
+// 0x24, 0x25, 0x26, 0x27, 0x2a, 0x2b, 0x2d, 0x2e,
+// // ^ _ ` | ~ 11 C u
+// 0x5e, 0x5f, 0x60, 0x7c, 0x7e, 0x0b, 0x43, 0x75,
+// // s t o m V a l u
+// 0x73, 0x74, 0x6f, 0x6d, 0x56, 0x61, 0x6c, 0x75,
+// // e
+// 0x65
+// },
+// null);
+// // Single Payload
+// data.Add(
+// new[]
+// {
+// new KeyValuePair("date", "Mon, 24 Jul 2017 19:22:30 GMT"),
+// new KeyValuePair("content-type", "text/html; charset=utf-8"),
+// new KeyValuePair("server", "Kestrel")
+// },
+// new byte[]
+// {
+// 0x88, 0x00, 0x04, 0x64, 0x61, 0x74, 0x65, 0x1d,
+// 0x4d, 0x6f, 0x6e, 0x2c, 0x20, 0x32, 0x34, 0x20,
+// 0x4a, 0x75, 0x6c, 0x20, 0x32, 0x30, 0x31, 0x37,
+// 0x20, 0x31, 0x39, 0x3a, 0x32, 0x32, 0x3a, 0x33,
+// 0x30, 0x20, 0x47, 0x4d, 0x54, 0x00, 0x0c, 0x63,
+// 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x74,
+// 0x79, 0x70, 0x65, 0x18, 0x74, 0x65, 0x78, 0x74,
+// 0x2f, 0x68, 0x74, 0x6d, 0x6c, 0x3b, 0x20, 0x63,
+// 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x75,
+// 0x74, 0x66, 0x2d, 0x38, 0x00, 0x06, 0x73, 0x65,
+// 0x72, 0x76, 0x65, 0x72, 0x07, 0x4b, 0x65, 0x73,
+// 0x74, 0x72, 0x65, 0x6c
+// },
+// 200);
- return data;
- }
- }
+// return data;
+// }
+// }
- [Theory]
- [MemberData(nameof(SinglePayloadData))]
- public void EncodesHeadersInSinglePayloadWhenSpaceAvailable(KeyValuePair[] headers, byte[] expectedPayload, int? statusCode)
- {
- var payload = new byte[1024];
- var length = 0;
- if (statusCode.HasValue)
- {
- Assert.True(HPackHeaderWriter.BeginEncodeHeaders(statusCode.Value, GetHeadersEnumerator(headers), payload, out length));
- }
- else
- {
- Assert.True(HPackHeaderWriter.BeginEncodeHeaders(GetHeadersEnumerator(headers), payload, out length));
- }
- Assert.Equal(expectedPayload.Length, length);
+// [Theory]
+// [MemberData(nameof(SinglePayloadData))]
+// public void EncodesHeadersInSinglePayloadWhenSpaceAvailable(KeyValuePair[] headers, byte[] expectedPayload, int? statusCode)
+// {
+// var payload = new byte[1024];
+// var length = 0;
+// if (statusCode.HasValue)
+// {
+// Assert.True(HPackHeaderWriter.BeginEncodeHeaders(statusCode.Value, GetHeadersEnumerator(headers), payload, out length));
+// }
+// else
+// {
+// Assert.True(HPackHeaderWriter.BeginEncodeHeaders(GetHeadersEnumerator(headers), payload, out length));
+// }
+// Assert.Equal(expectedPayload.Length, length);
- for (var i = 0; i < length; i++)
- {
- Assert.True(expectedPayload[i] == payload[i], $"{expectedPayload[i]} != {payload[i]} at {i} (len {length})");
- }
+// for (var i = 0; i < length; i++)
+// {
+// Assert.True(expectedPayload[i] == payload[i], $"{expectedPayload[i]} != {payload[i]} at {i} (len {length})");
+// }
- Assert.Equal(expectedPayload, new ArraySegment(payload, 0, length));
- }
+// Assert.Equal(expectedPayload, new ArraySegment(payload, 0, length));
+// }
- [Theory]
- [InlineData(true)]
- [InlineData(false)]
- public void EncodesHeadersInMultiplePayloadsWhenSpaceNotAvailable(bool exactSize)
- {
- var statusCode = 200;
- var headers = new[]
- {
- new KeyValuePair("date", "Mon, 24 Jul 2017 19:22:30 GMT"),
- new KeyValuePair("content-type", "text/html; charset=utf-8"),
- new KeyValuePair("server", "Kestrel")
- };
+// [Theory]
+// [InlineData(true)]
+// [InlineData(false)]
+// public void EncodesHeadersInMultiplePayloadsWhenSpaceNotAvailable(bool exactSize)
+// {
+// var statusCode = 200;
+// var headers = new[]
+// {
+// new KeyValuePair("date", "Mon, 24 Jul 2017 19:22:30 GMT"),
+// new KeyValuePair("content-type", "text/html; charset=utf-8"),
+// new KeyValuePair("server", "Kestrel")
+// };
- var expectedStatusCodePayload = new byte[]
- {
- 0x88
- };
+// var expectedStatusCodePayload = new byte[]
+// {
+// 0x88
+// };
- var expectedDateHeaderPayload = new byte[]
- {
- 0x00, 0x04, 0x64, 0x61, 0x74, 0x65, 0x1d, 0x4d,
- 0x6f, 0x6e, 0x2c, 0x20, 0x32, 0x34, 0x20, 0x4a,
- 0x75, 0x6c, 0x20, 0x32, 0x30, 0x31, 0x37, 0x20,
- 0x31, 0x39, 0x3a, 0x32, 0x32, 0x3a, 0x33, 0x30,
- 0x20, 0x47, 0x4d, 0x54
- };
+// var expectedDateHeaderPayload = new byte[]
+// {
+// 0x00, 0x04, 0x64, 0x61, 0x74, 0x65, 0x1d, 0x4d,
+// 0x6f, 0x6e, 0x2c, 0x20, 0x32, 0x34, 0x20, 0x4a,
+// 0x75, 0x6c, 0x20, 0x32, 0x30, 0x31, 0x37, 0x20,
+// 0x31, 0x39, 0x3a, 0x32, 0x32, 0x3a, 0x33, 0x30,
+// 0x20, 0x47, 0x4d, 0x54
+// };
- var expectedContentTypeHeaderPayload = new byte[]
- {
- 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e,
- 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x18, 0x74,
- 0x65, 0x78, 0x74, 0x2f, 0x68, 0x74, 0x6d, 0x6c,
- 0x3b, 0x20, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65,
- 0x74, 0x3d, 0x75, 0x74, 0x66, 0x2d, 0x38
- };
+// var expectedContentTypeHeaderPayload = new byte[]
+// {
+// 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e,
+// 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x18, 0x74,
+// 0x65, 0x78, 0x74, 0x2f, 0x68, 0x74, 0x6d, 0x6c,
+// 0x3b, 0x20, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65,
+// 0x74, 0x3d, 0x75, 0x74, 0x66, 0x2d, 0x38
+// };
- var expectedServerHeaderPayload = new byte[]
- {
- 0x00, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
- 0x07, 0x4b, 0x65, 0x73, 0x74, 0x72, 0x65, 0x6c
- };
+// var expectedServerHeaderPayload = new byte[]
+// {
+// 0x00, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
+// 0x07, 0x4b, 0x65, 0x73, 0x74, 0x72, 0x65, 0x6c
+// };
- Span payload = new byte[1024];
- var offset = 0;
- var headerEnumerator = GetHeadersEnumerator(headers);
+// Span payload = new byte[1024];
+// var offset = 0;
+// var headerEnumerator = GetHeadersEnumerator(headers);
- // When !exactSize, slices are one byte short of fitting the next header
- var sliceLength = expectedStatusCodePayload.Length + (exactSize ? 0 : expectedDateHeaderPayload.Length - 1);
- Assert.False(HPackHeaderWriter.BeginEncodeHeaders(statusCode, headerEnumerator, payload.Slice(offset, sliceLength), out var length));
- Assert.Equal(expectedStatusCodePayload.Length, length);
- Assert.Equal(expectedStatusCodePayload, payload.Slice(0, length).ToArray());
+// // When !exactSize, slices are one byte short of fitting the next header
+// var sliceLength = expectedStatusCodePayload.Length + (exactSize ? 0 : expectedDateHeaderPayload.Length - 1);
+// Assert.False(HPackHeaderWriter.BeginEncodeHeaders(statusCode, headerEnumerator, payload.Slice(offset, sliceLength), out var length));
+// Assert.Equal(expectedStatusCodePayload.Length, length);
+// Assert.Equal(expectedStatusCodePayload, payload.Slice(0, length).ToArray());
- offset += length;
+// offset += length;
- sliceLength = expectedDateHeaderPayload.Length + (exactSize ? 0 : expectedContentTypeHeaderPayload.Length - 1);
- Assert.False(HPackHeaderWriter.ContinueEncodeHeaders(headerEnumerator, payload.Slice(offset, sliceLength), out length));
- Assert.Equal(expectedDateHeaderPayload.Length, length);
- Assert.Equal(expectedDateHeaderPayload, payload.Slice(offset, length).ToArray());
+// sliceLength = expectedDateHeaderPayload.Length + (exactSize ? 0 : expectedContentTypeHeaderPayload.Length - 1);
+// Assert.False(HPackHeaderWriter.ContinueEncodeHeaders(headerEnumerator, payload.Slice(offset, sliceLength), out length));
+// Assert.Equal(expectedDateHeaderPayload.Length, length);
+// Assert.Equal(expectedDateHeaderPayload, payload.Slice(offset, length).ToArray());
- offset += length;
+// offset += length;
- sliceLength = expectedContentTypeHeaderPayload.Length + (exactSize ? 0 : expectedServerHeaderPayload.Length - 1);
- Assert.False(HPackHeaderWriter.ContinueEncodeHeaders(headerEnumerator, payload.Slice(offset, sliceLength), out length));
- Assert.Equal(expectedContentTypeHeaderPayload.Length, length);
- Assert.Equal(expectedContentTypeHeaderPayload, payload.Slice(offset, length).ToArray());
+// sliceLength = expectedContentTypeHeaderPayload.Length + (exactSize ? 0 : expectedServerHeaderPayload.Length - 1);
+// Assert.False(HPackHeaderWriter.ContinueEncodeHeaders(headerEnumerator, payload.Slice(offset, sliceLength), out length));
+// Assert.Equal(expectedContentTypeHeaderPayload.Length, length);
+// Assert.Equal(expectedContentTypeHeaderPayload, payload.Slice(offset, length).ToArray());
- offset += length;
+// offset += length;
- sliceLength = expectedServerHeaderPayload.Length;
- Assert.True(HPackHeaderWriter.ContinueEncodeHeaders(headerEnumerator, payload.Slice(offset, sliceLength), out length));
- Assert.Equal(expectedServerHeaderPayload.Length, length);
- Assert.Equal(expectedServerHeaderPayload, payload.Slice(offset, length).ToArray());
- }
+// sliceLength = expectedServerHeaderPayload.Length;
+// Assert.True(HPackHeaderWriter.ContinueEncodeHeaders(headerEnumerator, payload.Slice(offset, sliceLength), out length));
+// Assert.Equal(expectedServerHeaderPayload.Length, length);
+// Assert.Equal(expectedServerHeaderPayload, payload.Slice(offset, length).ToArray());
+// }
- private static Http2HeadersEnumerator GetHeadersEnumerator(IEnumerable> headers)
- {
- var groupedHeaders = headers
- .GroupBy(k => k.Key)
- .ToDictionary(g => g.Key, g => new StringValues(g.Select(gg => gg.Value).ToArray()));
+// private static Http2HeadersEnumerator GetHeadersEnumerator(IEnumerable> headers)
+// {
+// var groupedHeaders = headers
+// .GroupBy(k => k.Key)
+// .ToDictionary(g => g.Key, g => new StringValues(g.Select(gg => gg.Value).ToArray()));
- var enumerator = new Http2HeadersEnumerator();
- enumerator.Initialize(groupedHeaders);
- return enumerator;
- }
- }
-}
+// var enumerator = new Http2HeadersEnumerator();
+// enumerator.Initialize(groupedHeaders);
+// return enumerator;
+// }
+// }
+//}
diff --git a/src/Servers/Kestrel/Core/test/Http1ConnectionTests.cs b/src/Servers/Kestrel/Core/test/Http1ConnectionTests.cs
index 0992626a80..f60e3d9f91 100644
--- a/src/Servers/Kestrel/Core/test/Http1ConnectionTests.cs
+++ b/src/Servers/Kestrel/Core/test/Http1ConnectionTests.cs
@@ -127,7 +127,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await _application.Output.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine}\r\n"));
var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;
+#pragma warning disable CS0618 // Type or member is obsolete
var exception = Assert.Throws(() => _http1Connection.TakeMessageHeaders(readableBuffer, trailers: false, out _consumed, out _examined));
+#pragma warning restore CS0618 // Type or member is obsolete
_transport.Input.AdvanceTo(_consumed, _examined);
Assert.Equal(CoreStrings.BadRequest_HeadersExceedMaxTotalSize, exception.Message);
@@ -143,7 +145,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await _application.Output.WriteAsync(Encoding.ASCII.GetBytes($"{headerLines}\r\n"));
var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;
+#pragma warning disable CS0618 // Type or member is obsolete
var exception = Assert.Throws(() => _http1Connection.TakeMessageHeaders(readableBuffer, trailers: false, out _consumed, out _examined));
+#pragma warning restore CS0618 // Type or member is obsolete
_transport.Input.AdvanceTo(_consumed, _examined);
Assert.Equal(CoreStrings.BadRequest_TooManyHeaders, exception.Message);
@@ -438,7 +442,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await _application.Output.WriteAsync(requestLineBytes);
var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;
+#pragma warning disable CS0618 // Type or member is obsolete
var exception = Assert.Throws(() => _http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined));
+#pragma warning restore CS0618 // Type or member is obsolete
_transport.Input.AdvanceTo(_consumed, _examined);
Assert.Equal(CoreStrings.BadRequest_RequestLineTooLong, exception.Message);
@@ -452,7 +458,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await _application.Output.WriteAsync(Encoding.ASCII.GetBytes($"GET {target} HTTP/1.1\r\n"));
var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;
+#pragma warning disable CS0618 // Type or member is obsolete
var exception = Assert.Throws(() =>
+#pragma warning restore CS0618 // Type or member is obsolete
_http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined));
_transport.Input.AdvanceTo(_consumed, _examined);
@@ -466,7 +474,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await _application.Output.WriteAsync(Encoding.ASCII.GetBytes($"GET {target} HTTP/1.1\r\n"));
var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;
+#pragma warning disable CS0618 // Type or member is obsolete
var exception = Assert.Throws(() =>
+#pragma warning restore CS0618 // Type or member is obsolete
_http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined));
_transport.Input.AdvanceTo(_consumed, _examined);
@@ -482,7 +492,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await _application.Output.WriteAsync(Encoding.ASCII.GetBytes(requestLine));
var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;
+#pragma warning disable CS0618 // Type or member is obsolete
var exception = Assert.Throws(() =>
+#pragma warning restore CS0618 // Type or member is obsolete
_http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined));
_transport.Input.AdvanceTo(_consumed, _examined);
@@ -498,7 +510,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await _application.Output.WriteAsync(Encoding.ASCII.GetBytes($"GET {target} HTTP/1.1\r\n"));
var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;
+#pragma warning disable CS0618 // Type or member is obsolete
var exception = Assert.Throws(() =>
+#pragma warning restore CS0618 // Type or member is obsolete
_http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined));
_transport.Input.AdvanceTo(_consumed, _examined);
@@ -514,7 +528,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await _application.Output.WriteAsync(Encoding.ASCII.GetBytes(requestLine));
var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;
+#pragma warning disable CS0618 // Type or member is obsolete
var exception = Assert.Throws(() =>
+#pragma warning restore CS0618 // Type or member is obsolete
_http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined));
_transport.Input.AdvanceTo(_consumed, _examined);
@@ -529,7 +545,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await _application.Output.WriteAsync(Encoding.ASCII.GetBytes(requestLine));
var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;
+#pragma warning disable CS0618 // Type or member is obsolete
var exception = Assert.Throws(() =>
+#pragma warning restore CS0618 // Type or member is obsolete
_http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined));
_transport.Input.AdvanceTo(_consumed, _examined);
@@ -774,7 +792,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await _application.Output.WriteAsync(Encoding.ASCII.GetBytes($"GET /%00 HTTP/1.1\r\n"));
var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;
+#pragma warning disable CS0618 // Type or member is obsolete
var exception = Assert.Throws(() =>
+#pragma warning restore CS0618 // Type or member is obsolete
_http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined));
_transport.Input.AdvanceTo(_consumed, _examined);
@@ -934,7 +954,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{
_http1Connection.HttpVersion = "HTTP/1.0";
_http1Connection.RequestHeaders[HeaderNames.Host] = "a=b";
+#pragma warning disable CS0618 // Type or member is obsolete
var ex = Assert.Throws(() => _http1Connection.EnsureHostHeaderExists());
+#pragma warning restore CS0618 // Type or member is obsolete
Assert.Equal(CoreStrings.FormatBadRequest_InvalidHostHeader_Detail("a=b"), ex.Message);
}
@@ -943,7 +965,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{
_http1Connection.HttpVersion = "HTTP/1.1";
_http1Connection.RequestHeaders[HeaderNames.Host] = "a=b";
+#pragma warning disable CS0618 // Type or member is obsolete
var ex = Assert.Throws(() => _http1Connection.EnsureHostHeaderExists());
+#pragma warning restore CS0618 // Type or member is obsolete
Assert.Equal(CoreStrings.FormatBadRequest_InvalidHostHeader_Detail("a=b"), ex.Message);
}
diff --git a/src/Servers/Kestrel/Core/test/Http2FrameWriterTests.cs b/src/Servers/Kestrel/Core/test/Http2FrameWriterTests.cs
index 6300fcf85b..30913d952e 100644
--- a/src/Servers/Kestrel/Core/test/Http2FrameWriterTests.cs
+++ b/src/Servers/Kestrel/Core/test/Http2FrameWriterTests.cs
@@ -41,7 +41,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{
// Arrange
var pipe = new Pipe(new PipeOptions(_dirtyMemoryPool, PipeScheduler.Inline, PipeScheduler.Inline));
- var frameWriter = new Http2FrameWriter(pipe.Writer, null, null, null, null, null, null, _dirtyMemoryPool, new Mock().Object);
+ var frameWriter = CreateFrameWriter(pipe);
// Act
await frameWriter.WriteWindowUpdateAsync(1, 1);
@@ -52,12 +52,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
Assert.Equal(new byte[] { 0x00, 0x00, 0x00, 0x01 }, payload.Skip(Http2FrameReader.HeaderLength).Take(4).ToArray());
}
+ private Http2FrameWriter CreateFrameWriter(Pipe pipe)
+ {
+ var serviceContext = new Internal.ServiceContext
+ {
+ ServerOptions = new KestrelServerOptions(),
+ Log = new Mock().Object
+ };
+ return new Http2FrameWriter(pipe.Writer, null, null, null, null, null, null, _dirtyMemoryPool, serviceContext);
+ }
+
[Fact]
public async Task WriteGoAway_UnsetsReservedBit()
{
// Arrange
var pipe = new Pipe(new PipeOptions(_dirtyMemoryPool, PipeScheduler.Inline, PipeScheduler.Inline));
- var frameWriter = new Http2FrameWriter(pipe.Writer, null, null, null, null, null, null, _dirtyMemoryPool, new Mock().Object);
+ var frameWriter = CreateFrameWriter(pipe);
// Act
await frameWriter.WriteGoAwayAsync(1, Http2ErrorCode.NO_ERROR);
diff --git a/src/Servers/Kestrel/Core/test/Http2HPackEncoderTests.cs b/src/Servers/Kestrel/Core/test/Http2HPackEncoderTests.cs
new file mode 100644
index 0000000000..c9dcdb00ec
--- /dev/null
+++ b/src/Servers/Kestrel/Core/test/Http2HPackEncoderTests.cs
@@ -0,0 +1,479 @@
+// 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.Linq;
+using System.Net.Http.HPack;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
+using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2;
+using Microsoft.Extensions.Primitives;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
+{
+ public class Http2HPackEncoderTests
+ {
+ [Fact]
+ public void BeginEncodeHeaders_Status302_NewIndexValue()
+ {
+ Span buffer = new byte[1024 * 16];
+
+ var headers = new HttpResponseHeaders();
+ var enumerator = new Http2HeadersEnumerator();
+ enumerator.Initialize(headers);
+
+ var hpackEncoder = new HPackEncoder();
+ Assert.True(HPackHeaderWriter.BeginEncodeHeaders(302, hpackEncoder, enumerator, buffer, out var length));
+
+ var result = buffer.Slice(0, length).ToArray();
+ var hex = BitConverter.ToString(result);
+ Assert.Equal("48-03-33-30-32", hex);
+
+ var statusHeader = GetHeaderEntry(hpackEncoder, 0);
+ Assert.Equal(":status", statusHeader.Name);
+ Assert.Equal("302", statusHeader.Value);
+ }
+
+ [Fact]
+ public void BeginEncodeHeaders_CacheControlPrivate_NewIndexValue()
+ {
+ Span buffer = new byte[1024 * 16];
+
+ var headers = new HttpResponseHeaders();
+ headers.HeaderCacheControl = "private";
+
+ var enumerator = new Http2HeadersEnumerator();
+ enumerator.Initialize(headers);
+
+ var hpackEncoder = new HPackEncoder();
+ Assert.True(HPackHeaderWriter.BeginEncodeHeaders(302, hpackEncoder, enumerator, buffer, out var length));
+
+ var result = buffer.Slice(5, length - 5).ToArray();
+ var hex = BitConverter.ToString(result);
+ Assert.Equal("58-07-70-72-69-76-61-74-65", hex);
+
+ var statusHeader = GetHeaderEntry(hpackEncoder, 0);
+ Assert.Equal("Cache-Control", statusHeader.Name);
+ Assert.Equal("private", statusHeader.Value);
+ }
+
+ [Fact]
+ public void BeginEncodeHeaders_MaxHeaderTableSizeExceeded_EvictionsToFit()
+ {
+ // Test follows example https://tools.ietf.org/html/rfc7541#appendix-C.5
+
+ Span buffer = new byte[1024 * 16];
+
+ var headers = new HttpResponseHeaders();
+ headers.HeaderCacheControl = "private";
+ headers.HeaderDate = "Mon, 21 Oct 2013 20:13:21 GMT";
+ headers.HeaderLocation = "https://www.example.com";
+
+ var enumerator = new Http2HeadersEnumerator();
+
+ var hpackEncoder = new HPackEncoder(maxHeaderTableSize: 256);
+
+ // First response
+ enumerator.Initialize(headers);
+ Assert.True(HPackHeaderWriter.BeginEncodeHeaders(302, hpackEncoder, enumerator, buffer, out var length));
+
+ var result = buffer.Slice(0, length).ToArray();
+ var hex = BitConverter.ToString(result);
+ Assert.Equal(
+ "48-03-33-30-32-58-07-70-72-69-76-61-74-65-61-1D-" +
+ "4D-6F-6E-2C-20-32-31-20-4F-63-74-20-32-30-31-33-" +
+ "20-32-30-3A-31-33-3A-32-31-20-47-4D-54-6E-17-68-" +
+ "74-74-70-73-3A-2F-2F-77-77-77-2E-65-78-61-6D-70-" +
+ "6C-65-2E-63-6F-6D", hex);
+
+ var entries = GetHeaderEntries(hpackEncoder);
+ Assert.Collection(entries,
+ e =>
+ {
+ Assert.Equal("Location", e.Name);
+ Assert.Equal("https://www.example.com", e.Value);
+ },
+ e =>
+ {
+ Assert.Equal("Date", e.Name);
+ Assert.Equal("Mon, 21 Oct 2013 20:13:21 GMT", e.Value);
+ },
+ e =>
+ {
+ Assert.Equal("Cache-Control", e.Name);
+ Assert.Equal("private", e.Value);
+ },
+ e =>
+ {
+ Assert.Equal(":status", e.Name);
+ Assert.Equal("302", e.Value);
+ });
+
+ // Second response
+ enumerator.Initialize(headers);
+ Assert.True(HPackHeaderWriter.BeginEncodeHeaders(307, hpackEncoder, enumerator, buffer, out length));
+
+ result = buffer.Slice(0, length).ToArray();
+ hex = BitConverter.ToString(result);
+ Assert.Equal("48-03-33-30-37-C1-C0-BF", hex);
+
+ entries = GetHeaderEntries(hpackEncoder);
+ Assert.Collection(entries,
+ e =>
+ {
+ Assert.Equal(":status", e.Name);
+ Assert.Equal("307", e.Value);
+ },
+ e =>
+ {
+ Assert.Equal("Location", e.Name);
+ Assert.Equal("https://www.example.com", e.Value);
+ },
+ e =>
+ {
+ Assert.Equal("Date", e.Name);
+ Assert.Equal("Mon, 21 Oct 2013 20:13:21 GMT", e.Value);
+ },
+ e =>
+ {
+ Assert.Equal("Cache-Control", e.Name);
+ Assert.Equal("private", e.Value);
+ });
+
+ // Third response
+ headers.HeaderDate = "Mon, 21 Oct 2013 20:13:22 GMT";
+ headers.HeaderContentEncoding = "gzip";
+ headers.HeaderSetCookie = "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1";
+
+ enumerator.Initialize(headers);
+ Assert.True(HPackHeaderWriter.BeginEncodeHeaders(200, hpackEncoder, enumerator, buffer, out length));
+
+ result = buffer.Slice(0, length).ToArray();
+ hex = BitConverter.ToString(result);
+ Assert.Equal(
+ "88-C1-61-1D-4D-6F-6E-2C-20-32-31-20-4F-63-74-20-" +
+ "32-30-31-33-20-32-30-3A-31-33-3A-32-32-20-47-4D-" +
+ "54-5A-04-67-7A-69-70-C1-1F-28-38-66-6F-6F-3D-41-" +
+ "53-44-4A-4B-48-51-4B-42-5A-58-4F-51-57-45-4F-50-" +
+ "49-55-41-58-51-57-45-4F-49-55-3B-20-6D-61-78-2D-" +
+ "61-67-65-3D-33-36-30-30-3B-20-76-65-72-73-69-6F-" +
+ "6E-3D-31", hex);
+
+ entries = GetHeaderEntries(hpackEncoder);
+ Assert.Collection(entries,
+ e =>
+ {
+ Assert.Equal("Content-Encoding", e.Name);
+ Assert.Equal("gzip", e.Value);
+ },
+ e =>
+ {
+ Assert.Equal("Date", e.Name);
+ Assert.Equal("Mon, 21 Oct 2013 20:13:22 GMT", e.Value);
+ },
+ e =>
+ {
+ Assert.Equal(":status", e.Name);
+ Assert.Equal("307", e.Value);
+ },
+ e =>
+ {
+ Assert.Equal("Location", e.Name);
+ Assert.Equal("https://www.example.com", e.Value);
+ });
+ }
+
+ [Theory]
+ [InlineData("Set-Cookie", true)]
+ [InlineData("Content-Disposition", true)]
+ [InlineData("Content-Length", false)]
+ public void BeginEncodeHeaders_ExcludedHeaders_NotAddedToTable(string headerName, bool neverIndex)
+ {
+ Span buffer = new byte[1024 * 16];
+
+ var headers = new HttpResponseHeaders();
+ headers.Append(headerName, "1");
+
+ var enumerator = new Http2HeadersEnumerator();
+ enumerator.Initialize(headers);
+
+ var hpackEncoder = new HPackEncoder(maxHeaderTableSize: Http2PeerSettings.DefaultHeaderTableSize);
+ Assert.True(HPackHeaderWriter.BeginEncodeHeaders(hpackEncoder, enumerator, buffer, out _));
+
+ if (neverIndex)
+ {
+ Assert.Equal(0x10, buffer[0] & 0x10);
+ }
+ else
+ {
+ Assert.Equal(0, buffer[0] & 0x40);
+ }
+
+ Assert.Empty(GetHeaderEntries(hpackEncoder));
+ }
+
+ [Fact]
+ public void BeginEncodeHeaders_HeaderExceedHeaderTableSize_NoIndexAndNoHeaderEntry()
+ {
+ Span