diff --git a/.azure/pipelines/jobs/default-build.yml b/.azure/pipelines/jobs/default-build.yml
index 65307d5d0ad7..15cf2272358c 100644
--- a/.azure/pipelines/jobs/default-build.yml
+++ b/.azure/pipelines/jobs/default-build.yml
@@ -109,10 +109,10 @@ jobs:
vmImage: macOS-13
${{ if eq(parameters.agentOs, 'Linux') }}:
${{ if eq(parameters.useHostedUbuntu, true) }}:
- vmImage: ubuntu-20.04
+ vmImage: ubuntu-22.04
${{ else }}:
name: $(DncEngPublicBuildPool)
- demands: ImageOverride -equals Build.Ubuntu.2004.Amd64.Open
+ demands: ImageOverride -equals Build.Ubuntu.2204.Amd64.Open
${{ if eq(parameters.agentOs, 'Windows') }}:
name: $(DncEngPublicBuildPool)
demands: ImageOverride -equals windows.vs2022preview.amd64.open
@@ -327,7 +327,7 @@ jobs:
os: macOS
${{ if eq(parameters.agentOs, 'Linux') }}:
name: $(DncEngInternalBuildPool)
- image: 1es-ubuntu-2004
+ image: 1es-ubuntu-2204
os: linux
${{ if eq(parameters.agentOs, 'Windows') }}:
name: $(DncEngInternalBuildPool)
diff --git a/NuGet.config b/NuGet.config
index 9bc1f58f58ef..cd8902cfb5ec 100644
--- a/NuGet.config
+++ b/NuGet.config
@@ -4,10 +4,10 @@
-
+
-
+
@@ -30,10 +30,10 @@
-
+
-
+
diff --git a/eng/Baseline.Designer.props b/eng/Baseline.Designer.props
index 038221fceef4..4ff4b2c148c6 100644
--- a/eng/Baseline.Designer.props
+++ b/eng/Baseline.Designer.props
@@ -2,117 +2,117 @@
$(MSBuildAllProjects);$(MSBuildThisFileFullPath)
- 9.0.2
+ 9.0.8
- 9.0.2
+ 9.0.8
- 9.0.2
+ 9.0.8
- 9.0.2
+ 9.0.8
- 9.0.2
+ 9.0.8
- 9.0.2
+ 9.0.8
- 9.0.2
+ 9.0.8
- 9.0.2
+ 9.0.8
- 9.0.2
+ 9.0.8
- 9.0.2
+ 9.0.8
- 9.0.2
+ 9.0.8
- 9.0.2
+ 9.0.8
- 9.0.2
+ 9.0.8
- 9.0.2
+ 9.0.8
- 9.0.2
+ 9.0.8
- 9.0.2
+ 9.0.8
- 9.0.2
+ 9.0.8
-
+
- 9.0.2
+ 9.0.8
- 9.0.2
+ 9.0.8
- 9.0.2
+ 9.0.8
- 9.0.2
+ 9.0.8
- 9.0.2
+ 9.0.8
-
-
-
+
+
+
- 9.0.2
+ 9.0.8
- 9.0.2
+ 9.0.8
- 9.0.2
+ 9.0.8
@@ -120,279 +120,279 @@
- 9.0.2
+ 9.0.8
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
- 9.0.2
+ 9.0.8
-
-
-
+
+
+
- 9.0.2
+ 9.0.8
- 9.0.2
+ 9.0.8
-
+
- 9.0.2
+ 9.0.8
-
-
+
+
- 9.0.2
+ 9.0.8
- 9.0.2
+ 9.0.8
-
-
+
+
- 9.0.2
+ 9.0.8
-
+
- 9.0.2
+ 9.0.8
-
+
- 9.0.2
+ 9.0.8
-
+
- 9.0.2
+ 9.0.8
-
-
+
+
- 9.0.2
+ 9.0.8
-
-
-
-
-
+
+
+
+
+
- 9.0.2
+ 9.0.8
-
-
-
-
-
+
+
+
+
+
- 9.0.2
+ 9.0.8
-
-
+
+
- 9.0.2
+ 9.0.8
- 9.0.2
+ 9.0.8
- 9.0.2
+ 9.0.8
-
-
-
-
-
-
+
+
+
+
+
+
- 9.0.2
+ 9.0.8
-
-
-
+
+
+
- 9.0.2
+ 9.0.8
-
-
-
+
+
+
-
+
-
-
-
+
+
+
-
-
-
+
+
+
- 9.0.2
+ 9.0.8
- 9.0.2
+ 9.0.8
-
+
-
+
-
+
- 9.0.2
+ 9.0.8
-
-
-
-
-
-
+
+
+
+
+
+
-
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
+
- 9.0.2
+ 9.0.8
- 9.0.2
+ 9.0.8
-
-
+
+
- 9.0.2
+ 9.0.8
-
-
+
+
-
-
+
+
-
-
+
+
- 9.0.2
+ 9.0.8
-
+
-
+
-
+
- 9.0.2
+ 9.0.8
-
+
- 9.0.2
+ 9.0.8
@@ -401,83 +401,83 @@
- 9.0.2
+ 9.0.8
-
-
+
+
- 9.0.2
+ 9.0.8
-
+
- 9.0.2
+ 9.0.8
-
-
-
+
+
+
-
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
- 9.0.2
+ 9.0.8
-
-
+
+
-
+
-
-
+
+
- 9.0.2
+ 9.0.8
-
-
+
+
- 9.0.2
+ 9.0.8
-
-
+
+
- 9.0.2
+ 9.0.8
@@ -493,510 +493,510 @@
- 9.0.2
+ 9.0.8
- 9.0.2
+ 9.0.8
-
+
- 9.0.2
+ 9.0.8
-
+
- 9.0.2
+ 9.0.8
-
+
- 9.0.2
+ 9.0.8
-
-
-
+
+
+
- 9.0.2
+ 9.0.8
- 9.0.2
+ 9.0.8
-
-
+
+
- 9.0.2
+ 9.0.8
- 9.0.2
+ 9.0.8
-
-
+
+
-
-
+
+
-
-
+
+
- 9.0.2
+ 9.0.8
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
- 9.0.2
+ 9.0.8
-
-
+
+
-
+
-
-
+
+
-
-
-
+
+
+
- 9.0.2
+ 9.0.8
-
+
-
+
-
+
- 9.0.2
+ 9.0.8
-
+
-
+
-
+
- 9.0.2
+ 9.0.8
-
+
-
+
-
+
- 9.0.2
+ 9.0.8
-
-
-
-
+
+
+
+
- 9.0.2
+ 9.0.8
-
+
- 9.0.2
+ 9.0.8
- 9.0.2
+ 9.0.8
-
+
- 9.0.2
+ 9.0.8
- 9.0.2
+ 9.0.8
-
+
- 9.0.2
+ 9.0.8
-
+
- 9.0.2
+ 9.0.8
- 9.0.2
+ 9.0.8
- 9.0.2
+ 9.0.8
- 9.0.2
+ 9.0.8
- 9.0.2
+ 9.0.8
- 9.0.2
+ 9.0.8
- 9.0.2
+ 9.0.8
-
-
+
+
-
-
+
+
-
-
+
+
- 9.0.2
+ 9.0.8
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
- 9.0.2
+ 9.0.8
-
-
+
+
-
-
+
+
-
-
+
+
- 9.0.2
+ 9.0.8
-
-
-
-
-
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
+
+
+
+
+
- 9.0.2
+ 9.0.8
- 9.0.2
+ 9.0.8
-
-
-
+
+
+
- 9.0.2
+ 9.0.8
- 9.0.2
+ 9.0.8
-
+
-
+
- 9.0.2
+ 9.0.8
-
+
- 9.0.2
+ 9.0.8
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
- 9.0.2
+ 9.0.8
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
- 9.0.2
+ 9.0.8
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
- 9.0.2
+ 9.0.8
- 9.0.2
+ 9.0.8
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
- 9.0.2
+ 9.0.8
- 9.0.2
+ 9.0.8
-
-
-
+
+
+
-
-
+
+
-
-
-
+
+
+
- 9.0.2
+ 9.0.8
- 9.0.2
+ 9.0.8
-
+
- 9.0.2
+ 9.0.8
-
+
\ No newline at end of file
diff --git a/eng/Baseline.xml b/eng/Baseline.xml
index d15a11b1f2fd..71f542dc6973 100644
--- a/eng/Baseline.xml
+++ b/eng/Baseline.xml
@@ -4,110 +4,110 @@ This file contains a list of all the packages and their versions which were rele
Update this list when preparing for a new patch.
-->
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/eng/SourceBuildPrebuiltBaseline.xml b/eng/SourceBuildPrebuiltBaseline.xml
index 8e86375464ef..2c8d20d3234b 100644
--- a/eng/SourceBuildPrebuiltBaseline.xml
+++ b/eng/SourceBuildPrebuiltBaseline.xml
@@ -46,6 +46,13 @@
+
+
+
+
+
+
+
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index 28d773064132..3ebbb1853f12 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -9,325 +9,325 @@
-->
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-efcore
- 9275e9ac55e413546a09551c29d5227d6d009747
+ 78871c83aac6c38eb5476c2f34aae98ef65314f5
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-efcore
- 9275e9ac55e413546a09551c29d5227d6d009747
+ 78871c83aac6c38eb5476c2f34aae98ef65314f5
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-efcore
- 9275e9ac55e413546a09551c29d5227d6d009747
+ 78871c83aac6c38eb5476c2f34aae98ef65314f5
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-efcore
- 9275e9ac55e413546a09551c29d5227d6d009747
+ 78871c83aac6c38eb5476c2f34aae98ef65314f5
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-efcore
- 9275e9ac55e413546a09551c29d5227d6d009747
+ 78871c83aac6c38eb5476c2f34aae98ef65314f5
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-efcore
- 9275e9ac55e413546a09551c29d5227d6d009747
+ 78871c83aac6c38eb5476c2f34aae98ef65314f5
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-efcore
- 9275e9ac55e413546a09551c29d5227d6d009747
+ 78871c83aac6c38eb5476c2f34aae98ef65314f5
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-efcore
- 9275e9ac55e413546a09551c29d5227d6d009747
+ 78871c83aac6c38eb5476c2f34aae98ef65314f5
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
https://github.com/dotnet/xdt
@@ -367,9 +367,9 @@
bc1c3011064a493b0ca527df6fb7215e2e5cfa96
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
@@ -380,47 +380,47 @@
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- f57e6dc747158ab7ade4e62a75a6750d16b771e8
+ 893c2ebbd49952ca49e93298148af2d95a61a0a4
https://github.com/dotnet/winforms
9b822fd70005bf5632d12fe76811b97b3dd044e4
-
+
https://github.com/dotnet/arcade
- 5da211e1c42254cb35e7ef3d5a8428fb24853169
+ e29823691315ed6b3acff20d5bdf3b0be7628283
-
+
https://github.com/dotnet/arcade
- 5da211e1c42254cb35e7ef3d5a8428fb24853169
+ e29823691315ed6b3acff20d5bdf3b0be7628283
-
+
https://github.com/dotnet/arcade
- 5da211e1c42254cb35e7ef3d5a8428fb24853169
+ e29823691315ed6b3acff20d5bdf3b0be7628283
-
+
https://github.com/dotnet/arcade
- 5da211e1c42254cb35e7ef3d5a8428fb24853169
+ e29823691315ed6b3acff20d5bdf3b0be7628283
-
+
https://github.com/dotnet/arcade
- 5da211e1c42254cb35e7ef3d5a8428fb24853169
+ e29823691315ed6b3acff20d5bdf3b0be7628283
-
+
https://github.com/dotnet/arcade
- 5da211e1c42254cb35e7ef3d5a8428fb24853169
+ e29823691315ed6b3acff20d5bdf3b0be7628283
-
+
https://github.com/dotnet/extensions
- c221abef4b4f1bf3fcf0bda27490e8b26bb479f4
+ ed336d147b46d36edad3e9441398de636b67cf5d
-
+
https://github.com/dotnet/extensions
- c221abef4b4f1bf3fcf0bda27490e8b26bb479f4
+ ed336d147b46d36edad3e9441398de636b67cf5d
https://github.com/nuget/nuget.client
diff --git a/eng/Versions.props b/eng/Versions.props
index 3e45eded9065..a2d7c6b770e6 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -8,10 +8,10 @@
9
0
- 4
+ 9
- false
+ true
8.0.1
*-*
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4-servicing.25163.5
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4-servicing.25163.5
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4-servicing.25163.5
- 9.0.4-servicing.25163.5
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9-servicing.25419.16
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9-servicing.25419.16
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9-servicing.25419.16
+ 9.0.9-servicing.25419.16
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
- 9.0.4-servicing.25163.5
- 9.0.4
+ 9.0.9-servicing.25419.16
+ 9.0.9
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
- 9.3.0-preview.1.25156.1
- 9.3.0-preview.1.25156.1
+ 9.9.0-preview.1.25409.1
+ 9.9.0-preview.1.25409.1
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
- 9.0.4
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
4.11.0-3.24554.2
4.11.0-3.24554.2
@@ -166,10 +166,10 @@
6.2.4
6.2.4
- 9.0.0-beta.25111.5
- 9.0.0-beta.25111.5
- 9.0.0-beta.25111.5
- 9.0.0-beta.25111.5
+ 9.0.0-beta.25407.2
+ 9.0.0-beta.25407.2
+ 9.0.0-beta.25407.2
+ 9.0.0-beta.25407.2
9.0.0-alpha.1.24575.1
@@ -227,13 +227,12 @@
6.0.0
1.1.1
- 17.4.0
+ 17.8.29
1.2.0
- 17.4.0
- 17.4.0
- 17.4.0
+ 17.8.29
+ 17.8.29
+ 17.8.29
1.2.6
- 17.4.0
(AlmaLinux.8.Amd64.Open)Ubuntu.2204.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:almalinux-8-helix-amd64
- (Alpine.318.Amd64.Open)Ubuntu.2204.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:alpine-3.18-helix-amd64
+ (Alpine.321.Amd64.Open)azurelinux.3.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:alpine-3.21-helix-amd64
(Debian.12.Amd64.Open)Ubuntu.2204.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-12-helix-amd64
(Fedora.41.Amd64.Open)Ubuntu.2204.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:fedora-41-helix
(Mariner)Ubuntu.2204.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:cbl-mariner-2.0-helix-amd64
@@ -42,7 +42,7 @@
-
+
diff --git a/eng/targets/Helix.targets b/eng/targets/Helix.targets
index 70e01877befa..0aab28ef20cc 100644
--- a/eng/targets/Helix.targets
+++ b/eng/targets/Helix.targets
@@ -17,7 +17,7 @@
$(HelixQueueAlmaLinux8);
- $(HelixQueueAlpine318);
+ $(HelixQueueAlpine);
$(HelixQueueDebian12);
$(HelixQueueFedora40);
$(HelixQueueMariner);
diff --git a/global.json b/global.json
index e52525e2ebde..ae875b2ea090 100644
--- a/global.json
+++ b/global.json
@@ -1,9 +1,9 @@
{
"sdk": {
- "version": "9.0.103"
+ "version": "9.0.109"
},
"tools": {
- "dotnet": "9.0.103",
+ "dotnet": "9.0.109",
"runtimes": {
"dotnet/x86": [
"$(MicrosoftNETCoreBrowserDebugHostTransportVersion)"
@@ -27,7 +27,7 @@
"jdk": "latest"
},
"msbuild-sdks": {
- "Microsoft.DotNet.Arcade.Sdk": "9.0.0-beta.25111.5",
- "Microsoft.DotNet.Helix.Sdk": "9.0.0-beta.25111.5"
+ "Microsoft.DotNet.Arcade.Sdk": "9.0.0-beta.25407.2",
+ "Microsoft.DotNet.Helix.Sdk": "9.0.0-beta.25407.2"
}
}
diff --git a/src/Caching/StackExchangeRedis/src/RedisCache.cs b/src/Caching/StackExchangeRedis/src/RedisCache.cs
index 4ecbf3628222..95bba7dd0088 100644
--- a/src/Caching/StackExchangeRedis/src/RedisCache.cs
+++ b/src/Caching/StackExchangeRedis/src/RedisCache.cs
@@ -53,7 +53,7 @@ private static RedisValue[] GetHashFields(bool getData) => getData
private long _firstErrorTimeTicks;
private long _previousErrorTimeTicks;
- internal bool HybridCacheActive { get; set; }
+ internal virtual bool IsHybridCacheActive() => false;
// StackExchange.Redis will also be trying to reconnect internally,
// so limit how often we recreate the ConnectionMultiplexer instance
@@ -378,7 +378,7 @@ private void TryAddSuffix(IConnectionMultiplexer connection)
connection.AddLibraryNameSuffix("aspnet");
connection.AddLibraryNameSuffix("DC");
- if (HybridCacheActive)
+ if (IsHybridCacheActive())
{
connection.AddLibraryNameSuffix("HC");
}
diff --git a/src/Caching/StackExchangeRedis/src/RedisCacheImpl.cs b/src/Caching/StackExchangeRedis/src/RedisCacheImpl.cs
index 67d262002eb3..0a58d43e136d 100644
--- a/src/Caching/StackExchangeRedis/src/RedisCacheImpl.cs
+++ b/src/Caching/StackExchangeRedis/src/RedisCacheImpl.cs
@@ -11,19 +11,20 @@ namespace Microsoft.Extensions.Caching.StackExchangeRedis;
internal sealed class RedisCacheImpl : RedisCache
{
+ private readonly IServiceProvider _services;
+
+ internal override bool IsHybridCacheActive()
+ => _services.GetService() is not null;
+
public RedisCacheImpl(IOptions optionsAccessor, ILogger logger, IServiceProvider services)
: base(optionsAccessor, logger)
{
- HybridCacheActive = IsHybridCacheDefined(services);
+ _services = services; // important: do not check for HybridCache here due to dependency - creates a cycle
}
public RedisCacheImpl(IOptions optionsAccessor, IServiceProvider services)
: base(optionsAccessor)
{
- HybridCacheActive = IsHybridCacheDefined(services);
+ _services = services; // important: do not check for HybridCache here due to dependency - creates a cycle
}
-
- // HybridCache optionally uses IDistributedCache; if we're here, then *we are* the DC
- private static bool IsHybridCacheDefined(IServiceProvider services)
- => services.GetService() is not null;
}
diff --git a/src/Caching/StackExchangeRedis/test/CacheServiceExtensionsTests.cs b/src/Caching/StackExchangeRedis/test/CacheServiceExtensionsTests.cs
index 1d8ce4c3fd40..71e31d19928a 100644
--- a/src/Caching/StackExchangeRedis/test/CacheServiceExtensionsTests.cs
+++ b/src/Caching/StackExchangeRedis/test/CacheServiceExtensionsTests.cs
@@ -8,10 +8,12 @@
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Caching.Hybrid;
+using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Extensions.Options;
using Moq;
using Xunit;
@@ -142,16 +144,46 @@ public void AddStackExchangeRedisCache_HybridCacheDetected(bool hybridCacheActiv
services.AddStackExchangeRedisCache(options => { });
if (hybridCacheActive)
{
- services.TryAddSingleton(new DummyHybridCache());
+ services.AddMemoryCache();
+ services.TryAddSingleton();
}
using var provider = services.BuildServiceProvider();
var cache = Assert.IsAssignableFrom(provider.GetRequiredService());
- Assert.Equal(hybridCacheActive, cache.HybridCacheActive);
+ Assert.Equal(hybridCacheActive, cache.IsHybridCacheActive());
}
sealed class DummyHybridCache : HybridCache
{
+ // emulate the layout from HybridCache in dotnet/extensions
+ public DummyHybridCache(IOptions options, IServiceProvider services)
+ {
+ if (services is null)
+ {
+ throw new ArgumentNullException(nameof(services));
+ }
+
+ var l1 = services.GetRequiredService();
+ _ = options.Value;
+ var logger = services.GetService()?.CreateLogger(typeof(HybridCache)) ?? NullLogger.Instance;
+ // var clock = services.GetService() ?? TimeProvider.System;
+ var l2 = services.GetService(); // note optional
+
+ // ignore L2 if it is really just the same L1, wrapped
+ // (note not just an "is" test; if someone has a custom subclass, who knows what it does?)
+ if (l2 is not null
+ && l2.GetType() == typeof(MemoryDistributedCache)
+ && l1.GetType() == typeof(MemoryCache))
+ {
+ l2 = null;
+ }
+
+ IHybridCacheSerializerFactory[] factories = services.GetServices().ToArray();
+ Array.Reverse(factories);
+ }
+
+ public class HybridCacheOptions { }
+
public override ValueTask GetOrCreateAsync(string key, TState state, Func> factory, HybridCacheEntryOptions options = null, IEnumerable tags = null, CancellationToken cancellationToken = default)
=> throw new NotSupportedException();
diff --git a/src/Components/test/E2ETest/ServerRenderingTests/EnhancedNavigationTest.cs b/src/Components/test/E2ETest/ServerRenderingTests/EnhancedNavigationTest.cs
index 94b6b80f53a1..0ba10d655cdf 100644
--- a/src/Components/test/E2ETest/ServerRenderingTests/EnhancedNavigationTest.cs
+++ b/src/Components/test/E2ETest/ServerRenderingTests/EnhancedNavigationTest.cs
@@ -4,6 +4,7 @@
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
using Microsoft.AspNetCore.E2ETesting;
+using Microsoft.AspNetCore.InternalTesting;
using TestServer;
using Xunit.Abstractions;
using Components.TestServer.RazorComponents;
@@ -519,6 +520,7 @@ public void NavigationManagerUriGetsUpdatedOnEnhancedNavigation_OnlyServerOrWebA
[Theory]
[InlineData("server")]
[InlineData("wasm")]
+ [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/61143")]
public void NavigationManagerUriGetsUpdatedOnEnhancedNavigation_BothServerAndWebAssembly(string runtimeThatInvokedNavigation)
{
Navigate($"{ServerPathBase}/nav");
diff --git a/src/Components/test/E2ETest/ServerRenderingTests/FormHandlingTests/FormWithParentBindingContextTest.cs b/src/Components/test/E2ETest/ServerRenderingTests/FormHandlingTests/FormWithParentBindingContextTest.cs
index 4b35b0c5eed0..53fd2addd404 100644
--- a/src/Components/test/E2ETest/ServerRenderingTests/FormHandlingTests/FormWithParentBindingContextTest.cs
+++ b/src/Components/test/E2ETest/ServerRenderingTests/FormHandlingTests/FormWithParentBindingContextTest.cs
@@ -1287,7 +1287,7 @@ public void CanBindToFormWithFiles()
}
[Theory]
- [InlineData(true)]
+ // [InlineData(true)] QuarantinedTest: https://github.com/dotnet/aspnetcore/issues/61882
[InlineData(false)]
public void CanUseFormWithMethodGet(bool suppressEnhancedNavigation)
{
@@ -1340,6 +1340,7 @@ void AssertUiState(string expectedStringValue, bool expectedBoolValue)
}
[Fact]
+ [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/61144")]
public void RadioButtonGetsResetAfterSubmittingEnhancedForm()
{
GoTo("forms/form-with-checkbox-and-radio-button");
diff --git a/src/Http/Headers/test/CookieHeaderValueTest.cs b/src/Http/Headers/test/CookieHeaderValueTest.cs
index 6623a8ed13dd..6ad2e962d005 100644
--- a/src/Http/Headers/test/CookieHeaderValueTest.cs
+++ b/src/Http/Headers/test/CookieHeaderValueTest.cs
@@ -75,7 +75,7 @@ public static TheoryData InvalidCookieValues
}
}
- public static TheoryData, string?[]> ListOfCookieHeaderDataSet
+ public static TheoryData, string?[]> ListOfStrictCookieHeaderDataSet
{
get
{
@@ -94,19 +94,30 @@ public static TheoryData InvalidCookieValues
dataset.Add(new[] { header1 }.ToList(), new[] { string1 });
dataset.Add(new[] { header1, header1 }.ToList(), new[] { string1, string1 });
- dataset.Add(new[] { header1, header1 }.ToList(), new[] { string1, null, "", " ", ";", " , ", string1 });
dataset.Add(new[] { header2 }.ToList(), new[] { string2 });
dataset.Add(new[] { header1, header2 }.ToList(), new[] { string1, string2 });
- dataset.Add(new[] { header1, header2 }.ToList(), new[] { string1 + ", " + string2 });
dataset.Add(new[] { header2, header1 }.ToList(), new[] { string2 + "; " + string1 });
dataset.Add(new[] { header1, header2, header3, header4 }.ToList(), new[] { string1, string2, string3, string4 });
- dataset.Add(new[] { header1, header2, header3, header4 }.ToList(), new[] { string.Join(",", string1, string2, string3, string4) });
dataset.Add(new[] { header1, header2, header3, header4 }.ToList(), new[] { string.Join(";", string1, string2, string3, string4) });
return dataset;
}
}
+ public static TheoryData, string?[]> ListOfCookieHeaderDataSet
+ {
+ get
+ {
+ var header1 = new CookieHeaderValue("name1", "n1=v1&n2=v2&n3=v3");
+ var string1 = "name1=n1=v1&n2=v2&n3=v3";
+
+ var dataset = new TheoryData, string?[]>();
+ dataset.Concat(ListOfStrictCookieHeaderDataSet);
+ dataset.Add(new[] { header1, header1 }.ToList(), new[] { string1, null, "", " ", ";", " , ", string1 });
+ return dataset;
+ }
+ }
+
public static TheoryData?, string?[]> ListWithInvalidCookieHeaderDataSet
{
get
@@ -127,18 +138,19 @@ public static TheoryData InvalidCookieValues
dataset.Add(new[] { header1 }.ToList(), new[] { validString1, invalidString1 });
dataset.Add(new[] { header1 }.ToList(), new[] { validString1, null, "", " ", ";", " , ", invalidString1 });
dataset.Add(new[] { header1 }.ToList(), new[] { invalidString1, null, "", " ", ";", " , ", validString1 });
- dataset.Add(new[] { header1 }.ToList(), new[] { validString1 + ", " + invalidString1 });
- dataset.Add(new[] { header2 }.ToList(), new[] { invalidString1 + ", " + validString2 });
+ dataset.Add(null, new[] { validString1 + ", " });
+ dataset.Add(null, new[] { invalidString1 + ", " + validString2 });
dataset.Add(new[] { header1 }.ToList(), new[] { invalidString1 + "; " + validString1 });
dataset.Add(new[] { header2 }.ToList(), new[] { validString2 + "; " + invalidString1 });
dataset.Add(new[] { header1, header2, header3 }.ToList(), new[] { invalidString1, validString1, validString2, validString3 });
dataset.Add(new[] { header1, header2, header3 }.ToList(), new[] { validString1, invalidString1, validString2, validString3 });
dataset.Add(new[] { header1, header2, header3 }.ToList(), new[] { validString1, validString2, invalidString1, validString3 });
dataset.Add(new[] { header1, header2, header3 }.ToList(), new[] { validString1, validString2, validString3, invalidString1 });
- dataset.Add(new[] { header1, header2, header3 }.ToList(), new[] { string.Join(",", invalidString1, validString1, validString2, validString3) });
- dataset.Add(new[] { header1, header2, header3 }.ToList(), new[] { string.Join(",", validString1, invalidString1, validString2, validString3) });
- dataset.Add(new[] { header1, header2, header3 }.ToList(), new[] { string.Join(",", validString1, validString2, invalidString1, validString3) });
- dataset.Add(new[] { header1, header2, header3 }.ToList(), new[] { string.Join(",", validString1, validString2, validString3, invalidString1) });
+ dataset.Add(null, new[] { string.Join(",", invalidString1, validString1, validString2, validString3) });
+ dataset.Add(null, new[] { string.Join(",", validString1, invalidString1, validString2, validString3) });
+ dataset.Add(null, new[] { string.Join(",", validString1, validString2, invalidString1, validString3) });
+ dataset.Add(null, new[] { string.Join(",", validString1, validString2, validString3, invalidString1) });
+ dataset.Add(null, new[] { string.Join(",", validString1, validString2, validString3) });
dataset.Add(new[] { header1, header2, header3 }.ToList(), new[] { string.Join(";", invalidString1, validString1, validString2, validString3) });
dataset.Add(new[] { header1, header2, header3 }.ToList(), new[] { string.Join(";", validString1, invalidString1, validString2, validString3) });
dataset.Add(new[] { header1, header2, header3 }.ToList(), new[] { string.Join(";", validString1, validString2, invalidString1, validString3) });
@@ -248,7 +260,7 @@ public void CookieHeaderValue_ParseList_AcceptsValidValues(IList cookies, string[] input)
{
var results = CookieHeaderValue.ParseStrictList(input);
@@ -267,7 +279,7 @@ public void CookieHeaderValue_TryParseList_AcceptsValidValues(IList cookies, string[] input)
{
var result = CookieHeaderValue.TryParseStrictList(input, out var results);
diff --git a/src/Http/Http.Extensions/test/HeaderDictionaryTypeExtensionsTest.cs b/src/Http/Http.Extensions/test/HeaderDictionaryTypeExtensionsTest.cs
index 33ecc3ff1ea8..b4071866534b 100644
--- a/src/Http/Http.Extensions/test/HeaderDictionaryTypeExtensionsTest.cs
+++ b/src/Http/Http.Extensions/test/HeaderDictionaryTypeExtensionsTest.cs
@@ -214,7 +214,7 @@ public void GetListT_StringWithQualityHeaderValidValue_Success()
public void GetListT_CookieHeaderValue_Success()
{
var context = new DefaultHttpContext();
- context.Request.Headers.Cookie = "cookie1=a,cookie2=b";
+ context.Request.Headers.Cookie = "cookie1=a;cookie2=b";
var result = context.Request.GetTypedHeaders().GetList(HeaderNames.Cookie);
diff --git a/src/Http/Http/test/RequestCookiesCollectionTests.cs b/src/Http/Http/test/RequestCookiesCollectionTests.cs
index e08a53f29711..d584f2db0599 100644
--- a/src/Http/Http/test/RequestCookiesCollectionTests.cs
+++ b/src/Http/Http/test/RequestCookiesCollectionTests.cs
@@ -33,11 +33,18 @@ public void ParseManyCookies()
[Theory]
[InlineData(",", null)]
[InlineData(";", null)]
- [InlineData("er=dd,cc,bb", new[] { "dd" })]
- [InlineData("er=dd,err=cc,errr=bb", new[] { "dd", "cc", "bb" })]
- [InlineData("errorcookie=dd,:(\"sa;", new[] { "dd" })]
+ [InlineData("er=dd,cc,bb", null)]
+ [InlineData("er=dd,err=cc,errr=bb", null)]
+ [InlineData("errorcookie=dd,:(\"sa;", null)]
[InlineData("s;", null)]
[InlineData("er=;,err=,errr=\\,errrr=\"", null)]
+ [InlineData("a@a=a;", null)]
+ [InlineData("a@ a=a;", null)]
+ [InlineData("a a=a;", null)]
+ [InlineData(",a=a;", null)]
+ [InlineData(",a=a", null)]
+ [InlineData("a=a;,b=b", new []{ "a" })] // valid cookie followed by invalid cookie
+ [InlineData(",a=a;b=b", new[] { "b" })] // invalid cookie followed by valid cookie
public void ParseInvalidCookies(string cookieToParse, string[] expectedCookieValues)
{
var cookies = RequestCookieCollection.Parse(new StringValues(new[] { cookieToParse }));
diff --git a/src/Http/Shared/CookieHeaderParserShared.cs b/src/Http/Shared/CookieHeaderParserShared.cs
index e4b1d83e519a..0eb1c64d533a 100644
--- a/src/Http/Shared/CookieHeaderParserShared.cs
+++ b/src/Http/Shared/CookieHeaderParserShared.cs
@@ -89,6 +89,17 @@ public static bool TryParseValue(StringSegment value, ref int index, bool suppor
if (!TryGetCookieLength(value, ref current, out parsedName, out parsedValue))
{
+ var separatorIndex = value.IndexOf(';', current);
+ if (separatorIndex > 0)
+ {
+ // Skip the invalid values and keep trying.
+ index = separatorIndex;
+ }
+ else
+ {
+ // No more separators, so we're done.
+ index = value.Length;
+ }
return false;
}
@@ -97,6 +108,17 @@ public static bool TryParseValue(StringSegment value, ref int index, bool suppor
// If we support multiple values and we've not reached the end of the string, then we must have a separator.
if ((separatorFound && !supportsMultipleValues) || (!separatorFound && (current < value.Length)))
{
+ var separatorIndex = value.IndexOf(';', current);
+ if (separatorIndex > 0)
+ {
+ // Skip the invalid values and keep trying.
+ index = separatorIndex;
+ }
+ else
+ {
+ // No more separators, so we're done.
+ index = value.Length;
+ }
return false;
}
@@ -112,7 +134,7 @@ private static int GetNextNonEmptyOrWhitespaceIndex(StringSegment input, int sta
separatorFound = false;
var current = startIndex + HttpRuleParser.GetWhitespaceLength(input, startIndex);
- if ((current == input.Length) || (input[current] != ',' && input[current] != ';'))
+ if (current == input.Length || input[current] != ';')
{
return current;
}
@@ -125,8 +147,8 @@ private static int GetNextNonEmptyOrWhitespaceIndex(StringSegment input, int sta
if (skipEmptyValues)
{
- // Most headers only split on ',', but cookies primarily split on ';'
- while ((current < input.Length) && ((input[current] == ',') || (input[current] == ';')))
+ // Cookies are split on ';'
+ while (current < input.Length && input[current] == ';')
{
current++; // skip delimiter.
current = current + HttpRuleParser.GetWhitespaceLength(input, current);
@@ -136,6 +158,18 @@ private static int GetNextNonEmptyOrWhitespaceIndex(StringSegment input, int sta
return current;
}
+ /*
+ * https://www.rfc-editor.org/rfc/rfc6265#section-4.1.1
+ * cookie-pair = cookie-name "=" cookie-value
+ * cookie-name = token
+ * token = 1*
+ separators = "(" | ")" | "<" | ">" | "@"
+ | "," | ";" | ":" | "\" | <">
+ | "/" | "[" | "]" | "?" | "="
+ | "{" | "}" | SP | HT
+ CTL =
+ */
// name=value; name="value"
internal static bool TryGetCookieLength(StringSegment input, ref int offset, [NotNullWhen(true)] out StringSegment? parsedName, [NotNullWhen(true)] out StringSegment? parsedValue)
{
diff --git a/src/Identity/EntityFrameworkCore/test/EF.Test/UserStoreTest.cs b/src/Identity/EntityFrameworkCore/test/EF.Test/UserStoreTest.cs
index f55c2b48340d..fe7eb5ee9003 100644
--- a/src/Identity/EntityFrameworkCore/test/EF.Test/UserStoreTest.cs
+++ b/src/Identity/EntityFrameworkCore/test/EF.Test/UserStoreTest.cs
@@ -144,6 +144,9 @@ await Assert.ThrowsAsync("user",
await Assert.ThrowsAsync("user", async () => await store.GetTwoFactorEnabledAsync(null));
await Assert.ThrowsAsync("user",
async () => await store.SetTwoFactorEnabledAsync(null, true));
+ await Assert.ThrowsAsync("user", async () => await store.RedeemCodeAsync(user: null, code: "fake", default));
+ await Assert.ThrowsAsync("code", async () => await store.RedeemCodeAsync(new IdentityUser("fake"), code: null, default));
+ await Assert.ThrowsAsync("code", async () => await store.RedeemCodeAsync(new IdentityUser("fake"), code: "", default));
await Assert.ThrowsAsync("user", async () => await store.GetAccessFailedCountAsync(null));
await Assert.ThrowsAsync("user", async () => await store.GetLockoutEnabledAsync(null));
await Assert.ThrowsAsync("user", async () => await store.SetLockoutEnabledAsync(null, false));
diff --git a/src/Identity/Extensions.Stores/src/UserStoreBase.cs b/src/Identity/Extensions.Stores/src/UserStoreBase.cs
index c45dd197e4a2..804ebcbad7dc 100644
--- a/src/Identity/Extensions.Stores/src/UserStoreBase.cs
+++ b/src/Identity/Extensions.Stores/src/UserStoreBase.cs
@@ -969,7 +969,7 @@ public virtual async Task RedeemCodeAsync(TUser user, string code, Cancell
ThrowIfDisposed();
ArgumentNullThrowHelper.ThrowIfNull(user);
- ArgumentNullThrowHelper.ThrowIfNull(code);
+ ArgumentNullThrowHelper.ThrowIfNullOrEmpty(code);
var mergedCodes = await GetTokenAsync(user, InternalLoginProvider, RecoveryCodeTokenName, cancellationToken).ConfigureAwait(false) ?? "";
var splitCodes = mergedCodes.Split(';');
diff --git a/src/Identity/UI/src/Microsoft.AspNetCore.Identity.UI.csproj b/src/Identity/UI/src/Microsoft.AspNetCore.Identity.UI.csproj
index a43b8e998eec..d36fe09d60c4 100644
--- a/src/Identity/UI/src/Microsoft.AspNetCore.Identity.UI.csproj
+++ b/src/Identity/UI/src/Microsoft.AspNetCore.Identity.UI.csproj
@@ -90,11 +90,27 @@
%(RecursiveDir)%(FileName)%(Extension)
-
-
+
+
-
+
+
@@ -106,7 +122,7 @@
>
-
+
-
-
+
+
+ Discovered
+
+
+ Discovered
+
+
+
+
+
-
+
-
+
diff --git a/src/Middleware/HttpOverrides/src/ForwardedHeadersMiddleware.cs b/src/Middleware/HttpOverrides/src/ForwardedHeadersMiddleware.cs
index 6b2a118cb132..72cae1507a0e 100644
--- a/src/Middleware/HttpOverrides/src/ForwardedHeadersMiddleware.cs
+++ b/src/Middleware/HttpOverrides/src/ForwardedHeadersMiddleware.cs
@@ -21,6 +21,7 @@ public class ForwardedHeadersMiddleware
private readonly ForwardedHeadersOptions _options;
private readonly RequestDelegate _next;
private readonly ILogger _logger;
+ private readonly bool _ignoreUnknownProxiesWithoutFor;
private bool _allowAllHosts;
private IList? _allowedHosts;
@@ -63,6 +64,18 @@ public ForwardedHeadersMiddleware(RequestDelegate next, ILoggerFactory loggerFac
_logger = loggerFactory.CreateLogger();
_next = next;
+ if (AppContext.TryGetSwitch("Microsoft.AspNetCore.HttpOverrides.IgnoreUnknownProxiesWithoutFor", out var enabled)
+ && enabled)
+ {
+ _ignoreUnknownProxiesWithoutFor = true;
+ }
+
+ if (Environment.GetEnvironmentVariable("MICROSOFT_ASPNETCORE_HTTPOVERRIDES_IGNORE_UNKNOWN_PROXIES_WITHOUT_FOR") is string env
+ && (env.Equals("true", StringComparison.OrdinalIgnoreCase) || env.Equals("1")))
+ {
+ _ignoreUnknownProxiesWithoutFor = true;
+ }
+
PreProcessHosts();
}
@@ -220,19 +233,24 @@ public void ApplyForwarders(HttpContext context)
for (; entriesConsumed < sets.Length; entriesConsumed++)
{
var set = sets[entriesConsumed];
- if (checkFor)
+ // Opt-out of breaking change behavior where we now always check KnownProxies and KnownNetworks
+ // It used to be guarded by the ForwardedHeaders.XForwardedFor flag, but now we always check it.
+ if (!_ignoreUnknownProxiesWithoutFor || checkFor)
{
// For the first instance, allow remoteIp to be null for servers that don't support it natively.
if (currentValues.RemoteIpAndPort != null && checkKnownIps && !CheckKnownAddress(currentValues.RemoteIpAndPort.Address))
{
// Stop at the first unknown remote IP, but still apply changes processed so far.
- if (_logger.IsEnabled(LogLevel.Debug))
+ if (_logger.IsEnabled(LogLevel.Warning))
{
- _logger.LogDebug(1, "Unknown proxy: {RemoteIpAndPort}", currentValues.RemoteIpAndPort);
+ _logger.LogWarning(1, "Unknown proxy: {RemoteIpAndPort}", currentValues.RemoteIpAndPort);
}
break;
}
+ }
+ if (checkFor)
+ {
if (IPEndPoint.TryParse(set.IpAndPortText, out var parsedEndPoint))
{
applyChanges = true;
diff --git a/src/Middleware/HttpOverrides/test/ForwardedHeadersMiddlewareTest.cs b/src/Middleware/HttpOverrides/test/ForwardedHeadersMiddlewareTest.cs
index aa33a191e7b7..627ad96a3cd6 100644
--- a/src/Middleware/HttpOverrides/test/ForwardedHeadersMiddlewareTest.cs
+++ b/src/Middleware/HttpOverrides/test/ForwardedHeadersMiddlewareTest.cs
@@ -7,6 +7,7 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.TestHost;
+using Microsoft.DotNet.RemoteExecutor;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
@@ -962,6 +963,201 @@ public async Task AllOptionsDisabledRequestDoesntChange()
Assert.Equal(PathString.Empty, context.Request.PathBase);
}
+ [Theory]
+ [InlineData(ForwardedHeaders.XForwardedFor, false)]
+ [InlineData(ForwardedHeaders.XForwardedFor, true)]
+ [InlineData(ForwardedHeaders.XForwardedHost, false)]
+ [InlineData(ForwardedHeaders.XForwardedHost, true)]
+ [InlineData(ForwardedHeaders.XForwardedProto, false)]
+ [InlineData(ForwardedHeaders.XForwardedProto, true)]
+ [InlineData(ForwardedHeaders.XForwardedPrefix, false)]
+ [InlineData(ForwardedHeaders.XForwardedPrefix, true)]
+ public async Task IgnoreXForwardedHeadersFromUnknownProxy(ForwardedHeaders forwardedHeaders, bool unknownProxy)
+ {
+ using var host = new HostBuilder()
+ .ConfigureWebHost(webHostBuilder =>
+ {
+ webHostBuilder
+ .UseTestServer()
+ .Configure(app =>
+ {
+ var options = new ForwardedHeadersOptions
+ {
+ ForwardedHeaders = forwardedHeaders
+ };
+ if (!unknownProxy)
+ {
+ var proxy = IPAddress.Parse("10.0.0.1");
+ options.KnownProxies.Add(proxy);
+ }
+ app.UseForwardedHeaders(options);
+ });
+ }).Build();
+
+ await host.StartAsync();
+
+ var server = host.GetTestServer();
+
+ var context = await server.SendAsync(c =>
+ {
+ c.Request.Headers["X-Forwarded-For"] = "11.111.111.11";
+ c.Request.Headers["X-Forwarded-Host"] = "testhost";
+ c.Request.Headers["X-Forwarded-Proto"] = "Protocol";
+ c.Request.Headers["X-Forwarded-Prefix"] = "/pathbase";
+ c.Connection.RemoteIpAddress = IPAddress.Parse("10.0.0.1");
+ c.Connection.RemotePort = 99;
+ });
+
+ if (unknownProxy)
+ {
+ Assert.Equal("10.0.0.1", context.Connection.RemoteIpAddress.ToString());
+ Assert.Equal("localhost", context.Request.Host.ToString());
+ Assert.Equal("http", context.Request.Scheme);
+ Assert.Equal(PathString.Empty, context.Request.PathBase);
+ }
+ else
+ {
+ if (forwardedHeaders.HasFlag(ForwardedHeaders.XForwardedFor))
+ {
+ Assert.Equal("11.111.111.11", context.Connection.RemoteIpAddress.ToString());
+ }
+ if (forwardedHeaders.HasFlag(ForwardedHeaders.XForwardedHost))
+ {
+ Assert.Equal("testhost", context.Request.Host.ToString());
+ }
+ if (forwardedHeaders.HasFlag(ForwardedHeaders.XForwardedProto))
+ {
+ Assert.Equal("Protocol", context.Request.Scheme);
+ }
+ if (forwardedHeaders.HasFlag(ForwardedHeaders.XForwardedPrefix))
+ {
+ Assert.Equal("/pathbase", context.Request.PathBase);
+ }
+ }
+ }
+
+ [Theory]
+ [InlineData(ForwardedHeaders.XForwardedFor)]
+ [InlineData(ForwardedHeaders.XForwardedHost)]
+ [InlineData(ForwardedHeaders.XForwardedProto)]
+ [InlineData(ForwardedHeaders.XForwardedPrefix)]
+ public void AppContextDoesNotValidateUnknownProxyWithoutForwardedFor(ForwardedHeaders forwardedHeaders)
+ {
+ RemoteExecutor.Invoke(static async (forwardedHeadersName) =>
+ {
+ Assert.True(Enum.TryParse(forwardedHeadersName, out var forwardedHeaders));
+ AppContext.SetSwitch("Microsoft.AspNetCore.HttpOverrides.IgnoreUnknownProxiesWithoutFor", true);
+ using var host = new HostBuilder()
+ .ConfigureWebHost(webHostBuilder =>
+ {
+ webHostBuilder
+ .UseTestServer()
+ .Configure(app =>
+ {
+ var options = new ForwardedHeadersOptions
+ {
+ ForwardedHeaders = forwardedHeaders
+ };
+ app.UseForwardedHeaders(options);
+ });
+ }).Build();
+
+ await host.StartAsync();
+
+ var server = host.GetTestServer();
+
+ var context = await server.SendAsync(c =>
+ {
+ c.Request.Headers["X-Forwarded-For"] = "11.111.111.11";
+ c.Request.Headers["X-Forwarded-Host"] = "testhost";
+ c.Request.Headers["X-Forwarded-Proto"] = "Protocol";
+ c.Request.Headers["X-Forwarded-Prefix"] = "/pathbase";
+ c.Connection.RemoteIpAddress = IPAddress.Parse("10.0.0.1");
+ c.Connection.RemotePort = 99;
+ });
+
+ if (forwardedHeaders.HasFlag(ForwardedHeaders.XForwardedFor))
+ {
+ // X-Forwarded-For ignored since 10.0.0.1 isn't in KnownProxies
+ Assert.Equal("10.0.0.1", context.Connection.RemoteIpAddress.ToString());
+ }
+ if (forwardedHeaders.HasFlag(ForwardedHeaders.XForwardedHost))
+ {
+ Assert.Equal("testhost", context.Request.Host.ToString());
+ }
+ if (forwardedHeaders.HasFlag(ForwardedHeaders.XForwardedProto))
+ {
+ Assert.Equal("Protocol", context.Request.Scheme);
+ }
+ if (forwardedHeaders.HasFlag(ForwardedHeaders.XForwardedPrefix))
+ {
+ Assert.Equal("/pathbase", context.Request.PathBase);
+ }
+ return RemoteExecutor.SuccessExitCode;
+ }, forwardedHeaders.ToString()).Dispose();
+ }
+
+ [Theory]
+ [InlineData(ForwardedHeaders.XForwardedFor)]
+ [InlineData(ForwardedHeaders.XForwardedHost)]
+ [InlineData(ForwardedHeaders.XForwardedProto)]
+ [InlineData(ForwardedHeaders.XForwardedPrefix)]
+ public void EnvVariableDoesNotValidateUnknownProxyWithoutForwardedFor(ForwardedHeaders forwardedHeaders)
+ {
+ RemoteExecutor.Invoke(static async (forwardedHeadersName) =>
+ {
+ Assert.True(Enum.TryParse(forwardedHeadersName, out var forwardedHeaders));
+ Environment.SetEnvironmentVariable("MICROSOFT_ASPNETCORE_HTTPOVERRIDES_IGNORE_UNKNOWN_PROXIES_WITHOUT_FOR", "true");
+ using var host = new HostBuilder()
+ .ConfigureWebHost(webHostBuilder =>
+ {
+ webHostBuilder
+ .UseTestServer()
+ .Configure(app =>
+ {
+ var options = new ForwardedHeadersOptions
+ {
+ ForwardedHeaders = forwardedHeaders
+ };
+ app.UseForwardedHeaders(options);
+ });
+ }).Build();
+
+ await host.StartAsync();
+
+ var server = host.GetTestServer();
+
+ var context = await server.SendAsync(c =>
+ {
+ c.Request.Headers["X-Forwarded-For"] = "11.111.111.11";
+ c.Request.Headers["X-Forwarded-Host"] = "testhost";
+ c.Request.Headers["X-Forwarded-Proto"] = "Protocol";
+ c.Request.Headers["X-Forwarded-Prefix"] = "/pathbase";
+ c.Connection.RemoteIpAddress = IPAddress.Parse("10.0.0.1");
+ c.Connection.RemotePort = 99;
+ });
+
+ if (forwardedHeaders.HasFlag(ForwardedHeaders.XForwardedFor))
+ {
+ // X-Forwarded-For ignored since 10.0.0.1 isn't in KnownProxies
+ Assert.Equal("10.0.0.1", context.Connection.RemoteIpAddress.ToString());
+ }
+ if (forwardedHeaders.HasFlag(ForwardedHeaders.XForwardedHost))
+ {
+ Assert.Equal("testhost", context.Request.Host.ToString());
+ }
+ if (forwardedHeaders.HasFlag(ForwardedHeaders.XForwardedProto))
+ {
+ Assert.Equal("Protocol", context.Request.Scheme);
+ }
+ if (forwardedHeaders.HasFlag(ForwardedHeaders.XForwardedPrefix))
+ {
+ Assert.Equal("/pathbase", context.Request.PathBase);
+ }
+ return RemoteExecutor.SuccessExitCode;
+ }, forwardedHeaders.ToString()).Dispose();
+ }
+
[Fact]
public async Task PartiallyEnabledForwardsPartiallyChangesRequest()
{
diff --git a/src/Middleware/OutputCaching/src/Memory/MemoryOutputCacheStore.cs b/src/Middleware/OutputCaching/src/Memory/MemoryOutputCacheStore.cs
index a75546b6793f..38a38069b32c 100644
--- a/src/Middleware/OutputCaching/src/Memory/MemoryOutputCacheStore.cs
+++ b/src/Middleware/OutputCaching/src/Memory/MemoryOutputCacheStore.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics;
+using System.Linq;
using Microsoft.Extensions.Caching.Memory;
namespace Microsoft.AspNetCore.OutputCaching.Memory;
@@ -9,7 +10,7 @@ namespace Microsoft.AspNetCore.OutputCaching.Memory;
internal sealed class MemoryOutputCacheStore : IOutputCacheStore
{
private readonly MemoryCache _cache;
- private readonly Dictionary> _taggedEntries = new();
+ private readonly Dictionary> _taggedEntries = [];
private readonly object _tagsLock = new();
internal MemoryOutputCacheStore(MemoryCache cache)
@@ -20,7 +21,7 @@ internal MemoryOutputCacheStore(MemoryCache cache)
}
// For testing
- internal Dictionary> TaggedEntries => _taggedEntries;
+ internal Dictionary> TaggedEntries => _taggedEntries.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Select(t => t.Key).ToHashSet());
public ValueTask EvictByTagAsync(string tag, CancellationToken cancellationToken)
{
@@ -30,7 +31,7 @@ public ValueTask EvictByTagAsync(string tag, CancellationToken cancellationToken
{
if (_taggedEntries.TryGetValue(tag, out var keys))
{
- if (keys != null && keys.Count > 0)
+ if (keys is { Count: > 0 })
{
// If MemoryCache changed to run eviction callbacks inline in Remove, iterating over keys could throw
// To prevent allocating a copy of the keys we check if the eviction callback ran,
@@ -40,7 +41,7 @@ public ValueTask EvictByTagAsync(string tag, CancellationToken cancellationToken
while (i > 0)
{
var oldCount = keys.Count;
- foreach (var key in keys)
+ foreach (var (key, _) in keys)
{
_cache.Remove(key);
i--;
@@ -74,6 +75,8 @@ public ValueTask SetAsync(string key, byte[] value, string[]? tags, TimeSpan val
ArgumentNullException.ThrowIfNull(key);
ArgumentNullException.ThrowIfNull(value);
+ var entryId = Guid.NewGuid();
+
if (tags != null)
{
// Lock with SetEntry() to prevent EvictByTagAsync() from trying to remove a tag whose entry hasn't been added yet.
@@ -90,27 +93,27 @@ public ValueTask SetAsync(string key, byte[] value, string[]? tags, TimeSpan val
if (!_taggedEntries.TryGetValue(tag, out var keys))
{
- keys = new HashSet();
+ keys = new HashSet();
_taggedEntries[tag] = keys;
}
Debug.Assert(keys != null);
- keys.Add(key);
+ keys.Add(new TaggedEntry(key, entryId));
}
- SetEntry(key, value, tags, validFor);
+ SetEntry(key, value, tags, validFor, entryId);
}
}
else
{
- SetEntry(key, value, tags, validFor);
+ SetEntry(key, value, tags, validFor, entryId);
}
return ValueTask.CompletedTask;
}
- void SetEntry(string key, byte[] value, string[]? tags, TimeSpan validFor)
+ private void SetEntry(string key, byte[] value, string[]? tags, TimeSpan validFor, Guid entryId)
{
Debug.Assert(key != null);
@@ -120,22 +123,25 @@ void SetEntry(string key, byte[] value, string[]? tags, TimeSpan validFor)
Size = value.Length
};
- if (tags != null && tags.Length > 0)
+ if (tags is { Length: > 0 })
{
// Remove cache keys from tag lists when the entry is evicted
- options.RegisterPostEvictionCallback(RemoveFromTags, tags);
+ options.RegisterPostEvictionCallback(RemoveFromTags, (tags, entryId));
}
_cache.Set(key, value, options);
}
- void RemoveFromTags(object key, object? value, EvictionReason reason, object? state)
+ private void RemoveFromTags(object key, object? value, EvictionReason reason, object? state)
{
- var tags = state as string[];
+ Debug.Assert(state != null);
+
+ var (tags, entryId) = ((string[] Tags, Guid EntryId))state;
Debug.Assert(tags != null);
Debug.Assert(tags.Length > 0);
Debug.Assert(key is string);
+ Debug.Assert(entryId != Guid.Empty);
lock (_tagsLock)
{
@@ -143,7 +149,7 @@ void RemoveFromTags(object key, object? value, EvictionReason reason, object? st
{
if (_taggedEntries.TryGetValue(tag, out var tagged))
{
- tagged.Remove((string)key);
+ tagged.Remove(new TaggedEntry((string)key, entryId));
// Remove the collection if there is no more keys in it
if (tagged.Count == 0)
@@ -154,4 +160,6 @@ void RemoveFromTags(object key, object? value, EvictionReason reason, object? st
}
}
}
+
+ private record TaggedEntry(string Key, Guid EntryId);
}
diff --git a/src/Middleware/OutputCaching/test/MemoryOutputCacheStoreTests.cs b/src/Middleware/OutputCaching/test/MemoryOutputCacheStoreTests.cs
index e8c809911add..c1ad1d708f4b 100644
--- a/src/Middleware/OutputCaching/test/MemoryOutputCacheStoreTests.cs
+++ b/src/Middleware/OutputCaching/test/MemoryOutputCacheStoreTests.cs
@@ -197,6 +197,43 @@ public async Task ExpiredEntries_AreRemovedFromTags()
Assert.Single(tag2s);
}
+ [Fact]
+ public async Task ReplacedEntries_AreNotRemovedFromTags()
+ {
+ var testClock = new TestMemoryOptionsClock { UtcNow = DateTimeOffset.UtcNow };
+ var cache = new MemoryCache(new MemoryCacheOptions { SizeLimit = 1000, Clock = testClock, ExpirationScanFrequency = TimeSpan.FromMilliseconds(1) });
+ var store = new MemoryOutputCacheStore(cache);
+ var value = "abc"u8.ToArray();
+
+ await store.SetAsync("a", value, new[] { "tag1", "tag2" }, TimeSpan.FromMilliseconds(5), default);
+ await store.SetAsync("a", value, new[] { "tag1" }, TimeSpan.FromMilliseconds(20), default);
+
+ testClock.Advance(TimeSpan.FromMilliseconds(10));
+
+ // Trigger background expiration by accessing the cache.
+ _ = cache.Get("a");
+
+ var resulta = await store.GetAsync("a", default);
+
+ Assert.NotNull(resulta);
+
+ HashSet tag1s, tag2s;
+
+ // Wait for the tag2 HashSet to be removed by the background expiration thread.
+
+ using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
+
+ while (store.TaggedEntries.TryGetValue("tag2", out tag2s) && !cts.IsCancellationRequested)
+ {
+ await Task.Yield();
+ }
+
+ store.TaggedEntries.TryGetValue("tag1", out tag1s);
+
+ Assert.Null(tag2s);
+ Assert.Single(tag1s);
+ }
+
[Theory]
[InlineData(null)]
public async Task Store_Throws_OnInvalidTag(string tag)
diff --git a/src/OpenApi/src/Schemas/OpenApiJsonSchema.Helpers.cs b/src/OpenApi/src/Schemas/OpenApiJsonSchema.Helpers.cs
index 830b0375d396..f05c595b5c2b 100644
--- a/src/OpenApi/src/Schemas/OpenApiJsonSchema.Helpers.cs
+++ b/src/OpenApi/src/Schemas/OpenApiJsonSchema.Helpers.cs
@@ -136,6 +136,8 @@ internal sealed partial class OpenApiJsonSchema
{
type = "array";
var array = new OpenApiArray();
+ // Read to process JsonTokenType.StartArray before advancing
+ reader.Read();
while (reader.TokenType != JsonTokenType.EndArray)
{
array.Add(ReadOpenApiAny(ref reader));
diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.ParameterSchemas.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.ParameterSchemas.cs
index 1c7cb1ba5746..6f87374b1c92 100644
--- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.ParameterSchemas.cs
+++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.ParameterSchemas.cs
@@ -722,4 +722,93 @@ public static bool TryParse(string value, out Student result)
return true;
}
}
+
+ // Regression test for https://github.com/dotnet/aspnetcore/issues/62023
+ // Testing that the array parsing in our OpenApiJsonSchema works
+ [Fact]
+ public async Task CustomConverterThatOutputsArrayWithDefaultValue()
+ {
+ // Arrange
+ var serviceCollection = new ServiceCollection();
+ serviceCollection.ConfigureHttpJsonOptions(options =>
+ {
+ options.SerializerOptions.Converters.Add(new EnumArrayTypeConverter());
+ });
+ var builder = CreateBuilder(serviceCollection);
+
+ // Act
+ builder.MapPost("/api", (EnumArrayType e = EnumArrayType.None) => { });
+
+ // Assert
+ await VerifyOpenApiDocument(builder, document =>
+ {
+ var operation = document.Paths["/api"].Operations[OperationType.Post];
+ var param = Assert.Single(operation.Parameters);
+ Assert.NotNull(param.Schema);
+ Assert.IsType(param.Schema.Default);
+ // Type is null, it's up to the user to configure this via a custom schema
+ // transformer for types with a converter.
+ Assert.Null(param.Schema.Type);
+ });
+ }
+
+ [Fact]
+ public async Task CustomConverterThatOutputsObjectWithDefaultValue()
+ {
+ // Arrange
+ var serviceCollection = new ServiceCollection();
+ serviceCollection.ConfigureHttpJsonOptions(options =>
+ {
+ options.SerializerOptions.Converters.Add(new EnumObjectTypeConverter());
+ });
+ var builder = CreateBuilder(serviceCollection);
+
+ // Act
+ builder.MapPost("/api", (EnumArrayType e = EnumArrayType.None) => { });
+
+ // Assert
+ await VerifyOpenApiDocument(builder, document =>
+ {
+ var operation = document.Paths["/api"].Operations[OperationType.Post];
+ var param = Assert.Single(operation.Parameters);
+ Assert.NotNull(param.Schema);
+ Assert.IsType(param.Schema.Default);
+ // Type is null, it's up to the user to configure this via a custom schema
+ // transformer for types with a converter.
+ Assert.Null(param.Schema.Type);
+ });
+ }
+
+ public enum EnumArrayType
+ {
+ None = 1
+ }
+
+ public class EnumArrayTypeConverter : JsonConverter
+ {
+ public override EnumArrayType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ return new EnumArrayType();
+ }
+
+ public override void Write(Utf8JsonWriter writer, EnumArrayType value, JsonSerializerOptions options)
+ {
+ writer.WriteStartArray();
+ writer.WriteEndArray();
+ }
+ }
+
+ public class EnumObjectTypeConverter : JsonConverter
+ {
+ public override EnumArrayType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ return new EnumArrayType();
+ }
+
+ public override void Write(Utf8JsonWriter writer, EnumArrayType value, JsonSerializerOptions options)
+ {
+ writer.WriteStartObject();
+ writer.WriteEndObject();
+ }
+ }
}
diff --git a/src/Security/Authentication/Certificate/src/CertificateAuthenticationHandler.cs b/src/Security/Authentication/Certificate/src/CertificateAuthenticationHandler.cs
index 8f8873057027..de74d6e5bf7c 100644
--- a/src/Security/Authentication/Certificate/src/CertificateAuthenticationHandler.cs
+++ b/src/Security/Authentication/Certificate/src/CertificateAuthenticationHandler.cs
@@ -135,21 +135,35 @@ private async Task ValidateCertificateAsync(X509Certificate2
}
var chainPolicy = BuildChainPolicy(clientCertificate, isCertificateSelfSigned);
- using var chain = new X509Chain
+ var chain = new X509Chain
{
ChainPolicy = chainPolicy
};
- var certificateIsValid = chain.Build(clientCertificate);
- if (!certificateIsValid)
+ try
+ {
+ var certificateIsValid = chain.Build(clientCertificate);
+ if (!certificateIsValid)
+ {
+ var chainErrors = new List(chain.ChainStatus.Length);
+ foreach (var validationFailure in chain.ChainStatus)
+ {
+ chainErrors.Add($"{validationFailure.Status} {validationFailure.StatusInformation}");
+ }
+ Logger.CertificateFailedValidation(clientCertificate.Subject, chainErrors);
+ return AuthenticateResults.InvalidClientCertificate;
+ }
+ }
+ finally
{
- var chainErrors = new List(chain.ChainStatus.Length);
- foreach (var validationFailure in chain.ChainStatus)
+ // Disposing the chain does not dispose the elements we potentially built.
+ // Do the full walk manually to dispose.
+ for (var i = 0; i < chain.ChainElements.Count; i++)
{
- chainErrors.Add($"{validationFailure.Status} {validationFailure.StatusInformation}");
+ chain.ChainElements[i].Certificate.Dispose();
}
- Logger.CertificateFailedValidation(clientCertificate.Subject, chainErrors);
- return AuthenticateResults.InvalidClientCertificate;
+
+ chain.Dispose();
}
var certificateValidatedContext = new CertificateValidatedContext(Context, Scheme, Options)
diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolver.cpp b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolver.cpp
index 9b12cd0132b4..8fb960261590 100644
--- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolver.cpp
+++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolver.cpp
@@ -197,7 +197,7 @@ HostFxrResolver::TryGetHostFxrPath(
size_t size = MAX_PATH * 2;
hostfxrPath.resize(size);
- get_hostfxr_parameters params;
+ get_hostfxr_parameters params{};
params.size = sizeof(get_hostfxr_parameters);
params.assembly_path = applicationPath.c_str();
params.dotnet_root = dotnetRoot.c_str();
@@ -393,7 +393,7 @@ HostFxrResolver::GetAbsolutePathToDotnetFromHostfxr(const fs::path& hostfxrPath)
// Tries to call where.exe to find the location of dotnet.exe.
// Will check that the bitness of dotnet matches the current
// worker process bitness.
-// Returns true if a valid dotnet was found, else false.R
+// Returns true if a valid dotnet was found, else false.
//
std::optional
HostFxrResolver::InvokeWhereToFindDotnet()
@@ -415,8 +415,7 @@ HostFxrResolver::InvokeWhereToFindDotnet()
DWORD dwExitCode;
STRU struDotnetSubstring;
STRU struDotnetLocationsString;
- DWORD dwNumBytesRead;
- DWORD dwBinaryType;
+ DWORD dwNumBytesRead = 0;
INT index = 0;
INT prevIndex = 0;
std::optional result;
@@ -521,14 +520,7 @@ HostFxrResolver::InvokeWhereToFindDotnet()
LOG_INFOF(L"Processing entry '%ls'", struDotnetSubstring.QueryStr());
- if (LOG_LAST_ERROR_IF(!GetBinaryTypeW(struDotnetSubstring.QueryStr(), &dwBinaryType)))
- {
- continue;
- }
-
- LOG_INFOF(L"Binary type %d", dwBinaryType);
-
- if (fIsCurrentProcess64Bit == (dwBinaryType == SCS_64BIT_BINARY))
+ if (fIsCurrentProcess64Bit == IsX64(struDotnetSubstring.QueryStr()))
{
// The bitness of dotnet matched with the current worker process bitness.
return std::make_optional(struDotnetSubstring.QueryStr());
@@ -539,6 +531,62 @@ HostFxrResolver::InvokeWhereToFindDotnet()
return result;
}
+BOOL HostFxrResolver::IsX64(const WCHAR* dotnetPath)
+{
+ // Errors while reading from the file shouldn't throw unless
+ // file.exception(bits) is set
+ std::ifstream file(dotnetPath, std::ios::binary);
+ if (!file.is_open())
+ {
+ LOG_TRACEF(L"Failed to open file %ls", dotnetPath);
+ return false;
+ }
+
+ // Read the DOS header
+ IMAGE_DOS_HEADER dosHeader{};
+ file.read(reinterpret_cast(&dosHeader), sizeof(dosHeader));
+ if (dosHeader.e_magic != IMAGE_DOS_SIGNATURE) // 'MZ'
+ {
+ LOG_TRACEF(L"%ls is not a valid executable file (missing MZ header).", dotnetPath);
+ return false;
+ }
+
+ // Seek to the PE header
+ file.seekg(dosHeader.e_lfanew, std::ios::beg);
+
+ // Read the PE header
+ DWORD peSignature{};
+ file.read(reinterpret_cast(&peSignature), sizeof(peSignature));
+ if (peSignature != IMAGE_NT_SIGNATURE) // 'PE\0\0'
+ {
+ LOG_TRACEF(L"%ls is not a valid PE file (missing PE header).", dotnetPath);
+ return false;
+ }
+
+ // Read the file header
+ IMAGE_FILE_HEADER fileHeader{};
+ file.read(reinterpret_cast(&fileHeader), sizeof(fileHeader));
+
+ // Read the optional header magic field
+ WORD magic{};
+ file.read(reinterpret_cast(&magic), sizeof(magic));
+
+ // Determine the architecture based on the magic value
+ if (magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC)
+ {
+ LOG_INFOF(L"%ls is 32-bit", dotnetPath);
+ return false;
+ }
+ else if (magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC)
+ {
+ LOG_INFOF(L"%ls is 64-bit", dotnetPath);
+ return true;
+ }
+
+ LOG_INFOF(L"%ls is unknown architecture %i", dotnetPath, fileHeader.Machine);
+ return false;
+}
+
std::optional
HostFxrResolver::GetAbsolutePathToDotnetFromProgramFiles()
{
diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolver.h b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolver.h
index 519f6df52c97..08ec650aec54 100644
--- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolver.h
+++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolver.h
@@ -74,6 +74,8 @@ class HostFxrResolver
const std::filesystem::path & requestedPath
);
+ static BOOL IsX64(const WCHAR* dotnetPath);
+
struct LocalFreeDeleter
{
void operator ()(_In_ LPWSTR* ptr) const
diff --git a/src/Servers/IIS/build/Build.Lib.Settings b/src/Servers/IIS/build/Build.Lib.Settings
index 0dcba8c2011a..9327eb363771 100644
--- a/src/Servers/IIS/build/Build.Lib.Settings
+++ b/src/Servers/IIS/build/Build.Lib.Settings
@@ -9,7 +9,7 @@
- false
+ true
_LIB;%(PreprocessorDefinitions)
true
diff --git a/src/Shared/CertificateGeneration/UnixCertificateManager.cs b/src/Shared/CertificateGeneration/UnixCertificateManager.cs
index c583c4d370ed..9212fc475cf7 100644
--- a/src/Shared/CertificateGeneration/UnixCertificateManager.cs
+++ b/src/Shared/CertificateGeneration/UnixCertificateManager.cs
@@ -62,18 +62,32 @@ public override TrustLevel GetTrustLevel(X509Certificate2 certificate)
// Building the chain will check whether dotnet trusts the cert. We could, instead,
// enumerate the Root store and/or look for the file in the OpenSSL directory, but
// this tests the real-world behavior.
- using var chain = new X509Chain();
- // This is just a heuristic for whether or not we should prompt the user to re-run with `--trust`
- // so we don't need to check revocation (which doesn't really make sense for dev certs anyway)
- chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
- if (chain.Build(certificate))
+ var chain = new X509Chain();
+ try
{
- sawTrustSuccess = true;
+ // This is just a heuristic for whether or not we should prompt the user to re-run with `--trust`
+ // so we don't need to check revocation (which doesn't really make sense for dev certs anyway)
+ chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
+ if (chain.Build(certificate))
+ {
+ sawTrustSuccess = true;
+ }
+ else
+ {
+ sawTrustFailure = true;
+ Log.UnixNotTrustedByDotnet();
+ }
}
- else
+ finally
{
- sawTrustFailure = true;
- Log.UnixNotTrustedByDotnet();
+ // Disposing the chain does not dispose the elements we potentially built.
+ // Do the full walk manually to dispose.
+ for (var i = 0; i < chain.ChainElements.Count; i++)
+ {
+ chain.ChainElements[i].Certificate.Dispose();
+ }
+
+ chain.Dispose();
}
// Will become the name of the file on disk and the nickname in the NSS DBs
@@ -94,7 +108,7 @@ public override TrustLevel GetTrustLevel(X509Certificate2 certificate)
var certPath = Path.Combine(sslCertDir, certificateNickname + ".pem");
if (File.Exists(certPath))
{
- var candidate = X509CertificateLoader.LoadCertificateFromFile(certPath);
+ using var candidate = X509CertificateLoader.LoadCertificateFromFile(certPath);
if (AreCertificatesEqual(certificate, candidate))
{
foundCert = true;
diff --git a/src/Shared/ThrowHelpers/ArgumentNullThrowHelper.cs b/src/Shared/ThrowHelpers/ArgumentNullThrowHelper.cs
index fc1d5c847d74..e83e87423745 100644
--- a/src/Shared/ThrowHelpers/ArgumentNullThrowHelper.cs
+++ b/src/Shared/ThrowHelpers/ArgumentNullThrowHelper.cs
@@ -30,6 +30,29 @@ public static void ThrowIfNull(
#endif
}
+ /// Throws an if is null or empty.
+ /// The argument to validate as non-null and non-empty.
+ /// The name of the parameter with which corresponds.
+ public static void ThrowIfNullOrEmpty(
+#if INTERNAL_NULLABLE_ATTRIBUTES || NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER
+ [NotNull]
+#endif
+ string? argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null)
+ {
+#if !NET7_0_OR_GREATER || NETSTANDARD || NETFRAMEWORK
+ if (argument is null)
+ {
+ Throw(paramName);
+ }
+ else if (argument.Length == 0)
+ {
+ throw new ArgumentException("Must not be null or empty", paramName);
+ }
+#else
+ ArgumentException.ThrowIfNullOrEmpty(argument, paramName);
+#endif
+ }
+
#if !NET7_0_OR_GREATER || NETSTANDARD || NETFRAMEWORK
#if INTERNAL_NULLABLE_ATTRIBUTES || NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER
[DoesNotReturn]
diff --git a/src/SignalR/clients/java/signalr/core/src/main/java/com/microsoft/signalr/GsonHubProtocol.java b/src/SignalR/clients/java/signalr/core/src/main/java/com/microsoft/signalr/GsonHubProtocol.java
index 042ca484806f..4b0c8848e816 100644
--- a/src/SignalR/clients/java/signalr/core/src/main/java/com/microsoft/signalr/GsonHubProtocol.java
+++ b/src/SignalR/clients/java/signalr/core/src/main/java/com/microsoft/signalr/GsonHubProtocol.java
@@ -126,7 +126,14 @@ public List parseMessages(ByteBuffer payload, InvocationBinder binde
}
break;
case "headers":
- throw new RuntimeException("Headers not implemented yet.");
+ // Parse headers as Map but don't store for now as it's unused
+ reader.beginObject();
+ while (reader.hasNext()) {
+ reader.nextName(); // Read the key
+ reader.nextString(); // Read the value
+ }
+ reader.endObject();
+ break;
default:
// Skip unknown property, allows new clients to still work with old protocols
reader.skipValue();
diff --git a/src/SignalR/clients/java/signalr/test/src/main/java/com/microsoft/signalr/GsonHubProtocolTest.java b/src/SignalR/clients/java/signalr/test/src/main/java/com/microsoft/signalr/GsonHubProtocolTest.java
index d696a74850eb..2ecfd483c7f5 100644
--- a/src/SignalR/clients/java/signalr/test/src/main/java/com/microsoft/signalr/GsonHubProtocolTest.java
+++ b/src/SignalR/clients/java/signalr/test/src/main/java/com/microsoft/signalr/GsonHubProtocolTest.java
@@ -527,4 +527,98 @@ public void canRegisterTypeAdaptorWithoutAffectingJsonProtocol() {
assertEquals(3, (int) invocationMessage.getArguments()[0]);
assertEquals("four", invocationMessage.getArguments()[1]);
}
+
+ @Test
+ public void canParseInvocationMessageWithHeaders() {
+ String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[42],\"headers\":{\"a\":\"b\",\"c\":\"d\"}}\u001E";
+ ByteBuffer message = TestUtils.stringToByteBuffer(stringifiedMessage);
+ TestBinder binder = new TestBinder(new Type[] { int.class }, null);
+
+ List messages = hubProtocol.parseMessages(message, binder);
+
+ assertNotNull(messages);
+ assertEquals(1, messages.size());
+
+ assertEquals(HubMessageType.INVOCATION, messages.get(0).getMessageType());
+ InvocationMessage invocationMessage = (InvocationMessage) messages.get(0);
+
+ assertEquals("test", invocationMessage.getTarget());
+ assertEquals(null, invocationMessage.getInvocationId());
+ int messageResult = (int)invocationMessage.getArguments()[0];
+ assertEquals(42, messageResult);
+ // Headers are parsed but not stored, so we just verify the message was processed successfully
+ }
+
+ @Test
+ public void canParseInvocationMessageWithEmptyHeaders() {
+ String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[42],\"headers\":{}}\u001E";
+ ByteBuffer message = TestUtils.stringToByteBuffer(stringifiedMessage);
+ TestBinder binder = new TestBinder(new Type[] { int.class }, null);
+
+ List messages = hubProtocol.parseMessages(message, binder);
+
+ assertNotNull(messages);
+ assertEquals(1, messages.size());
+
+ assertEquals(HubMessageType.INVOCATION, messages.get(0).getMessageType());
+ InvocationMessage invocationMessage = (InvocationMessage) messages.get(0);
+
+ assertEquals("test", invocationMessage.getTarget());
+ int messageResult = (int)invocationMessage.getArguments()[0];
+ assertEquals(42, messageResult);
+ }
+
+ @Test
+ public void canParseCompletionMessageWithHeaders() {
+ String stringifiedMessage = "{\"type\":3,\"invocationId\":\"1\",\"result\":42,\"headers\":{\"a\":\"b\",\"c\":\"d\"}}\u001E";
+ ByteBuffer message = TestUtils.stringToByteBuffer(stringifiedMessage);
+ TestBinder binder = new TestBinder(null, int.class);
+
+ List messages = hubProtocol.parseMessages(message, binder);
+
+ assertNotNull(messages);
+ assertEquals(1, messages.size());
+
+ assertEquals(HubMessageType.COMPLETION, messages.get(0).getMessageType());
+ CompletionMessage completionMessage = (CompletionMessage) messages.get(0);
+ assertEquals("1", completionMessage.getInvocationId());
+ assertEquals(42, completionMessage.getResult());
+ assertEquals(null, completionMessage.getError());
+ }
+
+ @Test
+ public void canParseStreamItemMessageWithHeaders() {
+ String stringifiedMessage = "{\"type\":2,\"invocationId\":\"1\",\"item\":\"test-item\",\"headers\":{\"a\":\"b\"}}\u001E";
+ ByteBuffer message = TestUtils.stringToByteBuffer(stringifiedMessage);
+ TestBinder binder = new TestBinder(null, String.class);
+
+ List messages = hubProtocol.parseMessages(message, binder);
+
+ assertNotNull(messages);
+ assertEquals(1, messages.size());
+
+ assertEquals(HubMessageType.STREAM_ITEM, messages.get(0).getMessageType());
+ StreamItem streamItem = (StreamItem) messages.get(0);
+ assertEquals("1", streamItem.getInvocationId());
+ assertEquals("test-item", streamItem.getItem());
+ }
+
+ @Test
+ public void canParseMessageWithHeadersInDifferentOrder() {
+ String stringifiedMessage = "{\"headers\":{\"First\":\"value1\",\"Second\":\"value2\"},\"type\":1,\"target\":\"test\",\"arguments\":[42]}\u001E";
+ ByteBuffer message = TestUtils.stringToByteBuffer(stringifiedMessage);
+ TestBinder binder = new TestBinder(new Type[] { int.class }, null);
+
+ List messages = hubProtocol.parseMessages(message, binder);
+
+ assertNotNull(messages);
+ assertEquals(1, messages.size());
+
+ assertEquals(HubMessageType.INVOCATION, messages.get(0).getMessageType());
+ InvocationMessage invocationMessage = (InvocationMessage) messages.get(0);
+
+ assertEquals("test", invocationMessage.getTarget());
+ int messageResult = (int)invocationMessage.getArguments()[0];
+ assertEquals(42, messageResult);
+ }
}
diff --git a/src/SignalR/common/Shared/MessageBuffer.cs b/src/SignalR/common/Shared/MessageBuffer.cs
index 17b9ae170fe0..f08fff86aa40 100644
--- a/src/SignalR/common/Shared/MessageBuffer.cs
+++ b/src/SignalR/common/Shared/MessageBuffer.cs
@@ -121,15 +121,16 @@ private async Task RunTimer()
public ValueTask WriteAsync(SerializedHubMessage hubMessage, CancellationToken cancellationToken)
{
- return WriteAsyncCore(hubMessage.Message!, hubMessage.GetSerializedMessage(_protocol), cancellationToken);
+ // Default to HubInvocationMessage as that's the only type we use SerializedHubMessage for currently when Message is null. Should harden this in the future.
+ return WriteAsyncCore(hubMessage.Message?.GetType() ?? typeof(HubInvocationMessage), hubMessage.GetSerializedMessage(_protocol), cancellationToken);
}
public ValueTask WriteAsync(HubMessage hubMessage, CancellationToken cancellationToken)
{
- return WriteAsyncCore(hubMessage, _protocol.GetMessageBytes(hubMessage), cancellationToken);
+ return WriteAsyncCore(hubMessage.GetType(), _protocol.GetMessageBytes(hubMessage), cancellationToken);
}
- private async ValueTask WriteAsyncCore(HubMessage hubMessage, ReadOnlyMemory messageBytes, CancellationToken cancellationToken)
+ private async ValueTask WriteAsyncCore(Type hubMessageType, ReadOnlyMemory messageBytes, CancellationToken cancellationToken)
{
// TODO: Add backpressure based on message count
if (_bufferedByteCount > _bufferLimit)
@@ -158,7 +159,7 @@ private async ValueTask WriteAsyncCore(HubMessage hubMessage, ReadO
await _writeLock.WaitAsync(cancellationToken: default).ConfigureAwait(false);
try
{
- if (hubMessage is HubInvocationMessage invocationMessage)
+ if (typeof(HubInvocationMessage).IsAssignableFrom(hubMessageType))
{
_totalMessageCount++;
_bufferedByteCount += messageBytes.Length;
diff --git a/src/SignalR/server/Core/src/SerializedHubMessage.cs b/src/SignalR/server/Core/src/SerializedHubMessage.cs
index e355b0329128..9f4327a4cc58 100644
--- a/src/SignalR/server/Core/src/SerializedHubMessage.cs
+++ b/src/SignalR/server/Core/src/SerializedHubMessage.cs
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Diagnostics;
using Microsoft.AspNetCore.SignalR.Protocol;
namespace Microsoft.AspNetCore.SignalR;
@@ -40,6 +41,8 @@ public SerializedHubMessage(IReadOnlyList messages)
/// The hub message for the cache. This will be serialized with an in to get the message's serialized representation.
public SerializedHubMessage(HubMessage message)
{
+ // Type currently only used for invocation messages, we should probably refactor it to be explicit about that e.g. new property for message type?
+ Debug.Assert(message.GetType().IsAssignableTo(typeof(HubInvocationMessage)));
Message = message;
}
diff --git a/src/SignalR/server/SignalR/test/Microsoft.AspNetCore.SignalR.Tests/Internal/MessageBufferTests.cs b/src/SignalR/server/SignalR/test/Microsoft.AspNetCore.SignalR.Tests/Internal/MessageBufferTests.cs
index a7e87d2b8bda..540ea462e199 100644
--- a/src/SignalR/server/SignalR/test/Microsoft.AspNetCore.SignalR.Tests/Internal/MessageBufferTests.cs
+++ b/src/SignalR/server/SignalR/test/Microsoft.AspNetCore.SignalR.Tests/Internal/MessageBufferTests.cs
@@ -2,11 +2,12 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.IO.Pipelines;
+using System.Text.Json;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http.Features;
+using Microsoft.AspNetCore.InternalTesting;
using Microsoft.AspNetCore.SignalR.Internal;
using Microsoft.AspNetCore.SignalR.Protocol;
-using Microsoft.AspNetCore.InternalTesting;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Time.Testing;
@@ -169,6 +170,62 @@ public async Task UnAckedMessageResentOnReconnect()
Assert.False(messageBuffer.ShouldProcessMessage(CompletionMessage.WithResult("1", null)));
}
+ // Regression test for https://github.com/dotnet/aspnetcore/issues/55575
+ [Fact]
+ public async Task UnAckedSerializedMessageResentOnReconnect()
+ {
+ var protocol = new JsonHubProtocol();
+ var connection = new TestConnectionContext();
+ var pipes = DuplexPipe.CreateConnectionPair(new PipeOptions(), new PipeOptions());
+ connection.Transport = pipes.Transport;
+ using var messageBuffer = new MessageBuffer(connection, protocol, bufferLimit: 1000, NullLogger.Instance);
+
+ var invocationMessage = new SerializedHubMessage([new SerializedMessage(protocol.Name,
+ protocol.GetMessageBytes(new InvocationMessage("method1", [1])))]);
+ await messageBuffer.WriteAsync(invocationMessage, default);
+
+ var res = await pipes.Application.Input.ReadAsync();
+
+ var buffer = res.Buffer;
+ Assert.True(protocol.TryParseMessage(ref buffer, new TestBinder(), out var message));
+ var parsedMessage = Assert.IsType(message);
+ Assert.Equal("method1", parsedMessage.Target);
+ Assert.Equal(1, ((JsonElement)Assert.Single(parsedMessage.Arguments)).GetInt32());
+
+ pipes.Application.Input.AdvanceTo(buffer.Start);
+
+ DuplexPipe.UpdateConnectionPair(ref pipes, connection);
+ await messageBuffer.ResendAsync(pipes.Transport.Output);
+
+ Assert.True(messageBuffer.ShouldProcessMessage(PingMessage.Instance));
+ Assert.True(messageBuffer.ShouldProcessMessage(CompletionMessage.WithResult("1", null)));
+ Assert.True(messageBuffer.ShouldProcessMessage(new SequenceMessage(1)));
+
+ res = await pipes.Application.Input.ReadAsync();
+
+ buffer = res.Buffer;
+ Assert.True(protocol.TryParseMessage(ref buffer, new TestBinder(), out message));
+ var seqMessage = Assert.IsType(message);
+ Assert.Equal(1, seqMessage.SequenceId);
+
+ pipes.Application.Input.AdvanceTo(buffer.Start);
+
+ res = await pipes.Application.Input.ReadAsync();
+
+ buffer = res.Buffer;
+ Assert.True(protocol.TryParseMessage(ref buffer, new TestBinder(), out message));
+ parsedMessage = Assert.IsType(message);
+ Assert.Equal("method1", parsedMessage.Target);
+ Assert.Equal(1, ((JsonElement)Assert.Single(parsedMessage.Arguments)).GetInt32());
+
+ pipes.Application.Input.AdvanceTo(buffer.Start);
+
+ messageBuffer.ShouldProcessMessage(new SequenceMessage(1));
+
+ Assert.True(messageBuffer.ShouldProcessMessage(PingMessage.Instance));
+ Assert.False(messageBuffer.ShouldProcessMessage(CompletionMessage.WithResult("1", null)));
+ }
+
[Fact]
public async Task AckedMessageNotResentOnReconnect()
{
diff --git a/src/SignalR/server/StackExchangeRedis/test/Docker.cs b/src/SignalR/server/StackExchangeRedis/test/Docker.cs
index 76fa6440e672..41315734daea 100644
--- a/src/SignalR/server/StackExchangeRedis/test/Docker.cs
+++ b/src/SignalR/server/StackExchangeRedis/test/Docker.cs
@@ -16,7 +16,8 @@ public class Docker
{
private static readonly string _exeSuffix = OperatingSystem.IsWindows() ? ".exe" : string.Empty;
- private static readonly string _dockerContainerName = "redisTestContainer";
+ private static readonly string _redisImageName = "dotnetdhmirror-f8bzbjakh8cga6ab.azurecr.io/library/redis:7.4";
+ private static readonly string _dockerContainerName = "redisTestContainer74";
private static readonly string _dockerMonitorContainerName = _dockerContainerName + "Monitor";
private static readonly Lazy _instance = new Lazy(Create);
@@ -112,7 +113,7 @@ void Run()
// use static name 'redisTestContainer' so if the container doesn't get removed we don't keep adding more
// use redis base docker image
// 30 second timeout to allow redis image to be downloaded, should be a rare occurrence, only happening when a new version is released
- RunProcessAndThrowIfFailed(_path, $"run --rm -p 6379:6379 --name {_dockerContainerName} -d redis", "redis", logger, TimeSpan.FromMinutes(1));
+ RunProcessAndThrowIfFailed(_path, $"run --rm -p 6379:6379 --name {_dockerContainerName} -d {_redisImageName}", "redis", logger, TimeSpan.FromMinutes(1));
}
}
diff --git a/src/SignalR/server/StackExchangeRedis/test/RedisEndToEnd.cs b/src/SignalR/server/StackExchangeRedis/test/RedisEndToEnd.cs
index 63445379ac35..f3501fb69c1e 100644
--- a/src/SignalR/server/StackExchangeRedis/test/RedisEndToEnd.cs
+++ b/src/SignalR/server/StackExchangeRedis/test/RedisEndToEnd.cs
@@ -1,17 +1,15 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System;
-using System.Collections.Generic;
-using System.Threading.Tasks;
+using System.Net.WebSockets;
using Microsoft.AspNetCore.Http.Connections;
+using Microsoft.AspNetCore.Http.Connections.Client;
+using Microsoft.AspNetCore.InternalTesting;
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.AspNetCore.SignalR.Protocol;
using Microsoft.AspNetCore.SignalR.Tests;
-using Microsoft.AspNetCore.InternalTesting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
-using Xunit;
namespace Microsoft.AspNetCore.SignalR.StackExchangeRedis.Tests;
@@ -37,6 +35,7 @@ public RedisEndToEndTests(RedisServerFixture serverFixture)
[ConditionalTheory]
[SkipIfDockerNotPresent]
[MemberData(nameof(TransportTypesAndProtocolTypes))]
+ [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/62435")]
public async Task HubConnectionCanSendAndReceiveMessages(HttpTransportType transportType, string protocolName)
{
using (StartVerifiableLog())
@@ -57,6 +56,7 @@ public async Task HubConnectionCanSendAndReceiveMessages(HttpTransportType trans
[ConditionalTheory]
[SkipIfDockerNotPresent]
[MemberData(nameof(TransportTypesAndProtocolTypes))]
+ [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/62435")]
public async Task HubConnectionCanSendAndReceiveGroupMessages(HttpTransportType transportType, string protocolName)
{
using (StartVerifiableLog())
@@ -118,6 +118,7 @@ public async Task CanSendAndReceiveUserMessagesFromMultipleConnectionsWithSameUs
[ConditionalTheory]
[SkipIfDockerNotPresent]
[MemberData(nameof(TransportTypesAndProtocolTypes))]
+ [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/62435")]
public async Task CanSendAndReceiveUserMessagesWhenOneConnectionWithUserDisconnects(HttpTransportType transportType, string protocolName)
{
// Regression test:
@@ -147,6 +148,7 @@ public async Task CanSendAndReceiveUserMessagesWhenOneConnectionWithUserDisconne
[ConditionalTheory]
[SkipIfDockerNotPresent]
[MemberData(nameof(TransportTypesAndProtocolTypes))]
+ [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/62435")]
public async Task HubConnectionCanSendAndReceiveGroupMessagesGroupNameWithPatternIsTreatedAsLiteral(HttpTransportType transportType, string protocolName)
{
using (StartVerifiableLog())
@@ -211,7 +213,106 @@ public async Task CanSendAndReceiveUserMessagesUserNameWithPatternIsTreatedAsLit
}
}
- private static HubConnection CreateConnection(string url, HttpTransportType transportType, IHubProtocol protocol, ILoggerFactory loggerFactory, string userName = null)
+ [ConditionalTheory]
+ [SkipIfDockerNotPresent]
+ [InlineData("messagepack")]
+ [InlineData("json")]
+ [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/62435")]
+ public async Task StatefulReconnectPreservesMessageFromOtherServer(string protocolName)
+ {
+ using (StartVerifiableLog())
+ {
+ var protocol = HubProtocolHelpers.GetHubProtocol(protocolName);
+
+ ClientWebSocket innerWs = null;
+ WebSocketWrapper ws = null;
+ TaskCompletionSource reconnectTcs = null;
+ TaskCompletionSource startedReconnectTcs = null;
+
+ var connection = CreateConnection(_serverFixture.FirstServer.Url + "/stateful", HttpTransportType.WebSockets, protocol, LoggerFactory,
+ customizeConnection: builder =>
+ {
+ builder.WithStatefulReconnect();
+ builder.Services.Configure(o =>
+ {
+ // Replace the websocket creation for the first connection so we can make the client think there was an ungraceful closure
+ // Which will trigger the stateful reconnect flow
+ o.WebSocketFactory = async (context, token) =>
+ {
+ if (reconnectTcs is null)
+ {
+ reconnectTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ startedReconnectTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ }
+ else
+ {
+ startedReconnectTcs.SetResult();
+ // We only want to wait on the reconnect, not the initial connection attempt
+ await reconnectTcs.Task.DefaultTimeout();
+ }
+
+ innerWs = new ClientWebSocket();
+ ws = new WebSocketWrapper(innerWs);
+ await innerWs.ConnectAsync(context.Uri, token);
+
+ _ = Task.Run(async () =>
+ {
+ try
+ {
+ while (innerWs.State == WebSocketState.Open)
+ {
+ var buffer = new byte[1024];
+ var res = await innerWs.ReceiveAsync(buffer, default);
+ ws.SetReceiveResult((res, buffer.AsMemory(0, res.Count)));
+ }
+ }
+ // Log but ignore receive errors, that likely just means the connection closed
+ catch (Exception ex)
+ {
+ Logger.LogInformation(ex, "Error while reading from inner websocket");
+ }
+ });
+
+ return ws;
+ };
+ });
+ });
+ var secondConnection = CreateConnection(_serverFixture.SecondServer.Url + "/stateful", HttpTransportType.WebSockets, protocol, LoggerFactory);
+
+ var tcs = new TaskCompletionSource();
+ connection.On("SendToAll", message => tcs.TrySetResult(message));
+
+ var tcs2 = new TaskCompletionSource();
+ secondConnection.On("SendToAll", message => tcs2.TrySetResult(message));
+
+ await connection.StartAsync().DefaultTimeout();
+ await secondConnection.StartAsync().DefaultTimeout();
+
+ // Close first connection before the second connection sends a message to all clients
+ await ws.CloseOutputAsync(WebSocketCloseStatus.InternalServerError, statusDescription: null, default);
+ await startedReconnectTcs.Task.DefaultTimeout();
+
+ // Send to all clients, since both clients are on different servers this means the backplane will be used
+ // And we want to test that messages are still preserved for stateful reconnect purposes when a client disconnects
+ // But is on a different server from the original message sender.
+ await secondConnection.SendAsync("SendToAll", "test message").DefaultTimeout();
+
+ // Check that second connection still receives the message
+ Assert.Equal("test message", await tcs2.Task.DefaultTimeout());
+ Assert.False(tcs.Task.IsCompleted);
+
+ // allow first connection to reconnect
+ reconnectTcs.SetResult();
+
+ // Check that first connection received the message once it reconnected
+ Assert.Equal("test message", await tcs.Task.DefaultTimeout());
+
+ await connection.DisposeAsync().DefaultTimeout();
+ }
+ }
+
+ private static HubConnection CreateConnection(string url, HttpTransportType transportType, IHubProtocol protocol, ILoggerFactory loggerFactory, string userName = null,
+ Action customizeConnection = null)
{
var hubConnectionBuilder = new HubConnectionBuilder()
.WithLoggerFactory(loggerFactory)
@@ -225,6 +326,8 @@ private static HubConnection CreateConnection(string url, HttpTransportType tran
hubConnectionBuilder.Services.AddSingleton(protocol);
+ customizeConnection?.Invoke(hubConnectionBuilder);
+
return hubConnectionBuilder.Build();
}
@@ -253,4 +356,67 @@ public static IEnumerable