From da572dc43fe30070b8f943f771f9388052ca0b19 Mon Sep 17 00:00:00 2001
From: "dotnet-maestro[bot]"
<42748379+dotnet-maestro[bot]@users.noreply.github.com>
Date: Fri, 16 Jun 2023 13:17:04 +0000
Subject: [PATCH 01/38] Update dependencies from
https://github.com/dotnet/runtime build 20230616.1 (#48849)
[main] Update dependencies from dotnet/runtime
---
eng/Version.Details.xml | 276 ++++++++++++++++++++--------------------
eng/Versions.props | 138 ++++++++++----------
2 files changed, 207 insertions(+), 207 deletions(-)
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index 76b13dbca737..418d3cfea5e6 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -41,288 +41,288 @@
https://github.com/dotnet/efcore
ef30eb29656f1924c289c29278192de79ea98b42
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
https://github.com/dotnet/source-build-externals
7f9ae67f86a5adc1d9bf2f22f4bf3ec05b6d7b68
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
https://github.com/dotnet/xdt
@@ -355,9 +355,9 @@
-
+
https://github.com/dotnet/runtime
- 171a525880315369e48c6adf6c181f98357352a5
+ be6a1389c852fdc96a919769867fb3f3ddb0fbde
https://github.com/dotnet/arcade
diff --git a/eng/Versions.props b/eng/Versions.props
index d103a72997da..1386b694e8cf 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -63,77 +63,77 @@
-->
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
- 8.0.0-preview.6.23314.15
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
+ 8.0.0-preview.6.23316.1
8.0.0-preview.6.23315.1
8.0.0-preview.6.23315.1
From c53f18a72d047e58ce5f667773b28df4ddc202ad Mon Sep 17 00:00:00 2001
From: Safia Abdalla
Date: Fri, 16 Jun 2023 09:01:34 -0700
Subject: [PATCH 02/38] Add complex form binding support to RDF (#48790)
* Add complex form binding support to RDF
* Address feedback from peer review
---
.../Factories/CollectionConverterFactory.cs | 5 +
.../ConcreteTypeCollectionConverterFactory.cs | 6 +
.../TypedCollectionConverterFactory.cs | 9 +
.../ComplexTypeExpressionConverterFactory.cs | 4 +
...omplexTypeExpressionConverterFactoryOfT.cs | 5 +
.../Factories/ComplexTypeConverterFactory.cs | 6 +-
.../ConcreteTypeDictionaryConverterFactory.cs | 6 +
.../TypedDictionaryConverterFactory.cs | 5 +
.../Factories/DictionaryConverterFactory.cs | 5 +
.../Factories/NullableConverterFactory.cs | 5 +
.../Factories/ParsableConverterFactory.cs | 5 +
.../src/Binding/FormBindingHelpers.cs | 10 +
.../src/Binding/FormDataMapperOptions.cs | 5 +-
.../src/Binding/IFormDataConverterFactory.cs | 6 +
.../src/Microsoft.AspNetCore.JsonPatch.csproj | 1 +
...icrosoft.AspNetCore.Http.Extensions.csproj | 5 +
.../src/RequestDelegateFactory.cs | 58 ++++-
...RuntimeCreationTests.ComplexFormBinding.cs | 207 ++++++++++++++++++
.../RuntimeCreationTests.cs | 2 +-
.../ClosedGenericMatcher.cs | 5 +-
20 files changed, 353 insertions(+), 7 deletions(-)
create mode 100644 src/Components/Endpoints/src/Binding/FormBindingHelpers.cs
create mode 100644 src/Http/Http.Extensions/test/RequestDelegateGenerator/RuntimeCreationTests.ComplexFormBinding.cs
diff --git a/src/Components/Endpoints/src/Binding/Factories/CollectionConverterFactory.cs b/src/Components/Endpoints/src/Binding/Factories/CollectionConverterFactory.cs
index 0a9c17840d0b..9ca5edcd68b4 100644
--- a/src/Components/Endpoints/src/Binding/Factories/CollectionConverterFactory.cs
+++ b/src/Components/Endpoints/src/Binding/Factories/CollectionConverterFactory.cs
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Components.Endpoints.Binding;
@@ -9,6 +10,8 @@ internal class CollectionConverterFactory : IFormDataConverterFactory
{
public static readonly CollectionConverterFactory Instance = new();
+ [RequiresDynamicCode(FormBindingHelpers.RequiresDynamicCodeMessage)]
+ [RequiresUnreferencedCode(FormBindingHelpers.RequiresUnreferencedCodeMessage)]
public bool CanConvert(Type type, FormDataMapperOptions options)
{
var enumerable = ClosedGenericMatcher.ExtractGenericInterface(type, typeof(IEnumerable<>));
@@ -28,6 +31,8 @@ public bool CanConvert(Type type, FormDataMapperOptions options)
return factory.CanConvert(type, options);
}
+ [RequiresDynamicCode(FormBindingHelpers.RequiresDynamicCodeMessage)]
+ [RequiresUnreferencedCode(FormBindingHelpers.RequiresUnreferencedCodeMessage)]
public FormDataConverter CreateConverter(Type type, FormDataMapperOptions options)
{
ArgumentNullException.ThrowIfNull(type);
diff --git a/src/Components/Endpoints/src/Binding/Factories/Collections/ConcreteTypeCollectionConverterFactory.cs b/src/Components/Endpoints/src/Binding/Factories/Collections/ConcreteTypeCollectionConverterFactory.cs
index 667a76ffe3c2..1fa3bce2837b 100644
--- a/src/Components/Endpoints/src/Binding/Factories/Collections/ConcreteTypeCollectionConverterFactory.cs
+++ b/src/Components/Endpoints/src/Binding/Factories/Collections/ConcreteTypeCollectionConverterFactory.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;
+
namespace Microsoft.AspNetCore.Components.Endpoints.Binding;
internal class ConcreteTypeCollectionConverterFactory
@@ -9,8 +11,12 @@ internal class ConcreteTypeCollectionConverterFactory
public static readonly ConcreteTypeCollectionConverterFactory Instance =
new();
+ [UnconditionalSuppressMessage("Trimming", "IL2046", Justification = "This derived implementation doesn't require unreferenced code like other implementations of the interface.")]
+ [UnconditionalSuppressMessage("AOT", "IL3051", Justification = "This derived implementation doesn't use dynamic code like other implementations of the interface.")]
public bool CanConvert(Type _, FormDataMapperOptions options) => true;
+ [RequiresDynamicCode(FormBindingHelpers.RequiresDynamicCodeMessage)]
+ [RequiresUnreferencedCode(FormBindingHelpers.RequiresUnreferencedCodeMessage)]
public FormDataConverter CreateConverter(Type _, FormDataMapperOptions options)
{
// Resolve the element type converter
diff --git a/src/Components/Endpoints/src/Binding/Factories/Collections/TypedCollectionConverterFactory.cs b/src/Components/Endpoints/src/Binding/Factories/Collections/TypedCollectionConverterFactory.cs
index e3b922555748..29a491765909 100644
--- a/src/Components/Endpoints/src/Binding/Factories/Collections/TypedCollectionConverterFactory.cs
+++ b/src/Components/Endpoints/src/Binding/Factories/Collections/TypedCollectionConverterFactory.cs
@@ -4,18 +4,25 @@
using System.Collections.Concurrent;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
+using System.Diagnostics.CodeAnalysis;
namespace Microsoft.AspNetCore.Components.Endpoints.Binding;
internal abstract class TypedCollectionConverterFactory : IFormDataConverterFactory
{
+ [RequiresDynamicCode(FormBindingHelpers.RequiresDynamicCodeMessage)]
+ [RequiresUnreferencedCode(FormBindingHelpers.RequiresUnreferencedCodeMessage)]
public abstract bool CanConvert(Type type, FormDataMapperOptions options);
+ [RequiresDynamicCode(FormBindingHelpers.RequiresDynamicCodeMessage)]
+ [RequiresUnreferencedCode(FormBindingHelpers.RequiresUnreferencedCodeMessage)]
public abstract FormDataConverter CreateConverter(Type type, FormDataMapperOptions options);
}
internal sealed class TypedCollectionConverterFactory : TypedCollectionConverterFactory
{
+ [RequiresDynamicCode(FormBindingHelpers.RequiresDynamicCodeMessage)]
+ [RequiresUnreferencedCode(FormBindingHelpers.RequiresUnreferencedCodeMessage)]
public override bool CanConvert(Type _, FormDataMapperOptions options)
{
// Resolve the element type converter
@@ -101,6 +108,8 @@ var _ when type.IsAssignableTo(typeof(ICollection)) && type.GetConstru
// the Queue directly as the buffer (queues don't implement ICollection, so the adapter uses Push instead),
// or for ImmutableXXX we either use ImmuttableXXX.CreateBuilder to create a builder we use as a buffer,
// or collect the collection into an array buffer and call CreateRange to build the final collection.
+ [RequiresDynamicCode(FormBindingHelpers.RequiresDynamicCodeMessage)]
+ [RequiresUnreferencedCode(FormBindingHelpers.RequiresUnreferencedCodeMessage)]
public override FormDataConverter CreateConverter(Type _, FormDataMapperOptions options)
{
// Resolve the element type converter
diff --git a/src/Components/Endpoints/src/Binding/Factories/ComplexType/ComplexTypeExpressionConverterFactory.cs b/src/Components/Endpoints/src/Binding/Factories/ComplexType/ComplexTypeExpressionConverterFactory.cs
index 68d22f323d09..12503309bb8f 100644
--- a/src/Components/Endpoints/src/Binding/Factories/ComplexType/ComplexTypeExpressionConverterFactory.cs
+++ b/src/Components/Endpoints/src/Binding/Factories/ComplexType/ComplexTypeExpressionConverterFactory.cs
@@ -1,9 +1,13 @@
// 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.Endpoints.Binding;
internal abstract class ComplexTypeExpressionConverterFactory
{
+ [RequiresDynamicCode(FormBindingHelpers.RequiresDynamicCodeMessage)]
+ [RequiresUnreferencedCode(FormBindingHelpers.RequiresUnreferencedCodeMessage)]
internal abstract FormDataConverter CreateConverter(Type type, FormDataMapperOptions options);
}
diff --git a/src/Components/Endpoints/src/Binding/Factories/ComplexType/ComplexTypeExpressionConverterFactoryOfT.cs b/src/Components/Endpoints/src/Binding/Factories/ComplexType/ComplexTypeExpressionConverterFactoryOfT.cs
index 4b87ce6accff..91348bfbe642 100644
--- a/src/Components/Endpoints/src/Binding/Factories/ComplexType/ComplexTypeExpressionConverterFactoryOfT.cs
+++ b/src/Components/Endpoints/src/Binding/Factories/ComplexType/ComplexTypeExpressionConverterFactoryOfT.cs
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using Microsoft.Extensions.Internal;
@@ -8,12 +9,16 @@ namespace Microsoft.AspNetCore.Components.Endpoints.Binding;
internal sealed class ComplexTypeExpressionConverterFactory : ComplexTypeExpressionConverterFactory
{
+ [RequiresDynamicCode(FormBindingHelpers.RequiresDynamicCodeMessage)]
+ [RequiresUnreferencedCode(FormBindingHelpers.RequiresUnreferencedCodeMessage)]
internal override CompiledComplexTypeConverter CreateConverter(Type type, FormDataMapperOptions options)
{
var body = CreateConverterBody(type, options);
return new CompiledComplexTypeConverter(body);
}
+ [RequiresDynamicCode(FormBindingHelpers.RequiresDynamicCodeMessage)]
+ [RequiresUnreferencedCode(FormBindingHelpers.RequiresUnreferencedCodeMessage)]
private CompiledComplexTypeConverter.ConverterDelegate CreateConverterBody(Type type, FormDataMapperOptions options)
{
var properties = PropertyHelper.GetVisibleProperties(type);
diff --git a/src/Components/Endpoints/src/Binding/Factories/ComplexTypeConverterFactory.cs b/src/Components/Endpoints/src/Binding/Factories/ComplexTypeConverterFactory.cs
index f633aca53e1e..c67a521eea7c 100644
--- a/src/Components/Endpoints/src/Binding/Factories/ComplexTypeConverterFactory.cs
+++ b/src/Components/Endpoints/src/Binding/Factories/ComplexTypeConverterFactory.cs
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Components.Endpoints.Binding;
@@ -11,6 +12,8 @@ internal class ComplexTypeConverterFactory : IFormDataConverterFactory
{
internal static readonly ComplexTypeConverterFactory Instance = new();
+ [RequiresDynamicCode(FormBindingHelpers.RequiresDynamicCodeMessage)]
+ [RequiresUnreferencedCode(FormBindingHelpers.RequiresUnreferencedCodeMessage)]
public bool CanConvert(Type type, FormDataMapperOptions options)
{
if (type.GetConstructor(Type.EmptyTypes) == null && !type.IsValueType)
@@ -105,7 +108,8 @@ public bool CanConvert(Type type, FormDataMapperOptions options)
// return converterFunc(ref reader, type, options, out result, out found);
// }
// }
-
+ [RequiresDynamicCode(FormBindingHelpers.RequiresDynamicCodeMessage)]
+ [RequiresUnreferencedCode(FormBindingHelpers.RequiresUnreferencedCodeMessage)]
public FormDataConverter CreateConverter(Type type, FormDataMapperOptions options)
{
if (Activator.CreateInstance(typeof(ComplexTypeExpressionConverterFactory<>).MakeGenericType(type))
diff --git a/src/Components/Endpoints/src/Binding/Factories/Dictionary/ConcreteTypeDictionaryConverterFactory.cs b/src/Components/Endpoints/src/Binding/Factories/Dictionary/ConcreteTypeDictionaryConverterFactory.cs
index a3f77d3600c6..fc83a37ae600 100644
--- a/src/Components/Endpoints/src/Binding/Factories/Dictionary/ConcreteTypeDictionaryConverterFactory.cs
+++ b/src/Components/Endpoints/src/Binding/Factories/Dictionary/ConcreteTypeDictionaryConverterFactory.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;
+
namespace Microsoft.AspNetCore.Components.Endpoints.Binding;
internal sealed class ConcreteTypeDictionaryConverterFactory : IFormDataConverterFactory
@@ -8,8 +10,12 @@ internal sealed class ConcreteTypeDictionaryConverterFactory Instance = new();
+ [UnconditionalSuppressMessage("Trimming", "IL2046", Justification = "This derived implementation doesn't require unreferenced code like other implementations of the interface.")]
+ [UnconditionalSuppressMessage("AOT", "IL3051", Justification = "This derived implementation doesn't use dynamic code like other implementations of the interface.")]
public bool CanConvert(Type type, FormDataMapperOptions options) => true;
+ [RequiresDynamicCode(FormBindingHelpers.RequiresDynamicCodeMessage)]
+ [RequiresUnreferencedCode(FormBindingHelpers.RequiresUnreferencedCodeMessage)]
public FormDataConverter CreateConverter(Type type, FormDataMapperOptions options)
{
// Resolve the element type converter
diff --git a/src/Components/Endpoints/src/Binding/Factories/Dictionary/TypedDictionaryConverterFactory.cs b/src/Components/Endpoints/src/Binding/Factories/Dictionary/TypedDictionaryConverterFactory.cs
index a2822b4b9364..def5d833725d 100644
--- a/src/Components/Endpoints/src/Binding/Factories/Dictionary/TypedDictionaryConverterFactory.cs
+++ b/src/Components/Endpoints/src/Binding/Factories/Dictionary/TypedDictionaryConverterFactory.cs
@@ -4,12 +4,15 @@
using System.Collections.Concurrent;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
+using System.Diagnostics.CodeAnalysis;
namespace Microsoft.AspNetCore.Components.Endpoints.Binding;
internal sealed class TypedDictionaryConverterFactory : IFormDataConverterFactory
where TKey : IParsable
{
+ [RequiresDynamicCode(FormBindingHelpers.RequiresDynamicCodeMessage)]
+ [RequiresUnreferencedCode(FormBindingHelpers.RequiresUnreferencedCodeMessage)]
public bool CanConvert(Type type, FormDataMapperOptions options)
{
// Resolve the value type converter
@@ -70,6 +73,8 @@ var _ when type.IsAssignableTo(typeof(IDictionary)) && type.GetCon
return false;
}
+ [RequiresDynamicCode(FormBindingHelpers.RequiresDynamicCodeMessage)]
+ [RequiresUnreferencedCode(FormBindingHelpers.RequiresUnreferencedCodeMessage)]
public FormDataConverter CreateConverter(Type type, FormDataMapperOptions options)
{
// Resolve the value type converter
diff --git a/src/Components/Endpoints/src/Binding/Factories/DictionaryConverterFactory.cs b/src/Components/Endpoints/src/Binding/Factories/DictionaryConverterFactory.cs
index ee92552e357d..cc237af2bae6 100644
--- a/src/Components/Endpoints/src/Binding/Factories/DictionaryConverterFactory.cs
+++ b/src/Components/Endpoints/src/Binding/Factories/DictionaryConverterFactory.cs
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Components.Endpoints.Binding;
@@ -9,6 +10,8 @@ internal class DictionaryConverterFactory : IFormDataConverterFactory
{
internal static readonly DictionaryConverterFactory Instance = new();
+ [RequiresDynamicCode(FormBindingHelpers.RequiresDynamicCodeMessage)]
+ [RequiresUnreferencedCode(FormBindingHelpers.RequiresUnreferencedCodeMessage)]
public bool CanConvert(Type type, FormDataMapperOptions options)
{
// Type must implement IDictionary IReadOnlyDictionary
@@ -58,6 +61,8 @@ public bool CanConvert(Type type, FormDataMapperOptions options)
return factory.CanConvert(type, options);
}
+ [RequiresDynamicCode(FormBindingHelpers.RequiresDynamicCodeMessage)]
+ [RequiresUnreferencedCode(FormBindingHelpers.RequiresUnreferencedCodeMessage)]
public FormDataConverter CreateConverter(Type type, FormDataMapperOptions options)
{
// Type must implement IDictionary IReadOnlyDictionary
diff --git a/src/Components/Endpoints/src/Binding/Factories/NullableConverterFactory.cs b/src/Components/Endpoints/src/Binding/Factories/NullableConverterFactory.cs
index ea635aab6cb4..c99bedfe6763 100644
--- a/src/Components/Endpoints/src/Binding/Factories/NullableConverterFactory.cs
+++ b/src/Components/Endpoints/src/Binding/Factories/NullableConverterFactory.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
namespace Microsoft.AspNetCore.Components.Endpoints.Binding;
@@ -9,12 +10,16 @@ internal sealed class NullableConverterFactory : IFormDataConverterFactory
{
public static readonly NullableConverterFactory Instance = new();
+ [RequiresDynamicCode(FormBindingHelpers.RequiresDynamicCodeMessage)]
+ [RequiresUnreferencedCode(FormBindingHelpers.RequiresUnreferencedCodeMessage)]
public bool CanConvert(Type type, FormDataMapperOptions options)
{
var underlyingType = Nullable.GetUnderlyingType(type);
return underlyingType != null && options.ResolveConverter(underlyingType) != null;
}
+ [RequiresDynamicCode(FormBindingHelpers.RequiresDynamicCodeMessage)]
+ [RequiresUnreferencedCode(FormBindingHelpers.RequiresUnreferencedCodeMessage)]
public FormDataConverter CreateConverter(Type type, FormDataMapperOptions options)
{
var underlyingType = Nullable.GetUnderlyingType(type);
diff --git a/src/Components/Endpoints/src/Binding/Factories/ParsableConverterFactory.cs b/src/Components/Endpoints/src/Binding/Factories/ParsableConverterFactory.cs
index 73cc5fccf1b7..d6d151f71177 100644
--- a/src/Components/Endpoints/src/Binding/Factories/ParsableConverterFactory.cs
+++ b/src/Components/Endpoints/src/Binding/Factories/ParsableConverterFactory.cs
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Components.Endpoints.Binding;
@@ -9,11 +10,15 @@ internal sealed class ParsableConverterFactory : IFormDataConverterFactory
{
public static readonly ParsableConverterFactory Instance = new();
+ [RequiresDynamicCode(FormBindingHelpers.RequiresDynamicCodeMessage)]
+ [RequiresUnreferencedCode(FormBindingHelpers.RequiresUnreferencedCodeMessage)]
public bool CanConvert(Type type, FormDataMapperOptions options)
{
return ClosedGenericMatcher.ExtractGenericInterface(type, typeof(IParsable<>)) is not null;
}
+ [RequiresDynamicCode(FormBindingHelpers.RequiresDynamicCodeMessage)]
+ [RequiresUnreferencedCode(FormBindingHelpers.RequiresUnreferencedCodeMessage)]
public FormDataConverter CreateConverter(Type type, FormDataMapperOptions options)
{
return Activator.CreateInstance(typeof(ParsableConverter<>).MakeGenericType(type)) as FormDataConverter ??
diff --git a/src/Components/Endpoints/src/Binding/FormBindingHelpers.cs b/src/Components/Endpoints/src/Binding/FormBindingHelpers.cs
new file mode 100644
index 000000000000..de9a6b61240d
--- /dev/null
+++ b/src/Components/Endpoints/src/Binding/FormBindingHelpers.cs
@@ -0,0 +1,10 @@
+// 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 static class FormBindingHelpers
+{
+ public const string RequiresUnreferencedCodeMessage = "Form binding is not compatible with trimming, as it requires dynamic access to code that is not referenced statically.";
+ public const string RequiresDynamicCodeMessage = "Form binding may require dynamic code generation.";
+}
diff --git a/src/Components/Endpoints/src/Binding/FormDataMapperOptions.cs b/src/Components/Endpoints/src/Binding/FormDataMapperOptions.cs
index f678a579ff0e..e28a5cf51574 100644
--- a/src/Components/Endpoints/src/Binding/FormDataMapperOptions.cs
+++ b/src/Components/Endpoints/src/Binding/FormDataMapperOptions.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Concurrent;
+using System.Diagnostics.CodeAnalysis;
namespace Microsoft.AspNetCore.Components.Endpoints.Binding;
@@ -10,6 +11,8 @@ internal sealed class FormDataMapperOptions
private readonly ConcurrentDictionary _converters = new();
private readonly List> _factories = new();
+ [RequiresDynamicCode(FormBindingHelpers.RequiresDynamicCodeMessage)]
+ [RequiresUnreferencedCode(FormBindingHelpers.RequiresUnreferencedCodeMessage)]
public FormDataMapperOptions()
{
_converters = new(WellKnownConverters.Converters);
@@ -27,7 +30,7 @@ public FormDataMapperOptions()
// Binding to collection using hashes, where the payload can be crafted to force the worst case on insertion
// which is O(n).
internal int MaxCollectionSize = 100;
-
+
internal bool HasConverter(Type valueType) => _converters.ContainsKey(valueType);
internal bool IsSingleValueConverter(Type type)
diff --git a/src/Components/Endpoints/src/Binding/IFormDataConverterFactory.cs b/src/Components/Endpoints/src/Binding/IFormDataConverterFactory.cs
index 04a77cf83c49..f128b8c2c1c6 100644
--- a/src/Components/Endpoints/src/Binding/IFormDataConverterFactory.cs
+++ b/src/Components/Endpoints/src/Binding/IFormDataConverterFactory.cs
@@ -1,11 +1,17 @@
// 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.Endpoints.Binding;
internal interface IFormDataConverterFactory
{
+ [RequiresDynamicCode(FormBindingHelpers.RequiresDynamicCodeMessage)]
+ [RequiresUnreferencedCode(FormBindingHelpers.RequiresUnreferencedCodeMessage)]
public bool CanConvert(Type type, FormDataMapperOptions options);
+ [RequiresDynamicCode(FormBindingHelpers.RequiresDynamicCodeMessage)]
+ [RequiresUnreferencedCode(FormBindingHelpers.RequiresUnreferencedCodeMessage)]
public FormDataConverter CreateConverter(Type type, FormDataMapperOptions options);
}
diff --git a/src/Features/JsonPatch/src/Microsoft.AspNetCore.JsonPatch.csproj b/src/Features/JsonPatch/src/Microsoft.AspNetCore.JsonPatch.csproj
index 6c78abd09dbd..57cc0737ff21 100644
--- a/src/Features/JsonPatch/src/Microsoft.AspNetCore.JsonPatch.csproj
+++ b/src/Features/JsonPatch/src/Microsoft.AspNetCore.JsonPatch.csproj
@@ -18,6 +18,7 @@
+
diff --git a/src/Http/Http.Extensions/src/Microsoft.AspNetCore.Http.Extensions.csproj b/src/Http/Http.Extensions/src/Microsoft.AspNetCore.Http.Extensions.csproj
index c6472b75143d..b867d52b45ee 100644
--- a/src/Http/Http.Extensions/src/Microsoft.AspNetCore.Http.Extensions.csproj
+++ b/src/Http/Http.Extensions/src/Microsoft.AspNetCore.Http.Extensions.csproj
@@ -26,6 +26,11 @@
+
+
+
+
+
diff --git a/src/Http/Http.Extensions/src/RequestDelegateFactory.cs b/src/Http/Http.Extensions/src/RequestDelegateFactory.cs
index c6a0e2d5f432..36289c003ca4 100644
--- a/src/Http/Http.Extensions/src/RequestDelegateFactory.cs
+++ b/src/Http/Http.Extensions/src/RequestDelegateFactory.cs
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
@@ -14,6 +15,7 @@
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Components.Endpoints.Binding;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Http.Json;
@@ -36,6 +38,7 @@ namespace Microsoft.AspNetCore.Http;
public static partial class RequestDelegateFactory
{
private static readonly ParameterBindingMethodCache ParameterBindingMethodCache = new();
+ private static readonly FormDataMapperOptions FormDataMapperOptions = new();
private static readonly MethodInfo ExecuteTaskWithEmptyResultMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteTaskWithEmptyResult), BindingFlags.NonPublic | BindingFlags.Static)!;
private static readonly MethodInfo ExecuteValueTaskWithEmptyResultMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteValueTaskWithEmptyResult), BindingFlags.NonPublic | BindingFlags.Static)!;
@@ -113,6 +116,10 @@ public static partial class RequestDelegateFactory
private static readonly MemberExpression FilterContextHttpContextStatusCodeExpr = Expression.Property(FilterContextHttpContextResponseExpr, typeof(HttpResponse).GetProperty(nameof(HttpResponse.StatusCode))!);
private static readonly ParameterExpression InvokedFilterContextExpr = Expression.Parameter(typeof(EndpointFilterInvocationContext), "filterContext");
+ private static readonly ConstructorInfo FormDataReaderConstructor = typeof(FormDataReader).GetConstructor(new[] { typeof(IReadOnlyDictionary), typeof(CultureInfo) })!;
+ private static readonly MethodInfo FormToReadOnlyDictionaryMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ToReadOnlyDictionary), BindingFlags.Static | BindingFlags.NonPublic, new[] { typeof(IFormCollection) })!;
+ private static readonly MethodInfo FormDataMapperMapMethod = typeof(FormDataMapper).GetMethod(nameof(FormDataMapper.Map))!;
+
private static readonly string[] DefaultAcceptsAndProducesContentType = new[] { JsonConstants.JsonContentType };
private static readonly string[] FormFileContentType = new[] { "multipart/form-data" };
private static readonly string[] FormContentType = new[] { "multipart/form-data", "application/x-www-form-urlencoded" };
@@ -730,8 +737,19 @@ private static Expression CreateArgument(ParameterInfo parameter, RequestDelegat
}
return BindParameterFromFormCollection(parameter, factoryContext);
}
-
- return BindParameterFromFormItem(parameter, formAttribute.Name ?? parameter.Name, factoryContext);
+ // Continue to use the simple binding support that exists in RDF/RDG for currently
+ // supported scenarios to maintain compatible semantics between versions of RDG.
+ // For complex types, leverage the shared form binding infrastructure. For example,
+ // shared form binding does not currently only supports types that implement IParsable
+ // while RDF's binding implementation supports all TryParse implementations.
+ var useSimpleBinding = parameter.ParameterType == typeof(string) ||
+ parameter.ParameterType == typeof(StringValues) ||
+ parameter.ParameterType == typeof(StringValues?) ||
+ ParameterBindingMethodCache.HasTryParseMethod(parameter.ParameterType) ||
+ (parameter.ParameterType.IsArray && ParameterBindingMethodCache.HasTryParseMethod(parameter.ParameterType.GetElementType()!));
+ return useSimpleBinding
+ ? BindParameterFromFormItem(parameter, formAttribute.Name ?? parameter.Name, factoryContext)
+ : BindComplexParameterFromFormItem(parameter, formAttribute.Name ?? parameter.Name, factoryContext);
}
else if (parameter.CustomAttributes.Any(a => typeof(IFromServiceMetadata).IsAssignableFrom(a.AttributeType)))
{
@@ -1943,6 +1961,42 @@ private static Expression BindParameterFromFormItem(
"form");
}
+ private static Expression BindComplexParameterFromFormItem(
+ ParameterInfo parameter,
+ string key,
+ RequestDelegateFactoryContext factoryContext)
+ {
+ factoryContext.FirstFormRequestBodyParameter ??= parameter;
+ factoryContext.TrackedParameters.Add(key, RequestDelegateFactoryConstants.FormAttribute);
+ factoryContext.ReadForm = true;
+
+ // var name_local;
+ // var name_reader;
+ var formArgument = Expression.Variable(parameter.ParameterType, $"{parameter.Name}_local");
+ var formReader = Expression.Variable(typeof(FormDataReader), $"{parameter.Name}_reader");
+
+ // name_reader = new FormDataReader(context.Request.Form.ToReadOnlyDictionary()), CultureInfo.InvariantCulture);
+ var initializeReaderExpr = Expression.Assign(
+ formReader,
+ Expression.New(FormDataReaderConstructor,
+ Expression.Call(FormToReadOnlyDictionaryMethod, FormExpr),
+ Expression.Constant(CultureInfo.InvariantCulture)));
+ // FormDataMapper.Map(name_reader, FormDataMapperOptions);
+ var invokeMapMethodExpr = Expression.Call(
+ FormDataMapperMapMethod.MakeGenericMethod(parameter.ParameterType),
+ formReader,
+ Expression.Constant(FormDataMapperOptions));
+
+ return Expression.Block(
+ new[] { formArgument, formReader },
+ initializeReaderExpr,
+ Expression.Assign(formArgument, invokeMapMethodExpr)
+ );
+ }
+
+ private static IReadOnlyDictionary ToReadOnlyDictionary(IFormCollection form)
+ => new ReadOnlyDictionary(form.ToDictionary());
+
private static Expression BindParameterFromFormFiles(
ParameterInfo parameter,
RequestDelegateFactoryContext factoryContext)
diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/RuntimeCreationTests.ComplexFormBinding.cs b/src/Http/Http.Extensions/test/RequestDelegateGenerator/RuntimeCreationTests.ComplexFormBinding.cs
new file mode 100644
index 000000000000..a23ffc9896d4
--- /dev/null
+++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/RuntimeCreationTests.ComplexFormBinding.cs
@@ -0,0 +1,207 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Net.Http;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.AspNetCore.Http.Generators.Tests;
+
+public partial class RuntimeCreationTests : RequestDelegateCreationTests
+{
+ [Fact]
+ public async Task SupportsBindingComplexTypeFromForm_UrlEncoded()
+ {
+ var source = """
+app.MapPost("/", ([FromForm] Todo todo) => Results.Ok(todo));
+""";
+ var (_, compilation) = await RunGeneratorAsync(source);
+ var endpoint = GetEndpointFromCompilation(compilation);
+ var httpContext = CreateHttpContext();
+
+ var content = new FormUrlEncodedContent(new Dictionary { ["Id"] = "1", ["Name"] = "Write tests", ["IsComplete"] = "true" });
+ var stream = new MemoryStream();
+ await content.CopyToAsync(stream);
+
+ stream.Seek(0, SeekOrigin.Begin);
+
+ httpContext.Request.Body = stream;
+ httpContext.Request.Headers["Content-Type"] = "application/x-www-form-urlencoded";
+ httpContext.Features.Set(new RequestBodyDetectionFeature(true));
+
+ await endpoint.RequestDelegate(httpContext);
+
+ await VerifyResponseJsonBodyAsync(httpContext, (todo) =>
+ {
+ Assert.Equal(1, todo.Id);
+ Assert.Equal("Write tests", todo.Name);
+ Assert.True(todo.IsComplete);
+ });
+ }
+
+ [Fact]
+ public async Task SupportsBindingComplexTypeFromForm_Multipart()
+ {
+ var source = """
+app.MapPost("/", ([FromForm] Todo todo) => Results.Ok(todo));
+""";
+ var (_, compilation) = await RunGeneratorAsync(source);
+ var endpoint = GetEndpointFromCompilation(compilation);
+ var httpContext = CreateHttpContext();
+
+ var content = new MultipartFormDataContent("some-boundary");
+ content.Add(new StringContent("1"), "Id");
+ content.Add(new StringContent("Write tests"), "Name");
+ content.Add(new StringContent("true"), "IsComplete");
+
+ var stream = new MemoryStream();
+ await content.CopyToAsync(stream);
+
+ stream.Seek(0, SeekOrigin.Begin);
+
+ httpContext.Request.Body = stream;
+ httpContext.Request.Headers["Content-Type"] = "multipart/form-data;boundary=some-boundary";
+ httpContext.Features.Set(new RequestBodyDetectionFeature(true));
+
+ await endpoint.RequestDelegate(httpContext);
+
+ await VerifyResponseJsonBodyAsync(httpContext, (todo) =>
+ {
+ Assert.Equal(1, todo.Id);
+ Assert.Equal("Write tests", todo.Name);
+ Assert.True(todo.IsComplete);
+ });
+ }
+
+ [Fact]
+ public async Task SupportsBindingDictionaryFromForm_UrlEncoded()
+ {
+ var source = """
+app.MapPost("/", ([FromForm] Dictionary elements) => Results.Ok(elements));
+""";
+ var (_, compilation) = await RunGeneratorAsync(source);
+ var endpoint = GetEndpointFromCompilation(compilation);
+ var httpContext = CreateHttpContext();
+
+ var content = new FormUrlEncodedContent(new Dictionary { ["[foo]"] = "true", ["[bar]"] = "false", ["[baz]"] = "true" });
+ var stream = new MemoryStream();
+ await content.CopyToAsync(stream);
+
+ stream.Seek(0, SeekOrigin.Begin);
+
+ httpContext.Request.Body = stream;
+ httpContext.Request.Headers["Content-Type"] = "application/x-www-form-urlencoded";
+ httpContext.Features.Set(new RequestBodyDetectionFeature(true));
+
+ await endpoint.RequestDelegate(httpContext);
+
+ await VerifyResponseJsonBodyAsync>(httpContext, (elements) =>
+ {
+ Assert.Equal(3, elements.Count);
+ Assert.True(elements["foo"]);
+ Assert.False(elements["bar"]);
+ Assert.True(elements["baz"]);
+ });
+ }
+
+ [Fact]
+ public async Task SupportsBindingDictionaryFromForm_Multipart()
+ {
+ var source = """
+app.MapPost("/", ([FromForm] Dictionary elements) => Results.Ok(elements));
+""";
+ var (_, compilation) = await RunGeneratorAsync(source);
+ var endpoint = GetEndpointFromCompilation(compilation);
+ var httpContext = CreateHttpContext();
+
+ var content = new MultipartFormDataContent("some-boundary");
+ content.Add(new StringContent("true"), "[foo]");
+ content.Add(new StringContent("false"), "[bar]");
+ content.Add(new StringContent("true"), "[baz]");
+
+ var stream = new MemoryStream();
+ await content.CopyToAsync(stream);
+
+ stream.Seek(0, SeekOrigin.Begin);
+
+ httpContext.Request.Body = stream;
+ httpContext.Request.Headers["Content-Type"] = "multipart/form-data;boundary=some-boundary";
+ httpContext.Features.Set(new RequestBodyDetectionFeature(true));
+
+ await endpoint.RequestDelegate(httpContext);
+
+ await VerifyResponseJsonBodyAsync>(httpContext, (elements) =>
+ {
+ Assert.Equal(3, elements.Count);
+ Assert.True(elements["foo"]);
+ Assert.False(elements["bar"]);
+ Assert.True(elements["baz"]);
+ });
+ }
+
+ [Fact]
+ public async Task SupportsBindingListFromForm_UrlEncoded()
+ {
+ var source = """
+app.MapPost("/", ([FromForm] List elements) => Results.Ok(elements));
+""";
+ var (_, compilation) = await RunGeneratorAsync(source);
+ var endpoint = GetEndpointFromCompilation(compilation);
+ var httpContext = CreateHttpContext();
+
+ var content = new FormUrlEncodedContent(new Dictionary { ["[0]"] = "1", ["[1]"] = "3", ["[2]"] = "5" });
+ var stream = new MemoryStream();
+ await content.CopyToAsync(stream);
+
+ stream.Seek(0, SeekOrigin.Begin);
+
+ httpContext.Request.Body = stream;
+ httpContext.Request.Headers["Content-Type"] = "application/x-www-form-urlencoded";
+ httpContext.Features.Set(new RequestBodyDetectionFeature(true));
+
+ await endpoint.RequestDelegate(httpContext);
+
+ await VerifyResponseJsonBodyAsync>(httpContext, (elements) =>
+ {
+ Assert.Equal(3, elements.Count);
+ Assert.Equal(1, elements[0]);
+ Assert.Equal(3, elements[1]);
+ Assert.Equal(5, elements[2]);
+ });
+ }
+
+ [Fact]
+ public async Task SupportsBindingListFromForm_Multipart()
+ {
+ var source = """
+app.MapPost("/", ([FromForm] List elements) => Results.Ok(elements));
+""";
+ var (_, compilation) = await RunGeneratorAsync(source);
+ var endpoint = GetEndpointFromCompilation(compilation);
+ var httpContext = CreateHttpContext();
+
+ var content = new MultipartFormDataContent("some-boundary");
+ content.Add(new StringContent("1"), "[0]");
+ content.Add(new StringContent("3"), "[1]");
+ content.Add(new StringContent("5"), "[2]");
+
+ var stream = new MemoryStream();
+ await content.CopyToAsync(stream);
+
+ stream.Seek(0, SeekOrigin.Begin);
+
+ httpContext.Request.Body = stream;
+ httpContext.Request.Headers["Content-Type"] = "multipart/form-data;boundary=some-boundary";
+ httpContext.Features.Set(new RequestBodyDetectionFeature(true));
+
+ await endpoint.RequestDelegate(httpContext);
+
+ await VerifyResponseJsonBodyAsync>(httpContext, (elements) =>
+ {
+ Assert.Equal(3, elements.Count);
+ Assert.Equal(1, elements[0]);
+ Assert.Equal(3, elements[1]);
+ Assert.Equal(5, elements[2]);
+ });
+ }
+}
diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/RuntimeCreationTests.cs b/src/Http/Http.Extensions/test/RequestDelegateGenerator/RuntimeCreationTests.cs
index fd52208fd3f0..61e8e8145ea1 100644
--- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/RuntimeCreationTests.cs
+++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/RuntimeCreationTests.cs
@@ -8,7 +8,7 @@
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Http.Generators.Tests;
-public class RuntimeCreationTests : RequestDelegateCreationTests
+public partial class RuntimeCreationTests : RequestDelegateCreationTests
{
protected override bool IsGeneratorEnabled { get; } = false;
diff --git a/src/Shared/ClosedGenericMatcher/ClosedGenericMatcher.cs b/src/Shared/ClosedGenericMatcher/ClosedGenericMatcher.cs
index c1989174bc1e..235c9d5a4a8c 100644
--- a/src/Shared/ClosedGenericMatcher/ClosedGenericMatcher.cs
+++ b/src/Shared/ClosedGenericMatcher/ClosedGenericMatcher.cs
@@ -4,6 +4,7 @@
#nullable enable
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using Microsoft.AspNetCore.Shared;
@@ -30,7 +31,7 @@ internal static class ClosedGenericMatcher
/// typeof(KeyValuePair{,}), and is
/// typeof(KeyValuePair{string, object}).
///
- public static Type? ExtractGenericInterface(Type queryType, Type interfaceType)
+ public static Type? ExtractGenericInterface([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type queryType, Type interfaceType)
{
#if !NET8_0_OR_GREATER
ArgumentNullThrowHelper.ThrowIfNull(queryType);
@@ -62,7 +63,7 @@ private static bool IsGenericInstantiation(Type candidate, Type interfaceType)
candidate.GetGenericTypeDefinition() == interfaceType;
}
- private static Type? GetGenericInstantiation(Type queryType, Type interfaceType)
+ private static Type? GetGenericInstantiation([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type queryType, Type interfaceType)
{
Type? bestMatch = null;
var interfaces = queryType.GetInterfaces();
From 883f06cbf5bfa9d82ef797c09fbcb6af7cbb1536 Mon Sep 17 00:00:00 2001
From: Mackinnon Buck
Date: Fri, 16 Jun 2023 09:08:12 -0700
Subject: [PATCH 03/38] [Blazor] Take cascading parameter attribute type into
account when supplying cascading values (#48554)
---
.../test/AuthorizeRouteViewTest.cs | 1 -
.../src/Binding/CascadingModelBinder.cs | 146 ++--
.../Binding/CascadingModelBindingProvider.cs | 69 ++
.../CascadingQueryModelBindingProvider.cs | 138 ++++
.../src/Binding/IFormValueSupplier.cs | 2 +-
.../src/CascadingParameterAttribute.cs | 4 +-
.../src/CascadingParameterAttributeBase.cs | 16 +
.../Components/src/CascadingParameterInfo.cs | 32 +
.../Components/src/CascadingParameterState.cs | 73 +-
.../Components/src/CascadingValue.cs | 30 +-
.../src/ICascadingValueComponent.cs | 22 -
.../Components/src/ICascadingValueSupplier.cs | 19 +
.../src/IHostEnvironmentCascadingParameter.cs | 11 -
.../Components/src/ParameterView.cs | 3 +-
.../Components/src/PublicAPI.Unshipped.txt | 28 +
.../src/Reflection/ComponentProperties.cs | 15 +-
.../Components/src/RenderTree/Renderer.cs | 1 -
.../src/Rendering/ComponentState.cs | 8 +-
src/Components/Components/src/RouteView.cs | 18 -
.../Routing/QueryParameterValueSupplier.cs | 183 +----
.../src/SupplyParameterFromQueryAttribute.cs | 4 +-
.../test/CascadingModelBinderTest.cs | 1 +
.../test/CascadingParameterStateTest.cs | 98 ++-
.../Components/test/CascadingParameterTest.cs | 128 ++++
.../test/ParameterViewTest.Assignment.cs | 38 +-
.../Components/test/ParameterViewTest.cs | 31 +-
.../Components/test/RouteViewTest.cs | 1 +
.../QueryParameterValueSupplierTest.cs | 644 +++++++-----------
...orComponentsServiceCollectionExtensions.cs | 2 +
...mponentsServiceCollectionExtensionsTest.cs | 6 +
.../CascadingFormModelBindingProvider.cs | 64 ++
.../Web/src/PublicAPI.Unshipped.txt | 6 +-
.../src/SupplyParameterFromFormAttribute.cs | 4 +-
src/Components/Web/test/Forms/EditFormTest.cs | 2 +
.../src/Hosting/WebAssemblyHostBuilder.cs | 1 +
...nentsWebViewServiceCollectionExtensions.cs | 1 +
.../test/E2ETest/Tests/RoutingTest.cs | 14 +
.../RouterTest/NestedQueryParameters.razor | 12 +
.../RouterTest/WithQueryParameters.razor | 14 +-
39 files changed, 1045 insertions(+), 845 deletions(-)
create mode 100644 src/Components/Components/src/Binding/CascadingModelBindingProvider.cs
create mode 100644 src/Components/Components/src/Binding/CascadingQueryModelBindingProvider.cs
create mode 100644 src/Components/Components/src/CascadingParameterAttributeBase.cs
create mode 100644 src/Components/Components/src/CascadingParameterInfo.cs
delete mode 100644 src/Components/Components/src/ICascadingValueComponent.cs
create mode 100644 src/Components/Components/src/ICascadingValueSupplier.cs
delete mode 100644 src/Components/Components/src/IHostEnvironmentCascadingParameter.cs
create mode 100644 src/Components/Web/src/Binding/CascadingFormModelBindingProvider.cs
create mode 100644 src/Components/test/testassets/BasicTestApp/RouterTest/NestedQueryParameters.razor
diff --git a/src/Components/Authorization/test/AuthorizeRouteViewTest.cs b/src/Components/Authorization/test/AuthorizeRouteViewTest.cs
index 05609873187d..83c7549b574b 100644
--- a/src/Components/Authorization/test/AuthorizeRouteViewTest.cs
+++ b/src/Components/Authorization/test/AuthorizeRouteViewTest.cs
@@ -34,7 +34,6 @@ public AuthorizeRouteViewTest()
serviceCollection.AddSingleton();
serviceCollection.AddSingleton(_testAuthorizationService);
serviceCollection.AddSingleton();
- serviceCollection.AddSingleton();
var services = serviceCollection.BuildServiceProvider();
_renderer = new TestRenderer(services);
diff --git a/src/Components/Components/src/Binding/CascadingModelBinder.cs b/src/Components/Components/src/Binding/CascadingModelBinder.cs
index 994e8430c3d6..0b1b0e2a5d4e 100644
--- a/src/Components/Components/src/Binding/CascadingModelBinder.cs
+++ b/src/Components/Components/src/Binding/CascadingModelBinder.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 System.Linq;
using System.Reflection.Metadata;
using Microsoft.AspNetCore.Components.Binding;
using Microsoft.AspNetCore.Components.Rendering;
@@ -11,12 +13,13 @@ namespace Microsoft.AspNetCore.Components;
///
/// Defines the binding context for data bound from external sources.
///
-public sealed class CascadingModelBinder : IComponent, ICascadingValueComponent, IDisposable
+public sealed class CascadingModelBinder : IComponent, ICascadingValueSupplier, IDisposable
{
+ private readonly Dictionary _providersByCascadingParameterAttributeType = new();
+
private RenderHandle _handle;
private ModelBindingContext? _bindingContext;
private bool _hasPendingQueuedRender;
- private BindingInfo? _bindingInfo;
///
/// The binding context name.
@@ -40,7 +43,9 @@ public sealed class CascadingModelBinder : IComponent, ICascadingValueComponent,
[Inject] internal NavigationManager Navigation { get; set; } = null!;
- [Inject] internal IFormValueSupplier FormValueSupplier { get; set; } = null!;
+ [Inject] internal IEnumerable ModelBindingProviders { get; set; } = Enumerable.Empty();
+
+ internal ModelBindingContext? BindingContext => _bindingContext;
void IComponent.Attach(RenderHandle renderHandle)
{
@@ -110,24 +115,25 @@ internal void UpdateBindingInformation(string url)
// BindingContextId = <>((<>&)|?)handler=my-handler
var name = ModelBindingContext.Combine(ParentContext, Name);
var bindingId = string.IsNullOrEmpty(name) ? "" : GenerateBindingContextId(name);
+ var bindingContextDidChange =
+ _bindingContext is null ||
+ !string.Equals(_bindingContext.Name, name, StringComparison.Ordinal) ||
+ !string.Equals(_bindingContext.BindingContextId, bindingId, StringComparison.Ordinal);
- var bindingContext = _bindingContext != null &&
- string.Equals(_bindingContext.Name, name, StringComparison.Ordinal) &&
- string.Equals(_bindingContext.BindingContextId, bindingId, StringComparison.Ordinal) ?
- _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)
+ if (bindingContextDidChange)
{
- // Throw an exception if either the Name or the BindingContextId changed. Once a CascadingModelBinder has been initialized
- // as fixed, it can't change it's name nor its BindingContextId. This can happen in several situations:
- // * Component ParentContext hierarchy changes.
- // * Technically, the component won't be retained in this case and will be destroyed instead.
- // * A parent changes Name.
- throw new InvalidOperationException($"'{nameof(CascadingModelBinder)}' 'Name' can't change after initialized.");
- }
+ if (IsFixed && _bindingContext is not null)
+ {
+ // Throw an exception if either the Name or the BindingContextId changed. Once a CascadingModelBinder has been initialized
+ // as fixed, it can't change it's name nor its BindingContextId. This can happen in several situations:
+ // * Component ParentContext hierarchy changes.
+ // * Technically, the component won't be retained in this case and will be destroyed instead.
+ // * A parent changes Name.
+ throw new InvalidOperationException($"'{nameof(CascadingModelBinder)}' 'Name' can't change after initialized.");
+ }
- _bindingContext = bindingContext;
+ _bindingContext = new ModelBindingContext(name, bindingId, CanBind);
+ }
string GenerateBindingContextId(string name)
{
@@ -135,60 +141,92 @@ string GenerateBindingContextId(string name)
var hashIndex = bindingId.IndexOf('#');
return hashIndex == -1 ? bindingId : new string(bindingId.AsSpan(0, hashIndex));
}
+
+ bool CanBind(Type type)
+ {
+ foreach (var provider in ModelBindingProviders)
+ {
+ if (provider.SupportsParameterType(type))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
}
- void IDisposable.Dispose()
+ bool ICascadingValueSupplier.CanSupplyValue(in CascadingParameterInfo parameterInfo)
+ => TryGetProvider(in parameterInfo, out var provider)
+ && provider.CanSupplyValue(_bindingContext, parameterInfo);
+
+ void ICascadingValueSupplier.Subscribe(ComponentState subscriber, in CascadingParameterInfo parameterInfo)
{
- Navigation.LocationChanged -= HandleLocationChanged;
+ // We expect there to always be a provider at this point, because CanSupplyValue must have returned true.
+ var provider = GetProviderOrThrow(parameterInfo);
+
+ if (!provider.AreValuesFixed)
+ {
+ provider.Subscribe(subscriber);
+ }
}
- bool ICascadingValueComponent.CanSupplyValue(Type valueType, string? valueName)
+ void ICascadingValueSupplier.Unsubscribe(ComponentState subscriber, in CascadingParameterInfo parameterInfo)
{
- var formName = string.IsNullOrEmpty(valueName) ?
- (_bindingContext?.Name) :
- ModelBindingContext.Combine(_bindingContext, valueName);
+ // We expect there to always be a provider at this point, because CanSupplyValue must have returned true.
+ var provider = GetProviderOrThrow(parameterInfo);
- if (_bindingInfo != null &&
- string.Equals(_bindingInfo.FormName, formName, StringComparison.Ordinal) &&
- _bindingInfo.ValueType.Equals(valueType))
+ if (!provider.AreValuesFixed)
{
- // 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;
+ provider.Unsubscribe(subscriber);
}
+ }
- // 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
- }
+ object? ICascadingValueSupplier.GetCurrentValue(in CascadingParameterInfo parameterInfo)
+ => TryGetProvider(in parameterInfo, out var provider)
+ ? provider.GetCurrentValue(_bindingContext, parameterInfo)
+ : null;
- return true;
+ private CascadingModelBindingProvider GetProviderOrThrow(in CascadingParameterInfo parameterInfo)
+ {
+ if (!TryGetProvider(parameterInfo, out var provider))
+ {
+ throw new InvalidOperationException($"No model binding provider could be found for parameter '{parameterInfo.PropertyName}'.");
}
- return false;
+ return provider;
}
- void ICascadingValueComponent.Subscribe(ComponentState subscriber)
+ private bool TryGetProvider(in CascadingParameterInfo parameterInfo, [NotNullWhen(true)] out CascadingModelBindingProvider? result)
{
- throw new InvalidOperationException("Form values are always fixed.");
- }
+ var attributeType = parameterInfo.Attribute.GetType();
- void ICascadingValueComponent.Unsubscribe(ComponentState subscriber)
- {
- throw new InvalidOperationException("Form values are always fixed.");
- }
+ if (_providersByCascadingParameterAttributeType.TryGetValue(attributeType, out result))
+ {
+ return result is not null;
+ }
- object? ICascadingValueComponent.CurrentValue => _bindingInfo == null ?
- throw new InvalidOperationException("Tried to access form value before it was bound.") :
- _bindingInfo.BoundValue;
+ // We deliberately cache 'null' results to avoid searching for the same attribute type multiple times.
+ result = FindProviderForAttributeType(attributeType);
+ _providersByCascadingParameterAttributeType[attributeType] = result;
+ return result is not null;
- bool ICascadingValueComponent.CurrentValueIsFixed => true;
+ CascadingModelBindingProvider? FindProviderForAttributeType(Type attributeType)
+ {
+ foreach (var provider in ModelBindingProviders)
+ {
+ if (provider.SupportsCascadingParameterAttributeType(attributeType))
+ {
+ return provider;
+ }
+ }
+
+ return null;
+ }
+ }
- private record BindingInfo(string? FormName, Type ValueType, bool BindingResult, object? BoundValue);
+ void IDisposable.Dispose()
+ {
+ Navigation.LocationChanged -= HandleLocationChanged;
+ }
}
diff --git a/src/Components/Components/src/Binding/CascadingModelBindingProvider.cs b/src/Components/Components/src/Binding/CascadingModelBindingProvider.cs
new file mode 100644
index 000000000000..4b75e496a3ce
--- /dev/null
+++ b/src/Components/Components/src/Binding/CascadingModelBindingProvider.cs
@@ -0,0 +1,69 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.AspNetCore.Components.Rendering;
+
+namespace Microsoft.AspNetCore.Components.Binding;
+
+///
+/// Provides values that get supplied to cascading parameters with .
+///
+public abstract class CascadingModelBindingProvider
+{
+ ///
+ /// Gets whether values supplied by this instance will not change.
+ ///
+ protected internal abstract bool AreValuesFixed { get; }
+
+ ///
+ /// Determines whether this instance can provide values for parameters annotated with the specified attribute type.
+ ///
+ /// The attribute type.
+ /// true if this instance can provide values for parameters annotated with the specified attribute type, otherwise false.
+ protected internal abstract bool SupportsCascadingParameterAttributeType(Type attributeType);
+
+ ///
+ /// Determines whether this instance can provide values to parameters with the specified type.
+ ///
+ /// The parameter type.
+ /// true if this instance can provide values to parameters with the specified type, otherwise false.
+ protected internal abstract bool SupportsParameterType(Type parameterType);
+
+ ///
+ /// Determines whether this instance can supply a value for the specified parameter.
+ ///
+ /// The current .
+ /// The for the component parameter.
+ /// true if a value can be supplied, otherwise false.
+ protected internal abstract bool CanSupplyValue(ModelBindingContext? bindingContext, in CascadingParameterInfo parameterInfo);
+
+ ///
+ /// Gets the value for the specified parameter.
+ ///
+ /// The current .
+ /// The for the component parameter.
+ /// The value to supply to the parameter.
+ protected internal abstract object? GetCurrentValue(ModelBindingContext? bindingContext, in CascadingParameterInfo parameterInfo);
+
+ ///
+ /// Subscribes to changes in supplied values, if they can change.
+ ///
+ ///
+ /// This method must be implemented if is false.
+ ///
+ /// The for the subscribing component.
+ protected internal virtual void Subscribe(ComponentState subscriber)
+ => throw new InvalidOperationException(
+ $"'{nameof(CascadingModelBindingProvider)}' instances that have non-fixed values must provide an implementation for '{nameof(Subscribe)}'.");
+
+ ///
+ /// Unsubscribes from changes in supplied values, if they can change.
+ ///
+ ///
+ /// This method must be implemented if is false.
+ ///
+ /// The for the unsubscribing component.
+ protected internal virtual void Unsubscribe(ComponentState subscriber)
+ => throw new InvalidOperationException(
+ $"'{nameof(CascadingModelBindingProvider)}' instances that have non-fixed values must provide an implementation for '{nameof(Unsubscribe)}'.");
+}
diff --git a/src/Components/Components/src/Binding/CascadingQueryModelBindingProvider.cs b/src/Components/Components/src/Binding/CascadingQueryModelBindingProvider.cs
new file mode 100644
index 000000000000..e6e9c976016b
--- /dev/null
+++ b/src/Components/Components/src/Binding/CascadingQueryModelBindingProvider.cs
@@ -0,0 +1,138 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.AspNetCore.Components.Rendering;
+using Microsoft.AspNetCore.Components.Routing;
+
+namespace Microsoft.AspNetCore.Components.Binding;
+
+///
+/// Enables component parameters to be supplied from the query string with .
+///
+public sealed class CascadingQueryModelBindingProvider : CascadingModelBindingProvider, IDisposable
+{
+ private readonly QueryParameterValueSupplier _queryParameterValueSupplier = new();
+ private readonly NavigationManager _navigationManager;
+
+ private HashSet? _subscribers;
+ private bool _isSubscribedToLocationChanges;
+ private bool _queryParametersMightHaveChanged = true;
+
+ ///
+ protected internal override bool AreValuesFixed => false;
+
+ ///
+ /// Constructs a new instance of .
+ ///
+ public CascadingQueryModelBindingProvider(NavigationManager navigationManager)
+ {
+ _navigationManager = navigationManager;
+ }
+
+ ///
+ protected internal override bool SupportsCascadingParameterAttributeType(Type attributeType)
+ => attributeType == typeof(SupplyParameterFromQueryAttribute);
+
+ ///
+ protected internal override bool SupportsParameterType(Type type)
+ => QueryParameterValueSupplier.CanSupplyValueForType(type);
+
+ ///
+ protected internal override bool CanSupplyValue(ModelBindingContext? bindingContext, in CascadingParameterInfo parameterInfo)
+ // We can always supply a value; it'll just be null if there's no match.
+ => true;
+
+ ///
+ protected internal override object? GetCurrentValue(ModelBindingContext? bindingContext, in CascadingParameterInfo parameterInfo)
+ {
+ if (_queryParametersMightHaveChanged)
+ {
+ _queryParametersMightHaveChanged = false;
+ UpdateQueryParameters();
+ }
+
+ var queryParameterName = parameterInfo.Attribute.Name ?? parameterInfo.PropertyName;
+ return _queryParameterValueSupplier.GetQueryParameterValue(parameterInfo.PropertyType, queryParameterName);
+ }
+
+ ///
+ protected internal override void Subscribe(ComponentState subscriber)
+ {
+ SubscribeToLocationChanges();
+
+ _subscribers ??= new();
+ _subscribers.Add(subscriber);
+ }
+
+ ///
+ protected internal override void Unsubscribe(ComponentState subscriber)
+ {
+ _subscribers!.Remove(subscriber);
+
+ if (_subscribers.Count == 0)
+ {
+ UnsubscribeFromLocationChanges();
+ }
+ }
+
+ private void UpdateQueryParameters()
+ {
+ var query = GetQueryString(_navigationManager.Uri);
+
+ _queryParameterValueSupplier.ReadParametersFromQuery(query);
+
+ static ReadOnlyMemory GetQueryString(string url)
+ {
+ var queryStartPos = url.IndexOf('?');
+
+ if (queryStartPos < 0)
+ {
+ return default;
+ }
+
+ var queryEndPos = url.IndexOf('#', queryStartPos);
+ return url.AsMemory(queryStartPos..(queryEndPos < 0 ? url.Length : queryEndPos));
+ }
+ }
+
+ private void SubscribeToLocationChanges()
+ {
+ if (_isSubscribedToLocationChanges)
+ {
+ return;
+ }
+
+ _isSubscribedToLocationChanges = true;
+ _queryParametersMightHaveChanged = true;
+ _navigationManager.LocationChanged += OnLocationChanged;
+ }
+
+ private void UnsubscribeFromLocationChanges()
+ {
+ if (!_isSubscribedToLocationChanges)
+ {
+ return;
+ }
+
+ _isSubscribedToLocationChanges = false;
+ _navigationManager.LocationChanged -= OnLocationChanged;
+ }
+
+ private void OnLocationChanged(object? sender, LocationChangedEventArgs args)
+ {
+ _queryParametersMightHaveChanged = true;
+
+ if (_subscribers is not null)
+ {
+ foreach (var subscriber in _subscribers)
+ {
+ subscriber.NotifyCascadingValueChanged(ParameterViewLifetime.Unbound);
+ }
+ }
+ }
+
+ void IDisposable.Dispose()
+ {
+ UnsubscribeFromLocationChanges();
+ }
+}
diff --git a/src/Components/Components/src/Binding/IFormValueSupplier.cs b/src/Components/Components/src/Binding/IFormValueSupplier.cs
index b8696a224339..4b5403222409 100644
--- a/src/Components/Components/src/Binding/IFormValueSupplier.cs
+++ b/src/Components/Components/src/Binding/IFormValueSupplier.cs
@@ -6,7 +6,7 @@
namespace Microsoft.AspNetCore.Components.Binding;
///
-/// Binds form data valuesto a model.
+/// Binds form data values to a model.
///
public interface IFormValueSupplier
{
diff --git a/src/Components/Components/src/CascadingParameterAttribute.cs b/src/Components/Components/src/CascadingParameterAttribute.cs
index 70cb5998ff72..bb9be43a5b08 100644
--- a/src/Components/Components/src/CascadingParameterAttribute.cs
+++ b/src/Components/Components/src/CascadingParameterAttribute.cs
@@ -9,7 +9,7 @@ namespace Microsoft.AspNetCore.Components;
/// supplies values with a compatible type and name.
///
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
-public sealed class CascadingParameterAttribute : Attribute
+public sealed class CascadingParameterAttribute : CascadingParameterAttributeBase
{
///
/// If specified, the parameter value will be supplied by the closest
@@ -20,5 +20,5 @@ public sealed class CascadingParameterAttribute : Attribute
/// that supplies a value with a compatible
/// type.
///
- public string? Name { get; set; }
+ public override string? Name { get; set; }
}
diff --git a/src/Components/Components/src/CascadingParameterAttributeBase.cs b/src/Components/Components/src/CascadingParameterAttributeBase.cs
new file mode 100644
index 000000000000..f85fbaa6cffc
--- /dev/null
+++ b/src/Components/Components/src/CascadingParameterAttributeBase.cs
@@ -0,0 +1,16 @@
+// 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;
+
+///
+/// Represents a parameter whose value cascades down the component hierarchy.
+///
+public abstract class CascadingParameterAttributeBase : Attribute
+{
+ ///
+ /// Gets or sets the name for the parameter, which correlates to the name
+ /// of a cascading value.
+ ///
+ public abstract string? Name { get; set; }
+}
diff --git a/src/Components/Components/src/CascadingParameterInfo.cs b/src/Components/Components/src/CascadingParameterInfo.cs
new file mode 100644
index 000000000000..2d8493ff70f4
--- /dev/null
+++ b/src/Components/Components/src/CascadingParameterInfo.cs
@@ -0,0 +1,32 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.AspNetCore.Components;
+
+///
+/// Contains information about a cascading parameter.
+///
+public readonly struct CascadingParameterInfo
+{
+ ///
+ /// Gets the property's attribute.
+ ///
+ public CascadingParameterAttributeBase Attribute { get; }
+
+ ///
+ /// Gets the name of the parameter's property.
+ ///
+ public string PropertyName { get; }
+
+ ///
+ /// Gets the type of the parameter's property.
+ ///
+ public Type PropertyType { get; }
+
+ internal CascadingParameterInfo(CascadingParameterAttributeBase attribute, string propertyName, Type propertyType)
+ {
+ Attribute = attribute;
+ PropertyName = propertyName;
+ PropertyType = propertyType;
+ }
+}
diff --git a/src/Components/Components/src/CascadingParameterState.cs b/src/Components/Components/src/CascadingParameterState.cs
index 7af4dc1b9cce..1c9b95b720b4 100644
--- a/src/Components/Components/src/CascadingParameterState.cs
+++ b/src/Components/Components/src/CascadingParameterState.cs
@@ -13,21 +13,21 @@ namespace Microsoft.AspNetCore.Components;
internal readonly struct CascadingParameterState
{
- private static readonly ConcurrentDictionary _cachedInfos = new();
+ private static readonly ConcurrentDictionary _cachedInfos = new();
- public string LocalValueName { get; }
- public ICascadingValueComponent ValueSupplier { get; }
+ public CascadingParameterInfo ParameterInfo { get; }
+ public ICascadingValueSupplier ValueSupplier { get; }
- public CascadingParameterState(string localValueName, ICascadingValueComponent valueSupplier)
+ public CascadingParameterState(in CascadingParameterInfo parameterInfo, ICascadingValueSupplier valueSupplier)
{
- LocalValueName = localValueName;
+ ParameterInfo = parameterInfo;
ValueSupplier = valueSupplier;
}
public static IReadOnlyList FindCascadingParameters(ComponentState componentState)
{
var componentType = componentState.Component.GetType();
- var infos = GetReflectedCascadingParameterInfos(componentType);
+ var infos = GetCascadingParameterInfos(componentType);
// For components known not to have any cascading parameters, bail out early
if (infos.Length == 0)
@@ -48,23 +48,21 @@ public static IReadOnlyList FindCascadingParameters(Com
{
// 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));
+ resultStates.Add(new CascadingParameterState(info, supplier));
}
}
return resultStates ?? (IReadOnlyList)Array.Empty();
}
- private static ICascadingValueComponent? GetMatchingCascadingValueSupplier(in ReflectedCascadingParameterInfo info, ComponentState componentState)
+ private static ICascadingValueSupplier? GetMatchingCascadingValueSupplier(in CascadingParameterInfo info, ComponentState componentState)
{
var candidate = componentState;
do
{
- if (candidate.Component is ICascadingValueComponent candidateSupplier
- && candidateSupplier.CanSupplyValue(info.ValueType, info.SupplierValueName))
+ if (candidate.Component is ICascadingValueSupplier valueSupplier && valueSupplier.CanSupplyValue(info))
{
- return candidateSupplier;
+ return valueSupplier;
}
candidate = candidate.ParentComponentState;
@@ -74,64 +72,37 @@ public static IReadOnlyList FindCascadingParameters(Com
return null;
}
- private static ReflectedCascadingParameterInfo[] GetReflectedCascadingParameterInfos(
+ private static CascadingParameterInfo[] GetCascadingParameterInfos(
[DynamicallyAccessedMembers(Component)] Type componentType)
{
if (!_cachedInfos.TryGetValue(componentType, out var infos))
{
- infos = CreateReflectedCascadingParameterInfos(componentType);
+ infos = CreateCascadingParameterInfos(componentType);
_cachedInfos[componentType] = infos;
}
return infos;
}
- private static ReflectedCascadingParameterInfo[] CreateReflectedCascadingParameterInfos(
+ private static CascadingParameterInfo[] CreateCascadingParameterInfos(
[DynamicallyAccessedMembers(Component)] Type componentType)
{
- List? result = null;
+ List? result = null;
var candidateProps = ComponentProperties.GetCandidateBindableProperties(componentType);
foreach (var prop in candidateProps)
{
- var attribute = prop.GetCustomAttribute();
- if (attribute != null)
+ var cascadingParameterAttribute = prop.GetCustomAttributes()
+ .OfType().SingleOrDefault();
+ if (cascadingParameterAttribute != null)
{
- 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(
+ result ??= new List();
+ result.Add(new CascadingParameterInfo(
+ cascadingParameterAttribute,
prop.Name,
- prop.PropertyType,
- hostParameterAttribute.Name));
+ prop.PropertyType));
}
}
- return result?.ToArray() ?? Array.Empty();
- }
-
- readonly struct ReflectedCascadingParameterInfo
- {
- public string ConsumerValueName { get; }
- public string? SupplierValueName { get; }
- public Type ValueType { get; }
-
- public ReflectedCascadingParameterInfo(
- string consumerValueName, Type valueType, string? supplierValueName)
- {
- ConsumerValueName = consumerValueName;
- SupplierValueName = supplierValueName;
- ValueType = valueType;
- }
+ return result?.ToArray() ?? Array.Empty();
}
}
diff --git a/src/Components/Components/src/CascadingValue.cs b/src/Components/Components/src/CascadingValue.cs
index facb9821e415..a040894ac57c 100644
--- a/src/Components/Components/src/CascadingValue.cs
+++ b/src/Components/Components/src/CascadingValue.cs
@@ -8,7 +8,7 @@ namespace Microsoft.AspNetCore.Components;
///
/// A component that provides a cascading value to all descendant components.
///
-public class CascadingValue : ICascadingValueComponent, IComponent
+public class CascadingValue : ICascadingValueSupplier, IComponent
{
private RenderHandle _renderHandle;
private HashSet? _subscribers; // Lazily instantiated
@@ -41,10 +41,6 @@ public class CascadingValue : ICascadingValueComponent, IComponent
///
[Parameter] public bool IsFixed { get; set; }
- object? ICascadingValueComponent.CurrentValue => Value;
-
- bool ICascadingValueComponent.CurrentValueIsFixed => IsFixed;
-
///
public void Attach(RenderHandle renderHandle)
{
@@ -130,37 +126,39 @@ public Task SetParametersAsync(ParameterView parameters)
return Task.CompletedTask;
}
- bool ICascadingValueComponent.CanSupplyValue(Type requestedType, string? requestedName)
+ bool ICascadingValueSupplier.CanSupplyValue(in CascadingParameterInfo parameterInfo)
{
- if (!requestedType.IsAssignableFrom(typeof(TValue)))
+ if (parameterInfo.Attribute is not CascadingParameterAttribute cascadingParameterAttribute || !parameterInfo.PropertyType.IsAssignableFrom(typeof(TValue)))
{
return false;
}
+ // We only consider explicitly requested names, not the property name.
+ var requestedName = cascadingParameterAttribute.Name;
+
return (requestedName == null && Name == null) // Match on type alone
|| string.Equals(requestedName, Name, StringComparison.OrdinalIgnoreCase); // Also match on name
}
- void ICascadingValueComponent.Subscribe(ComponentState subscriber)
+ object? ICascadingValueSupplier.GetCurrentValue(in CascadingParameterInfo parameterInfo)
+ {
+ return Value;
+ }
+
+ void ICascadingValueSupplier.Subscribe(ComponentState subscriber, in CascadingParameterInfo parameterInfo)
{
-#if DEBUG
if (IsFixed)
{
// Should not be possible. User code cannot trigger this.
// Checking only to catch possible future framework bugs.
throw new InvalidOperationException($"Cannot subscribe to a {typeof(CascadingValue<>).Name} when {nameof(IsFixed)} is true.");
}
-#endif
-
- if (_subscribers == null)
- {
- _subscribers = new HashSet();
- }
+ _subscribers ??= new HashSet();
_subscribers.Add(subscriber);
}
- void ICascadingValueComponent.Unsubscribe(ComponentState subscriber)
+ void ICascadingValueSupplier.Unsubscribe(ComponentState subscriber, in CascadingParameterInfo parameterInfo)
{
_subscribers?.Remove(subscriber);
}
diff --git a/src/Components/Components/src/ICascadingValueComponent.cs b/src/Components/Components/src/ICascadingValueComponent.cs
deleted file mode 100644
index b18735c86a9e..000000000000
--- a/src/Components/Components/src/ICascadingValueComponent.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using Microsoft.AspNetCore.Components.Rendering;
-
-namespace Microsoft.AspNetCore.Components;
-
-internal interface ICascadingValueComponent
-{
- // This interface exists only so that CascadingParameterState has a way
- // to work with all CascadingValue types regardless of T.
-
- bool CanSupplyValue(Type valueType, string? valueName);
-
- object? CurrentValue { get; }
-
- bool CurrentValueIsFixed { get; }
-
- void Subscribe(ComponentState subscriber);
-
- void Unsubscribe(ComponentState subscriber);
-}
diff --git a/src/Components/Components/src/ICascadingValueSupplier.cs b/src/Components/Components/src/ICascadingValueSupplier.cs
new file mode 100644
index 000000000000..c535d9cfda16
--- /dev/null
+++ b/src/Components/Components/src/ICascadingValueSupplier.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 Microsoft.AspNetCore.Components.Rendering;
+
+namespace Microsoft.AspNetCore.Components;
+
+internal interface ICascadingValueSupplier
+{
+ bool IsFixed { get; }
+
+ bool CanSupplyValue(in CascadingParameterInfo parameterInfo);
+
+ object? GetCurrentValue(in CascadingParameterInfo parameterInfo);
+
+ void Subscribe(ComponentState subscriber, in CascadingParameterInfo parameterInfo);
+
+ void Unsubscribe(ComponentState subscriber, in CascadingParameterInfo parameterInfo);
+}
diff --git a/src/Components/Components/src/IHostEnvironmentCascadingParameter.cs b/src/Components/Components/src/IHostEnvironmentCascadingParameter.cs
deleted file mode 100644
index 8f407e0cdd5e..000000000000
--- a/src/Components/Components/src/IHostEnvironmentCascadingParameter.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-// 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/ParameterView.cs b/src/Components/Components/src/ParameterView.cs
index 37ba9c2dfbcd..1b3a25d635d1 100644
--- a/src/Components/Components/src/ParameterView.cs
+++ b/src/Components/Components/src/ParameterView.cs
@@ -423,7 +423,8 @@ public bool MoveNext()
_currentIndex = nextIndex;
var state = _cascadingParameters[_currentIndex];
- _current = new ParameterValue(state.LocalValueName, state.ValueSupplier.CurrentValue!, true);
+ var currentValue = state.ValueSupplier.GetCurrentValue(state.ParameterInfo);
+ _current = new ParameterValue(state.ParameterInfo.PropertyName, currentValue!, true);
return true;
}
else
diff --git a/src/Components/Components/src/PublicAPI.Unshipped.txt b/src/Components/Components/src/PublicAPI.Unshipped.txt
index 625c0e05e595..f37e9e93b125 100644
--- a/src/Components/Components/src/PublicAPI.Unshipped.txt
+++ b/src/Components/Components/src/PublicAPI.Unshipped.txt
@@ -1,5 +1,16 @@
#nullable enable
+abstract Microsoft.AspNetCore.Components.Binding.CascadingModelBindingProvider.AreValuesFixed.get -> bool
+abstract Microsoft.AspNetCore.Components.Binding.CascadingModelBindingProvider.CanSupplyValue(Microsoft.AspNetCore.Components.ModelBindingContext? bindingContext, in Microsoft.AspNetCore.Components.CascadingParameterInfo parameterInfo) -> bool
+abstract Microsoft.AspNetCore.Components.Binding.CascadingModelBindingProvider.GetCurrentValue(Microsoft.AspNetCore.Components.ModelBindingContext? bindingContext, in Microsoft.AspNetCore.Components.CascadingParameterInfo parameterInfo) -> object?
+abstract Microsoft.AspNetCore.Components.Binding.CascadingModelBindingProvider.SupportsCascadingParameterAttributeType(System.Type! attributeType) -> bool
+abstract Microsoft.AspNetCore.Components.Binding.CascadingModelBindingProvider.SupportsParameterType(System.Type! parameterType) -> bool
+abstract Microsoft.AspNetCore.Components.CascadingParameterAttributeBase.Name.get -> string?
+abstract Microsoft.AspNetCore.Components.CascadingParameterAttributeBase.Name.set -> void
abstract Microsoft.AspNetCore.Components.RenderModeAttribute.Mode.get -> Microsoft.AspNetCore.Components.IComponentRenderMode!
+Microsoft.AspNetCore.Components.Binding.CascadingModelBindingProvider
+Microsoft.AspNetCore.Components.Binding.CascadingModelBindingProvider.CascadingModelBindingProvider() -> void
+Microsoft.AspNetCore.Components.Binding.CascadingQueryModelBindingProvider
+Microsoft.AspNetCore.Components.Binding.CascadingQueryModelBindingProvider.CascadingQueryModelBindingProvider(Microsoft.AspNetCore.Components.NavigationManager! navigationManager) -> void
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
@@ -12,6 +23,13 @@ Microsoft.AspNetCore.Components.CascadingModelBinder.IsFixed.get -> bool
Microsoft.AspNetCore.Components.CascadingModelBinder.IsFixed.set -> void
Microsoft.AspNetCore.Components.CascadingModelBinder.Name.get -> string!
Microsoft.AspNetCore.Components.CascadingModelBinder.Name.set -> void
+Microsoft.AspNetCore.Components.CascadingParameterAttributeBase
+Microsoft.AspNetCore.Components.CascadingParameterAttributeBase.CascadingParameterAttributeBase() -> void
+Microsoft.AspNetCore.Components.CascadingParameterInfo
+Microsoft.AspNetCore.Components.CascadingParameterInfo.Attribute.get -> Microsoft.AspNetCore.Components.CascadingParameterAttributeBase!
+Microsoft.AspNetCore.Components.CascadingParameterInfo.CascadingParameterInfo() -> void
+Microsoft.AspNetCore.Components.CascadingParameterInfo.PropertyName.get -> string!
+Microsoft.AspNetCore.Components.CascadingParameterInfo.PropertyType.get -> System.Type!
Microsoft.AspNetCore.Components.ComponentBase.DispatchExceptionAsync(System.Exception! exception) -> System.Threading.Tasks.Task!
Microsoft.AspNetCore.Components.IComponentRenderMode
Microsoft.AspNetCore.Components.Infrastructure.ComponentStatePersistenceManager.PersistStateAsync(Microsoft.AspNetCore.Components.IPersistentComponentStateStore! store, Microsoft.AspNetCore.Components.Dispatcher! dispatcher) -> System.Threading.Tasks.Task!
@@ -60,10 +78,20 @@ Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder.AddComponentParamete
Microsoft.AspNetCore.Components.StreamRenderingAttribute
Microsoft.AspNetCore.Components.StreamRenderingAttribute.Enabled.get -> bool
Microsoft.AspNetCore.Components.StreamRenderingAttribute.StreamRenderingAttribute(bool enabled) -> void
+*REMOVED*Microsoft.AspNetCore.Components.CascadingParameterAttribute.Name.get -> string?
+*REMOVED*Microsoft.AspNetCore.Components.CascadingParameterAttribute.Name.set -> void
+override Microsoft.AspNetCore.Components.CascadingParameterAttribute.Name.get -> string?
+override Microsoft.AspNetCore.Components.CascadingParameterAttribute.Name.set -> void
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
+*REMOVED*Microsoft.AspNetCore.Components.SupplyParameterFromQueryAttribute.Name.get -> string?
+*REMOVED*Microsoft.AspNetCore.Components.SupplyParameterFromQueryAttribute.Name.set -> void
+override Microsoft.AspNetCore.Components.SupplyParameterFromQueryAttribute.Name.get -> string?
+override Microsoft.AspNetCore.Components.SupplyParameterFromQueryAttribute.Name.set -> void
+virtual Microsoft.AspNetCore.Components.Binding.CascadingModelBindingProvider.Subscribe(Microsoft.AspNetCore.Components.Rendering.ComponentState! subscriber) -> void
+virtual Microsoft.AspNetCore.Components.Binding.CascadingModelBindingProvider.Unsubscribe(Microsoft.AspNetCore.Components.Rendering.ComponentState! subscriber) -> void
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!
diff --git a/src/Components/Components/src/Reflection/ComponentProperties.cs b/src/Components/Components/src/Reflection/ComponentProperties.cs
index 9569e8e6a84e..507d5bcd353a 100644
--- a/src/Components/Components/src/Reflection/ComponentProperties.cs
+++ b/src/Components/Components/src/Reflection/ComponentProperties.cs
@@ -170,8 +170,7 @@ private static void ThrowForUnknownIncomingParameterName([DynamicallyAccessedMem
if (propertyInfo != null)
{
if (!propertyInfo.IsDefined(typeof(ParameterAttribute)) &&
- !propertyInfo.IsDefined(typeof(CascadingParameterAttribute)) &&
- !propertyInfo.GetCustomAttributes().OfType().Any())
+ !propertyInfo.GetCustomAttributes().OfType().Any())
{
throw new InvalidOperationException(
$"Object of type '{targetType.FullName}' has a property matching the name '{parameterName}', " +
@@ -262,8 +261,7 @@ public WritersForType([DynamicallyAccessedMembers(Component)] Type targetType)
foreach (var propertyInfo in GetCandidateBindableProperties(targetType))
{
ParameterAttribute? parameterAttribute = null;
- CascadingParameterAttribute? cascadingParameterAttribute = null;
- IHostEnvironmentCascadingParameter? hostEnvironmentCascadingParameter = null;
+ CascadingParameterAttributeBase? cascadingParameterAttribute = null;
var attributes = propertyInfo.GetCustomAttributes();
foreach (var attribute in attributes)
@@ -273,18 +271,15 @@ public WritersForType([DynamicallyAccessedMembers(Component)] Type targetType)
case ParameterAttribute parameter:
parameterAttribute = parameter;
break;
- case CascadingParameterAttribute cascadingParameter:
+ case CascadingParameterAttributeBase cascadingParameter:
cascadingParameterAttribute = cascadingParameter;
break;
- case IHostEnvironmentCascadingParameter hostEnvironmentAttribute:
- hostEnvironmentCascadingParameter = hostEnvironmentAttribute;
- break;
default:
break;
}
}
- var isParameter = parameterAttribute != null || cascadingParameterAttribute != null || hostEnvironmentCascadingParameter != null;
+ var isParameter = parameterAttribute != null || cascadingParameterAttribute != null;
if (!isParameter)
{
continue;
@@ -299,7 +294,7 @@ public WritersForType([DynamicallyAccessedMembers(Component)] Type targetType)
var propertySetter = new PropertySetter(targetType, propertyInfo)
{
- Cascading = cascadingParameterAttribute != null || hostEnvironmentCascadingParameter != null,
+ Cascading = cascadingParameterAttribute != null,
};
if (_underlyingWriters.ContainsKey(propertyName))
diff --git a/src/Components/Components/src/RenderTree/Renderer.cs b/src/Components/Components/src/RenderTree/Renderer.cs
index 187ffb6d8f79..9262cb7423c4 100644
--- a/src/Components/Components/src/RenderTree/Renderer.cs
+++ b/src/Components/Components/src/RenderTree/Renderer.cs
@@ -131,7 +131,6 @@ private async void RenderRootComponentsOnHotReload()
// Before re-rendering the root component, also clear any well-known caches in the framework
ComponentFactory.ClearCache();
ComponentProperties.ClearCache();
- Routing.QueryParameterValueSupplier.ClearCache();
await Dispatcher.InvokeAsync(() =>
{
diff --git a/src/Components/Components/src/Rendering/ComponentState.cs b/src/Components/Components/src/Rendering/ComponentState.cs
index f7de9f106215..c2b9276485a1 100644
--- a/src/Components/Components/src/Rendering/ComponentState.cs
+++ b/src/Components/Components/src/Rendering/ComponentState.cs
@@ -194,9 +194,9 @@ private bool AddCascadingParameterSubscriptions()
for (var i = 0; i < numCascadingParameters; i++)
{
var valueSupplier = _cascadingParameters[i].ValueSupplier;
- if (!valueSupplier.CurrentValueIsFixed)
+ if (!valueSupplier.IsFixed)
{
- valueSupplier.Subscribe(this);
+ valueSupplier.Subscribe(this, _cascadingParameters[i].ParameterInfo);
hasSubscription = true;
}
}
@@ -210,9 +210,9 @@ private void RemoveCascadingParameterSubscriptions()
for (var i = 0; i < numCascadingParameters; i++)
{
var supplier = _cascadingParameters[i].ValueSupplier;
- if (!supplier.CurrentValueIsFixed)
+ if (!supplier.IsFixed)
{
- supplier.Unsubscribe(this);
+ supplier.Unsubscribe(this, _cascadingParameters[i].ParameterInfo);
}
}
}
diff --git a/src/Components/Components/src/RouteView.cs b/src/Components/Components/src/RouteView.cs
index 3d07cbc4f903..bb778771a09f 100644
--- a/src/Components/Components/src/RouteView.cs
+++ b/src/Components/Components/src/RouteView.cs
@@ -8,7 +8,6 @@
using System.Reflection;
using Microsoft.AspNetCore.Components.HotReload;
using Microsoft.AspNetCore.Components.Rendering;
-using Microsoft.AspNetCore.Components.Routing;
namespace Microsoft.AspNetCore.Components;
@@ -103,23 +102,6 @@ void RenderPageCore(RenderTreeBuilder builder)
builder.AddComponentParameter(1, kvp.Key, kvp.Value);
}
- var queryParameterSupplier = QueryParameterValueSupplier.ForType(RouteData.PageType);
- if (queryParameterSupplier is not null)
- {
- // Since this component does accept some parameters from query, we must supply values for all of them,
- // even if the querystring in the URI is empty. So don't skip the following logic.
- var relativeUrl = NavigationManager.ToBaseRelativePath(NavigationManager.Uri);
- var url = NavigationManager.Uri;
- ReadOnlyMemory query = default;
- var queryStartPos = url.IndexOf('?');
- if (queryStartPos >= 0)
- {
- var queryEndPos = url.IndexOf('#', queryStartPos);
- query = url.AsMemory(queryStartPos..(queryEndPos < 0 ? url.Length : queryEndPos));
- }
- queryParameterSupplier.RenderParametersFromQueryString(builder, query);
- }
-
builder.CloseComponent();
}
}
diff --git a/src/Components/Components/src/Routing/QueryParameterValueSupplier.cs b/src/Components/Components/src/Routing/QueryParameterValueSupplier.cs
index b217d09878ff..967169ee0191 100644
--- a/src/Components/Components/src/Routing/QueryParameterValueSupplier.cs
+++ b/src/Components/Components/src/Routing/QueryParameterValueSupplier.cs
@@ -1,189 +1,60 @@
// 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;
-using System.Collections.Concurrent;
-using System.Diagnostics.CodeAnalysis;
-using System.Reflection;
-using Microsoft.AspNetCore.Components.Reflection;
-using Microsoft.AspNetCore.Components.Rendering;
+using System.Runtime.InteropServices;
using Microsoft.AspNetCore.Internal;
-using static Microsoft.AspNetCore.Internal.LinkerFlags;
namespace Microsoft.AspNetCore.Components.Routing;
internal sealed class QueryParameterValueSupplier
{
- public static void ClearCache() => _cacheByType.Clear();
+ private readonly Dictionary, StringSegmentAccumulator> _queryParameterValuesByName = new(QueryParameterNameComparer.Instance);
- private static readonly ConcurrentDictionary _cacheByType = new();
-
- // These two arrays contain the same number of entries, and their corresponding positions refer to each other.
- // Holding the info like this means we can use Array.BinarySearch with less custom implementation.
- private readonly ReadOnlyMemory[] _queryParameterNames;
- private readonly QueryParameterDestination[] _destinations;
-
- public static QueryParameterValueSupplier? ForType([DynamicallyAccessedMembers(Component)] Type componentType)
+ public void ReadParametersFromQuery(ReadOnlyMemory query)
{
- if (!_cacheByType.TryGetValue(componentType, out var instanceOrNull))
- {
- // If the component doesn't have any query parameters, store a null value for it
- // so we know the upstream code can't try to render query parameter frames for it.
- var sortedMappings = GetSortedMappings(componentType);
- instanceOrNull = sortedMappings == null ? null : new QueryParameterValueSupplier(sortedMappings);
- _cacheByType.TryAdd(componentType, instanceOrNull);
- }
+ _queryParameterValuesByName.Clear();
- return instanceOrNull;
- }
+ var queryStringEnumerable = new QueryStringEnumerable(query);
- private QueryParameterValueSupplier(QueryParameterMapping[] sortedMappings)
- {
- _queryParameterNames = new ReadOnlyMemory[sortedMappings.Length];
- _destinations = new QueryParameterDestination[sortedMappings.Length];
- for (var i = 0; i < sortedMappings.Length; i++)
+ foreach (var suppliedPair in queryStringEnumerable)
{
- ref var mapping = ref sortedMappings[i];
- _queryParameterNames[i] = mapping.QueryParameterName;
- _destinations[i] = mapping.Destination;
+ var decodedName = suppliedPair.DecodeName();
+ var decodedValue = suppliedPair.DecodeValue();
+
+ // This is safe because we don't mutate the dictionary while the ref local is in scope.
+ ref var values = ref CollectionsMarshal.GetValueRefOrAddDefault(_queryParameterValuesByName, decodedName, out _);
+ values.Add(decodedValue);
}
}
- public void RenderParametersFromQueryString(RenderTreeBuilder builder, ReadOnlyMemory queryString)
+ public object? GetQueryParameterValue(Type targetType, string queryParameterName)
{
- // If there's no querystring contents, we can skip renting from the pool
- if (queryString.IsEmpty)
- {
- for (var destinationIndex = 0; destinationIndex < _destinations.Length; destinationIndex++)
- {
- ref var destination = ref _destinations[destinationIndex];
- var blankValue = destination.IsArray ? destination.Parser.ParseMultiple(default, string.Empty) : null;
- builder.AddComponentParameter(0, destination.ComponentParameterName, blankValue);
- }
- return;
- }
+ var isArray = targetType.IsArray;
+ var elementType = isArray ? targetType.GetElementType()! : targetType;
- // Temporary workspace in which we accumulate the data while walking the querystring.
- var valuesByMapping = ArrayPool.Shared.Rent(_destinations.Length);
-
- try
+ if (!UrlValueConstraint.TryGetByTargetType(elementType, out var parser))
{
- // Capture values by destination in a single pass through the querystring
- var queryStringEnumerable = new QueryStringEnumerable(queryString);
- foreach (var suppliedPair in queryStringEnumerable)
- {
- var decodedName = suppliedPair.DecodeName();
- var mappingIndex = Array.BinarySearch(_queryParameterNames, decodedName, QueryParameterNameComparer.Instance);
- if (mappingIndex >= 0)
- {
- var decodedValue = suppliedPair.DecodeValue();
-
- if (_destinations[mappingIndex].IsArray)
- {
- valuesByMapping[mappingIndex].Add(decodedValue);
- }
- else
- {
- valuesByMapping[mappingIndex].SetSingle(decodedValue);
- }
- }
- }
-
- // Finally, emit the parameter attributes by parsing all the string segments and building arrays
- for (var mappingIndex = 0; mappingIndex < _destinations.Length; mappingIndex++)
- {
- ref var destination = ref _destinations[mappingIndex];
- ref var values = ref valuesByMapping[mappingIndex];
+ throw new InvalidOperationException($"Querystring values cannot be parsed as type '{elementType}'.");
+ }
- var parsedValue = destination.IsArray
- ? destination.Parser.ParseMultiple(values, destination.ComponentParameterName)
- : values.Count == 0
- ? default
- : destination.Parser.Parse(values[0].Span, destination.ComponentParameterName);
+ var values = _queryParameterValuesByName.GetValueOrDefault(queryParameterName.AsMemory());
- builder.AddComponentParameter(0, destination.ComponentParameterName, parsedValue);
- }
- }
- finally
+ if (isArray)
{
- ArrayPool.Shared.Return(valuesByMapping, true);
+ return parser.ParseMultiple(values, queryParameterName);
}
- }
-
- private static QueryParameterMapping[]? GetSortedMappings([DynamicallyAccessedMembers(Component)] Type componentType)
- {
- var candidateProperties = MemberAssignment.GetPropertiesIncludingInherited(componentType, ComponentProperties.BindablePropertyFlags);
- HashSet>? usedQueryParameterNames = null;
- List? mappings = null;
- foreach (var propertyInfo in candidateProperties)
+ if (values.Count > 0)
{
- if (!propertyInfo.IsDefined(typeof(ParameterAttribute)))
- {
- continue;
- }
-
- var fromQueryAttribute = propertyInfo.GetCustomAttribute();
- if (fromQueryAttribute is not null)
- {
- // Found a parameter that's assignable from querystring
- var componentParameterName = propertyInfo.Name;
- var queryParameterName = (string.IsNullOrEmpty(fromQueryAttribute.Name)
- ? componentParameterName
- : fromQueryAttribute.Name).AsMemory();
-
- // If it's an array type, capture that info and prepare to parse the element type
- Type effectiveType = propertyInfo.PropertyType;
- var isArray = false;
- if (effectiveType.IsArray)
- {
- isArray = true;
- effectiveType = effectiveType.GetElementType()!;
- }
-
- if (!UrlValueConstraint.TryGetByTargetType(effectiveType, out var parser))
- {
- throw new NotSupportedException($"Querystring values cannot be parsed as type '{propertyInfo.PropertyType}'.");
- }
-
- // Add the destination for this component parameter name
- usedQueryParameterNames ??= new(QueryParameterNameComparer.Instance);
- if (usedQueryParameterNames.Contains(queryParameterName))
- {
- throw new InvalidOperationException($"The component '{componentType}' declares more than one mapping for the query parameter '{queryParameterName}'.");
- }
- usedQueryParameterNames.Add(queryParameterName);
-
- mappings ??= new();
- mappings.Add(new QueryParameterMapping
- {
- QueryParameterName = queryParameterName,
- Destination = new QueryParameterDestination(componentParameterName, parser, isArray)
- });
- }
+ return parser.Parse(values[0].Span, queryParameterName);
}
- mappings?.Sort((a, b) => QueryParameterNameComparer.Instance.Compare(a.QueryParameterName, b.QueryParameterName));
- return mappings?.ToArray();
- }
-
- private readonly struct QueryParameterMapping
- {
- public ReadOnlyMemory QueryParameterName { get; init; }
- public QueryParameterDestination Destination { get; init; }
+ return default;
}
- private readonly struct QueryParameterDestination
+ public static bool CanSupplyValueForType(Type targetType)
{
- public readonly string ComponentParameterName;
- public readonly UrlValueConstraint Parser;
- public readonly bool IsArray;
-
- public QueryParameterDestination(string componentParameterName, UrlValueConstraint parser, bool isArray)
- {
- ComponentParameterName = componentParameterName;
- Parser = parser;
- IsArray = isArray;
- }
+ var elementType = targetType.IsArray ? targetType.GetElementType()! : targetType;
+ return UrlValueConstraint.TryGetByTargetType(elementType, out _);
}
}
diff --git a/src/Components/Components/src/SupplyParameterFromQueryAttribute.cs b/src/Components/Components/src/SupplyParameterFromQueryAttribute.cs
index f88fe737c598..ffae75576ff7 100644
--- a/src/Components/Components/src/SupplyParameterFromQueryAttribute.cs
+++ b/src/Components/Components/src/SupplyParameterFromQueryAttribute.cs
@@ -8,11 +8,11 @@ namespace Microsoft.AspNetCore.Components;
/// current URL querystring. They may also supply further values if the URL querystring changes.
///
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
-public sealed class SupplyParameterFromQueryAttribute : Attribute
+public sealed class SupplyParameterFromQueryAttribute : CascadingParameterAttributeBase
{
///
/// Gets or sets the name of the querystring parameter. If null, the querystring
/// parameter is assumed to have the same name as the associated property.
///
- public string? Name { get; set; }
+ public override string? Name { get; set; }
}
diff --git a/src/Components/Components/test/CascadingModelBinderTest.cs b/src/Components/Components/test/CascadingModelBinderTest.cs
index c20703b51d8f..48a6f5ecff15 100644
--- a/src/Components/Components/test/CascadingModelBinderTest.cs
+++ b/src/Components/Components/test/CascadingModelBinderTest.cs
@@ -20,6 +20,7 @@ public CascadingModelBinderTest()
_navigationManager = new TestNavigationManager();
serviceCollection.AddSingleton(_navigationManager);
serviceCollection.AddSingleton();
+ serviceCollection.AddSingleton();
var services = serviceCollection.BuildServiceProvider();
_renderer = new TestRenderer(services);
}
diff --git a/src/Components/Components/test/CascadingParameterStateTest.cs b/src/Components/Components/test/CascadingParameterStateTest.cs
index 6edf09f31e21..89a1a0148402 100644
--- a/src/Components/Components/test/CascadingParameterStateTest.cs
+++ b/src/Components/Components/test/CascadingParameterStateTest.cs
@@ -1,7 +1,6 @@
// 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;
@@ -83,7 +82,7 @@ public void FindCascadingParameters_IfHasPartialMatchesInAncestors_ReturnsMatche
// Assert
Assert.Collection(result, match =>
{
- Assert.Equal(nameof(ComponentWithCascadingParams.CascadingParam2), match.LocalValueName);
+ Assert.Equal(nameof(ComponentWithCascadingParams.CascadingParam2), match.ParameterInfo.PropertyName);
Assert.Same(states[1].Component, match.ValueSupplier);
});
}
@@ -103,15 +102,15 @@ public void FindCascadingParameters_IfHasMultipleMatchesInAncestors_ReturnsMatch
var result = CascadingParameterState.FindCascadingParameters(states.Last());
// Assert
- Assert.Collection(result.OrderBy(x => x.LocalValueName),
+ Assert.Collection(result.OrderBy(x => x.ParameterInfo.PropertyName),
match =>
{
- Assert.Equal(nameof(ComponentWithCascadingParams.CascadingParam1), match.LocalValueName);
+ Assert.Equal(nameof(ComponentWithCascadingParams.CascadingParam1), match.ParameterInfo.PropertyName);
Assert.Same(states[3].Component, match.ValueSupplier);
},
match =>
{
- Assert.Equal(nameof(ComponentWithCascadingParams.CascadingParam2), match.LocalValueName);
+ Assert.Equal(nameof(ComponentWithCascadingParams.CascadingParam2), match.ParameterInfo.PropertyName);
Assert.Same(states[1].Component, match.ValueSupplier);
});
}
@@ -129,15 +128,15 @@ public void FindCascadingParameters_InheritedParameters_ReturnsMatches()
var result = CascadingParameterState.FindCascadingParameters(states.Last());
// Assert
- Assert.Collection(result.OrderBy(x => x.LocalValueName),
+ Assert.Collection(result.OrderBy(x => x.ParameterInfo.PropertyName),
match =>
{
- Assert.Equal(nameof(ComponentWithCascadingParams.CascadingParam1), match.LocalValueName);
+ Assert.Equal(nameof(ComponentWithCascadingParams.CascadingParam1), match.ParameterInfo.PropertyName);
Assert.Same(states[0].Component, match.ValueSupplier);
},
match =>
{
- Assert.Equal(nameof(ComponentWithInheritedCascadingParams.CascadingParam3), match.LocalValueName);
+ Assert.Equal(nameof(ComponentWithInheritedCascadingParams.CascadingParam3), match.ParameterInfo.PropertyName);
Assert.Same(states[1].Component, match.ValueSupplier);
});
}
@@ -156,7 +155,7 @@ public void FindCascadingParameters_ComponentRequestsBaseType_ReturnsMatches()
// Assert
Assert.Collection(result, match =>
{
- Assert.Equal(nameof(ComponentWithGenericCascadingParam