diff --git a/NuGet.config b/NuGet.config
index 50e58d647089..973492be36fe 100644
--- a/NuGet.config
+++ b/NuGet.config
@@ -5,6 +5,7 @@
+
diff --git a/eng/Dependencies.props b/eng/Dependencies.props
index 663aca404a22..0413b558a486 100644
--- a/eng/Dependencies.props
+++ b/eng/Dependencies.props
@@ -40,6 +40,8 @@ and are generated based on the last package release.
+
+
diff --git a/eng/SharedFramework.External.props b/eng/SharedFramework.External.props
index 2f7d2bdc3786..175c7848407a 100644
--- a/eng/SharedFramework.External.props
+++ b/eng/SharedFramework.External.props
@@ -21,6 +21,8 @@
+
+
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index 5bf2d93ee5c3..dff6d7b2fba1 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -9,300 +9,329 @@
-->
-
+
https://github.com/dotnet/efcore
- d3362f65007769fd1c8ef442743832d91c7a34cb
+ 62238db4577182dc5c41bcd4813424906e2c6799
-
+
https://github.com/dotnet/efcore
- d3362f65007769fd1c8ef442743832d91c7a34cb
+ 62238db4577182dc5c41bcd4813424906e2c6799
-
+
https://github.com/dotnet/efcore
- d3362f65007769fd1c8ef442743832d91c7a34cb
+ 62238db4577182dc5c41bcd4813424906e2c6799
-
+
https://github.com/dotnet/efcore
- d3362f65007769fd1c8ef442743832d91c7a34cb
+ 62238db4577182dc5c41bcd4813424906e2c6799
-
+
https://github.com/dotnet/efcore
- d3362f65007769fd1c8ef442743832d91c7a34cb
+ 62238db4577182dc5c41bcd4813424906e2c6799
-
+
https://github.com/dotnet/efcore
- d3362f65007769fd1c8ef442743832d91c7a34cb
+ 62238db4577182dc5c41bcd4813424906e2c6799
-
+
https://github.com/dotnet/efcore
- d3362f65007769fd1c8ef442743832d91c7a34cb
+ 62238db4577182dc5c41bcd4813424906e2c6799
-
+
https://github.com/dotnet/efcore
- d3362f65007769fd1c8ef442743832d91c7a34cb
+ 62238db4577182dc5c41bcd4813424906e2c6799
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
+ https://github.com/dotnet/runtime
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
+
+
+ https://github.com/dotnet/runtime
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
+
+
https://github.com/dotnet/source-build-externals
- 21d564f9e16f7986d8af0692258afb91a52dda80
+ 9439becb24301b70f92ce2534e2cb8a1ca9b7a99
-
+
+ https://github.com/dotnet/runtime
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
+
+
+ https://github.com/dotnet/runtime
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
+
+
+ https://github.com/dotnet/runtime
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
+
+
+ https://github.com/dotnet/runtime
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
+
+
+ https://github.com/dotnet/runtime
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
+
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
https://github.com/dotnet/xdt
9a1c3e1b7f0c8763d4c96e593961a61a72679a7b
-
+
https://github.com/dotnet/source-build-reference-packages
- 43c337012443bb42d4baf97b4232dd04b443b5ef
+ db0cbe78748b71b00df05aff15cac2c8ce870cfd
@@ -326,9 +355,9 @@
-
+
https://github.com/dotnet/runtime
- 1e421670a6456d9c5b924b7ffea14cab8559a2e9
+ bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a
https://github.com/dotnet/arcade
diff --git a/eng/Versions.props b/eng/Versions.props
index 0bdde4945282..7043491348b7 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -63,78 +63,86 @@
-->
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
- 8.0.0-preview.5.23273.1
- 8.0.0-preview.5.23273.1
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
+ 8.0.0-preview.6.23280.5
- 8.0.0-preview.6.23273.4
- 8.0.0-preview.6.23273.4
- 8.0.0-preview.6.23273.4
- 8.0.0-preview.6.23273.4
- 8.0.0-preview.6.23273.4
- 8.0.0-preview.6.23273.4
- 8.0.0-preview.6.23273.4
- 8.0.0-preview.6.23273.4
+ 8.0.0-preview.6.23279.4
+ 8.0.0-preview.6.23279.4
+ 8.0.0-preview.6.23279.4
+ 8.0.0-preview.6.23279.4
+ 8.0.0-preview.6.23279.4
+ 8.0.0-preview.6.23279.4
+ 8.0.0-preview.6.23279.4
+ 8.0.0-preview.6.23279.4
4.4.0-4.22520.2
4.4.0-4.22520.2
@@ -152,9 +160,9 @@
8.0.0-beta.23218.3
- 8.0.0-alpha.1.23268.1
+ 8.0.0-alpha.1.23274.1
- 8.0.0-alpha.1.23273.1
+ 8.0.0-alpha.1.23274.2
7.0.0-preview.22423.2
diff --git a/eng/test-configuration.json b/eng/test-configuration.json
index 16a5879abbb1..a471108ae6a0 100644
--- a/eng/test-configuration.json
+++ b/eng/test-configuration.json
@@ -17,6 +17,7 @@
{"testName": {"contains": "POST_ServerAbort_ClientReceivesAbort"}},
{"testName": {"contains": "POST_ServerAbortAfterWrite_ClientReceivesAbort"}},
{"testName": {"contains": "ServerReset_BeforeRequestBody_ClientBodyThrows"}},
+ {"testName": {"contains": "InjectedStartup_DefaultApplicationNameIsEntryAssembly"}},
{"testAssembly": {"contains": "IIS"}},
{"testAssembly": {"contains": "Template"}},
{"failureMessage": {"contains":"(Site is started but no worker process found)"}},
diff --git a/eng/testing/linker/SupportFiles/Directory.Build.props b/eng/testing/linker/SupportFiles/Directory.Build.props
index 38efb264b42d..d5fb01366512 100644
--- a/eng/testing/linker/SupportFiles/Directory.Build.props
+++ b/eng/testing/linker/SupportFiles/Directory.Build.props
@@ -5,7 +5,7 @@
true
full
- true
+ true
true
true
diff --git a/eng/tools/GenerateFiles/Directory.Build.targets.in b/eng/tools/GenerateFiles/Directory.Build.targets.in
index e202f98c2f93..9595d23b4930 100644
--- a/eng/tools/GenerateFiles/Directory.Build.targets.in
+++ b/eng/tools/GenerateFiles/Directory.Build.targets.in
@@ -139,7 +139,7 @@
BeforeTargets="ProcessFrameworkReferences">
diff --git a/global.json b/global.json
index 6cd31d7e099b..e325b43e1a6c 100644
--- a/global.json
+++ b/global.json
@@ -1,9 +1,9 @@
{
"sdk": {
- "version": "8.0.100-preview.5.23257.1"
+ "version": "8.0.100-preview.6.23305.3"
},
"tools": {
- "dotnet": "8.0.100-preview.5.23257.1",
+ "dotnet": "8.0.100-preview.6.23305.3",
"runtimes": {
"dotnet/x86": [
"$(MicrosoftNETCoreBrowserDebugHostTransportVersion)"
diff --git a/src/Components/Authorization/test/AuthorizeRouteViewTest.cs b/src/Components/Authorization/test/AuthorizeRouteViewTest.cs
index e0db0c46914d..05609873187d 100644
--- a/src/Components/Authorization/test/AuthorizeRouteViewTest.cs
+++ b/src/Components/Authorization/test/AuthorizeRouteViewTest.cs
@@ -1,8 +1,10 @@
// 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.CodeAnalysis;
using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Components.Binding;
using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.AspNetCore.Components.RenderTree;
using Microsoft.AspNetCore.Components.Test.Helpers;
@@ -32,6 +34,7 @@ public AuthorizeRouteViewTest()
serviceCollection.AddSingleton();
serviceCollection.AddSingleton(_testAuthorizationService);
serviceCollection.AddSingleton();
+ serviceCollection.AddSingleton();
var services = serviceCollection.BuildServiceProvider();
_renderer = new TestRenderer(services);
@@ -467,4 +470,23 @@ public TestNavigationManager()
Initialize("https://localhost:85/subdir/", "https://localhost:85/subdir/path?query=value#hash");
}
}
+
+ private class TestFormValueSupplier : IFormValueSupplier
+ {
+ public bool CanBind(string formName, Type valueType)
+ {
+ return false;
+ }
+
+ public bool CanConvertSingleValue(Type type)
+ {
+ return false;
+ }
+
+ public bool TryBind(string formName, Type valueType, [NotNullWhen(true)] out object boundValue)
+ {
+ boundValue = null;
+ return false;
+ }
+ }
}
diff --git a/src/Components/Components/src/BindConverter.cs b/src/Components/Components/src/BindConverter.cs
index 441d84f6e1b3..8320e835325d 100644
--- a/src/Components/Components/src/BindConverter.cs
+++ b/src/Components/Components/src/BindConverter.cs
@@ -1223,7 +1223,7 @@ private static bool ConvertToDateTimeCore(object? obj, CultureInfo? culture, out
return ConvertToDateTimeCore(obj, culture, format: null, out value);
}
- private static bool ConvertToDateTimeCore(object? obj, CultureInfo? culture, string? format, out DateTime value)
+ private static bool ConvertToDateTimeCore(object? obj, CultureInfo? culture, [StringSyntax(StringSyntaxAttribute.TimeSpanFormat)] string? format, out DateTime value)
{
var text = (string?)obj;
if (string.IsNullOrEmpty(text))
@@ -1252,7 +1252,7 @@ private static bool ConvertToNullableDateTimeCore(object? obj, CultureInfo? cult
return ConvertToNullableDateTimeCore(obj, culture, format: null, out value);
}
- private static bool ConvertToNullableDateTimeCore(object? obj, CultureInfo? culture, string? format, out DateTime? value)
+ private static bool ConvertToNullableDateTimeCore(object? obj, CultureInfo? culture, [StringSyntax(StringSyntaxAttribute.DateTimeFormat)] string? format, out DateTime? value)
{
var text = (string?)obj;
if (string.IsNullOrEmpty(text))
@@ -1336,7 +1336,7 @@ private static bool ConvertToDateTimeOffsetCore(object? obj, CultureInfo? cultur
return ConvertToDateTimeOffsetCore(obj, culture, format: null, out value);
}
- private static bool ConvertToDateTimeOffsetCore(object? obj, CultureInfo? culture, string? format, out DateTimeOffset value)
+ private static bool ConvertToDateTimeOffsetCore(object? obj, CultureInfo? culture, [StringSyntax(StringSyntaxAttribute.DateTimeFormat)] string? format, out DateTimeOffset value)
{
var text = (string?)obj;
if (string.IsNullOrEmpty(text))
@@ -1365,7 +1365,7 @@ private static bool ConvertToNullableDateTimeOffsetCore(object? obj, CultureInfo
return ConvertToNullableDateTimeOffsetCore(obj, culture, format: null, out value);
}
- private static bool ConvertToNullableDateTimeOffsetCore(object? obj, CultureInfo? culture, string? format, out DateTimeOffset? value)
+ private static bool ConvertToNullableDateTimeOffsetCore(object? obj, CultureInfo? culture, [StringSyntax(StringSyntaxAttribute.DateTimeFormat)] string? format, out DateTimeOffset? value)
{
var text = (string?)obj;
if (string.IsNullOrEmpty(text))
diff --git a/src/Components/Components/src/Binding/CascadingModelBinder.cs b/src/Components/Components/src/Binding/CascadingModelBinder.cs
index 4a6b842cc4f9..994e8430c3d6 100644
--- a/src/Components/Components/src/Binding/CascadingModelBinder.cs
+++ b/src/Components/Components/src/Binding/CascadingModelBinder.cs
@@ -2,6 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Reflection.Metadata;
+using Microsoft.AspNetCore.Components.Binding;
+using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.AspNetCore.Components.Routing;
namespace Microsoft.AspNetCore.Components;
@@ -9,11 +11,12 @@ namespace Microsoft.AspNetCore.Components;
///
/// Defines the binding context for data bound from external sources.
///
-public sealed class CascadingModelBinder : IComponent, IDisposable
+public sealed class CascadingModelBinder : IComponent, ICascadingValueComponent, IDisposable
{
private RenderHandle _handle;
private ModelBindingContext? _bindingContext;
private bool _hasPendingQueuedRender;
+ private BindingInfo? _bindingInfo;
///
/// The binding context name.
@@ -35,7 +38,9 @@ public sealed class CascadingModelBinder : IComponent, IDisposable
[CascadingParameter] ModelBindingContext? ParentContext { get; set; }
- [Inject] private NavigationManager Navigation { get; set; } = null!;
+ [Inject] internal NavigationManager Navigation { get; set; } = null!;
+
+ [Inject] internal IFormValueSupplier FormValueSupplier { get; set; } = null!;
void IComponent.Attach(RenderHandle renderHandle)
{
@@ -87,7 +92,7 @@ private void HandleLocationChanged(object? sender, LocationChangedEventArgs e)
Render();
}
- private void UpdateBindingInformation(string url)
+ internal void UpdateBindingInformation(string url)
{
// BindingContextId: action parameter used to define the handler
// Name: form name and context used to bind
@@ -103,13 +108,13 @@ private void UpdateBindingInformation(string url)
// 3) Parent has a name "parent-name"
// Name = "parent-name.my-handler";
// BindingContextId = <>((<>&)|?)handler=my-handler
- var name = string.IsNullOrEmpty(ParentContext?.Name) ? Name : $"{ParentContext.Name}.{Name}";
+ var name = ModelBindingContext.Combine(ParentContext, Name);
var bindingId = string.IsNullOrEmpty(name) ? "" : GenerateBindingContextId(name);
var bindingContext = _bindingContext != null &&
- string.Equals(_bindingContext.Name, Name, StringComparison.Ordinal) &&
+ string.Equals(_bindingContext.Name, name, StringComparison.Ordinal) &&
string.Equals(_bindingContext.BindingContextId, bindingId, StringComparison.Ordinal) ?
- _bindingContext : new ModelBindingContext(name, bindingId);
+ _bindingContext : new ModelBindingContext(name, bindingId, FormValueSupplier.CanConvertSingleValue);
// It doesn't matter that we don't check IsFixed, since the CascadingValue we are setting up will throw if the app changes.
if (IsFixed && _bindingContext != null && _bindingContext != bindingContext)
@@ -136,4 +141,54 @@ void IDisposable.Dispose()
{
Navigation.LocationChanged -= HandleLocationChanged;
}
+
+ bool ICascadingValueComponent.CanSupplyValue(Type valueType, string? valueName)
+ {
+ var formName = string.IsNullOrEmpty(valueName) ?
+ (_bindingContext?.Name) :
+ ModelBindingContext.Combine(_bindingContext, valueName);
+
+ if (_bindingInfo != null &&
+ string.Equals(_bindingInfo.FormName, formName, StringComparison.Ordinal) &&
+ _bindingInfo.ValueType.Equals(valueType))
+ {
+ // We already bound the value, but some component might have been destroyed and
+ // re-created. If the type and name of the value that we bound are the same,
+ // we can provide the value that we bound.
+ return true;
+ }
+
+ // Can't supply the value if this context is for a form with a different name.
+ if (FormValueSupplier.CanBind(formName!, valueType))
+ {
+ var bindingSucceeded = FormValueSupplier.TryBind(formName!, valueType, out var boundValue);
+ _bindingInfo = new BindingInfo(formName, valueType, bindingSucceeded, boundValue);
+ if (!bindingSucceeded)
+ {
+ // Report errors
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ void ICascadingValueComponent.Subscribe(ComponentState subscriber)
+ {
+ throw new InvalidOperationException("Form values are always fixed.");
+ }
+
+ void ICascadingValueComponent.Unsubscribe(ComponentState subscriber)
+ {
+ throw new InvalidOperationException("Form values are always fixed.");
+ }
+
+ object? ICascadingValueComponent.CurrentValue => _bindingInfo == null ?
+ throw new InvalidOperationException("Tried to access form value before it was bound.") :
+ _bindingInfo.BoundValue;
+
+ bool ICascadingValueComponent.CurrentValueIsFixed => true;
+
+ private record BindingInfo(string? FormName, Type ValueType, bool BindingResult, object? BoundValue);
}
diff --git a/src/Components/Components/src/Binding/IFormValueSupplier.cs b/src/Components/Components/src/Binding/IFormValueSupplier.cs
new file mode 100644
index 000000000000..b8696a224339
--- /dev/null
+++ b/src/Components/Components/src/Binding/IFormValueSupplier.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.
+
+using System.Diagnostics.CodeAnalysis;
+
+namespace Microsoft.AspNetCore.Components.Binding;
+
+///
+/// Binds form data valuesto a model.
+///
+public interface IFormValueSupplier
+{
+ ///
+ /// Determines whether the specified value type can be bound.
+ ///
+ /// The form name to bind data from.
+ /// The for the value to bind.
+ /// true if the value type can be bound; otherwise, false.
+ bool CanBind(string formName, Type valueType);
+
+ ///
+ /// Determines whether a given can be converted from a single string value.
+ /// For example, strings, numbers, boolean values, enums, guids, etc. fall in this category.
+ ///
+ /// The to check.
+ /// true if the type can be converted from a single string value; otherwise, false.
+ bool CanConvertSingleValue(Type type);
+
+ ///
+ /// Tries to bind the form with the specified name to a value of the specified type.
+ ///
+ /// The form name to bind data from.
+ /// The for the value to bind.
+ /// The bound value if succeeded.
+ /// true if the form was bound successfully; otherwise, false.
+ bool TryBind(string formName, Type valueType, [NotNullWhen(true)] out object? boundValue);
+}
diff --git a/src/Components/Components/src/Binding/ModelBindingContext.cs b/src/Components/Components/src/Binding/ModelBindingContext.cs
index 0659b325d8e1..75be17857de8 100644
--- a/src/Components/Components/src/Binding/ModelBindingContext.cs
+++ b/src/Components/Components/src/Binding/ModelBindingContext.cs
@@ -8,10 +8,13 @@ namespace Microsoft.AspNetCore.Components;
///
public sealed class ModelBindingContext
{
- internal ModelBindingContext(string name, string bindingContextId)
+ private readonly Predicate _canBind;
+
+ internal ModelBindingContext(string name, string bindingContextId, Predicate canBind)
{
ArgumentNullException.ThrowIfNull(name);
ArgumentNullException.ThrowIfNull(bindingContextId);
+ ArgumentNullException.ThrowIfNull(canBind);
// We are initializing the root context, that can be a "named" root context, or the default context.
// A named root context only provides a name, and that acts as the BindingId
// A "default" root context does not provide a name, and instead it provides an explicit Binding ID.
@@ -23,6 +26,7 @@ internal ModelBindingContext(string name, string bindingContextId)
Name = name;
BindingContextId = bindingContextId ?? name;
+ _canBind = canBind;
}
///
@@ -34,4 +38,12 @@ internal ModelBindingContext(string name, string bindingContextId)
/// The computed identifier used to determine what parts of the app can bind data.
///
public string BindingContextId { get; }
+
+ internal static string Combine(ModelBindingContext? parentContext, string name) =>
+ string.IsNullOrEmpty(parentContext?.Name) ? name : $"{parentContext.Name}.{name}";
+
+ internal bool CanConvert(Type type)
+ {
+ return _canBind(type);
+ }
}
diff --git a/src/Components/Components/src/CascadingParameterState.cs b/src/Components/Components/src/CascadingParameterState.cs
index 52caad659d0b..7af4dc1b9cce 100644
--- a/src/Components/Components/src/CascadingParameterState.cs
+++ b/src/Components/Components/src/CascadingParameterState.cs
@@ -3,6 +3,7 @@
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
+using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Components.Reflection;
using Microsoft.AspNetCore.Components.Rendering;
@@ -45,11 +46,8 @@ public static IReadOnlyList FindCascadingParameters(Com
var supplier = GetMatchingCascadingValueSupplier(info, componentState);
if (supplier != null)
{
- if (resultStates == null)
- {
- // Although not all parameters might be matched, we know the maximum number
- resultStates = new List(infos.Length - infoIndex);
- }
+ // Although not all parameters might be matched, we know the maximum number
+ resultStates ??= new List(infos.Length - infoIndex);
resultStates.Add(new CascadingParameterState(info.ConsumerValueName, supplier));
}
@@ -98,16 +96,25 @@ private static ReflectedCascadingParameterInfo[] CreateReflectedCascadingParamet
var attribute = prop.GetCustomAttribute();
if (attribute != null)
{
- if (result == null)
- {
- result = new List();
- }
+ result ??= new List();
result.Add(new ReflectedCascadingParameterInfo(
prop.Name,
prop.PropertyType,
attribute.Name));
}
+
+ var hostParameterAttribute = prop.GetCustomAttributes()
+ .OfType().SingleOrDefault();
+ if (hostParameterAttribute != null)
+ {
+ result ??= new List();
+
+ result.Add(new ReflectedCascadingParameterInfo(
+ prop.Name,
+ prop.PropertyType,
+ hostParameterAttribute.Name));
+ }
}
return result?.ToArray() ?? Array.Empty();
diff --git a/src/Components/Components/src/ComponentBase.cs b/src/Components/Components/src/ComponentBase.cs
index 0496e1e028a5..e0150ec3dc62 100644
--- a/src/Components/Components/src/ComponentBase.cs
+++ b/src/Components/Components/src/ComponentBase.cs
@@ -327,7 +327,7 @@ Task IHandleEvent.HandleEventAsync(EventCallbackWorkItem callback, object? arg)
Task IHandleAfterRender.OnAfterRenderAsync()
{
var firstRender = !_hasCalledOnAfterRender;
- _hasCalledOnAfterRender |= true;
+ _hasCalledOnAfterRender = true;
OnAfterRender(firstRender);
diff --git a/src/Components/Components/src/IHostEnvironmentCascadingParameter.cs b/src/Components/Components/src/IHostEnvironmentCascadingParameter.cs
new file mode 100644
index 000000000000..8f407e0cdd5e
--- /dev/null
+++ b/src/Components/Components/src/IHostEnvironmentCascadingParameter.cs
@@ -0,0 +1,11 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.AspNetCore.Components;
+
+// Marks a cascading parameter that can be offered via an attribute that is not
+// directly defined in the Components assembly. For example [SupplyParameterFromForm].
+internal interface IHostEnvironmentCascadingParameter
+{
+ public string? Name { get; set; }
+}
diff --git a/src/Components/Components/src/PublicAPI.Unshipped.txt b/src/Components/Components/src/PublicAPI.Unshipped.txt
index 6048b3a28d79..625c0e05e595 100644
--- a/src/Components/Components/src/PublicAPI.Unshipped.txt
+++ b/src/Components/Components/src/PublicAPI.Unshipped.txt
@@ -1,5 +1,9 @@
#nullable enable
abstract Microsoft.AspNetCore.Components.RenderModeAttribute.Mode.get -> Microsoft.AspNetCore.Components.IComponentRenderMode!
+Microsoft.AspNetCore.Components.Binding.IFormValueSupplier
+Microsoft.AspNetCore.Components.Binding.IFormValueSupplier.CanBind(string! formName, System.Type! valueType) -> bool
+Microsoft.AspNetCore.Components.Binding.IFormValueSupplier.CanConvertSingleValue(System.Type! type) -> bool
+Microsoft.AspNetCore.Components.Binding.IFormValueSupplier.TryBind(string! formName, System.Type! valueType, out object? boundValue) -> bool
Microsoft.AspNetCore.Components.CascadingModelBinder
Microsoft.AspNetCore.Components.CascadingModelBinder.CascadingModelBinder() -> void
Microsoft.AspNetCore.Components.CascadingModelBinder.ChildContent.get -> Microsoft.AspNetCore.Components.RenderFragment!
@@ -34,7 +38,6 @@ Microsoft.AspNetCore.Components.Rendering.ComponentState
Microsoft.AspNetCore.Components.Rendering.ComponentState.Component.get -> Microsoft.AspNetCore.Components.IComponent!
Microsoft.AspNetCore.Components.Rendering.ComponentState.ComponentId.get -> int
Microsoft.AspNetCore.Components.Rendering.ComponentState.ComponentState(Microsoft.AspNetCore.Components.RenderTree.Renderer! renderer, int componentId, Microsoft.AspNetCore.Components.IComponent! component, Microsoft.AspNetCore.Components.Rendering.ComponentState? parentComponentState) -> void
-Microsoft.AspNetCore.Components.Rendering.ComponentState.Dispose() -> void
Microsoft.AspNetCore.Components.Rendering.ComponentState.ParentComponentState.get -> Microsoft.AspNetCore.Components.Rendering.ComponentState?
Microsoft.AspNetCore.Components.RenderTree.Renderer.GetComponentState(int componentId) -> Microsoft.AspNetCore.Components.Rendering.ComponentState!
Microsoft.AspNetCore.Components.Sections.SectionContent
@@ -61,6 +64,7 @@ override Microsoft.AspNetCore.Components.EventCallback.GetHashCode() -> int
override Microsoft.AspNetCore.Components.EventCallback.Equals(object? obj) -> bool
override Microsoft.AspNetCore.Components.EventCallback.GetHashCode() -> int
override Microsoft.AspNetCore.Components.EventCallback.Equals(object? obj) -> bool
+virtual Microsoft.AspNetCore.Components.Rendering.ComponentState.DisposeAsync() -> System.Threading.Tasks.ValueTask
virtual Microsoft.AspNetCore.Components.RenderTree.Renderer.AddPendingTask(Microsoft.AspNetCore.Components.Rendering.ComponentState? componentState, System.Threading.Tasks.Task! task) -> void
virtual Microsoft.AspNetCore.Components.RenderTree.Renderer.CreateComponentState(int componentId, Microsoft.AspNetCore.Components.IComponent! component, Microsoft.AspNetCore.Components.Rendering.ComponentState? parentComponentState) -> Microsoft.AspNetCore.Components.Rendering.ComponentState!
virtual Microsoft.AspNetCore.Components.RenderTree.Renderer.DispatchEventAsync(ulong eventHandlerId, Microsoft.AspNetCore.Components.RenderTree.EventFieldInfo? fieldInfo, System.EventArgs! eventArgs, bool quiesce) -> System.Threading.Tasks.Task!
diff --git a/src/Components/Components/src/Reflection/ComponentProperties.cs b/src/Components/Components/src/Reflection/ComponentProperties.cs
index dd0df1e094cd..9569e8e6a84e 100644
--- a/src/Components/Components/src/Reflection/ComponentProperties.cs
+++ b/src/Components/Components/src/Reflection/ComponentProperties.cs
@@ -3,6 +3,7 @@
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
+using System.Linq;
using System.Reflection;
using static Microsoft.AspNetCore.Internal.LinkerFlags;
@@ -168,11 +169,14 @@ private static void ThrowForUnknownIncomingParameterName([DynamicallyAccessedMem
var propertyInfo = targetType.GetProperty(parameterName, BindablePropertyFlags);
if (propertyInfo != null)
{
- if (!propertyInfo.IsDefined(typeof(ParameterAttribute)) && !propertyInfo.IsDefined(typeof(CascadingParameterAttribute)))
+ if (!propertyInfo.IsDefined(typeof(ParameterAttribute)) &&
+ !propertyInfo.IsDefined(typeof(CascadingParameterAttribute)) &&
+ !propertyInfo.GetCustomAttributes().OfType().Any())
{
throw new InvalidOperationException(
$"Object of type '{targetType.FullName}' has a property matching the name '{parameterName}', " +
- $"but it does not have [{nameof(ParameterAttribute)}] or [{nameof(CascadingParameterAttribute)}] applied.");
+ $"but it does not have [{nameof(ParameterAttribute)}], [{nameof(CascadingParameterAttribute)}] or " +
+ $"[SupplyParameterFromFormAttribute] applied.");
}
else
{
@@ -257,9 +261,30 @@ public WritersForType([DynamicallyAccessedMembers(Component)] Type targetType)
foreach (var propertyInfo in GetCandidateBindableProperties(targetType))
{
- var parameterAttribute = propertyInfo.GetCustomAttribute();
- var cascadingParameterAttribute = propertyInfo.GetCustomAttribute();
- var isParameter = parameterAttribute != null || cascadingParameterAttribute != null;
+ ParameterAttribute? parameterAttribute = null;
+ CascadingParameterAttribute? cascadingParameterAttribute = null;
+ IHostEnvironmentCascadingParameter? hostEnvironmentCascadingParameter = null;
+
+ var attributes = propertyInfo.GetCustomAttributes();
+ foreach (var attribute in attributes)
+ {
+ switch (attribute)
+ {
+ case ParameterAttribute parameter:
+ parameterAttribute = parameter;
+ break;
+ case CascadingParameterAttribute cascadingParameter:
+ cascadingParameterAttribute = cascadingParameter;
+ break;
+ case IHostEnvironmentCascadingParameter hostEnvironmentAttribute:
+ hostEnvironmentCascadingParameter = hostEnvironmentAttribute;
+ break;
+ default:
+ break;
+ }
+ }
+
+ var isParameter = parameterAttribute != null || cascadingParameterAttribute != null || hostEnvironmentCascadingParameter != null;
if (!isParameter)
{
continue;
@@ -274,7 +299,7 @@ public WritersForType([DynamicallyAccessedMembers(Component)] Type targetType)
var propertySetter = new PropertySetter(targetType, propertyInfo)
{
- Cascading = cascadingParameterAttribute != null,
+ Cascading = cascadingParameterAttribute != null || hostEnvironmentCascadingParameter != null,
};
if (_underlyingWriters.ContainsKey(propertyName))
diff --git a/src/Components/Components/src/RenderTree/Renderer.cs b/src/Components/Components/src/RenderTree/Renderer.cs
index 001454f09b02..187ffb6d8f79 100644
--- a/src/Components/Components/src/RenderTree/Renderer.cs
+++ b/src/Components/Components/src/RenderTree/Renderer.cs
@@ -876,28 +876,20 @@ private void ProcessDisposalQueueInExistingBatch()
var disposeComponentId = _batchBuilder.ComponentDisposalQueue.Dequeue();
var disposeComponentState = GetRequiredComponentState(disposeComponentId);
Log.DisposingComponent(_logger, disposeComponentState);
- if (!(disposeComponentState.Component is IAsyncDisposable))
- {
- if (!disposeComponentState.TryDisposeInBatch(_batchBuilder, out var exception))
- {
- exceptions ??= new List();
- exceptions.Add(exception);
- }
- }
- else
+
+ try
{
- var result = disposeComponentState.DisposeInBatchAsync(_batchBuilder);
- if (result.IsCompleted)
+ var disposalTask = disposeComponentState.DisposeInBatchAsync(_batchBuilder);
+ if (disposalTask.IsCompletedSuccessfully)
{
- if (!result.IsCompletedSuccessfully)
- {
- exceptions ??= new List();
- exceptions.Add(result.Exception);
- }
+ // If it's a IValueTaskSource backed ValueTask,
+ // inform it its result has been read so it can reset
+ disposalTask.GetAwaiter().GetResult();
}
else
{
// We set owningComponentState to null because we don't want exceptions during disposal to be recoverable
+ var result = disposalTask.AsTask();
AddToPendingTasksWithErrorHandling(GetHandledAsynchronousDisposalErrorsTask(result), owningComponentState: null);
async Task GetHandledAsynchronousDisposalErrorsTask(Task result)
@@ -913,6 +905,11 @@ async Task GetHandledAsynchronousDisposalErrorsTask(Task result)
}
}
}
+ catch (Exception exception)
+ {
+ exceptions ??= new List();
+ exceptions.Add(exception);
+ }
_componentStateById.Remove(disposeComponentId);
_componentStateByComponent.Remove(disposeComponentState.Component);
@@ -1080,39 +1077,25 @@ protected virtual void Dispose(bool disposing)
{
Log.DisposingComponent(_logger, componentState);
- // Components shouldn't need to implement IAsyncDisposable and IDisposable simultaneously,
- // but in case they do, we prefer the async overload since we understand the sync overload
- // is implemented for more "constrained" scenarios.
- // Component authors are responsible for their IAsyncDisposable implementations not taking
- // forever.
- if (componentState.Component is IAsyncDisposable asyncDisposable)
+ try
{
- try
+ var task = componentState.DisposeAsync();
+ if (task.IsCompletedSuccessfully)
{
- var task = asyncDisposable.DisposeAsync();
- if (!task.IsCompletedSuccessfully)
- {
- asyncDisposables ??= new();
- asyncDisposables.Add(task.AsTask());
- }
+ // If it's a IValueTaskSource backed ValueTask,
+ // inform it its result has been read so it can reset
+ task.GetAwaiter().GetResult();
}
- catch (Exception exception)
+ else
{
- exceptions ??= new List();
- exceptions.Add(exception);
+ asyncDisposables ??= new();
+ asyncDisposables.Add(task.AsTask());
}
}
- else if (componentState.Component is IDisposable disposable)
+ catch (Exception exception)
{
- try
- {
- componentState.Dispose();
- }
- catch (Exception exception)
- {
- exceptions ??= new List();
- exceptions.Add(exception);
- }
+ exceptions ??= new List();
+ exceptions.Add(exception);
}
}
diff --git a/src/Components/Components/src/Rendering/ComponentState.cs b/src/Components/Components/src/Rendering/ComponentState.cs
index 8252c36a1a6d..f7de9f106215 100644
--- a/src/Components/Components/src/Rendering/ComponentState.cs
+++ b/src/Components/Components/src/Rendering/ComponentState.cs
@@ -2,7 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Components.RenderTree;
namespace Microsoft.AspNetCore.Components.Rendering;
@@ -13,7 +12,7 @@ namespace Microsoft.AspNetCore.Components.Rendering;
/// detail of .
///
[DebuggerDisplay($"{{{nameof(GetDebuggerDisplay)}(),nq}}")]
-public class ComponentState : IDisposable
+public class ComponentState : IAsyncDisposable
{
private readonly Renderer _renderer;
private readonly IReadOnlyList _cascadingParameters;
@@ -108,41 +107,6 @@ internal void RenderIntoBatch(RenderBatchBuilder batchBuilder, RenderFragment re
batchBuilder.InvalidateParameterViews();
}
- internal bool TryDisposeInBatch(RenderBatchBuilder batchBuilder, [NotNullWhen(false)] out Exception? exception)
- {
- _componentWasDisposed = true;
- exception = null;
-
- try
- {
- if (Component is IDisposable disposable)
- {
- disposable.Dispose();
- }
- }
- catch (Exception ex)
- {
- exception = ex;
- }
-
- CleanupComponentStateResources(batchBuilder);
-
- return exception == null;
- }
-
- private void CleanupComponentStateResources(RenderBatchBuilder batchBuilder)
- {
- // We don't expect these things to throw.
- RenderTreeDiffBuilder.DisposeFrames(batchBuilder, CurrentRenderTree.GetFrames());
-
- if (_hasAnyCascadingParameterSubscriptions)
- {
- RemoveCascadingParameterSubscriptions();
- }
-
- DisposeBuffers();
- }
-
// Callers expect this method to always return a faulted task.
internal Task NotifyRenderCompletedAsync()
{
@@ -254,15 +218,26 @@ private void RemoveCascadingParameterSubscriptions()
}
///
- /// Disposes this instance.
+ /// Disposes this instance and its associated component.
///
- public void Dispose()
+ public virtual ValueTask DisposeAsync()
{
+ _componentWasDisposed = true;
DisposeBuffers();
- if (Component is IDisposable disposable)
+ // Components shouldn't need to implement IAsyncDisposable and IDisposable simultaneously,
+ // but in case they do, we prefer the async overload since we understand the sync overload
+ // is implemented for more "constrained" scenarios.
+ // Component authors are responsible for their IAsyncDisposable implementations not taking
+ // forever.
+ if (Component is IAsyncDisposable asyncDisposable)
{
- disposable.Dispose();
+ return asyncDisposable.DisposeAsync();
+ }
+ else
+ {
+ (Component as IDisposable)?.Dispose();
+ return ValueTask.CompletedTask;
}
}
@@ -273,33 +248,17 @@ private void DisposeBuffers()
_latestDirectParametersSnapshot?.Dispose();
}
- internal Task DisposeInBatchAsync(RenderBatchBuilder batchBuilder)
+ internal ValueTask DisposeInBatchAsync(RenderBatchBuilder batchBuilder)
{
- _componentWasDisposed = true;
-
- CleanupComponentStateResources(batchBuilder);
+ // We don't expect these things to throw.
+ RenderTreeDiffBuilder.DisposeFrames(batchBuilder, CurrentRenderTree.GetFrames());
- try
- {
- var result = ((IAsyncDisposable)Component).DisposeAsync();
- if (result.IsCompletedSuccessfully)
- {
- // If it's a IValueTaskSource backed ValueTask,
- // inform it its result has been read so it can reset
- result.GetAwaiter().GetResult();
- return Task.CompletedTask;
- }
- else
- {
- // We know we are dealing with an exception that happened asynchronously, so return a task
- // to the caller so that he can unwrap it.
- return result.AsTask();
- }
- }
- catch (Exception e)
+ if (_hasAnyCascadingParameterSubscriptions)
{
- return Task.FromException(e);
+ RemoveCascadingParameterSubscriptions();
}
+
+ return DisposeAsync();
}
private string GetDebuggerDisplay()
diff --git a/src/Components/Components/src/RouteView.cs b/src/Components/Components/src/RouteView.cs
index e3c9f6b4a54e..3d07cbc4f903 100644
--- a/src/Components/Components/src/RouteView.cs
+++ b/src/Components/Components/src/RouteView.cs
@@ -3,8 +3,10 @@
#nullable disable warnings
+using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
+using Microsoft.AspNetCore.Components.HotReload;
using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.AspNetCore.Components.Routing;
@@ -17,6 +19,15 @@ namespace Microsoft.AspNetCore.Components;
public class RouteView : IComponent
{
private RenderHandle _renderHandle;
+ private static readonly ConcurrentDictionary _layoutAttributeCache = new();
+
+ static RouteView()
+ {
+ if (HotReloadManager.Default.MetadataUpdateSupported)
+ {
+ HotReloadManager.Default.OnDeltaApplied += _layoutAttributeCache.Clear;
+ }
+ }
[Inject]
private NavigationManager NavigationManager { get; set; }
@@ -65,7 +76,8 @@ public Task SetParametersAsync(ParameterView parameters)
[UnconditionalSuppressMessage("Trimming", "IL2118", Justification = "Layout components are preserved because the LayoutAttribute constructor parameter is correctly annotated.")]
protected virtual void Render(RenderTreeBuilder builder)
{
- var pageLayoutType = RouteData.PageType.GetCustomAttribute()?.LayoutType
+ var pageLayoutType = _layoutAttributeCache
+ .GetOrAdd(RouteData.PageType, static type => type.GetCustomAttribute()?.LayoutType)
?? DefaultLayout;
builder.OpenComponent(0);
diff --git a/src/Components/Components/test/CascadingModelBinderTest.cs b/src/Components/Components/test/CascadingModelBinderTest.cs
index e8a60445b8c9..c20703b51d8f 100644
--- a/src/Components/Components/test/CascadingModelBinderTest.cs
+++ b/src/Components/Components/test/CascadingModelBinderTest.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics.CodeAnalysis;
+using Microsoft.AspNetCore.Components.Binding;
using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.AspNetCore.Components.Test.Helpers;
using Microsoft.Extensions.DependencyInjection;
@@ -18,6 +19,7 @@ public CascadingModelBinderTest()
var serviceCollection = new ServiceCollection();
_navigationManager = new TestNavigationManager();
serviceCollection.AddSingleton(_navigationManager);
+ serviceCollection.AddSingleton();
var services = serviceCollection.BuildServiceProvider();
_renderer = new TestRenderer(services);
}
@@ -328,4 +330,23 @@ public TestComponent(RenderFragment renderFragment)
protected override void BuildRenderTree(RenderTreeBuilder builder)
=> _renderFragment(builder);
}
+
+ private class TestFormValueSupplier : IFormValueSupplier
+ {
+ public bool CanBind(string formName, Type valueType)
+ {
+ return false;
+ }
+
+ public bool CanConvertSingleValue(Type type)
+ {
+ return false;
+ }
+
+ public bool TryBind(string formName, Type valueType, [NotNullWhen(true)] out object boundValue)
+ {
+ boundValue = null;
+ return false;
+ }
+ }
}
diff --git a/src/Components/Components/test/CascadingParameterStateTest.cs b/src/Components/Components/test/CascadingParameterStateTest.cs
index 648056c152c0..6edf09f31e21 100644
--- a/src/Components/Components/test/CascadingParameterStateTest.cs
+++ b/src/Components/Components/test/CascadingParameterStateTest.cs
@@ -1,8 +1,11 @@
// 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.CodeAnalysis;
+using Microsoft.AspNetCore.Components.Binding;
using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.AspNetCore.Components.Test.Helpers;
+using Moq;
namespace Microsoft.AspNetCore.Components;
@@ -355,6 +358,68 @@ public void FindCascadingParameters_CanOverrideNonNullValueWithNull()
});
}
+ [Fact]
+ public void FindCascadingParameters_HandlesSupplyParameterFromFormValues()
+ {
+ // Arrange
+ var cascadingModelBinder = new CascadingModelBinder
+ {
+ FormValueSupplier = new TestFormValueSupplier()
+ {
+ FormName = "",
+ ValueType = typeof(string),
+ BindResult = true,
+ BoundValue = "some value"
+ },
+ Navigation = Mock.Of(),
+ Name = ""
+ };
+
+ cascadingModelBinder.UpdateBindingInformation("https://localhost/");
+
+ var states = CreateAncestry(
+ cascadingModelBinder,
+ new FormParametersComponent());
+
+ // Act
+ var result = CascadingParameterState.FindCascadingParameters(states.Last());
+
+ // Assert
+ var supplier = Assert.Single(result);
+ Assert.Equal(cascadingModelBinder, supplier.ValueSupplier);
+ }
+
+ [Fact]
+ public void FindCascadingParameters_HandlesSupplyParameterFromFormValues_WithName()
+ {
+ // Arrange
+ var cascadingModelBinder = new CascadingModelBinder
+ {
+ FormValueSupplier = new TestFormValueSupplier()
+ {
+ FormName = "some-name",
+ ValueType = typeof(string),
+ BindResult = true,
+ BoundValue = "some value"
+ },
+ Navigation = new TestNavigationManager(),
+ Name = ""
+ };
+
+ cascadingModelBinder.UpdateBindingInformation("https://localhost/");
+
+ var states = CreateAncestry(
+ cascadingModelBinder,
+ new FormParametersComponentWithName());
+
+ // Act
+ var result = CascadingParameterState.FindCascadingParameters(states.Last());
+
+ // Assert
+ var supplier = Assert.Single(result);
+ Assert.Equal(cascadingModelBinder, supplier.ValueSupplier);
+ }
+
static ComponentState[] CreateAncestry(params IComponent[] components)
{
var result = new ComponentState[components.Length];
@@ -399,6 +464,16 @@ class ComponentWithNoParams : TestComponentBase
{
}
+ class FormParametersComponent : TestComponentBase
+ {
+ [SupplyParameterFromForm] public string FormParameter { get; set; }
+ }
+
+ class FormParametersComponentWithName : TestComponentBase
+ {
+ [SupplyParameterFromForm(Name = "some-name")] public string FormParameter { get; set; }
+ }
+
class ComponentWithNoCascadingParams : TestComponentBase
{
[Parameter] public bool SomeRegularParameter { get; set; }
@@ -443,4 +518,50 @@ class ValueType3 { }
class CascadingValueTypeBaseClass { }
class CascadingValueTypeDerivedClass : CascadingValueTypeBaseClass, ICascadingValueTypeDerivedClassInterface { }
interface ICascadingValueTypeDerivedClassInterface { }
+
+ private class TestFormValueSupplier : IFormValueSupplier
+ {
+ public string FormName { get; set; }
+
+ public Type ValueType { get; set; }
+
+ public object BoundValue { get; set; }
+
+ public bool BindResult { get; set; }
+
+ public bool CanBind(string formName, Type valueType)
+ {
+ return string.Equals(formName, FormName, StringComparison.Ordinal) &&
+ valueType == ValueType;
+ }
+
+ public bool CanConvertSingleValue(Type type)
+ {
+ return type == ValueType;
+ }
+
+ public bool TryBind(string formName, Type valueType, [NotNullWhen(true)] out object boundValue)
+ {
+ boundValue = BoundValue;
+ return BindResult;
+ }
+ }
+
+ class TestNavigationManager : NavigationManager
+ {
+ public TestNavigationManager()
+ {
+ Initialize("https://localhost:85/subdir/", "https://localhost:85/subdir/path?query=value#hash");
+ }
+ }
+}
+
+[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
+public sealed class SupplyParameterFromFormAttribute : Attribute, IHostEnvironmentCascadingParameter
+{
+ ///
+ /// Gets or sets the name for the parameter. The name is used to match
+ /// the form data and decide whether or not the value needs to be bound.
+ ///
+ public string Name { get; set; }
}
diff --git a/src/Components/Components/test/ModelBindingContextTest.cs b/src/Components/Components/test/ModelBindingContextTest.cs
index 4f82bdc4290d..76742c34cef2 100644
--- a/src/Components/Components/test/ModelBindingContextTest.cs
+++ b/src/Components/Components/test/ModelBindingContextTest.cs
@@ -8,7 +8,7 @@ public class ModelBindingContextTest
[Fact]
public void CanCreate_BindingContext_WithDefaultName()
{
- var context = new ModelBindingContext("", "");
+ var context = new ModelBindingContext("", "", t => true);
Assert.Equal("", context.Name);
Assert.Equal("", context.BindingContextId);
}
@@ -16,7 +16,7 @@ public void CanCreate_BindingContext_WithDefaultName()
[Fact]
public void CanCreate_BindingContext_WithName()
{
- var context = new ModelBindingContext("name", "path?handler=name");
+ var context = new ModelBindingContext("name", "path?handler=name", t => true);
Assert.Equal("name", context.Name);
Assert.Equal("path?handler=name", context.BindingContextId);
}
@@ -24,14 +24,14 @@ public void CanCreate_BindingContext_WithName()
[Fact]
public void Throws_WhenNameIsProvided_AndNoBindingContextId()
{
- var exception = Assert.Throws(() => new ModelBindingContext("name", ""));
+ var exception = Assert.Throws(() => new ModelBindingContext("name", "", t => true));
Assert.Equal("A root binding context needs to provide a name and explicit binding context id or none.", exception.Message);
}
[Fact]
public void Throws_WhenBindingContextId_IsProvidedForDefaultName()
{
- var exception = Assert.Throws(() => new ModelBindingContext("", "context"));
+ var exception = Assert.Throws(() => new ModelBindingContext("", "context", t => true));
Assert.Equal("A root binding context needs to provide a name and explicit binding context id or none.", exception.Message);
}
}
diff --git a/src/Components/Components/test/ParameterViewTest.Assignment.cs b/src/Components/Components/test/ParameterViewTest.Assignment.cs
index 8fff5c2003d0..bcc032c7e080 100644
--- a/src/Components/Components/test/ParameterViewTest.Assignment.cs
+++ b/src/Components/Components/test/ParameterViewTest.Assignment.cs
@@ -181,7 +181,7 @@ public void IncomingParameterMatchesPropertyNotDeclaredAsParameter_Throws()
Assert.Equal(default, target.IntProp);
Assert.Equal(
$"Object of type '{typeof(HasPropertyWithoutParameterAttribute).FullName}' has a property matching the name '{nameof(HasPropertyWithoutParameterAttribute.IntProp)}', " +
- $"but it does not have [{nameof(ParameterAttribute)}] or [{nameof(CascadingParameterAttribute)}] applied.",
+ $"but it does not have [{nameof(ParameterAttribute)}], [{nameof(CascadingParameterAttribute)}] or [{nameof(SupplyParameterFromFormAttribute)}] applied.",
ex.Message);
}
diff --git a/src/Components/Components/test/RendererTest.cs b/src/Components/Components/test/RendererTest.cs
index 29dd39f23805..02ecce2bbdcd 100644
--- a/src/Components/Components/test/RendererTest.cs
+++ b/src/Components/Components/test/RendererTest.cs
@@ -2334,9 +2334,8 @@ public void RenderBatch_HandlesSynchronousExceptionsInAsyncDisposableComponents(
// Outer component is still alive and not disposed.
Assert.False(component.Disposed);
- var aex = Assert.IsType(Assert.Single(renderer.HandledExceptions));
- var innerException = Assert.Single(aex.Flatten().InnerExceptions);
- Assert.Same(exception1, innerException);
+ var aex = Assert.Single(renderer.HandledExceptions);
+ Assert.Same(exception1, aex);
}
[Fact]
@@ -2493,8 +2492,7 @@ public void RenderBatch_ReportsSynchronousCancelationsAsErrors()
// Outer component is still alive and not disposed.
Assert.False(component.Disposed);
- var aex = Assert.IsType(Assert.Single(renderer.HandledExceptions));
- Assert.IsType(Assert.Single(aex.Flatten().InnerExceptions));
+ Assert.IsType(Assert.Single(renderer.HandledExceptions));
}
[Fact]
diff --git a/src/Components/Components/test/RouteViewTest.cs b/src/Components/Components/test/RouteViewTest.cs
index 92abd7e97b47..c791d7243363 100644
--- a/src/Components/Components/test/RouteViewTest.cs
+++ b/src/Components/Components/test/RouteViewTest.cs
@@ -1,6 +1,8 @@
// 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.CodeAnalysis;
+using Microsoft.AspNetCore.Components.Binding;
using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.AspNetCore.Components.Test.Helpers;
using Microsoft.Extensions.DependencyInjection;
@@ -19,6 +21,7 @@ public RouteViewTest()
var serviceCollection = new ServiceCollection();
_navigationManager = new RouteViewTestNavigationManager();
serviceCollection.AddSingleton(_navigationManager);
+ serviceCollection.AddSingleton();
var services = serviceCollection.BuildServiceProvider();
_renderer = new TestRenderer(services);
@@ -237,4 +240,23 @@ protected override void BuildRenderTree(RenderTreeBuilder builder)
builder.AddContent(2, "OtherLayout ends here");
}
}
+
+ private class TestFormValueSupplier : IFormValueSupplier
+ {
+ public bool CanBind(string formName, Type valueType)
+ {
+ return false;
+ }
+
+ public bool CanConvertSingleValue(Type type)
+ {
+ return false;
+ }
+
+ public bool TryBind(string formName, Type valueType, [NotNullWhen(true)] out object boundValue)
+ {
+ boundValue = null;
+ return false;
+ }
+ }
}
diff --git a/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/ArrayCollectionFactory.cs b/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/ArrayCollectionFactory.cs
new file mode 100644
index 000000000000..2b6b690d120f
--- /dev/null
+++ b/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/ArrayCollectionFactory.cs
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.AspNetCore.Components.Endpoints.Binding;
+
+internal sealed class ArrayCollectionFactory : ICollectionFactory
+{
+ public static TElement[] ToResultCore(TElement[] buffer, int size)
+ {
+ var result = new TElement[size];
+ Array.Copy(buffer, result, size);
+ return result;
+ }
+}
diff --git a/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/ArrayPoolBufferAdapter.cs b/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/ArrayPoolBufferAdapter.cs
new file mode 100644
index 000000000000..188ef4faa0ed
--- /dev/null
+++ b/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/ArrayPoolBufferAdapter.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.
+
+using System.Buffers;
+
+namespace Microsoft.AspNetCore.Components.Endpoints.Binding;
+
+internal abstract class ArrayPoolBufferAdapter
+ : ICollectionBufferAdapter.PooledBuffer, TElement>
+ where TCollectionFactory : ICollectionFactory
+{
+ public static PooledBuffer CreateBuffer() => new() { Data = ArrayPool.Shared.Rent(16), Count = 0 };
+
+ public static PooledBuffer Add(ref PooledBuffer buffer, TElement element)
+ {
+ if (buffer.Count >= buffer.Data.Length)
+ {
+ var newBuffer = ArrayPool.Shared.Rent(buffer.Data.Length * 2);
+ Array.Copy(buffer.Data, newBuffer, buffer.Data.Length);
+ ArrayPool.Shared.Return(buffer.Data);
+ buffer.Data = newBuffer;
+ }
+
+ buffer.Data[buffer.Count++] = element;
+ return buffer;
+ }
+
+ public static TCollection ToResult(PooledBuffer buffer)
+ {
+ var result = TCollectionFactory.ToResultCore(buffer.Data, buffer.Count);
+ ArrayPool.Shared.Return(buffer.Data);
+ return result;
+ }
+
+ public struct PooledBuffer
+ {
+ public TElement[] Data { get; set; }
+ public int Count { get; set; }
+ }
+}
diff --git a/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/ConcurrentBagBufferAdapter.cs b/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/ConcurrentBagBufferAdapter.cs
new file mode 100644
index 000000000000..6d4590bc390d
--- /dev/null
+++ b/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/ConcurrentBagBufferAdapter.cs
@@ -0,0 +1,19 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Concurrent;
+
+namespace Microsoft.AspNetCore.Components.Endpoints.Binding;
+
+internal sealed class ConcurrentBagBufferAdapter : ICollectionBufferAdapter, ConcurrentBag, TElement>
+{
+ public static ConcurrentBag CreateBuffer() => new();
+
+ public static ConcurrentBag Add(ref ConcurrentBag buffer, TElement element)
+ {
+ buffer.Add(element);
+ return buffer;
+ }
+
+ public static ConcurrentBag ToResult(ConcurrentBag buffer) => buffer;
+}
diff --git a/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/ConcurrentQueueBufferAdapter.cs b/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/ConcurrentQueueBufferAdapter.cs
new file mode 100644
index 000000000000..c54b08d8bd2b
--- /dev/null
+++ b/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/ConcurrentQueueBufferAdapter.cs
@@ -0,0 +1,19 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Concurrent;
+
+namespace Microsoft.AspNetCore.Components.Endpoints.Binding;
+
+internal sealed class ConcurrentQueueBufferAdapter : ICollectionBufferAdapter, ConcurrentQueue, TElement>
+{
+ public static ConcurrentQueue CreateBuffer() => new();
+
+ public static ConcurrentQueue Add(ref ConcurrentQueue buffer, TElement element)
+ {
+ buffer.Enqueue(element);
+ return buffer;
+ }
+
+ public static ConcurrentQueue ToResult(ConcurrentQueue buffer) => buffer;
+}
diff --git a/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/ConcurrentStackBufferAdapter.cs b/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/ConcurrentStackBufferAdapter.cs
new file mode 100644
index 000000000000..349856559739
--- /dev/null
+++ b/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/ConcurrentStackBufferAdapter.cs
@@ -0,0 +1,19 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Concurrent;
+
+namespace Microsoft.AspNetCore.Components.Endpoints.Binding;
+
+internal sealed class ConcurrentStackBufferAdapter : ICollectionBufferAdapter, ConcurrentStack, TElement>
+{
+ public static ConcurrentStack CreateBuffer() => new();
+
+ public static ConcurrentStack Add(ref ConcurrentStack buffer, TElement element)
+ {
+ buffer.Push(element);
+ return buffer;
+ }
+
+ public static ConcurrentStack ToResult(ConcurrentStack buffer) => buffer;
+}
diff --git a/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/ICollectionBufferAdapter.cs b/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/ICollectionBufferAdapter.cs
new file mode 100644
index 000000000000..58af62b4ca74
--- /dev/null
+++ b/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/ICollectionBufferAdapter.cs
@@ -0,0 +1,11 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.AspNetCore.Components.Endpoints.Binding;
+
+internal interface ICollectionBufferAdapter
+{
+ public static abstract TBuffer CreateBuffer();
+ public static abstract TBuffer Add(ref TBuffer buffer, TElement element);
+ public static abstract TCollection ToResult(TBuffer buffer);
+}
diff --git a/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/ICollectionFactory.cs b/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/ICollectionFactory.cs
new file mode 100644
index 000000000000..bad088ada5c2
--- /dev/null
+++ b/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/ICollectionFactory.cs
@@ -0,0 +1,9 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.AspNetCore.Components.Endpoints.Binding;
+
+internal interface ICollectionFactory
+{
+ public static abstract TCollection ToResultCore(TElement[] buffer, int size);
+}
diff --git a/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/ImmutableArrayBufferAdapter.cs b/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/ImmutableArrayBufferAdapter.cs
new file mode 100644
index 000000000000..9aa3dbfcabe8
--- /dev/null
+++ b/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/ImmutableArrayBufferAdapter.cs
@@ -0,0 +1,19 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Immutable;
+
+namespace Microsoft.AspNetCore.Components.Endpoints.Binding;
+
+internal sealed class ImmutableArrayBufferAdapter : ICollectionBufferAdapter, ImmutableArray.Builder, TElement>
+{
+ public static ImmutableArray.Builder CreateBuffer() => ImmutableArray.CreateBuilder();
+
+ public static ImmutableArray.Builder Add(ref ImmutableArray.Builder buffer, TElement element)
+ {
+ buffer.Add(element);
+ return buffer;
+ }
+
+ public static ImmutableArray ToResult(ImmutableArray.Builder buffer) => buffer.ToImmutable();
+}
diff --git a/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/ImmutableHashSetBufferAdapter.cs b/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/ImmutableHashSetBufferAdapter.cs
new file mode 100644
index 000000000000..1f93d977ff2e
--- /dev/null
+++ b/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/ImmutableHashSetBufferAdapter.cs
@@ -0,0 +1,33 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Immutable;
+
+namespace Microsoft.AspNetCore.Components.Endpoints.Binding;
+
+internal sealed class ImmutableHashSetBufferAdapter : ICollectionBufferAdapter, ImmutableHashSet.Builder, TElement>
+{
+ public static ImmutableHashSet.Builder CreateBuffer() => ImmutableHashSet.CreateBuilder();
+
+ public static ImmutableHashSet.Builder Add(ref ImmutableHashSet.Builder buffer, TElement element)
+ {
+ buffer.Add(element);
+ return buffer;
+ }
+
+ public static ImmutableHashSet ToResult(ImmutableHashSet.Builder buffer) => buffer.ToImmutable();
+
+ public static CollectionConverter> CreateInterfaceConverter(FormDataConverter elementConverter)
+ {
+ return new CollectionConverter<
+ IImmutableSet,
+ StaticCastAdapter<
+ IImmutableSet,
+ ImmutableHashSet,
+ ImmutableHashSetBufferAdapter,
+ ImmutableHashSet.Builder,
+ TElement>,
+ ImmutableHashSet.Builder,
+ TElement>(elementConverter);
+ }
+}
diff --git a/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/ImmutableListBufferAdapter.cs b/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/ImmutableListBufferAdapter.cs
new file mode 100644
index 000000000000..2a00632136a5
--- /dev/null
+++ b/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/ImmutableListBufferAdapter.cs
@@ -0,0 +1,33 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Immutable;
+
+namespace Microsoft.AspNetCore.Components.Endpoints.Binding;
+
+internal sealed class ImmutableListBufferAdapter : ICollectionBufferAdapter, ImmutableList.Builder, TElement>
+{
+ public static ImmutableList.Builder CreateBuffer() => ImmutableList.CreateBuilder();
+
+ public static ImmutableList.Builder Add(ref ImmutableList.Builder buffer, TElement element)
+ {
+ buffer.Add(element);
+ return buffer;
+ }
+
+ public static ImmutableList ToResult(ImmutableList.Builder buffer) => buffer.ToImmutable();
+
+ public static CollectionConverter> CreateInterfaceConverter(FormDataConverter elementConverter)
+ {
+ return new CollectionConverter<
+ IImmutableList,
+ StaticCastAdapter<
+ IImmutableList,
+ ImmutableList,
+ ImmutableListBufferAdapter,
+ ImmutableList.Builder,
+ TElement>,
+ ImmutableList.Builder,
+ TElement>(elementConverter);
+ }
+}
diff --git a/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/ImmutableQueueBufferAdapter.cs b/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/ImmutableQueueBufferAdapter.cs
new file mode 100644
index 000000000000..59ad0b1c5df6
--- /dev/null
+++ b/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/ImmutableQueueBufferAdapter.cs
@@ -0,0 +1,33 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Immutable;
+using System.Linq;
+
+namespace Microsoft.AspNetCore.Components.Endpoints.Binding;
+
+internal sealed class ImmutableQueueBufferAdapter
+ : ArrayPoolBufferAdapter, ImmutableQueueBufferAdapter.ImmutableQueueFactory, TElement>
+{
+ internal class ImmutableQueueFactory : ICollectionFactory, TElement>
+ {
+ public static ImmutableQueue ToResultCore(TElement[] buffer, int size)
+ {
+ return ImmutableQueue.CreateRange(buffer.Take(size));
+ }
+ }
+
+ public static CollectionConverter> CreateInterfaceConverter(FormDataConverter elementConverter)
+ {
+ return new CollectionConverter<
+ IImmutableQueue,
+ StaticCastAdapter<
+ IImmutableQueue,
+ ImmutableQueue,
+ ImmutableQueueBufferAdapter,
+ PooledBuffer,
+ TElement>,
+ PooledBuffer,
+ TElement>(elementConverter);
+ }
+}
diff --git a/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/ImmutableSortedSetBufferAdapter.cs b/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/ImmutableSortedSetBufferAdapter.cs
new file mode 100644
index 000000000000..f9562f048e07
--- /dev/null
+++ b/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/ImmutableSortedSetBufferAdapter.cs
@@ -0,0 +1,19 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Immutable;
+
+namespace Microsoft.AspNetCore.Components.Endpoints.Binding;
+
+internal sealed class ImmutableSortedSetBufferAdapter : ICollectionBufferAdapter, ImmutableSortedSet.Builder, TElement>
+{
+ public static ImmutableSortedSet.Builder CreateBuffer() => ImmutableSortedSet.CreateBuilder();
+
+ public static ImmutableSortedSet.Builder Add(ref ImmutableSortedSet.Builder buffer, TElement element)
+ {
+ buffer.Add(element);
+ return buffer;
+ }
+
+ public static ImmutableSortedSet ToResult(ImmutableSortedSet.Builder buffer) => buffer.ToImmutable();
+}
diff --git a/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/ImmutableStackBufferAdapter.cs b/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/ImmutableStackBufferAdapter.cs
new file mode 100644
index 000000000000..bff06bab2302
--- /dev/null
+++ b/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/ImmutableStackBufferAdapter.cs
@@ -0,0 +1,45 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Immutable;
+using System.Linq;
+
+namespace Microsoft.AspNetCore.Components.Endpoints.Binding;
+
+internal sealed class ImmutableStackBufferAdapter
+ : ArrayPoolBufferAdapter, ImmutableStackBufferAdapter.ImmutableStackFactory, TElement>
+{
+ internal class ImmutableStackFactory : ICollectionFactory, TElement>
+ {
+ public static ImmutableStack ToResultCore(TElement[] buffer, int size)
+ {
+ return ImmutableStack.CreateRange(buffer.Take(size));
+ }
+ }
+
+ public static CollectionConverter> CreateInterfaceConverter(FormDataConverter elementConverter)
+ {
+ return new CollectionConverter<
+ IImmutableStack,
+ StaticCastAdapter<
+ IImmutableStack,
+ ImmutableStack,
+ ImmutableStackBufferAdapter,
+ PooledBuffer,
+ TElement>,
+ PooledBuffer,
+ TElement>(elementConverter);
+ }
+}
+
+internal class StaticCastAdapter
+ : ICollectionBufferAdapter
+ where TCollectionAdapter : ICollectionBufferAdapter
+ where TCollectionImplementation : TCollectionInterface
+{
+ public static TBuffer CreateBuffer() => TCollectionAdapter.CreateBuffer();
+
+ public static TBuffer Add(ref TBuffer buffer, TElement element) => TCollectionAdapter.Add(ref buffer, element);
+
+ public static TCollectionInterface ToResult(TBuffer buffer) => TCollectionAdapter.ToResult(buffer);
+}
diff --git a/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/ImplementingCollectionBufferAdapter.cs b/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/ImplementingCollectionBufferAdapter.cs
new file mode 100644
index 000000000000..5659e483d75b
--- /dev/null
+++ b/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/ImplementingCollectionBufferAdapter.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.AspNetCore.Components.Endpoints.Binding;
+
+internal sealed class ImplementingCollectionBufferAdapter : ICollectionBufferAdapter
+ where TBuffer : TCollection, ICollection, new()
+{
+ public static TBuffer CreateBuffer() => new();
+
+ public static TBuffer Add(ref TBuffer buffer, TElement element)
+ {
+ buffer.Add(element);
+ return buffer;
+ }
+
+ public static TCollection ToResult(TBuffer buffer) => buffer;
+}
diff --git a/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/QueueBufferAdapter.cs b/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/QueueBufferAdapter.cs
new file mode 100644
index 000000000000..59176a66e858
--- /dev/null
+++ b/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/QueueBufferAdapter.cs
@@ -0,0 +1,17 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.AspNetCore.Components.Endpoints.Binding;
+
+internal sealed class QueueBufferAdapter : ICollectionBufferAdapter, Queue, TElement>
+{
+ public static Queue CreateBuffer() => new();
+
+ public static Queue Add(ref Queue buffer, TElement element)
+ {
+ buffer.Enqueue(element);
+ return buffer;
+ }
+
+ public static Queue ToResult(Queue buffer) => buffer;
+}
diff --git a/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/ReadOnlyCollectionBufferAdapter.cs b/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/ReadOnlyCollectionBufferAdapter.cs
new file mode 100644
index 000000000000..dad0f5471798
--- /dev/null
+++ b/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/ReadOnlyCollectionBufferAdapter.cs
@@ -0,0 +1,19 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.ObjectModel;
+
+namespace Microsoft.AspNetCore.Components.Endpoints.Binding;
+
+internal sealed class ReadOnlyCollectionBufferAdapter : ICollectionBufferAdapter, IList, TElement>
+{
+ public static IList CreateBuffer() => new List();
+
+ public static IList Add(ref IList buffer, TElement element)
+ {
+ buffer.Add(element);
+ return buffer;
+ }
+
+ public static ReadOnlyCollection ToResult(IList buffer) => new(buffer);
+}
diff --git a/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/StackBufferAdapter.cs b/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/StackBufferAdapter.cs
new file mode 100644
index 000000000000..04be226b0774
--- /dev/null
+++ b/src/Components/Endpoints/src/Binding/Converters/CollectionAdapters/StackBufferAdapter.cs
@@ -0,0 +1,17 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.AspNetCore.Components.Endpoints.Binding;
+
+internal sealed class StackBufferAdapter : ICollectionBufferAdapter, Stack, TElement>
+{
+ public static Stack CreateBuffer() => new();
+
+ public static Stack Add(ref Stack buffer, TElement element)
+ {
+ buffer.Push(element);
+ return buffer;
+ }
+
+ public static Stack ToResult(Stack buffer) => buffer;
+}
diff --git a/src/Components/Endpoints/src/Binding/Converters/CollectionConverter.cs b/src/Components/Endpoints/src/Binding/Converters/CollectionConverter.cs
new file mode 100644
index 000000000000..4bbe309f394b
--- /dev/null
+++ b/src/Components/Endpoints/src/Binding/Converters/CollectionConverter.cs
@@ -0,0 +1,153 @@
+// 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.CodeAnalysis;
+using System.Globalization;
+
+namespace Microsoft.AspNetCore.Components.Endpoints.Binding;
+
+// The algorithm for collections is always the same. There are two main cases:
+// The collection can be modified, so we can add items to it.
+// The collection cannot be modified, so we need to use a buffer and copy the items to it once we are done.
+// When adding to the buffer, there are two cases:
+// The buffer implements ICollection, so we can add items to it.
+// The buffer does not implement ICollection, so we need to use custom code to add items to it.
+// These aspects are captured in the TCollectionPolicy type parameter.
+// Instead of creating a hierachy with virtual members, we are using generics and virtual interface dispatch to achieve the same result.
+// This allows us to avoid virtual dispatch at runtime, and enables us to easily adapt to different types of collections.
+
+internal abstract class CollectionConverter : FormDataConverter
+{
+}
+
+internal class CollectionConverter : CollectionConverter
+ where TCollectionPolicy : ICollectionBufferAdapter
+{
+ // Indexes up to 100 are pre-allocated to avoid allocations for common cases.
+ private static readonly string[] Indexes = new string[] {
+ "[0]", "[1]", "[2]", "[3]", "[4]", "[5]", "[6]", "[7]", "[8]", "[9]",
+ "[10]", "[11]", "[12]", "[13]", "[14]", "[15]", "[16]", "[17]", "[18]", "[19]",
+ "[20]", "[21]", "[22]", "[23]", "[24]", "[25]", "[26]", "[27]", "[28]", "[29]",
+ "[30]", "[31]", "[32]", "[33]", "[34]", "[35]", "[36]", "[37]", "[38]", "[39]",
+ "[40]", "[41]", "[42]", "[43]", "[44]", "[45]", "[46]", "[47]", "[48]", "[49]",
+ "[50]", "[51]", "[52]", "[53]", "[54]", "[55]", "[56]", "[57]", "[58]", "[59]",
+ "[60]", "[61]", "[62]", "[63]", "[64]", "[65]", "[66]", "[67]", "[68]", "[69]",
+ "[70]", "[71]", "[72]", "[73]", "[74]", "[75]", "[76]", "[77]", "[78]", "[79]",
+ "[80]", "[81]", "[82]", "[83]", "[84]", "[85]", "[86]", "[87]", "[88]", "[89]",
+ "[90]", "[91]", "[92]", "[93]", "[94]", "[95]", "[96]", "[97]", "[98]", "[99]",
+ };
+
+ private readonly FormDataConverter _elementConverter;
+
+ public CollectionConverter(FormDataConverter elementConverter)
+ {
+ ArgumentNullException.ThrowIfNull(elementConverter);
+ _elementConverter = elementConverter;
+ }
+
+ internal override bool TryRead(
+ ref FormDataReader context,
+ Type type,
+ FormDataMapperOptions options,
+ [NotNullWhen(true)] out TCollection? result,
+ out bool found)
+ {
+ TElement currentElement;
+ TBuffer buffer;
+ bool foundCurrentElement;
+ bool currentElementSuccess;
+ bool succeded;
+ // Even though we have indexes, we special case 0 an 1 and use literals directly. We leave them in the indexes
+ // collection because it makes other indexes align.
+ context.PushPrefix("[0]");
+ succeded = _elementConverter.TryRead(ref context, typeof(TElement), options, out currentElement!, out found);
+ context.PopPrefix("[0]");
+ if (!found)
+ {
+ result = default;
+ return succeded;
+ }
+
+ // We already know we found an element;
+ found = true;
+ // At this point we have at least found one element, we can create the collection and assign it to it.
+ buffer = TCollectionPolicy.CreateBuffer();
+ buffer = TCollectionPolicy.Add(ref buffer, currentElement!);
+
+ // Read element 1 and set conditions to enter the loop
+ context.PushPrefix("[1]");
+ currentElementSuccess = _elementConverter.TryRead(ref context, typeof(TElement), options, out currentElement!, out foundCurrentElement);
+ succeded = succeded && currentElementSuccess;
+ context.PopPrefix("[1]");
+ // We need to iterate while we keep finding values, even if some of them have errors. This is because we don't want data to be lost just
+ // because we are not able to parse it. For example, if [5] = "asdf", we don't want to loose the values for [6], [7], etc. that can be
+ // valid.
+ // There will be a limit to how many errors we collect, at which point we will stop capturing errors.
+ // Similarly, over 100 elements, we'll start doing more work to compute the index prefix. We chose 100 because that's the default
+ // max collection size that we will support.
+ var index = 2;
+ for (; index < 100 && foundCurrentElement; index++)
+ {
+ // Add the current element
+ buffer = TCollectionPolicy.Add(ref buffer, currentElement!);
+
+ // Get the precomputed prefix and try and bind the element.
+ var prefix = Indexes[index];
+ context.PushPrefix(prefix);
+ currentElementSuccess = _elementConverter.TryRead(ref context, typeof(TElement), options, out currentElement!, out foundCurrentElement);
+ succeded = succeded && currentElementSuccess;
+ context.PopPrefix(prefix);
+ }
+
+ if (!foundCurrentElement)
+ {
+ result = TCollectionPolicy.ToResult(buffer);
+ return succeded;
+ }
+
+ // We have a "large" collection. Loop until we stop finding elements or reach the max collection size.
+ var maxCollectionSize = options.MaxCollectionSize;
+
+ // We need to compute the prefix for the index, since it's not precomputed.
+ // The biggest UInt32 representation is 4294967295, which is 10 characters, so 16 chars is more than enough to
+ // hold the representation.
+ Span computedPrefix = stackalloc char[16];
+ computedPrefix[0] = '[';
+
+ // index is 100 here.
+ for (; index < maxCollectionSize && foundCurrentElement; index++)
+ {
+ // Add the current element
+ buffer = TCollectionPolicy.Add(ref buffer, currentElement!);
+
+ // We need to compute the prefix for the index, since it's not precomputed.
+ if (!index.TryFormat(computedPrefix[1..], out var charsWritten, provider: CultureInfo.InvariantCulture))
+ {
+ succeded = false;
+ break;
+ }
+
+ computedPrefix[charsWritten + 1] = ']';
+ context.PushPrefix(computedPrefix[..(charsWritten + 2)]);
+ currentElementSuccess = _elementConverter.TryRead(ref context, typeof(TElement), options, out currentElement!, out foundCurrentElement);
+ succeded = succeded && currentElementSuccess;
+ context.PopPrefix(computedPrefix[..(charsWritten + 2)]);
+ }
+
+ if (!foundCurrentElement)
+ {
+ result = TCollectionPolicy.ToResult(buffer);
+ return succeded;
+ }
+ else
+ {
+ // We reached the max collection size.
+ buffer = TCollectionPolicy.Add(ref buffer, currentElement!);
+ result = TCollectionPolicy.ToResult(buffer);
+
+ // Signal failure because we have stopped binding.
+ // We will include an error in the list of reported errors.
+ return false;
+ }
+ }
+}
diff --git a/src/Components/Endpoints/src/Binding/Converters/CompiledComplexTypeConverter.cs b/src/Components/Endpoints/src/Binding/Converters/CompiledComplexTypeConverter.cs
new file mode 100644
index 000000000000..e78c99d18f09
--- /dev/null
+++ b/src/Components/Endpoints/src/Binding/Converters/CompiledComplexTypeConverter.cs
@@ -0,0 +1,12 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.AspNetCore.Components.Endpoints.Binding;
+
+internal class CompiledComplexTypeConverter(CompiledComplexTypeConverter.ConverterDelegate body) : FormDataConverter
+{
+ public delegate bool ConverterDelegate(ref FormDataReader reader, Type type, FormDataMapperOptions options, out T? result, out bool found);
+
+ internal override bool TryRead(ref FormDataReader context, Type type, FormDataMapperOptions options, out T? result, out bool found) =>
+ body(ref context, type, options, out result, out found);
+}
diff --git a/src/Components/Endpoints/src/Binding/Converters/DictionaryAdapters/DictionaryBufferAdapter.cs b/src/Components/Endpoints/src/Binding/Converters/DictionaryAdapters/DictionaryBufferAdapter.cs
new file mode 100644
index 000000000000..0e7939018f10
--- /dev/null
+++ b/src/Components/Endpoints/src/Binding/Converters/DictionaryAdapters/DictionaryBufferAdapter.cs
@@ -0,0 +1,21 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.AspNetCore.Components.Endpoints.Binding;
+
+// Uses a concrete type that implements IDictionary as a buffer.
+internal sealed class DictionaryBufferAdapter
+ : IDictionaryBufferAdapter
+ where TDictionaryType : IDictionary, new()
+ where TKey : IParsable
+{
+ public static TDictionaryType Add(ref TDictionaryType buffer, TKey key, TValue value)
+ {
+ buffer.Add(key, value);
+ return buffer;
+ }
+
+ public static TDictionaryType CreateBuffer() => new TDictionaryType();
+
+ public static TDictionaryType ToResult(TDictionaryType buffer) => buffer;
+}
diff --git a/src/Components/Endpoints/src/Binding/Converters/DictionaryAdapters/DictionaryStaticCastAdapter.cs b/src/Components/Endpoints/src/Binding/Converters/DictionaryAdapters/DictionaryStaticCastAdapter.cs
new file mode 100644
index 000000000000..6e722d2be90d
--- /dev/null
+++ b/src/Components/Endpoints/src/Binding/Converters/DictionaryAdapters/DictionaryStaticCastAdapter.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.AspNetCore.Components.Endpoints.Binding;
+
+// Adapts a concrete dictionary type into an interface.
+internal sealed class DictionaryStaticCastAdapter
+ : IDictionaryBufferAdapter
+ where TDictionaryAdapter : IDictionaryBufferAdapter
+ where TDictionaryImplementation : TDictionaryInterface
+ where TKey : IParsable
+{
+ public static TBuffer CreateBuffer() => TDictionaryAdapter.CreateBuffer();
+
+ public static TBuffer Add(ref TBuffer buffer, TKey key, TValue element) => TDictionaryAdapter.Add(ref buffer, key, element);
+
+ public static TDictionaryInterface ToResult(TBuffer buffer) => TDictionaryAdapter.ToResult(buffer);
+}
diff --git a/src/Components/Endpoints/src/Binding/Converters/DictionaryAdapters/IDictionaryBufferAdapter.cs b/src/Components/Endpoints/src/Binding/Converters/DictionaryAdapters/IDictionaryBufferAdapter.cs
new file mode 100644
index 000000000000..e2b4bc6c8b89
--- /dev/null
+++ b/src/Components/Endpoints/src/Binding/Converters/DictionaryAdapters/IDictionaryBufferAdapter.cs
@@ -0,0 +1,19 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.AspNetCore.Components.Endpoints.Binding;
+
+// Interface for constructing a dictionary like instance using the
+// dictionary converter.
+// This interface abstracts over the different ways of constructing a dictionary.
+// For example, Immutable types use a builder as a buffer, while other types
+// use an instance of the dictionary itself as a buffer.
+internal interface IDictionaryBufferAdapter
+ where TKey : IParsable
+{
+ public static abstract TBuffer CreateBuffer();
+
+ public static abstract TBuffer Add(ref TBuffer buffer, TKey key, TValue value);
+
+ public static abstract TDictionary ToResult(TBuffer buffer);
+}
diff --git a/src/Components/Endpoints/src/Binding/Converters/DictionaryAdapters/ImmutableDictionaryBufferAdapter.cs b/src/Components/Endpoints/src/Binding/Converters/DictionaryAdapters/ImmutableDictionaryBufferAdapter.cs
new file mode 100644
index 000000000000..4e42ad552e0b
--- /dev/null
+++ b/src/Components/Endpoints/src/Binding/Converters/DictionaryAdapters/ImmutableDictionaryBufferAdapter.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.
+
+using System.Collections.Immutable;
+
+namespace Microsoft.AspNetCore.Components.Endpoints.Binding;
+
+internal sealed class ImmutableDictionaryBufferAdapter
+ : IDictionaryBufferAdapter, ImmutableDictionary.Builder, TKey, TValue>
+ where TKey : IParsable
+{
+ public static ImmutableDictionary.Builder Add(ref ImmutableDictionary.Builder buffer, TKey key, TValue value)
+ {
+ buffer.Add(key, value);
+ return buffer;
+ }
+
+ public static ImmutableDictionary.Builder CreateBuffer() => ImmutableDictionary.CreateBuilder();
+
+ public static ImmutableDictionary ToResult(ImmutableDictionary.Builder buffer) => buffer.ToImmutable();
+
+ internal static DictionaryConverter> CreateInterfaceConverter(FormDataConverter valueTypeConverter)
+ {
+ return new DictionaryConverter,
+ DictionaryStaticCastAdapter<
+ IImmutableDictionary,
+ ImmutableDictionary,
+ ImmutableDictionaryBufferAdapter,
+ ImmutableDictionary.Builder,
+ TKey,
+ TValue>,
+ ImmutableDictionary.Builder,
+ TKey,
+ TValue>(valueTypeConverter);
+ }
+}
diff --git a/src/Components/Endpoints/src/Binding/Converters/DictionaryAdapters/ImmutableSortedDictionaryBufferAdapter.cs b/src/Components/Endpoints/src/Binding/Converters/DictionaryAdapters/ImmutableSortedDictionaryBufferAdapter.cs
new file mode 100644
index 000000000000..1ebde40f6b9f
--- /dev/null
+++ b/src/Components/Endpoints/src/Binding/Converters/DictionaryAdapters/ImmutableSortedDictionaryBufferAdapter.cs
@@ -0,0 +1,21 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Immutable;
+
+namespace Microsoft.AspNetCore.Components.Endpoints.Binding;
+
+internal sealed class ImmutableSortedDictionaryBufferAdapter
+ : IDictionaryBufferAdapter, ImmutableSortedDictionary.Builder, TKey, TValue>
+ where TKey : IParsable
+{
+ public static ImmutableSortedDictionary.Builder Add(ref ImmutableSortedDictionary.Builder buffer, TKey key, TValue value)
+ {
+ buffer.Add(key, value);
+ return buffer;
+ }
+
+ public static ImmutableSortedDictionary.Builder CreateBuffer() => ImmutableSortedDictionary.CreateBuilder();
+
+ public static ImmutableSortedDictionary ToResult(ImmutableSortedDictionary.Builder buffer) => buffer.ToImmutable();
+}
diff --git a/src/Components/Endpoints/src/Binding/Converters/DictionaryAdapters/ReadOnlyDictionaryBufferAdapter.cs b/src/Components/Endpoints/src/Binding/Converters/DictionaryAdapters/ReadOnlyDictionaryBufferAdapter.cs
new file mode 100644
index 000000000000..6c5215a7caa9
--- /dev/null
+++ b/src/Components/Endpoints/src/Binding/Converters/DictionaryAdapters/ReadOnlyDictionaryBufferAdapter.cs
@@ -0,0 +1,47 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.ObjectModel;
+
+namespace Microsoft.AspNetCore.Components.Endpoints.Binding;
+
+internal sealed class ReadOnlyDictionaryBufferAdapter
+ : IDictionaryBufferAdapter, Dictionary, TKey, TValue>
+ where TKey : IParsable
+{
+ public static Dictionary Add(ref Dictionary buffer, TKey key, TValue value)
+ {
+ buffer.Add(key, value);
+ return buffer;
+ }
+
+ public static Dictionary CreateBuffer() =>
+ new Dictionary();
+
+ public static ReadOnlyDictionary ToResult(Dictionary buffer) =>
+ new ReadOnlyDictionary(buffer);
+
+ internal static DictionaryConverter> CreateInterfaceConverter(FormDataConverter valueTypeConverter)
+ {
+ return new DictionaryConverter