diff --git a/Directory.Build.targets b/Directory.Build.targets index 077353216668..6d564cde7912 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -1,7 +1,5 @@ - - $([MSBuild]::ValueOrDefault($(IsTrimmable),'false')) $(EnableAOTAnalyzer) diff --git a/eng/CodeGen.proj b/eng/CodeGen.proj index d5d8f0a5f6b6..1de7e09de4db 100644 --- a/eng/CodeGen.proj +++ b/eng/CodeGen.proj @@ -24,12 +24,10 @@ <_RequiresDelayedBuild Include="@(_ProvidesReferenceOrRequiresDelay->WithMetadataValue('RequiresDelayedBuild','true')->Distinct())" /> <_SharedFrameworkAndPackageRef Include="@(_ProjectReferenceProvider->WithMetadataValue('IsAspNetCoreApp','true')->WithMetadataValue('IsPackable', 'true'))" /> <_SharedFrameworkRef Include="@(_ProjectReferenceProvider->WithMetadataValue('IsAspNetCoreApp','true')->WithMetadataValue('IsPackable', 'false'))" /> + <_TrimmableProject Include="@(_ProjectReferenceProvider->WithMetadataValue('IsTrimmable', 'true'))" /> <_ShippingAssemblyWithDupes Include="@(_ProjectReferenceProvider->WithMetadataValue('IsAspNetCoreApp', 'true'))" /> <_ShippingAssemblyWithDupes Include="@(_ProjectReferenceProvider->WithMetadataValue('IsShippingPackage', 'true'))" /> <_ShippingAssembly Include="@(_ShippingAssemblyWithDupes->Distinct())" /> - - - <_TrimmableProject Include="@(_ProvidesReferenceOrRequiresDelay->WithMetadataValue('IsTrimmable', 'true')->WithMetadataValue('IsProjectReferenceProvider','true')->Distinct())" /> diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index eecba9b68efa..bbf3caca0f44 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -41,288 +41,288 @@ https://github.com/dotnet/efcore 8f52c86d7a31810711e3fa4c56a970bc10a1b026 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/source-build-externals - e3cc6c792114ebdfe6627742d2820dbe1ae5bc47 + 8fc77fa8f591051da1120ebb76c3795b7b584495 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 https://github.com/dotnet/xdt @@ -352,16 +352,16 @@ https://github.com/dotnet/roslyn 1aa759af23d2a29043ea44fcef5bd6823dafa5d0 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 - + https://github.com/dotnet/runtime - 4122c63a13cfe40e97ac1f9ef01d8110a66943f4 + f8c110b8003d68cc635add4ca791d6cf2e645561 https://github.com/dotnet/winforms diff --git a/eng/Versions.props b/eng/Versions.props index fa0a8433f515..70c8340b33bf 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -11,7 +11,7 @@ 0 1 true - 7.0.0-preview3 + 7.0.0-preview @@ -64,78 +64,78 @@ --> - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 9.0.0-alpha.1.23420.1 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 - 8.0.0-rc.2.23426.4 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 + 8.0.0-rc.1.23421.3 9.0.0-alpha.1.23421.4 @@ -162,13 +162,13 @@ 8.0.0-beta.23425.2 8.0.0-beta.23425.2 - 8.0.0-alpha.1.23424.1 + 8.0.0-alpha.1.23418.1 8.0.0-alpha.1.23424.1 2.1.0-beta.23409.1 - 8.0.0-rc.2.23426.4 + 8.0.0-rc.1.23421.3 7.0.0-preview.22423.2 diff --git a/eng/test-configuration.json b/eng/test-configuration.json index 2dd203e2ea05..3f5af7a1b766 100644 --- a/eng/test-configuration.json +++ b/eng/test-configuration.json @@ -22,7 +22,6 @@ {"testName": {"contains": "HEADERS_Received_SecondRequest_ConnectProtocolReset"}}, {"testName": {"contains": "ClientUsingOldCallWithNewProtocol"}}, {"testName": {"contains": "CertificateChangedOnDisk"}}, - {"testName": {"contains": "CertificateChangedOnDisk_Symlink"}}, {"testAssembly": {"contains": "IIS"}}, {"testAssembly": {"contains": "Template"}}, {"failureMessage": {"contains":"(Site is started but no worker process found)"}}, diff --git a/eng/tools/GenerateFiles/Directory.Build.props.in b/eng/tools/GenerateFiles/Directory.Build.props.in index 619ec1ded3d9..dd4ea40b86b6 100644 --- a/eng/tools/GenerateFiles/Directory.Build.props.in +++ b/eng/tools/GenerateFiles/Directory.Build.props.in @@ -8,4 +8,11 @@ true + + + + + + + diff --git a/global.json b/global.json index 394a42897f46..5f4bd889b7dc 100644 --- a/global.json +++ b/global.json @@ -1,9 +1,9 @@ { "sdk": { - "version": "8.0.100-rc.2.23422.11" + "version": "8.0.100-rc.1.23381.2" }, "tools": { - "dotnet": "8.0.100-rc.2.23422.11", + "dotnet": "8.0.100-rc.1.23381.2", "runtimes": { "dotnet/x86": [ "$(MicrosoftNETCoreBrowserDebugHostTransportVersion)" diff --git a/src/Components/Components/src/CascadingParameterAttribute.cs b/src/Components/Components/src/CascadingParameterAttribute.cs index becc2ce1cb57..bb9be43a5b08 100644 --- a/src/Components/Components/src/CascadingParameterAttribute.cs +++ b/src/Components/Components/src/CascadingParameterAttribute.cs @@ -20,5 +20,5 @@ public sealed class CascadingParameterAttribute : CascadingParameterAttributeBas /// 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 index 47a50edce641..307743c890cb 100644 --- a/src/Components/Components/src/CascadingParameterAttributeBase.cs +++ b/src/Components/Components/src/CascadingParameterAttributeBase.cs @@ -8,6 +8,12 @@ namespace Microsoft.AspNetCore.Components; /// 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; } + /// /// Gets a flag indicating whether the cascading parameter should /// be supplied only once per component. diff --git a/src/Components/Components/src/CascadingValueServiceCollectionExtensions.cs b/src/Components/Components/src/CascadingValueServiceCollectionExtensions.cs index f4dc1ab0b892..07e0ae985b58 100644 --- a/src/Components/Components/src/CascadingValueServiceCollectionExtensions.cs +++ b/src/Components/Components/src/CascadingValueServiceCollectionExtensions.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.AspNetCore.Components; -using Microsoft.Extensions.DependencyInjection.Extensions; namespace Microsoft.Extensions.DependencyInjection; @@ -17,11 +16,11 @@ public static class CascadingValueServiceCollectionExtensions /// /// The value type. /// The . - /// A callback that supplies a fixed value within each service provider scope. + /// A callback that supplies a fixed value within each service provider scope. /// The . public static IServiceCollection AddCascadingValue( - this IServiceCollection serviceCollection, Func initialValueFactory) - => serviceCollection.AddScoped(sp => new CascadingValueSource(() => initialValueFactory(sp), isFixed: true)); + this IServiceCollection serviceCollection, Func valueFactory) + => serviceCollection.AddScoped(sp => new CascadingValueSource(() => valueFactory(sp), isFixed: true)); /// /// Adds a cascading value to the . This is equivalent to having @@ -30,11 +29,11 @@ public static IServiceCollection AddCascadingValue( /// The value type. /// The . /// A name for the cascading value. If set, can be configured to match based on this name. - /// A callback that supplies a fixed value within each service provider scope. + /// A callback that supplies a fixed value within each service provider scope. /// The . public static IServiceCollection AddCascadingValue( - this IServiceCollection serviceCollection, string name, Func initialValueFactory) - => serviceCollection.AddScoped(sp => new CascadingValueSource(name, () => initialValueFactory(sp), isFixed: true)); + this IServiceCollection serviceCollection, string name, Func valueFactory) + => serviceCollection.AddScoped(sp => new CascadingValueSource(name, () => valueFactory(sp), isFixed: true)); /// /// Adds a cascading value to the . This is equivalent to having @@ -51,59 +50,4 @@ public static IServiceCollection AddCascadingValue( public static IServiceCollection AddCascadingValue( this IServiceCollection serviceCollection, Func> sourceFactory) => serviceCollection.AddScoped(sourceFactory); - - /// - /// Adds a cascading value to the , if none is already registered - /// with the value type. This is equivalent to having a fixed at - /// the root of the component hierarchy. - /// - /// The value type. - /// The . - /// A callback that supplies a fixed value within each service provider scope. - /// The . - public static void TryAddCascadingValue( - this IServiceCollection serviceCollection, Func valueFactory) - { - serviceCollection.TryAddEnumerable( - ServiceDescriptor.Scoped>( - sp => new CascadingValueSource(() => valueFactory(sp), isFixed: true))); - } - - /// - /// Adds a cascading value to the , if none is already registered - /// with the value type, regardless of the . This is equivalent to having a fixed - /// at the root of the component hierarchy. - /// - /// The value type. - /// The . - /// A name for the cascading value. If set, can be configured to match based on this name. - /// A callback that supplies a fixed value within each service provider scope. - /// The . - public static void TryAddCascadingValue( - this IServiceCollection serviceCollection, string name, Func valueFactory) - { - serviceCollection.TryAddEnumerable( - ServiceDescriptor.Scoped>( - sp => new CascadingValueSource(name, () => valueFactory(sp), isFixed: true))); - } - - /// - /// Adds a cascading value to the , if none is already registered - /// with the value type. This is equivalent to having a fixed at - /// the root of the component hierarchy. - /// - /// With this overload, you can supply a which allows you - /// to notify about updates to the value later, causing recipients to re-render. This overload should - /// only be used if you plan to update the value dynamically. - /// - /// The value type. - /// The . - /// A callback that supplies a within each service provider scope. - /// The . - public static void TryAddCascadingValue( - this IServiceCollection serviceCollection, Func> sourceFactory) - { - serviceCollection.TryAddEnumerable( - ServiceDescriptor.Scoped>(sourceFactory)); - } } diff --git a/src/Components/Components/src/CascadingValueSource.cs b/src/Components/Components/src/CascadingValueSource.cs index dbf9a4662265..680c022c71ee 100644 --- a/src/Components/Components/src/CascadingValueSource.cs +++ b/src/Components/Components/src/CascadingValueSource.cs @@ -48,20 +48,20 @@ public CascadingValueSource(string name, TValue value, bool isFixed) : this(valu /// /// Constructs an instance of . /// - /// A callback that produces the initial value when first required. + /// A callback that produces the initial value when first required. /// A flag to indicate whether the value is fixed. If false, all receipients will subscribe for update notifications, which you can issue by calling . These subscriptions come at a performance cost, so if the value will not change, set to true. - public CascadingValueSource(Func initialValueFactory, bool isFixed) : this(isFixed) + public CascadingValueSource(Func valueFactory, bool isFixed) : this(isFixed) { - _initialValueFactory = initialValueFactory; + _initialValueFactory = valueFactory; } /// /// Constructs an instance of . /// /// A name for the cascading value. If set, can be configured to match based on this name. - /// A callback that produces the initial value when first required. + /// A callback that produces the initial value when first required. /// A flag to indicate whether the value is fixed. If false, all receipients will subscribe for update notifications, which you can issue by calling . These subscriptions come at a performance cost, so if the value will not change, set to true. - public CascadingValueSource(string name, Func initialValueFactory, bool isFixed) : this(initialValueFactory, isFixed) + public CascadingValueSource(string name, Func valueFactory, bool isFixed) : this(valueFactory, isFixed) { ArgumentNullException.ThrowIfNull(name); _name = name; diff --git a/src/Components/Components/src/PublicAPI.Unshipped.txt b/src/Components/Components/src/PublicAPI.Unshipped.txt index df77f9475253..f892dc2cbd74 100644 --- a/src/Components/Components/src/PublicAPI.Unshipped.txt +++ b/src/Components/Components/src/PublicAPI.Unshipped.txt @@ -1,4 +1,6 @@ #nullable enable +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.CascadingParameterAttributeBase Microsoft.AspNetCore.Components.CascadingParameterAttributeBase.CascadingParameterAttributeBase() -> void @@ -8,9 +10,9 @@ Microsoft.AspNetCore.Components.CascadingParameterInfo.CascadingParameterInfo() Microsoft.AspNetCore.Components.CascadingParameterInfo.PropertyName.get -> string! Microsoft.AspNetCore.Components.CascadingParameterInfo.PropertyType.get -> System.Type! Microsoft.AspNetCore.Components.CascadingValueSource -Microsoft.AspNetCore.Components.CascadingValueSource.CascadingValueSource(string! name, System.Func! initialValueFactory, bool isFixed) -> void +Microsoft.AspNetCore.Components.CascadingValueSource.CascadingValueSource(string! name, System.Func! valueFactory, bool isFixed) -> void Microsoft.AspNetCore.Components.CascadingValueSource.CascadingValueSource(string! name, TValue value, bool isFixed) -> void -Microsoft.AspNetCore.Components.CascadingValueSource.CascadingValueSource(System.Func! initialValueFactory, bool isFixed) -> void +Microsoft.AspNetCore.Components.CascadingValueSource.CascadingValueSource(System.Func! valueFactory, bool isFixed) -> void Microsoft.AspNetCore.Components.CascadingValueSource.CascadingValueSource(TValue value, bool isFixed) -> void Microsoft.AspNetCore.Components.CascadingValueSource.NotifyChangedAsync() -> System.Threading.Tasks.Task! Microsoft.AspNetCore.Components.CascadingValueSource.NotifyChangedAsync(TValue newValue) -> System.Threading.Tasks.Task! @@ -24,10 +26,10 @@ Microsoft.AspNetCore.Components.RenderHandle.DispatchExceptionAsync(System.Excep Microsoft.AspNetCore.Components.NavigationManager.ToAbsoluteUri(string? relativeUri) -> System.Uri! Microsoft.AspNetCore.Components.Rendering.ComponentState.LogicalParentComponentState.get -> Microsoft.AspNetCore.Components.Rendering.ComponentState? Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder.AddComponentParameter(int sequence, string! name, Microsoft.AspNetCore.Components.IComponentRenderMode! renderMode) -> void +Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder.AddComponentRenderMode(int sequence, Microsoft.AspNetCore.Components.IComponentRenderMode! renderMode) -> void *REMOVED*Microsoft.AspNetCore.Components.RouteData.RouteData(System.Type! pageType, System.Collections.Generic.IReadOnlyDictionary! routeValues) -> void *REMOVED*Microsoft.AspNetCore.Components.RouteData.RouteValues.get -> System.Collections.Generic.IReadOnlyDictionary! -Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder.AddComponentRenderMode(Microsoft.AspNetCore.Components.IComponentRenderMode! renderMode) -> void -Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder.AddNamedEvent(string! eventType, string! assignedName) -> void +Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder.AddNamedEvent(int sequence, string! eventType, string! assignedName) -> void Microsoft.AspNetCore.Components.RenderTree.ComponentFrameFlags Microsoft.AspNetCore.Components.RenderTree.ComponentFrameFlags.HasCallerSpecifiedRenderMode = 1 -> Microsoft.AspNetCore.Components.RenderTree.ComponentFrameFlags Microsoft.AspNetCore.Components.RenderTree.NamedEventChange @@ -80,20 +82,25 @@ Microsoft.AspNetCore.Components.Sections.SectionOutlet.SectionOutlet() -> void Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder.AddComponentParameter(int sequence, string! name, object? value) -> void Microsoft.AspNetCore.Components.StreamRenderingAttribute Microsoft.AspNetCore.Components.StreamRenderingAttribute.Enabled.get -> bool -Microsoft.AspNetCore.Components.StreamRenderingAttribute.StreamRenderingAttribute(bool enabled = true) -> void +Microsoft.AspNetCore.Components.StreamRenderingAttribute.StreamRenderingAttribute(bool enabled) -> void +*REMOVED*Microsoft.AspNetCore.Components.CascadingParameterAttribute.Name.get -> string? +*REMOVED*Microsoft.AspNetCore.Components.CascadingParameterAttribute.Name.set -> void Microsoft.AspNetCore.Components.SupplyParameterFromQueryProviderServiceCollectionExtensions Microsoft.Extensions.DependencyInjection.CascadingValueServiceCollectionExtensions +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 static Microsoft.AspNetCore.Components.SupplyParameterFromQueryProviderServiceCollectionExtensions.AddSupplyValueFromQueryProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! -static Microsoft.Extensions.DependencyInjection.CascadingValueServiceCollectionExtensions.AddCascadingValue(this Microsoft.Extensions.DependencyInjection.IServiceCollection! serviceCollection, string! name, System.Func! initialValueFactory) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! +static Microsoft.Extensions.DependencyInjection.CascadingValueServiceCollectionExtensions.AddCascadingValue(this Microsoft.Extensions.DependencyInjection.IServiceCollection! serviceCollection, string! name, System.Func! valueFactory) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! static Microsoft.Extensions.DependencyInjection.CascadingValueServiceCollectionExtensions.AddCascadingValue(this Microsoft.Extensions.DependencyInjection.IServiceCollection! serviceCollection, System.Func!>! sourceFactory) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! -static Microsoft.Extensions.DependencyInjection.CascadingValueServiceCollectionExtensions.AddCascadingValue(this Microsoft.Extensions.DependencyInjection.IServiceCollection! serviceCollection, System.Func! initialValueFactory) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! -static Microsoft.Extensions.DependencyInjection.CascadingValueServiceCollectionExtensions.TryAddCascadingValue(this Microsoft.Extensions.DependencyInjection.IServiceCollection! serviceCollection, string! name, System.Func! valueFactory) -> void -static Microsoft.Extensions.DependencyInjection.CascadingValueServiceCollectionExtensions.TryAddCascadingValue(this Microsoft.Extensions.DependencyInjection.IServiceCollection! serviceCollection, System.Func!>! sourceFactory) -> void -static Microsoft.Extensions.DependencyInjection.CascadingValueServiceCollectionExtensions.TryAddCascadingValue(this Microsoft.Extensions.DependencyInjection.IServiceCollection! serviceCollection, System.Func! valueFactory) -> void +static Microsoft.Extensions.DependencyInjection.CascadingValueServiceCollectionExtensions.AddCascadingValue(this Microsoft.Extensions.DependencyInjection.IServiceCollection! serviceCollection, System.Func! valueFactory) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! virtual Microsoft.AspNetCore.Components.NavigationManager.Refresh(bool forceReload = false) -> 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 diff --git a/src/Components/Components/src/Reflection/ComponentProperties.cs b/src/Components/Components/src/Reflection/ComponentProperties.cs index 9a0b3cbdfdb4..d49cfad7978a 100644 --- a/src/Components/Components/src/Reflection/ComponentProperties.cs +++ b/src/Components/Components/src/Reflection/ComponentProperties.cs @@ -184,7 +184,8 @@ private static void ThrowForUnknownIncomingParameterName([DynamicallyAccessedMem { throw new InvalidOperationException( $"Object of type '{targetType.FullName}' has a property matching the name '{parameterName}', " + - $"but it does not have [Parameter], [CascadingParameter], or any other parameter-supplying attribute."); + $"but it does not have [{nameof(ParameterAttribute)}], [{nameof(CascadingParameterAttribute)}] or " + + $"[SupplyParameterFromFormAttribute] applied."); } else { diff --git a/src/Components/Components/src/RenderModeAttribute.cs b/src/Components/Components/src/RenderModeAttribute.cs index 87fb58c0e9de..d9d7ff286270 100644 --- a/src/Components/Components/src/RenderModeAttribute.cs +++ b/src/Components/Components/src/RenderModeAttribute.cs @@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Components; /// be implemented to work across all render modes. Component authors should only specify /// a fixed rendering mode when the component is incapable of running in other modes. /// -[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] +[AttributeUsage(AttributeTargets.Class)] public abstract class RenderModeAttribute : Attribute { /// diff --git a/src/Components/Components/src/RenderTree/RenderTreeFrameArrayBuilder.cs b/src/Components/Components/src/RenderTree/RenderTreeFrameArrayBuilder.cs index 56cc77e9d966..3b946154a254 100644 --- a/src/Components/Components/src/RenderTree/RenderTreeFrameArrayBuilder.cs +++ b/src/Components/Components/src/RenderTree/RenderTreeFrameArrayBuilder.cs @@ -135,7 +135,7 @@ public void AppendRegion(int sequence) }; } - public void AppendComponentRenderMode(IComponentRenderMode renderMode) + public void AppendComponentRenderMode(int sequence, IComponentRenderMode renderMode) { if (_itemsInUse == _items.Length) { @@ -144,13 +144,13 @@ public void AppendComponentRenderMode(IComponentRenderMode renderMode) _items[_itemsInUse++] = new RenderTreeFrame { - SequenceField = 0, // We're only interested in one of these, so it's not useful to optimize diffing over multiple + SequenceField = sequence, FrameTypeField = RenderTreeFrameType.ComponentRenderMode, ComponentRenderModeField = renderMode, }; } - public void AppendNamedEvent(string eventType, string assignedName) + public void AppendNamedEvent(int sequence, string eventType, string assignedName) { if (_itemsInUse == _items.Length) { @@ -159,7 +159,7 @@ public void AppendNamedEvent(string eventType, string assignedName) _items[_itemsInUse++] = new RenderTreeFrame { - SequenceField = 0, // We're only interested in one of these per eventType, so it's not useful to optimize diffing over multiple + SequenceField = sequence, FrameTypeField = RenderTreeFrameType.NamedEvent, NamedEventTypeField = eventType, NamedEventAssignedNameField = assignedName, diff --git a/src/Components/Components/src/Rendering/RenderTreeBuilder.cs b/src/Components/Components/src/Rendering/RenderTreeBuilder.cs index 5d2cabc1ce4e..78f9cd39d57c 100644 --- a/src/Components/Components/src/Rendering/RenderTreeBuilder.cs +++ b/src/Components/Components/src/Rendering/RenderTreeBuilder.cs @@ -28,7 +28,7 @@ public sealed class RenderTreeBuilder : IDisposable private bool _hasSeenAddMultipleAttributes; private Dictionary? _seenAttributeNames; private IComponentRenderMode? _pendingComponentCallSiteRenderMode; // TODO: Remove when Razor compiler supports call-site @rendermode - private string? _pendingNamedSubmitEvent; // TODO: Remove when Razor compiler supports @formname + private (int Sequence, string AssignedName)? _pendingNamedSubmitEvent; // TODO: Remove when Razor compiler supports @formname /// /// The reserved parameter name used for supplying child content. @@ -83,9 +83,9 @@ public void CloseElement() // TODO: Remove this once Razor supports @formname private void CompletePendingNamedSubmitEvent() { - if (_pendingNamedSubmitEvent is not null) + if (_pendingNamedSubmitEvent is { } pendingNamedSubmitEvent) { - AddNamedEvent("onsubmit", _pendingNamedSubmitEvent); + AddNamedEvent(pendingNamedSubmitEvent.Sequence, "onsubmit", pendingNamedSubmitEvent.AssignedName); _pendingNamedSubmitEvent = default; } } @@ -241,7 +241,7 @@ public void AddAttribute(int sequence, string name, string? value) // That should compile directly as a call to AddNamedEvent. if (string.Equals(name, "@formname", StringComparison.Ordinal) && _lastNonAttributeFrameType == RenderTreeFrameType.Element) { - _pendingNamedSubmitEvent = value!; + _pendingNamedSubmitEvent = (sequence, value!); } else { @@ -623,7 +623,7 @@ public void CloseComponent() { if (_pendingComponentCallSiteRenderMode is not null) { - AddComponentRenderMode(_pendingComponentCallSiteRenderMode); + AddComponentRenderMode(0, _pendingComponentCallSiteRenderMode); _pendingComponentCallSiteRenderMode = null; } @@ -681,8 +681,9 @@ public void AddComponentReferenceCapture(int sequence, Action componentR /// /// Adds a frame indicating the render mode on the enclosing component frame. /// + /// An integer that represents the position of the instruction in the source code. /// The . - public void AddComponentRenderMode(IComponentRenderMode renderMode) + public void AddComponentRenderMode(int sequence, IComponentRenderMode renderMode) { ArgumentNullException.ThrowIfNull(renderMode); @@ -708,16 +709,17 @@ public void AddComponentRenderMode(IComponentRenderMode renderMode) parentFrame.ComponentFrameFlagsField |= ComponentFrameFlags.HasCallerSpecifiedRenderMode; - _entries.AppendComponentRenderMode(renderMode); + _entries.AppendComponentRenderMode(sequence, renderMode); _lastNonAttributeFrameType = RenderTreeFrameType.ComponentRenderMode; } /// /// Assigns a name to an event in the enclosing element. /// + /// An integer that represents the position of the instruction in the source code. /// The event type, e.g., 'onsubmit'. /// The application-assigned name. - public void AddNamedEvent(string eventType, string assignedName) + public void AddNamedEvent(int sequence, string eventType, string assignedName) { ArgumentNullException.ThrowIfNull(eventType); ArgumentException.ThrowIfNullOrEmpty(assignedName); @@ -731,7 +733,7 @@ public void AddNamedEvent(string eventType, string assignedName) throw new InvalidOperationException($"Named events may only be added as children of frames of type {RenderTreeFrameType.Element}"); } - _entries.AppendNamedEvent(eventType, assignedName); + _entries.AppendNamedEvent(sequence, eventType, assignedName); _lastNonAttributeFrameType = RenderTreeFrameType.NamedEvent; } diff --git a/src/Components/Components/src/StreamRenderingAttribute.cs b/src/Components/Components/src/StreamRenderingAttribute.cs index 3b96783de037..6abe38053954 100644 --- a/src/Components/Components/src/StreamRenderingAttribute.cs +++ b/src/Components/Components/src/StreamRenderingAttribute.cs @@ -18,8 +18,8 @@ public class StreamRenderingAttribute : Attribute /// /// Constructs an instance of /// - /// A flag to indicate whether this component and its descendants should stream their rendering. The default value is true. - public StreamRenderingAttribute(bool enabled = true) + /// A flag to indicate whether this component and its descendants should stream their rendering. + public StreamRenderingAttribute(bool enabled) { Enabled = enabled; } diff --git a/src/Components/Components/src/SupplyParameterFromQueryAttribute.cs b/src/Components/Components/src/SupplyParameterFromQueryAttribute.cs index 9177a8d652c2..ffae75576ff7 100644 --- a/src/Components/Components/src/SupplyParameterFromQueryAttribute.cs +++ b/src/Components/Components/src/SupplyParameterFromQueryAttribute.cs @@ -14,5 +14,5 @@ public sealed class SupplyParameterFromQueryAttribute : CascadingParameterAttrib /// 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/src/SupplyParameterFromQueryProviderServiceCollectionExtensions.cs b/src/Components/Components/src/SupplyParameterFromQueryProviderServiceCollectionExtensions.cs index 21cb8c98db19..3de3ab99ee9a 100644 --- a/src/Components/Components/src/SupplyParameterFromQueryProviderServiceCollectionExtensions.cs +++ b/src/Components/Components/src/SupplyParameterFromQueryProviderServiceCollectionExtensions.cs @@ -50,8 +50,7 @@ public bool CanSupplyValue(in CascadingParameterInfo parameterInfo) UpdateQueryParameters(); } - var attribute = (SupplyParameterFromQueryAttribute)parameterInfo.Attribute; // Must be a valid cast because we check in CanSupplyValue - var queryParameterName = attribute.Name ?? parameterInfo.PropertyName; + var queryParameterName = parameterInfo.Attribute.Name ?? parameterInfo.PropertyName; return _queryParameterValueSupplier.GetQueryParameterValue(parameterInfo.PropertyType, queryParameterName); } diff --git a/src/Components/Components/test/CascadingParameterStateTest.cs b/src/Components/Components/test/CascadingParameterStateTest.cs index e055b9c70801..ed6420fffb25 100644 --- a/src/Components/Components/test/CascadingParameterStateTest.cs +++ b/src/Components/Components/test/CascadingParameterStateTest.cs @@ -476,6 +476,8 @@ class ComponentWithNamedCascadingParam : TestComponentBase class SupplyParameterWithSingleDeliveryAttribute : CascadingParameterAttributeBase { + public override string Name { get; set; } + internal override bool SingleDelivery => true; } @@ -521,3 +523,19 @@ public TestNavigationManager() } } } + +[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] +public sealed class SupplyParameterFromFormAttribute : CascadingParameterAttributeBase +{ + /// + /// Gets or sets the name for the parameter. The name is used to match + /// the form data and decide whether or not the value needs to be bound. + /// + public override string Name { get; set; } + + /// + /// Gets or sets the name for the handler. The name is used to match + /// the form data and decide whether or not the value needs to be bound. + /// + public string Handler { get; set; } +} diff --git a/src/Components/Components/test/CascadingParameterTest.cs b/src/Components/Components/test/CascadingParameterTest.cs index a99baeb96833..9ce74e19708b 100644 --- a/src/Components/Components/test/CascadingParameterTest.cs +++ b/src/Components/Components/test/CascadingParameterTest.cs @@ -727,51 +727,6 @@ public void OmitsSingleDeliveryCascadingParametersWhenUpdatingDirectParameters() }); } - [Fact] - public void CanUseTryAddPatternForCascadingValuesInServiceCollection_ValueFactory() - { - // Arrange - var services = new ServiceCollection(); - - // Act - services.TryAddCascadingValue(_ => new Type1()); - services.TryAddCascadingValue(_ => new Type1()); - services.TryAddCascadingValue(_ => new Type2()); - - // Assert - Assert.Equal(2, services.Count()); - } - - [Fact] - public void CanUseTryAddPatternForCascadingValuesInServiceCollection_NamedValueFactory() - { - // Arrange - var services = new ServiceCollection(); - - // Act - services.TryAddCascadingValue("Name1", _ => new Type1()); - services.TryAddCascadingValue("Name2", _ => new Type1()); - services.TryAddCascadingValue("Name3", _ => new Type2()); - - // Assert - Assert.Equal(2, services.Count()); - } - - [Fact] - public void CanUseTryAddPatternForCascadingValuesInServiceCollection_CascadingValueSource() - { - // Arrange - var services = new ServiceCollection(); - - // Act - services.TryAddCascadingValue(_ => new CascadingValueSource("Name1", new Type1(), false)); - services.TryAddCascadingValue(_ => new CascadingValueSource("Name2", new Type1(), false)); - services.TryAddCascadingValue(_ => new CascadingValueSource("Name3", new Type2(), false)); - - // Assert - Assert.Equal(2, services.Count()); - } - private class SingleDeliveryValue(string text) { public string Text => text; @@ -779,6 +734,8 @@ private class SingleDeliveryValue(string text) private class SingleDeliveryCascadingParameterAttribute : CascadingParameterAttributeBase { + public override string Name { get; set; } + internal override bool SingleDelivery => true; } @@ -895,11 +852,13 @@ class SecondCascadingParameterConsumerComponent : CascadingParameterCons [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] class CustomCascadingParameter1Attribute : CascadingParameterAttributeBase { + public override string Name { get; set; } } [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] class CustomCascadingParameter2Attribute : CascadingParameterAttributeBase { + public override string Name { get; set; } } class CustomCascadingValueProducer : AutoRenderComponent, ICascadingValueSupplier @@ -945,7 +904,7 @@ void ICascadingValueSupplier.Unsubscribe(ComponentState subscriber, in Cascading class CustomCascadingValueConsumer1 : AutoRenderComponent { - [CustomCascadingParameter1] + [CustomCascadingParameter1(Name = nameof(Value))] public object Value { get; set; } protected override void BuildRenderTree(RenderTreeBuilder builder) @@ -956,7 +915,7 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) class CustomCascadingValueConsumer2 : AutoRenderComponent { - [CustomCascadingParameter2] + [CustomCascadingParameter2(Name = nameof(Value))] public object Value { get; set; } protected override void BuildRenderTree(RenderTreeBuilder builder) @@ -985,7 +944,4 @@ public void ChangeValue(string newValue) StringValue = newValue; } } - - class Type1 { } - class Type2 { } } diff --git a/src/Components/Components/test/ComponentFactoryTest.cs b/src/Components/Components/test/ComponentFactoryTest.cs index fd18139905bf..7eca91d4471a 100644 --- a/src/Components/Components/test/ComponentFactoryTest.cs +++ b/src/Components/Components/test/ComponentFactoryTest.cs @@ -151,29 +151,6 @@ public void InstantiateComponent_WithRenderModeOnComponent_UsesRenderModeResolve Assert.IsType(renderer.SuppliedRenderMode); } - [Fact] - public void InstantiateComponent_WithDerivedRenderModeOnDerivedComponent_CausesAmbiguousMatchException() - { - // We could allow derived components to override the rendermode, but: - // [1] It's unclear how that would be legitimate. If the base specifies a rendermode, it's saying - // it only works in that mode. It wouldn't be safe for a derived type to change that. - // [2] If we did want to implement this, we'd need to implement our own inheritance chain walking - // to make sure we find the rendermode from the *closest* ancestor type. GetCustomAttributes - // on its own isn't documented to return the results in any specific order. - // Since issue [1] makes it unclear we'd want to support this, for now we don't. - - // Arrange - var resolvedComponent = new ComponentWithInjectProperties(); - var componentType = typeof(DerivedComponentWithRenderMode); - var renderer = new RendererWithResolveComponentForRenderMode(resolvedComponent); - var componentActivator = new DefaultComponentActivator(); - var factory = new ComponentFactory(componentActivator, renderer); - - // Act/Assert - Assert.Throws( - () => factory.InstantiateComponent(GetServiceProvider(), componentType, null, 1234)); - } - [Fact] public void InstantiateComponent_WithRenderModeOnCallSite_UsesRenderModeResolver() { @@ -313,16 +290,6 @@ public IComponent CreateInstance(Type componentType) } private class TestRenderMode : IComponentRenderMode { } - private class DerivedComponentRenderMode : IComponentRenderMode { } - - [DerivedComponentRenderMode] - private class DerivedComponentWithRenderMode : ComponentWithRenderMode - { - class DerivedComponentRenderModeAttribute : RenderModeAttribute - { - public override IComponentRenderMode Mode => new DerivedComponentRenderMode(); - } - } [OwnRenderMode] private class ComponentWithRenderMode : IComponent diff --git a/src/Components/Components/test/ParameterViewTest.Assignment.cs b/src/Components/Components/test/ParameterViewTest.Assignment.cs index f0308c0182a0..262f9584c4e4 100644 --- a/src/Components/Components/test/ParameterViewTest.Assignment.cs +++ b/src/Components/Components/test/ParameterViewTest.Assignment.cs @@ -183,7 +183,7 @@ public void IncomingParameterMatchesPropertyNotDeclaredAsParameter_Throws() Assert.Equal(default, target.IntProp); Assert.Equal( $"Object of type '{typeof(HasPropertyWithoutParameterAttribute).FullName}' has a property matching the name '{nameof(HasPropertyWithoutParameterAttribute.IntProp)}', " + - "but it does not have [Parameter], [CascadingParameter], or any other parameter-supplying attribute.", + $"but it does not have [{nameof(ParameterAttribute)}], [{nameof(CascadingParameterAttribute)}] or [{nameof(SupplyParameterFromFormAttribute)}] applied.", ex.Message); } diff --git a/src/Components/Components/test/RenderTreeDiffBuilderTest.cs b/src/Components/Components/test/RenderTreeDiffBuilderTest.cs index f1d0766f1841..4cce00158c87 100644 --- a/src/Components/Components/test/RenderTreeDiffBuilderTest.cs +++ b/src/Components/Components/test/RenderTreeDiffBuilderTest.cs @@ -2220,10 +2220,10 @@ public void RecognizesNamedEventBeingAdded() newTree.OpenElement(0, "existing"); newTree.AddAttribute(1, "attr1", "unrelated val1"); - newTree.AddNamedEvent("someevent1", "added to existing element"); + newTree.AddNamedEvent(2, "someevent1", "added to existing element"); newTree.CloseElement(); - newTree.OpenElement(2, "new element"); - newTree.AddNamedEvent("someevent2", "added with new element"); + newTree.OpenElement(3, "new element"); + newTree.AddNamedEvent(4, "someevent2", "added with new element"); newTree.CloseElement(); // Act @@ -2247,10 +2247,10 @@ public void RecognizesNamedEventBeingRemoved() { oldTree.OpenElement(0, "retaining"); oldTree.AddAttribute(1, "attr1", "unrelated val1"); - oldTree.AddNamedEvent("someevent1", "removing from retained element"); + oldTree.AddNamedEvent(2, "someevent1", "removing from retained element"); oldTree.CloseElement(); - oldTree.OpenElement(2, "removing"); - oldTree.AddNamedEvent("someevent2", "removed because element was removed"); + oldTree.OpenElement(3, "removing"); + oldTree.AddNamedEvent(4, "someevent2", "removed because element was removed"); oldTree.CloseElement(); newTree.OpenElement(0, "retaining"); @@ -2272,12 +2272,12 @@ public void RecognizesNamedEventBeingRemoved() public void RecognizesNamedEventBeingMoved() { oldTree.OpenElement(0, "elem"); - oldTree.AddNamedEvent("eventname", "assigned name"); + oldTree.AddNamedEvent(2, "eventname", "assigned name"); oldTree.CloseElement(); newTree.OpenElement(0, "elem"); newTree.AddAttribute(1, "attr1", "unrelated val1"); - newTree.AddNamedEvent("eventname", "assigned name"); + newTree.AddNamedEvent(2, "eventname", "assigned name"); newTree.CloseElement(); // Act @@ -2300,13 +2300,13 @@ public void RecognizesNamedEventBeingMoved() public void RecognizesNamedEventChangingAssignedName() { oldTree.OpenElement(0, "elem"); - oldTree.AddNamedEvent("eventname1", "original name"); - oldTree.AddNamedEvent("eventname2", "will be left unchanged"); + oldTree.AddNamedEvent(1, "eventname1", "original name"); + oldTree.AddNamedEvent(2, "eventname2", "will be left unchanged"); oldTree.CloseElement(); newTree.OpenElement(0, "elem"); - newTree.AddNamedEvent("eventname1", "changed name"); - newTree.AddNamedEvent("eventname2", "will be left unchanged"); + newTree.AddNamedEvent(1, "eventname1", "changed name"); + newTree.AddNamedEvent(2, "eventname2", "will be left unchanged"); newTree.CloseElement(); // Act diff --git a/src/Components/Components/test/RendererTest.cs b/src/Components/Components/test/RendererTest.cs index ea383da376ee..1b7a91792bad 100644 --- a/src/Components/Components/test/RendererTest.cs +++ b/src/Components/Components/test/RendererTest.cs @@ -5002,7 +5002,7 @@ public void ThrowsForUnknownRenderMode_AtCallSite() var component = new TestComponent(builder => { builder.OpenComponent(0); - builder.AddComponentRenderMode(new ComponentWithUnknownRenderMode.UnknownRenderMode()); + builder.AddComponentRenderMode(1, new ComponentWithUnknownRenderMode.UnknownRenderMode()); builder.CloseComponent(); }); @@ -5046,7 +5046,7 @@ public void RenderModeResolverCanSupplyComponent_CallSiteRenderMode() { builder.OpenComponent(0); builder.AddComponentParameter(1, nameof(MessageComponent.Message), "Some message"); - builder.AddComponentRenderMode(new SubstituteComponentRenderMode()); + builder.AddComponentRenderMode(2, new SubstituteComponentRenderMode()); builder.CloseComponent(); }); diff --git a/src/Components/Components/test/Rendering/RenderTreeBuilderTest.cs b/src/Components/Components/test/Rendering/RenderTreeBuilderTest.cs index 8ef9bbea6dac..07f6e5e4554b 100644 --- a/src/Components/Components/test/Rendering/RenderTreeBuilderTest.cs +++ b/src/Components/Components/test/Rendering/RenderTreeBuilderTest.cs @@ -2110,7 +2110,7 @@ public void CanAddComponentRenderMode() // Act builder.OpenComponent(0); builder.AddComponentParameter(1, "param", 123); - builder.AddComponentRenderMode(renderMode); + builder.AddComponentRenderMode(2, renderMode); builder.CloseComponent(); // Assert @@ -2122,7 +2122,7 @@ public void CanAddComponentRenderMode() Assert.True(frame.ComponentFrameFlags.HasFlag(ComponentFrameFlags.HasCallerSpecifiedRenderMode)); }, frame => AssertFrame.Attribute(frame, "param", 123, 1), - frame => AssertFrame.ComponentRenderMode(frame, renderMode)); + frame => AssertFrame.ComponentRenderMode(frame, renderMode, 2)); } [Fact] @@ -2135,7 +2135,7 @@ public void CannotAddComponentRenderModeToElement() // Act/Assert var ex = Assert.Throws(() => { - builder.AddComponentRenderMode(new TestRenderMode()); + builder.AddComponentRenderMode(1, new TestRenderMode()); }); Assert.Equal($"The enclosing frame is not of the required type '{nameof(RenderTreeFrameType.Component)}'.", ex.Message); } @@ -2150,7 +2150,7 @@ public void CannotAddNullComponentRenderMode() // Act/Assert var ex = Assert.Throws(() => { - builder.AddComponentRenderMode(null); + builder.AddComponentRenderMode(1, null); }); Assert.Equal("renderMode", ex.ParamName); } @@ -2161,7 +2161,7 @@ public void CannotAddParametersAfterComponentRenderMode() // Arrange var builder = new RenderTreeBuilder(); builder.OpenComponent(0); - builder.AddComponentRenderMode(new TestRenderMode()); + builder.AddComponentRenderMode(1, new TestRenderMode()); // Act/Assert var ex = Assert.Throws(() => @@ -2201,7 +2201,7 @@ public void TemporaryApiForCallSiteComponentRenderModeWorksEvenIfOtherParameterA }, frame => AssertFrame.Attribute(frame, "param", 123, 1), frame => AssertFrame.Attribute(frame, "anotherparam", 456, 3), - frame => AssertFrame.ComponentRenderMode(frame, renderMode)); + frame => AssertFrame.ComponentRenderMode(frame, renderMode, 0)); } [Fact] @@ -2214,7 +2214,7 @@ public void CanAddNamedEvent() // Act builder.OpenElement(0, "elem"); builder.AddAttribute(1, "attr", 123); - builder.AddNamedEvent("myeventtype", "my event name"); + builder.AddNamedEvent(2, "myeventtype", "my event name"); builder.CloseElement(); // Assert @@ -2235,7 +2235,7 @@ public void CannotAddNamedEventToComponent() // Act/Assert var ex = Assert.Throws(() => { - builder.AddNamedEvent("x", "y"); + builder.AddNamedEvent(1, "x", "y"); }); Assert.Equal($"Named events may only be added as children of frames of type {RenderTreeFrameType.Element}", ex.Message); } @@ -2250,7 +2250,7 @@ public void CannotAddNamedEventWithNullEventType() // Act/Assert var ex = Assert.Throws(() => { - builder.AddNamedEvent(null, "assigned name"); + builder.AddNamedEvent(1, null, "assigned name"); }); Assert.Equal("eventType", ex.ParamName); } @@ -2265,7 +2265,7 @@ public void CannotAddNamedEventWithNullAssignedName() // Act/Assert var ex = Assert.Throws(() => { - builder.AddNamedEvent("eventtype", null); + builder.AddNamedEvent(1, "eventtype", null); }); Assert.Equal("assignedName", ex.ParamName); } @@ -2280,7 +2280,7 @@ public void CannotAddNamedEventWithEmptyAssignedName() // Act/Assert var ex = Assert.Throws(() => { - builder.AddNamedEvent("eventtype", ""); + builder.AddNamedEvent(1, "eventtype", ""); }); Assert.Equal("assignedName", ex.ParamName); } @@ -2291,7 +2291,7 @@ public void CannotAddAttributesAfterNamedEvent() // Arrange var builder = new RenderTreeBuilder(); builder.OpenElement(0, "elem"); - builder.AddNamedEvent("someevent", "somename"); + builder.AddNamedEvent(1, "someevent", "somename"); // Act/Assert var ex = Assert.Throws(() => @@ -2325,7 +2325,7 @@ public void TemporaryApiForFormNameEventsWorksEvenIfAttributesAddedAfter() frame => AssertFrame.Element(frame, "div", 5, 0), frame => AssertFrame.Attribute(frame, "attr1", "123", 1), frame => AssertFrame.Attribute(frame, "attr2", "456", 3), - frame => AssertFrame.NamedEvent(frame, "onsubmit", "some custom name"), + frame => AssertFrame.NamedEvent(frame, "onsubmit", "some custom name", 2), frame => AssertFrame.Element(frame, "other", 1)); } diff --git a/src/Components/Endpoints/src/Builder/ConfiguredRenderModesMetadata.cs b/src/Components/Endpoints/src/Builder/ConfiguredRenderModesMetadata.cs deleted file mode 100644 index 65c99a924843..000000000000 --- a/src/Components/Endpoints/src/Builder/ConfiguredRenderModesMetadata.cs +++ /dev/null @@ -1,9 +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.Endpoints; - -internal class ConfiguredRenderModesMetadata(IComponentRenderMode[] configuredRenderModes) -{ - public IComponentRenderMode[] ConfiguredRenderModes => configuredRenderModes; -} diff --git a/src/Components/Endpoints/src/Builder/RazorComponentEndpointDataSource.cs b/src/Components/Endpoints/src/Builder/RazorComponentEndpointDataSource.cs index 8013ab54794d..eac2bd011495 100644 --- a/src/Components/Endpoints/src/Builder/RazorComponentEndpointDataSource.cs +++ b/src/Components/Endpoints/src/Builder/RazorComponentEndpointDataSource.cs @@ -24,27 +24,22 @@ internal class RazorComponentEndpointDataSource<[DynamicallyAccessedMembers(Comp private readonly IApplicationBuilder _applicationBuilder; private readonly RenderModeEndpointProvider[] _renderModeEndpointProviders; private readonly RazorComponentEndpointFactory _factory; - private readonly HotReloadService? _hotReloadService; - private List? _endpoints; - private CancellationTokenSource _cancellationTokenSource; - private IChangeToken _changeToken; - // Internal for testing. - internal ComponentApplicationBuilder Builder => _builder; - internal List> Conventions => _conventions; + private List? _endpoints; + // TODO: Implement endpoint data source updates https://github.com/dotnet/aspnetcore/issues/47026 + private readonly CancellationTokenSource _cancellationTokenSource; + private readonly IChangeToken _changeToken; public RazorComponentEndpointDataSource( ComponentApplicationBuilder builder, IEnumerable renderModeEndpointProviders, IApplicationBuilder applicationBuilder, - RazorComponentEndpointFactory factory, - HotReloadService? hotReloadService = null) + RazorComponentEndpointFactory factory) { _builder = builder; _applicationBuilder = applicationBuilder; _renderModeEndpointProviders = renderModeEndpointProviders.ToArray(); _factory = factory; - _hotReloadService = hotReloadService; DefaultBuilder = new RazorComponentsEndpointConventionBuilder( _lock, builder, @@ -67,7 +62,7 @@ public override IReadOnlyList Endpoints // The order is as follows: // * MapRazorComponents gets called and the data source gets created. // * The RazorComponentEndpointConventionBuilder is returned and the user gets a chance to call on it to add conventions. - // * The first request arrives and the DfaMatcherBuilder accesses the data sources to get the endpoints. + // * The first request arrives and the DfaMatcherBuilder acesses the data sources to get the endpoints. // * The endpoints get created and the conventions get applied. Initialize(); Debug.Assert(_changeToken != null); @@ -94,63 +89,47 @@ private void Initialize() private void UpdateEndpoints() { - lock (_lock) - { - var endpoints = new List(); - var context = _builder.Build(); - var configuredRenderModesMetadata = new ConfiguredRenderModesMetadata( - Options.ConfiguredRenderModes.ToArray()); + var endpoints = new List(); + var context = _builder.Build(); - foreach (var definition in context.Pages) - { - _factory.AddEndpoints(endpoints, typeof(TRootComponent), definition, _conventions, _finallyConventions, configuredRenderModesMetadata); - } + foreach (var definition in context.Pages) + { + _factory.AddEndpoints(endpoints, typeof(TRootComponent), definition, _conventions, _finallyConventions); + } - ICollection renderModes = Options.ConfiguredRenderModes; + ICollection renderModes = Options.ConfiguredRenderModes; - foreach (var renderMode in renderModes) + foreach (var renderMode in renderModes) + { + var found = false; + foreach (var provider in _renderModeEndpointProviders) { - var found = false; - foreach (var provider in _renderModeEndpointProviders) - { - if (provider.Supports(renderMode)) - { - found = true; - RenderModeEndpointProvider.AddEndpoints( - endpoints, - typeof(TRootComponent), - provider.GetEndpointBuilders(renderMode, _applicationBuilder.New()), - renderMode, - _conventions, - _finallyConventions); - } - } - - if (!found) + if (provider.Supports(renderMode)) { - throw new InvalidOperationException($"Unable to find a provider for the render mode: {renderMode.GetType().FullName}. This generally " + - "means that a call to 'AddWebAssemblyComponents' or 'AddServerComponents' is missing. " + - "For example, change builder.Services.AddRazorComponents() to builder.Services.AddRazorComponents().AddServerComponents()."); + found = true; + RenderModeEndpointProvider.AddEndpoints( + endpoints, + typeof(TRootComponent), + provider.GetEndpointBuilders(renderMode, _applicationBuilder.New()), + renderMode, + _conventions, + _finallyConventions); } } - var oldCancellationTokenSource = _cancellationTokenSource; - _endpoints = endpoints; - _cancellationTokenSource = new CancellationTokenSource(); - _changeToken = new CancellationChangeToken(_cancellationTokenSource.Token); - oldCancellationTokenSource?.Cancel(); - if (_hotReloadService is { MetadataUpdateSupported : true }) + if (!found) { - ChangeToken.OnChange(_hotReloadService.GetChangeToken, UpdateEndpoints); + throw new InvalidOperationException($"Unable to find a provider for the render mode: {renderMode.GetType().FullName}. This generally " + + $"means that a call to 'AddWebAssemblyComponents' or 'AddServerComponents' is missing. " + + $"Alternatively call 'AddWebAssemblyRenderMode', 'AddServerRenderMode' might be missing if you have set UseDeclaredRenderModes = false."); } } - } + _endpoints = endpoints; + } public override IChangeToken GetChangeToken() { - Initialize(); - Debug.Assert(_changeToken != null); - Debug.Assert(_endpoints != null); + // TODO: Handle updates if necessary (for hot reload). return _changeToken; } } diff --git a/src/Components/Endpoints/src/Builder/RazorComponentEndpointDataSourceFactory.cs b/src/Components/Endpoints/src/Builder/RazorComponentEndpointDataSourceFactory.cs index 3dcee668151a..3c66aa7264dc 100644 --- a/src/Components/Endpoints/src/Builder/RazorComponentEndpointDataSourceFactory.cs +++ b/src/Components/Endpoints/src/Builder/RazorComponentEndpointDataSourceFactory.cs @@ -14,16 +14,13 @@ internal class RazorComponentEndpointDataSourceFactory { private readonly RazorComponentEndpointFactory _factory; private readonly IEnumerable _providers; - private readonly HotReloadService? _hotReloadService; public RazorComponentEndpointDataSourceFactory( RazorComponentEndpointFactory factory, - IEnumerable providers, - HotReloadService? hotReloadService = null) + IEnumerable providers) { _factory = factory; _providers = providers; - _hotReloadService = hotReloadService; } public RazorComponentEndpointDataSource CreateDataSource<[DynamicallyAccessedMembers(Component)] TRootComponent>(IEndpointRouteBuilder endpoints) @@ -31,6 +28,6 @@ public RazorComponentEndpointDataSourceFactory( var builder = ComponentApplicationBuilder.GetBuilder() ?? DefaultRazorComponentApplication.Instance.GetBuilder(); - return new RazorComponentEndpointDataSource(builder, _providers, endpoints.CreateApplicationBuilder(), _factory, _hotReloadService); + return new RazorComponentEndpointDataSource(builder, _providers, endpoints.CreateApplicationBuilder(), _factory); } } diff --git a/src/Components/Endpoints/src/Builder/RazorComponentEndpointFactory.cs b/src/Components/Endpoints/src/Builder/RazorComponentEndpointFactory.cs index 117aaea090f3..c14ae30a8f2b 100644 --- a/src/Components/Endpoints/src/Builder/RazorComponentEndpointFactory.cs +++ b/src/Components/Endpoints/src/Builder/RazorComponentEndpointFactory.cs @@ -24,8 +24,7 @@ internal void AddEndpoints( [DynamicallyAccessedMembers(Component)] Type rootComponent, PageComponentInfo pageDefinition, IReadOnlyList> conventions, - IReadOnlyList> finallyConventions, - ConfiguredRenderModesMetadata configuredRenderModesMetadata) + IReadOnlyList> finallyConventions) { // We do not provide a way to establish the order or the name for the page routes. // Order is not supported in our client router. @@ -49,7 +48,6 @@ internal void AddEndpoints( builder.Metadata.Add(HttpMethodsMetadata); builder.Metadata.Add(new ComponentTypeMetadata(pageDefinition.Type)); builder.Metadata.Add(new RootComponentMetadata(rootComponent)); - builder.Metadata.Add(configuredRenderModesMetadata); foreach (var convention in conventions) { diff --git a/src/Components/Endpoints/src/DependencyInjection/HotReloadService.cs b/src/Components/Endpoints/src/DependencyInjection/HotReloadService.cs deleted file mode 100644 index 51680ca61034..000000000000 --- a/src/Components/Endpoints/src/DependencyInjection/HotReloadService.cs +++ /dev/null @@ -1,42 +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 System.Reflection.Metadata; -using Microsoft.Extensions.Primitives; - -[assembly: MetadataUpdateHandler(typeof(Microsoft.AspNetCore.Components.Endpoints.HotReloadService))] - -namespace Microsoft.AspNetCore.Components.Endpoints; - -internal sealed class HotReloadService : IDisposable -{ - public HotReloadService() - { - UpdateApplicationEvent += NotifyUpdateApplication; - MetadataUpdateSupported = MetadataUpdater.IsSupported; - } - - private CancellationTokenSource _tokenSource = new(); - private static event Action? UpdateApplicationEvent; - - public bool MetadataUpdateSupported { get; internal set; } - - public IChangeToken GetChangeToken() => new CancellationChangeToken(_tokenSource.Token); - - public static void UpdateApplication(Type[]? changedTypes) - { - UpdateApplicationEvent?.Invoke(changedTypes); - } - - private void NotifyUpdateApplication(Type[]? changedTypes) - { - var current = Interlocked.Exchange(ref _tokenSource, new CancellationTokenSource()); - current.Cancel(); - } - - public void Dispose() - { - UpdateApplicationEvent -= NotifyUpdateApplication; - _tokenSource.Dispose(); - } -} diff --git a/src/Components/Endpoints/src/DependencyInjection/HttpNavigationManager.cs b/src/Components/Endpoints/src/DependencyInjection/HttpNavigationManager.cs index 8098deb3b6ce..0da30332d2c4 100644 --- a/src/Components/Endpoints/src/DependencyInjection/HttpNavigationManager.cs +++ b/src/Components/Endpoints/src/DependencyInjection/HttpNavigationManager.cs @@ -11,7 +11,6 @@ internal sealed class HttpNavigationManager : NavigationManager, IHostEnvironmen protected override void NavigateToCore(string uri, bool forceLoad) { - var absoluteUriString = ToAbsoluteUri(uri).ToString(); - throw new NavigationException(absoluteUriString); + throw new NavigationException(uri); } } diff --git a/src/Components/Endpoints/src/DependencyInjection/RazorComponentsServiceCollectionExtensions.cs b/src/Components/Endpoints/src/DependencyInjection/RazorComponentsServiceCollectionExtensions.cs index d361c17687c4..3b143bb6cff2 100644 --- a/src/Components/Endpoints/src/DependencyInjection/RazorComponentsServiceCollectionExtensions.cs +++ b/src/Components/Endpoints/src/DependencyInjection/RazorComponentsServiceCollectionExtensions.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.CodeAnalysis; -using System.Reflection.Metadata; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Endpoints; using Microsoft.AspNetCore.Components.Endpoints.DependencyInjection; @@ -40,13 +39,12 @@ public static IRazorComponentsBuilder AddRazorComponents(this IServiceCollection services.TryAddSingleton(); + // Results + services.TryAddSingleton(); + // Endpoints services.TryAddSingleton(); services.TryAddSingleton(); - if (MetadataUpdater.IsSupported) - { - services.TryAddSingleton(); - } services.TryAddScoped(); // Common services required for components server side rendering @@ -65,7 +63,6 @@ public static IRazorComponentsBuilder AddRazorComponents(this IServiceCollection services.TryAddScoped(); services.TryAddScoped(sp => sp.GetRequiredService()); services.AddSupplyValueFromQueryProvider(); - services.TryAddCascadingValue(sp => sp.GetRequiredService().HttpContext); // Form handling services.AddSupplyValueFromFormProvider(); diff --git a/src/Components/Endpoints/src/PublicAPI.Unshipped.txt b/src/Components/Endpoints/src/PublicAPI.Unshipped.txt index a3ca0d2f9115..a4f923605d68 100644 --- a/src/Components/Endpoints/src/PublicAPI.Unshipped.txt +++ b/src/Components/Endpoints/src/PublicAPI.Unshipped.txt @@ -18,6 +18,25 @@ Microsoft.AspNetCore.Components.Endpoints.Infrastructure.RenderModeEndpointProvi Microsoft.AspNetCore.Components.Endpoints.Infrastructure.RenderModeEndpointProvider.RenderModeEndpointProvider() -> void Microsoft.AspNetCore.Components.Endpoints.IRazorComponentEndpointInvoker Microsoft.AspNetCore.Components.Endpoints.IRazorComponentEndpointInvoker.Render(Microsoft.AspNetCore.Http.HttpContext! context) -> System.Threading.Tasks.Task! +Microsoft.AspNetCore.Components.Endpoints.RazorComponentResult +Microsoft.AspNetCore.Components.Endpoints.RazorComponentResult.ComponentType.get -> System.Type! +Microsoft.AspNetCore.Components.Endpoints.RazorComponentResult.ContentType.get -> string? +Microsoft.AspNetCore.Components.Endpoints.RazorComponentResult.ContentType.set -> void +Microsoft.AspNetCore.Components.Endpoints.RazorComponentResult.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext! httpContext) -> System.Threading.Tasks.Task! +Microsoft.AspNetCore.Components.Endpoints.RazorComponentResult.Parameters.get -> System.Collections.Generic.IReadOnlyDictionary! +Microsoft.AspNetCore.Components.Endpoints.RazorComponentResult.PreventStreamingRendering.get -> bool +Microsoft.AspNetCore.Components.Endpoints.RazorComponentResult.PreventStreamingRendering.set -> void +Microsoft.AspNetCore.Components.Endpoints.RazorComponentResult.RazorComponentResult(System.Type! componentType) -> void +Microsoft.AspNetCore.Components.Endpoints.RazorComponentResult.RazorComponentResult(System.Type! componentType, object? parameters) -> void +Microsoft.AspNetCore.Components.Endpoints.RazorComponentResult.RazorComponentResult(System.Type! componentType, System.Collections.Generic.IReadOnlyDictionary? parameters) -> void +Microsoft.AspNetCore.Components.Endpoints.RazorComponentResult.StatusCode.get -> int? +Microsoft.AspNetCore.Components.Endpoints.RazorComponentResult.StatusCode.set -> void +Microsoft.AspNetCore.Components.Endpoints.RazorComponentResult +Microsoft.AspNetCore.Components.Endpoints.RazorComponentResult.RazorComponentResult() -> void +Microsoft.AspNetCore.Components.Endpoints.RazorComponentResult.RazorComponentResult(object! parameters) -> void +Microsoft.AspNetCore.Components.Endpoints.RazorComponentResult.RazorComponentResult(System.Collections.Generic.IReadOnlyDictionary! parameters) -> void +Microsoft.AspNetCore.Components.Endpoints.RazorComponentResultExecutor +Microsoft.AspNetCore.Components.Endpoints.RazorComponentResultExecutor.RazorComponentResultExecutor() -> void Microsoft.AspNetCore.Components.Endpoints.RazorComponentsOptions Microsoft.AspNetCore.Components.Endpoints.RazorComponentsOptions.MaxFormMappingCollectionSize.get -> int Microsoft.AspNetCore.Components.Endpoints.RazorComponentsOptions.MaxFormMappingCollectionSize.set -> void @@ -35,26 +54,11 @@ Microsoft.AspNetCore.Components.PersistedStateSerializationMode Microsoft.AspNetCore.Components.PersistedStateSerializationMode.Infer = 1 -> Microsoft.AspNetCore.Components.PersistedStateSerializationMode Microsoft.AspNetCore.Components.PersistedStateSerializationMode.Server = 2 -> Microsoft.AspNetCore.Components.PersistedStateSerializationMode Microsoft.AspNetCore.Components.PersistedStateSerializationMode.WebAssembly = 3 -> Microsoft.AspNetCore.Components.PersistedStateSerializationMode -Microsoft.AspNetCore.Http.HttpResults.RazorComponentResult -Microsoft.AspNetCore.Http.HttpResults.RazorComponentResult.ComponentType.get -> System.Type! -Microsoft.AspNetCore.Http.HttpResults.RazorComponentResult.ContentType.get -> string? -Microsoft.AspNetCore.Http.HttpResults.RazorComponentResult.ContentType.set -> void -Microsoft.AspNetCore.Http.HttpResults.RazorComponentResult.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext! httpContext) -> System.Threading.Tasks.Task! -Microsoft.AspNetCore.Http.HttpResults.RazorComponentResult.Parameters.get -> System.Collections.Generic.IReadOnlyDictionary! -Microsoft.AspNetCore.Http.HttpResults.RazorComponentResult.PreventStreamingRendering.get -> bool -Microsoft.AspNetCore.Http.HttpResults.RazorComponentResult.PreventStreamingRendering.set -> void -Microsoft.AspNetCore.Http.HttpResults.RazorComponentResult.RazorComponentResult(System.Type! componentType) -> void -Microsoft.AspNetCore.Http.HttpResults.RazorComponentResult.RazorComponentResult(System.Type! componentType, object! parameters) -> void -Microsoft.AspNetCore.Http.HttpResults.RazorComponentResult.RazorComponentResult(System.Type! componentType, System.Collections.Generic.IReadOnlyDictionary! parameters) -> void -Microsoft.AspNetCore.Http.HttpResults.RazorComponentResult.StatusCode.get -> int? -Microsoft.AspNetCore.Http.HttpResults.RazorComponentResult.StatusCode.set -> void -Microsoft.AspNetCore.Http.HttpResults.RazorComponentResult -Microsoft.AspNetCore.Http.HttpResults.RazorComponentResult.RazorComponentResult() -> void -Microsoft.AspNetCore.Http.HttpResults.RazorComponentResult.RazorComponentResult(object! parameters) -> void -Microsoft.AspNetCore.Http.HttpResults.RazorComponentResult.RazorComponentResult(System.Collections.Generic.IReadOnlyDictionary! parameters) -> void Microsoft.Extensions.DependencyInjection.IRazorComponentsBuilder Microsoft.Extensions.DependencyInjection.IRazorComponentsBuilder.Services.get -> Microsoft.Extensions.DependencyInjection.IServiceCollection! Microsoft.Extensions.DependencyInjection.RazorComponentsServiceCollectionExtensions static Microsoft.AspNetCore.Builder.RazorComponentsEndpointConventionBuilderExtensions.AddAdditionalAssemblies(this Microsoft.AspNetCore.Builder.RazorComponentsEndpointConventionBuilder! builder, params System.Reflection.Assembly![]! assemblies) -> Microsoft.AspNetCore.Builder.RazorComponentsEndpointConventionBuilder! static Microsoft.AspNetCore.Builder.RazorComponentsEndpointRouteBuilderExtensions.MapRazorComponents(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints) -> Microsoft.AspNetCore.Builder.RazorComponentsEndpointConventionBuilder! static Microsoft.Extensions.DependencyInjection.RazorComponentsServiceCollectionExtensions.AddRazorComponents(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, System.Action? configure = null) -> Microsoft.Extensions.DependencyInjection.IRazorComponentsBuilder! +static readonly Microsoft.AspNetCore.Components.Endpoints.RazorComponentResultExecutor.DefaultContentType -> string! +virtual Microsoft.AspNetCore.Components.Endpoints.RazorComponentResultExecutor.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext! httpContext, Microsoft.AspNetCore.Components.Endpoints.RazorComponentResult! result) -> System.Threading.Tasks.Task! diff --git a/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.Prerendering.cs b/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.Prerendering.cs index 9a8663ec5098..979cfaa17e16 100644 --- a/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.Prerendering.cs +++ b/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.Prerendering.cs @@ -29,7 +29,7 @@ protected override IComponent ResolveComponentForRenderMode([DynamicallyAccessed else { // This component is the start of a subtree with a rendermode, so introduce a new rendermode boundary here - return new SSRRenderModeBoundary(_httpContext, componentType, renderMode); + return new SSRRenderModeBoundary(componentType, renderMode); } } @@ -84,7 +84,7 @@ public async ValueTask PrerenderComponentAsync( { var rootComponent = prerenderMode is null ? InstantiateComponent(componentType) - : new SSRRenderModeBoundary(_httpContext, componentType, prerenderMode); + : new SSRRenderModeBoundary(componentType, prerenderMode); var htmlRootComponent = await Dispatcher.InvokeAsync(() => BeginRenderingComponent(rootComponent, parameters)); var result = new PrerenderedComponentHtmlContent(Dispatcher, htmlRootComponent); diff --git a/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs b/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs index 0c46325988a1..1118dfeb5825 100644 --- a/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs +++ b/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs @@ -52,8 +52,6 @@ public EndpointHtmlRenderer(IServiceProvider serviceProvider, ILoggerFactory log _services = serviceProvider; } - internal HttpContext? HttpContext => _httpContext; - private void SetHttpContext(HttpContext httpContext) { if (_httpContext is null) diff --git a/src/Components/Endpoints/src/Rendering/SSRRenderModeBoundary.cs b/src/Components/Endpoints/src/Rendering/SSRRenderModeBoundary.cs index f0ac9aaba6db..8f1529e3e0a0 100644 --- a/src/Components/Endpoints/src/Rendering/SSRRenderModeBoundary.cs +++ b/src/Components/Endpoints/src/Rendering/SSRRenderModeBoundary.cs @@ -7,7 +7,6 @@ using System.Globalization; using System.Security.Cryptography; using System.Text; -using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Components.Rendering; using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Http; @@ -32,13 +31,8 @@ internal class SSRRenderModeBoundary : IComponent private IReadOnlyDictionary? _latestParameters; private string? _markerKey; - public SSRRenderModeBoundary( - HttpContext httpContext, - [DynamicallyAccessedMembers(Component)] Type componentType, - IComponentRenderMode renderMode) + public SSRRenderModeBoundary([DynamicallyAccessedMembers(Component)] Type componentType, IComponentRenderMode renderMode) { - AssertRenderModeIsConfigured(httpContext, componentType, renderMode); - _componentType = componentType; _renderMode = renderMode; _prerender = renderMode switch @@ -50,50 +44,6 @@ public SSRRenderModeBoundary( }; } - private static void AssertRenderModeIsConfigured(HttpContext httpContext, Type componentType, IComponentRenderMode renderMode) - { - var configuredRenderModesMetadata = httpContext.GetEndpoint()?.Metadata.GetMetadata(); - if (configuredRenderModesMetadata is null) - { - // This is not a Razor Components endpoint. It might be that the app is using RazorComponentResult, - // or perhaps something else has changed the endpoint dynamically. In this case we don't know how - // the app is configured so we just proceed and allow any errors to happen if the client-side code - // later tries to reach endpoints that aren't mapped. - return; - } - - var configuredModes = configuredRenderModesMetadata.ConfiguredRenderModes; - - // We have to allow for specified rendermodes being subclases of the known types - if (renderMode is ServerRenderMode || renderMode is AutoRenderMode) - { - AssertRenderModeIsConfigured(componentType, renderMode, configuredModes, "AddServerRenderMode"); - } - - if (renderMode is WebAssemblyRenderMode || renderMode is AutoRenderMode) - { - AssertRenderModeIsConfigured(componentType, renderMode, configuredModes, "AddWebAssemblyRenderMode"); - } - } - - private static void AssertRenderModeIsConfigured(Type componentType, IComponentRenderMode specifiedMode, IComponentRenderMode[] configuredModes, string expectedCall) where TRequiredMode: IComponentRenderMode - { - foreach (var configuredMode in configuredModes) - { - // We have to allow for configured rendermodes being subclases of the known types - if (configuredMode is TRequiredMode) - { - return; - } - } - - throw new InvalidOperationException($"A component of type '{componentType}' has render mode '{specifiedMode.GetType().Name}', " + - $"but the required endpoints are not mapped on the server. When calling " + - $"'{nameof(RazorComponentsEndpointRouteBuilderExtensions.MapRazorComponents)}', add a call to " + - $"'{expectedCall}'. For example, " + - $"'builder.{nameof(RazorComponentsEndpointRouteBuilderExtensions.MapRazorComponents)}<...>.{expectedCall}()'"); - } - public void Attach(RenderHandle renderHandle) { _renderHandle = renderHandle; diff --git a/src/Components/Endpoints/src/Results/RazorComponentResult.cs b/src/Components/Endpoints/src/Results/RazorComponentResult.cs index 0f4e385ac288..3049313f9a21 100644 --- a/src/Components/Endpoints/src/Results/RazorComponentResult.cs +++ b/src/Components/Endpoints/src/Results/RazorComponentResult.cs @@ -1,19 +1,18 @@ // 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.CodeAnalysis; -using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Endpoints; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Internal; using static Microsoft.AspNetCore.Internal.LinkerFlags; -namespace Microsoft.AspNetCore.Http.HttpResults; +namespace Microsoft.AspNetCore.Components.Endpoints; /// /// An that renders a Razor Component. /// -public class RazorComponentResult : IResult, IStatusCodeHttpResult, IContentTypeHttpResult +public class RazorComponentResult : IResult { private static readonly IReadOnlyDictionary EmptyParameters = new Dictionary().AsReadOnly(); @@ -23,7 +22,7 @@ public class RazorComponentResult : IResult, IStatusCodeHttpResult, IContentType /// /// The type of the component to render. This must implement . public RazorComponentResult([DynamicallyAccessedMembers(Component)] Type componentType) - : this(componentType, ReadOnlyDictionary.Empty) + : this(componentType, null) { } @@ -32,9 +31,7 @@ public RazorComponentResult([DynamicallyAccessedMembers(Component)] Type compone /// /// The type of the component to render. This must implement . /// Parameters for the component. - public RazorComponentResult( - [DynamicallyAccessedMembers(Component)] Type componentType, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] object parameters) + public RazorComponentResult([DynamicallyAccessedMembers(Component)] Type componentType, object? parameters) : this(componentType, CoerceParametersObjectToDictionary(parameters)) { } @@ -44,27 +41,25 @@ public RazorComponentResult( /// /// The type of the component to render. This must implement . /// Parameters for the component. - public RazorComponentResult( - [DynamicallyAccessedMembers(Component)] Type componentType, - IReadOnlyDictionary parameters) + public RazorComponentResult([DynamicallyAccessedMembers(Component)] Type componentType, IReadOnlyDictionary? parameters) { - ArgumentNullException.ThrowIfNull(componentType); - ArgumentNullException.ThrowIfNull(parameters); - // Note that the Blazor renderer will validate that componentType implements IComponent and throws a suitable // exception if not, so we don't need to duplicate that logic here. + + ArgumentNullException.ThrowIfNull(componentType); ComponentType = componentType; Parameters = parameters ?? EmptyParameters; } - private static IReadOnlyDictionary CoerceParametersObjectToDictionary(object? parameters) + private static IReadOnlyDictionary? CoerceParametersObjectToDictionary(object? parameters) => parameters is null - ? throw new ArgumentNullException(nameof(parameters)) + ? null : (IReadOnlyDictionary)PropertyHelper.ObjectToDictionary(parameters); /// /// Gets the component type. /// + [DynamicallyAccessedMembers(Component)] public Type ComponentType { get; } /// @@ -92,10 +87,15 @@ public RazorComponentResult( public bool PreventStreamingRendering { get; set; } /// - /// Processes this result in the given . + /// Requests the service of + /// + /// to process itself in the given . /// /// An associated with the current request. /// A which will complete when execution is completed. public Task ExecuteAsync(HttpContext httpContext) - => RazorComponentResultExecutor.ExecuteAsync(httpContext, this); + { + var executor = httpContext.RequestServices.GetRequiredService(); + return executor.ExecuteAsync(httpContext, this); + } } diff --git a/src/Components/Endpoints/src/Results/RazorComponentResultExecutor.cs b/src/Components/Endpoints/src/Results/RazorComponentResultExecutor.cs index 5467336e932d..af14fa146f70 100644 --- a/src/Components/Endpoints/src/Results/RazorComponentResultExecutor.cs +++ b/src/Components/Endpoints/src/Results/RazorComponentResultExecutor.cs @@ -9,15 +9,23 @@ using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.DependencyInjection; using static Microsoft.AspNetCore.Internal.LinkerFlags; -using Microsoft.AspNetCore.Http.HttpResults; namespace Microsoft.AspNetCore.Components.Endpoints; -internal static class RazorComponentResultExecutor +/// +/// Executes a . +/// +public class RazorComponentResultExecutor { - public const string DefaultContentType = "text/html; charset=utf-8"; + /// + /// The default content-type header value for Razor Components, text/html; charset=utf-8. + /// + public static readonly string DefaultContentType = "text/html; charset=utf-8"; - public static Task ExecuteAsync(HttpContext httpContext, RazorComponentResult result) + /// + /// Executes a asynchronously. + /// + public virtual Task ExecuteAsync(HttpContext httpContext, RazorComponentResult result) { ArgumentNullException.ThrowIfNull(httpContext); @@ -36,7 +44,7 @@ public static Task ExecuteAsync(HttpContext httpContext, RazorComponentResult re result.PreventStreamingRendering); } - private static Task RenderComponentToResponse( + internal static Task RenderComponentToResponse( HttpContext httpContext, [DynamicallyAccessedMembers(Component)] Type componentType, IReadOnlyDictionary? componentParameters, diff --git a/src/Components/Endpoints/src/Results/RazorComponentResultOfT.cs b/src/Components/Endpoints/src/Results/RazorComponentResultOfT.cs index 5ab0f23481f0..3019550b9100 100644 --- a/src/Components/Endpoints/src/Results/RazorComponentResultOfT.cs +++ b/src/Components/Endpoints/src/Results/RazorComponentResultOfT.cs @@ -2,16 +2,15 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.CodeAnalysis; -using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Http; using static Microsoft.AspNetCore.Internal.LinkerFlags; -namespace Microsoft.AspNetCore.Http.HttpResults; +namespace Microsoft.AspNetCore.Components.Endpoints; /// /// An that renders a Razor Component. /// -public class RazorComponentResult<[DynamicallyAccessedMembers(Component)] TComponent> - : RazorComponentResult where TComponent: IComponent +public class RazorComponentResult<[DynamicallyAccessedMembers(Component)] TComponent> : RazorComponentResult where TComponent: IComponent { /// /// Constructs an instance of . @@ -24,8 +23,7 @@ public RazorComponentResult() : base(typeof(TComponent)) /// Constructs an instance of . /// /// Parameters for the component. - public RazorComponentResult( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] object parameters) : base(typeof(TComponent), parameters) + public RazorComponentResult(object parameters) : base(typeof(TComponent), parameters) { } diff --git a/src/Components/Endpoints/test/EndpointHtmlRendererTest.cs b/src/Components/Endpoints/test/EndpointHtmlRendererTest.cs index a2c730f1f21c..7b3cda4f7ac4 100644 --- a/src/Components/Endpoints/test/EndpointHtmlRendererTest.cs +++ b/src/Components/Endpoints/test/EndpointHtmlRendererTest.cs @@ -980,7 +980,7 @@ public async Task Dispatching_WhenComponentReRendersNamedEventAtSameLocation() builder.SetKey(firstRender); builder.AddAttribute(1, "onsubmit", () => { eventReceivedCount++; component.TriggerRender(); }); - builder.AddNamedEvent("onsubmit", "my-name"); + builder.AddNamedEvent(2, "onsubmit", "my-name"); builder.CloseElement(); firstRender = false; @@ -1014,7 +1014,7 @@ public async Task Dispatching_WhenNamedEventChangesName() { builder.OpenElement(0, "form"); builder.AddAttribute(1, "onsubmit", () => { eventReceivedCount++; }); - builder.AddNamedEvent("onsubmit", firstRender ? "my-name-1" : "my-name-2"); + builder.AddNamedEvent(2, "onsubmit", firstRender ? "my-name-1" : "my-name-2"); builder.CloseElement(); firstRender = false; }); @@ -1186,7 +1186,7 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) { builder.OpenElement(0, "form"); builder.AddAttribute(1, "onsubmit", Handler ?? (() => { })); - builder.AddNamedEvent("onsubmit", "default"); + builder.AddNamedEvent(2, "onsubmit", "default"); builder.CloseElement(); } } @@ -1201,12 +1201,12 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) if (!hasRendered) { builder.AddAttribute(1, "onsubmit", () => { }); - builder.AddNamedEvent("onsubmit", "default"); + builder.AddNamedEvent(2, "onsubmit", "default"); } else { builder.AddAttribute(1, "onsubmit", () => { GC.KeepAlive(new object()); }); - builder.AddNamedEvent("onsubmit", "default"); + builder.AddNamedEvent(2, "onsubmit", "default"); } builder.CloseElement(); if (!hasRendered) @@ -1231,7 +1231,7 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) builder.AddAttribute(1, "onsubmit", !hasRendered ? () => { Message = "Received call to original handler"; } : () => { Message = "Received call to updated handler"; }); - builder.AddNamedEvent("onsubmit", "default"); + builder.AddNamedEvent(2, "onsubmit", "default"); builder.CloseElement(); } @@ -1248,7 +1248,7 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) { builder.OpenElement(0, "form"); builder.AddAttribute(1, "onsubmit", () => { }); - builder.AddNamedEvent("onsubmit", "default"); + builder.AddNamedEvent(2, "onsubmit", "default"); builder.CloseElement(); } } diff --git a/src/Components/Endpoints/test/HotReloadServiceTests.cs b/src/Components/Endpoints/test/HotReloadServiceTests.cs deleted file mode 100644 index afa86e09971e..000000000000 --- a/src/Components/Endpoints/test/HotReloadServiceTests.cs +++ /dev/null @@ -1,213 +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.Builder; -using System.Reflection; -using Microsoft.AspNetCore.Components.Discovery; -using Microsoft.AspNetCore.Routing; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Primitives; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.AspNetCore.Components.Web; -using Microsoft.AspNetCore.Routing.Patterns; -using Microsoft.AspNetCore.Components.Endpoints.Infrastructure; - -namespace Microsoft.AspNetCore.Components.Endpoints.Tests; - -public class HotReloadServiceTests -{ - [Fact] - public void UpdatesEndpointsWhenHotReloadChangeTokenTriggered() - { - // Arrange - var builder = CreateBuilder(typeof(ServerComponent)); - var services = CreateServices(typeof(MockEndpointProvider)); - var endpointDataSource = CreateDataSource(builder, services); - var invoked = false; - - // Act - ChangeToken.OnChange(endpointDataSource.GetChangeToken, () => invoked = true); - - // Assert - Assert.False(invoked); - HotReloadService.UpdateApplication(null); - Assert.True(invoked); - } - - [Fact] - public void AddNewEndpointWhenDataSourceChanges() - { - // Arrange - var builder = CreateBuilder(typeof(ServerComponent)); - var services = CreateServices(typeof(MockEndpointProvider)); - var endpointDataSource = CreateDataSource(builder, services); - - // Assert - 1 - var endpoint = Assert.IsType(Assert.Single(endpointDataSource.Endpoints)); - Assert.Equal("/server", endpoint.RoutePattern.RawText); - - // Act - 2 - endpointDataSource.Builder.Pages.AddFromLibraryInfo("TestAssembly2", new[] - { - new PageComponentBuilder - { - AssemblyName = "TestAssembly2", - PageType = typeof(StaticComponent), - RouteTemplates = new List { "/app/test" } - } - }); - HotReloadService.UpdateApplication(null); - - // Assert - 2 - Assert.Equal(2, endpointDataSource.Endpoints.Count); - Assert.Collection( - endpointDataSource.Endpoints, - (ep) => Assert.Equal("/app/test", ((RouteEndpoint)ep).RoutePattern.RawText), - (ep) => Assert.Equal("/server", ((RouteEndpoint)ep).RoutePattern.RawText)); - } - - [Fact] - public void RemovesEndpointWhenDataSourceChanges() - { - // Arrange - var builder = CreateBuilder(typeof(ServerComponent)); - var services = CreateServices(typeof(MockEndpointProvider)); - var endpointDataSource = CreateDataSource(builder, services); - - // Assert - 1 - var endpoint = Assert.IsType(Assert.Single(endpointDataSource.Endpoints)); - Assert.Equal("/server", endpoint.RoutePattern.RawText); - - // Act - 2 - endpointDataSource.Builder.RemoveLibrary("TestAssembly"); - endpointDataSource.Options.ConfiguredRenderModes.Clear(); - HotReloadService.UpdateApplication(null); - - // Assert - 2 - Assert.Empty(endpointDataSource.Endpoints); - } - - [Fact] - public void ModifiesEndpointWhenDataSourceChanges() - { - // Arrange - var builder = CreateBuilder(typeof(ServerComponent)); - var services = CreateServices(typeof(MockEndpointProvider)); - var endpointDataSource = CreateDataSource(builder, services); - - // Assert - 1 - var endpoint = Assert.IsType(Assert.Single(endpointDataSource.Endpoints)); - Assert.Equal("/server", endpoint.RoutePattern.RawText); - Assert.DoesNotContain(endpoint.Metadata, (element) => element is TestMetadata); - - // Act - 2 - endpointDataSource.Conventions.Add(builder => - builder.Metadata.Add(new TestMetadata())); - HotReloadService.UpdateApplication(null); - - // Assert - 2 - var updatedEndpoint = Assert.IsType(Assert.Single(endpointDataSource.Endpoints)); - Assert.Equal("/server", updatedEndpoint.RoutePattern.RawText); - Assert.Contains(updatedEndpoint.Metadata, (element) => element is TestMetadata); - } - - [Fact] - public void NotifiesCompositeEndpointDataSource() - { - // Arrange - var builder = CreateBuilder(typeof(ServerComponent)); - var services = CreateServices(typeof(MockEndpointProvider)); - var endpointDataSource = CreateDataSource(builder, services); - var compositeEndpointDataSource = new CompositeEndpointDataSource( - new[] { endpointDataSource }); - - // Assert - 1 - var endpoint = Assert.IsType(Assert.Single(endpointDataSource.Endpoints)); - Assert.Equal("/server", endpoint.RoutePattern.RawText); - var compositeEndpoint = Assert.IsType(Assert.Single(compositeEndpointDataSource.Endpoints)); - Assert.Equal("/server", compositeEndpoint.RoutePattern.RawText); - - // Act - 2 - endpointDataSource.Builder.Pages.RemoveFromAssembly("TestAssembly"); - endpointDataSource.Options.ConfiguredRenderModes.Clear(); - HotReloadService.UpdateApplication(null); - - // Assert - 2 - Assert.Empty(endpointDataSource.Endpoints); - Assert.Empty(compositeEndpointDataSource.Endpoints); - } - - private class TestMetadata { } - - private ComponentApplicationBuilder CreateBuilder(params Type[] types) - { - var builder = new ComponentApplicationBuilder(); - builder.AddLibrary(new AssemblyComponentLibraryDescriptor( - "TestAssembly", - Array.Empty(), - types.Select(t => new ComponentBuilder - { - AssemblyName = "TestAssembly", - ComponentType = t, - RenderMode = t.GetCustomAttribute() - }).ToArray())); - - return builder; - } - - private IServiceProvider CreateServices(params Type[] types) - { - var services = new ServiceCollection(); - foreach (var type in types) - { - services.TryAddEnumerable(ServiceDescriptor.Singleton(typeof(RenderModeEndpointProvider), type)); - } - - return services.BuildServiceProvider(); - } - - private static RazorComponentEndpointDataSource CreateDataSource( - ComponentApplicationBuilder builder, - IServiceProvider services, - IComponentRenderMode[] renderModes = null) - { - var result = new RazorComponentEndpointDataSource( - builder, - new[] { new MockEndpointProvider() }, - new ApplicationBuilder(services), - new RazorComponentEndpointFactory(), - new HotReloadService() { MetadataUpdateSupported = true }); - - if (renderModes != null) - { - foreach (var mode in renderModes) - { - result.Options.ConfiguredRenderModes.Add(mode); - } - } - else - { - result.Options.ConfiguredRenderModes.Add(new ServerRenderMode()); - } - - return result; - } - - private class StaticComponent : ComponentBase { } - - [RenderModeServer] - private class ServerComponent : ComponentBase { } - - private class MockEndpointProvider : RenderModeEndpointProvider - { - public override IEnumerable GetEndpointBuilders(IComponentRenderMode renderMode, IApplicationBuilder applicationBuilder) - { - yield return new RouteEndpointBuilder( - (context) => Task.CompletedTask, - RoutePatternFactory.Parse("/server"), - 0); - } - - public override bool Supports(IComponentRenderMode renderMode) => true; - } -} diff --git a/src/Components/Endpoints/test/RazorComponentEndpointDataSourceTest.cs b/src/Components/Endpoints/test/RazorComponentEndpointDataSourceTest.cs index c758d717c8c2..07606588ea10 100644 --- a/src/Components/Endpoints/test/RazorComponentEndpointDataSourceTest.cs +++ b/src/Components/Endpoints/test/RazorComponentEndpointDataSourceTest.cs @@ -228,8 +228,7 @@ private RazorComponentEndpointDataSource CreateDataSource.Instance.GetBuilder(), services?.GetService>() ?? Enumerable.Empty(), new ApplicationBuilder(services ?? new ServiceCollection().BuildServiceProvider()), - new RazorComponentEndpointFactory(), - new HotReloadService() { MetadataUpdateSupported = true }); + new RazorComponentEndpointFactory()); if (renderModes != null) { diff --git a/src/Components/Endpoints/test/RazorComponentEndpointFactoryTest.cs b/src/Components/Endpoints/test/RazorComponentEndpointFactoryTest.cs index a7dbd751bf25..0e35b75e68f4 100644 --- a/src/Components/Endpoints/test/RazorComponentEndpointFactoryTest.cs +++ b/src/Components/Endpoints/test/RazorComponentEndpointFactoryTest.cs @@ -18,16 +18,13 @@ public void AddEndpoints_CreatesEndpointWithExpectedMetadata() var factory = new RazorComponentEndpointFactory(); var conventions = new List>(); var finallyConventions = new List>(); - var testRenderMode = new TestRenderMode(); - var configuredRenderModes = new ConfiguredRenderModesMetadata(new[] { testRenderMode }); factory.AddEndpoints(endpoints, typeof(App), new PageComponentInfo( "App", typeof(App), "/", new object[] { new AuthorizeAttribute() }), conventions, - finallyConventions, - configuredRenderModes); + finallyConventions); var endpoint = Assert.Single(endpoints); Assert.Equal("/ (App)", endpoint.DisplayName); @@ -38,8 +35,6 @@ public void AddEndpoints_CreatesEndpointWithExpectedMetadata() Assert.Contains(endpoint.Metadata, m => m is ComponentTypeMetadata); Assert.Contains(endpoint.Metadata, m => m is SuppressLinkGenerationMetadata); Assert.Contains(endpoint.Metadata, m => m is AuthorizeAttribute); - Assert.Contains(endpoint.Metadata, m => m is ConfiguredRenderModesMetadata c - && c.ConfiguredRenderModes.Single() == testRenderMode); Assert.NotNull(endpoint.RequestDelegate); var methods = Assert.Single(endpoint.Metadata.GetOrderedMetadata()); @@ -68,8 +63,7 @@ public void AddEndpoints_RunsConventions() "/", Array.Empty()), conventions, - finallyConventions, - new ConfiguredRenderModesMetadata(Array.Empty())); + finallyConventions); var endpoint = Assert.Single(endpoints); Assert.Contains(endpoint.Metadata, m => m is AuthorizeAttribute); @@ -96,8 +90,7 @@ public void AddEndpoints_RunsFinallyConventions() "/", Array.Empty()), conventions, - finallyConventions, - new ConfiguredRenderModesMetadata(Array.Empty())); + finallyConventions); var endpoint = Assert.Single(endpoints); Assert.Contains(endpoint.Metadata, m => m is AuthorizeAttribute); @@ -124,8 +117,7 @@ public void AddEndpoints_RouteOrderCanNotBeChanged() "/", Array.Empty()), conventions, - finallyConventions, - new ConfiguredRenderModesMetadata(Array.Empty())); + finallyConventions); var endpoint = Assert.Single(endpoints); var routeEndpoint = Assert.IsType(endpoint); @@ -156,12 +148,9 @@ public void AddEndpoints_RunsFinallyConventionsAfterRegularConventions() "/", Array.Empty()), conventions, - finallyConventions, - new ConfiguredRenderModesMetadata(Array.Empty())); + finallyConventions); var endpoint = Assert.Single(endpoints); Assert.DoesNotContain(endpoint.Metadata, m => m is AuthorizeAttribute); } - - class TestRenderMode : IComponentRenderMode { } } diff --git a/src/Components/Endpoints/test/RazorComponentResultExecutorTest.cs b/src/Components/Endpoints/test/RazorComponentResultExecutorTest.cs new file mode 100644 index 000000000000..14c0646f9a28 --- /dev/null +++ b/src/Components/Endpoints/test/RazorComponentResultExecutorTest.cs @@ -0,0 +1,454 @@ +// 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.Infrastructure; +using System.Diagnostics; +using Microsoft.AspNetCore.Components.Routing; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.DataProtection; +using Microsoft.Extensions.Internal; +using Microsoft.AspNetCore.Http.Features; +using Moq; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; +using Microsoft.AspNetCore.Components.Endpoints.Tests.TestComponents; +using System.Text.RegularExpressions; +using Microsoft.AspNetCore.Components.Forms; +using Microsoft.AspNetCore.Components.Endpoints.Forms; + +namespace Microsoft.AspNetCore.Components.Endpoints; + +public class RazorComponentResultExecutorTest +{ + [Fact] + public async Task CanRenderComponentStatically() + { + // Arrange + var httpContext = GetTestHttpContext(); + var responseBody = new MemoryStream(); + httpContext.Response.Body = responseBody; + + // Act + await RazorComponentResultExecutor.RenderComponentToResponse( + httpContext, + typeof(SimpleComponent), + componentParameters: null, + preventStreamingRendering: false); + + // Assert + Assert.Equal("

Hello from SimpleComponent

", GetStringContent(responseBody)); + } + + [Fact] + public async Task PerformsStreamingRendering() + { + // Arrange + var tcs = new TaskCompletionSource(); + var httpContext = GetTestHttpContext(); + var responseBody = new MemoryStream(); + httpContext.Response.Body = responseBody; + + // Act/Assert 1: Emits the initial pre-quiescent output to the response + var completionTask = RazorComponentResultExecutor.RenderComponentToResponse( + httpContext, + typeof(StreamingAsyncLoadingComponent), + PropertyHelper.ObjectToDictionary(new { LoadingTask = tcs.Task }).AsReadOnly(), + preventStreamingRendering: false); + Assert.Equal( + "Loading task status: WaitingForActivation", + MaskComponentIds(GetStringContent(responseBody))); + + // Assert 2: Result task remains incomplete for as long as the component's loading operation remains in flight + // This keeps the HTTP response open + await Task.Yield(); + Assert.False(completionTask.IsCompleted); + + // Act/Assert 3: When loading completes, it emits a streaming batch update and completes the response + tcs.SetResult(); + await completionTask; + Assert.Equal( + "Loading task status: WaitingForActivation", + MaskComponentIds(GetStringContent(responseBody))); + } + + [Fact] + public async Task EmitsEachComponentOnlyOncePerStreamingUpdate_WhenAComponentRendersTwice() + { + // Arrange + var tcs = new TaskCompletionSource(); + var httpContext = GetTestHttpContext(); + var responseBody = new MemoryStream(); + httpContext.Response.Body = responseBody; + + // Act/Assert 1: Emits the initial pre-quiescent output to the response + var completionTask = RazorComponentResultExecutor.RenderComponentToResponse( + httpContext, + typeof(DoubleRenderingStreamingAsyncComponent), + PropertyHelper.ObjectToDictionary(new { WaitFor = tcs.Task }).AsReadOnly(), + preventStreamingRendering: false); + Assert.Equal( + "Loading...", + MaskComponentIds(GetStringContent(responseBody))); + + // Act/Assert 2: When loading completes, it emits a streaming batch update with only one copy of the final output, + // despite the RenderBatch containing two diffs from the component + tcs.SetResult(); + await completionTask; + Assert.Equal( + "Loading...", + MaskComponentIds(GetStringContent(responseBody))); + } + + [Fact] + public async Task EmitsEachComponentOnlyOncePerStreamingUpdate_WhenAnAncestorAlsoUpdated() + { + // Since the HTML rendered for each component also includes all its descendants, we don't + // want to render output for any component that also has an ancestor in the set of updates + // (as it would then be output twice) + + // Arrange + var tcs = new TaskCompletionSource(); + var httpContext = GetTestHttpContext(); + var responseBody = new MemoryStream(); + httpContext.Response.Body = responseBody; + + // Act/Assert 1: Emits the initial pre-quiescent output to the response + var completionTask = RazorComponentResultExecutor.RenderComponentToResponse( + httpContext, + typeof(StreamingComponentWithChild), + PropertyHelper.ObjectToDictionary(new { LoadingTask = tcs.Task }).AsReadOnly(), + preventStreamingRendering: false); + var expectedInitialHtml = "[LoadingTask: WaitingForActivation]\n[Child render: 1]\n"; + Assert.Equal( + expectedInitialHtml, + MaskComponentIds(GetStringContent(responseBody))); + + // Act/Assert 2: When loading completes, it emits a streaming batch update in which the + // child is present only within the parent markup, not as a separate entry + tcs.SetResult(); + await completionTask; + Assert.Equal( + $"{expectedInitialHtml}", + MaskComponentIds(GetStringContent(responseBody))); + } + + [Fact] + public async Task WaitsForQuiescenceIfPreventStreamingRenderingIsTrue() + { + // Arrange + var tcs = new TaskCompletionSource(); + var httpContext = GetTestHttpContext(); + var responseBody = new MemoryStream(); + httpContext.Response.Body = responseBody; + + // Act/Assert: Doesn't complete until loading finishes + var completionTask = RazorComponentResultExecutor.RenderComponentToResponse( + httpContext, + typeof(StreamingAsyncLoadingComponent), + PropertyHelper.ObjectToDictionary(new { LoadingTask = tcs.Task }).AsReadOnly(), + preventStreamingRendering: true); + await Task.Yield(); + Assert.False(completionTask.IsCompleted); + + // Act/Assert: Does complete when loading finishes + tcs.SetResult(); + await completionTask; + Assert.Equal( + "Loading task status: RanToCompletion", + MaskComponentIds(GetStringContent(responseBody))); + } + + [Fact] + public async Task SupportsLayouts() + { + // Arrange + var httpContext = GetTestHttpContext(); + var responseBody = new MemoryStream(); + httpContext.Response.Body = responseBody; + + // Act + await RazorComponentResultExecutor.RenderComponentToResponse( + httpContext, typeof(ComponentWithLayout), + null, false); + + // Assert + Assert.Equal($"[TestParentLayout with content: [TestLayout with content: Page\n]\n]\n", GetStringContent(responseBody)); + } + + [Fact] + public async Task OnNavigationBeforeResponseStarted_Redirects() + { + // Arrange + var httpContext = GetTestHttpContext(); + + // Act + await RazorComponentResultExecutor.RenderComponentToResponse( + httpContext, typeof(ComponentThatRedirectsSynchronously), + null, false); + + // Assert + Assert.Equal("https://test/somewhere/else", httpContext.Response.Headers.Location); + } + + [Fact] + public async Task OnNavigationAfterResponseStarted_WithStreamingOff_Throws() + { + // Arrange + var httpContext = GetTestHttpContext(); + var responseMock = new Mock(); + responseMock.Setup(r => r.HasStarted).Returns(true); + httpContext.Features.Set(responseMock.Object); + + // Act + var ex = await Assert.ThrowsAsync( + () => RazorComponentResultExecutor.RenderComponentToResponse( + httpContext, typeof(StreamingComponentThatRedirectsAsynchronously), + null, preventStreamingRendering: true)); + + // Assert + Assert.Contains("A navigation command was attempted during prerendering after the server already started sending the response", ex.Message); + } + + [Fact] + public async Task OnNavigationAfterResponseStarted_WithStreamingOn_EmitsCommand() + { + // Arrange + var httpContext = GetTestHttpContext(); + var responseBody = new MemoryStream(); + httpContext.Response.Body = responseBody; + + // Act + await RazorComponentResultExecutor.RenderComponentToResponse( + httpContext, typeof(StreamingComponentThatRedirectsAsynchronously), + null, preventStreamingRendering: false); + + // Assert + Assert.Equal( + $"Some output\n", + MaskComponentIds(GetStringContent(responseBody))); + } + + [Fact] + public async Task OnUnhandledExceptionBeforeResponseStarted_Throws() + { + // Arrange + var httpContext = GetTestHttpContext(); + + // Act + var ex = await Assert.ThrowsAsync(() => RazorComponentResultExecutor.RenderComponentToResponse( + httpContext, typeof(ComponentThatThrowsSynchronously), + null, false)); + + // Assert + Assert.Contains("Test message", ex.Message); + } + + [Fact] + public async Task OnUnhandledExceptionAfterResponseStarted_WithStreamingOff_Throws() + { + // Arrange + var httpContext = GetTestHttpContext(); + + // Act + var ex = await Assert.ThrowsAsync(() => RazorComponentResultExecutor.RenderComponentToResponse( + httpContext, typeof(StreamingComponentThatThrowsAsynchronously), + null, preventStreamingRendering: true)); + + // Assert + Assert.Contains("Test message", ex.Message); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task OnUnhandledExceptionAfterResponseStarted_WithStreamingOn_EmitsCommand(bool isDevelopmentEnvironment) + { + // Arrange + var httpContext = GetTestHttpContext(isDevelopmentEnvironment ? Environments.Development : Environments.Production); + var responseBody = new MemoryStream(); + httpContext.Response.Body = responseBody; + + var expectedResponseExceptionInfo = isDevelopmentEnvironment + ? "System.InvalidTimeZoneException: Test message with <b>markup</b>" + : "There was an unhandled exception on the current request. For more details turn on detailed exceptions by setting 'DetailedErrors: true' in 'appSettings.Development.json'"; + + // Act + var ex = await Assert.ThrowsAsync(() => RazorComponentResultExecutor.RenderComponentToResponse( + httpContext, typeof(StreamingComponentThatThrowsAsynchronously), + null, preventStreamingRendering: false)); + + // Assert + Assert.Contains("Test message with markup", ex.Message); + Assert.Contains( + $"Some output\n