diff --git a/Directory.Build.targets b/Directory.Build.targets
index 6d564cde7912..077353216668 100644
--- a/Directory.Build.targets
+++ b/Directory.Build.targets
@@ -1,5 +1,7 @@
+
+
$([MSBuild]::ValueOrDefault($(IsTrimmable),'false'))
$(EnableAOTAnalyzer)
diff --git a/eng/CodeGen.proj b/eng/CodeGen.proj
index 1de7e09de4db..d5d8f0a5f6b6 100644
--- a/eng/CodeGen.proj
+++ b/eng/CodeGen.proj
@@ -24,10 +24,12 @@
<_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 bbf3caca0f44..6770f6ccd9f3 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -189,9 +189,9 @@
https://github.com/dotnet/runtime
f8c110b8003d68cc635add4ca791d6cf2e645561
-
+
https://github.com/dotnet/source-build-externals
- 8fc77fa8f591051da1120ebb76c3795b7b584495
+ 0603839a51f5e18b89c60a3690aff5e81fa666ca
diff --git a/eng/Versions.props b/eng/Versions.props
index 70c8340b33bf..4311a25a81ed 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -11,7 +11,7 @@
0
1
true
- 7.0.0-preview
+ 7.0.0-preview3
@@ -162,7 +162,7 @@
8.0.0-beta.23425.2
8.0.0-beta.23425.2
- 8.0.0-alpha.1.23418.1
+ 8.0.0-alpha.1.23429.1
8.0.0-alpha.1.23424.1
@@ -304,7 +304,7 @@
0.11.2
2.2.1
1.0.2
- 13.0.1
+ 13.0.3
13.0.4
1.1.6
1.28.0
diff --git a/eng/scripts/CodeCheck.ps1 b/eng/scripts/CodeCheck.ps1
index 1b6f3c5d55ed..616e3dcc6693 100644
--- a/eng/scripts/CodeCheck.ps1
+++ b/eng/scripts/CodeCheck.ps1
@@ -253,7 +253,7 @@ try {
}
}
# Check for changes in Unshipped in servicing branches
- if ($targetBranch -like 'release*' -and $targetBranch -notlike '*preview*' -and $file -like '*PublicAPI.Unshipped.txt') {
+ if ($targetBranch -like 'release*' -and $targetBranch -notlike '*preview*' -and $targetBranch -notlike '*rc1*' -and $targetBranch -notlike '*rc2*' -and $file -like '*PublicAPI.Unshipped.txt') {
$changedAPIBaselines.Add($file)
}
}
@@ -263,7 +263,8 @@ try {
if ($changedAPIBaselines.count -gt 0) {
LogError ("Detected modification to baseline API files. PublicAPI.Shipped.txt files should only " +
- "be updated after a major release. See /docs/APIBaselines.md for more information.")
+ "be updated after a major release, and PublicAPI.Unshipped.txt files should not " +
+ "be updated in release branches. See /docs/APIBaselines.md for more information.")
LogError "Modified API baseline files:"
foreach ($file in $changedAPIBaselines) {
LogError $file
diff --git a/eng/test-configuration.json b/eng/test-configuration.json
index 3f5af7a1b766..2dd203e2ea05 100644
--- a/eng/test-configuration.json
+++ b/eng/test-configuration.json
@@ -22,6 +22,7 @@
{"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 dd4ea40b86b6..619ec1ded3d9 100644
--- a/eng/tools/GenerateFiles/Directory.Build.props.in
+++ b/eng/tools/GenerateFiles/Directory.Build.props.in
@@ -8,11 +8,4 @@
true
-
-
-
-
-
-
-
diff --git a/eng/tools/RepoTasks/RepoTasks.csproj b/eng/tools/RepoTasks/RepoTasks.csproj
index 5f9e4f60ae98..aa693e2131f6 100644
--- a/eng/tools/RepoTasks/RepoTasks.csproj
+++ b/eng/tools/RepoTasks/RepoTasks.csproj
@@ -15,6 +15,9 @@
+
+
diff --git a/global.json b/global.json
index 5f4bd889b7dc..394a42897f46 100644
--- a/global.json
+++ b/global.json
@@ -1,9 +1,9 @@
{
"sdk": {
- "version": "8.0.100-rc.1.23381.2"
+ "version": "8.0.100-rc.2.23422.11"
},
"tools": {
- "dotnet": "8.0.100-rc.1.23381.2",
+ "dotnet": "8.0.100-rc.2.23422.11",
"runtimes": {
"dotnet/x86": [
"$(MicrosoftNETCoreBrowserDebugHostTransportVersion)"
diff --git a/src/Components/Components/src/CascadingParameterAttribute.cs b/src/Components/Components/src/CascadingParameterAttribute.cs
index bb9be43a5b08..becc2ce1cb57 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 override string? Name { get; set; }
+ public string? Name { get; set; }
}
diff --git a/src/Components/Components/src/CascadingParameterAttributeBase.cs b/src/Components/Components/src/CascadingParameterAttributeBase.cs
index 307743c890cb..47a50edce641 100644
--- a/src/Components/Components/src/CascadingParameterAttributeBase.cs
+++ b/src/Components/Components/src/CascadingParameterAttributeBase.cs
@@ -8,12 +8,6 @@ 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 07e0ae985b58..f4dc1ab0b892 100644
--- a/src/Components/Components/src/CascadingValueServiceCollectionExtensions.cs
+++ b/src/Components/Components/src/CascadingValueServiceCollectionExtensions.cs
@@ -2,6 +2,7 @@
// 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;
@@ -16,11 +17,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 valueFactory)
- => serviceCollection.AddScoped(sp => new CascadingValueSource(() => valueFactory(sp), isFixed: true));
+ this IServiceCollection serviceCollection, Func initialValueFactory)
+ => serviceCollection.AddScoped(sp => new CascadingValueSource(() => initialValueFactory(sp), isFixed: true));
///
/// Adds a cascading value to the . This is equivalent to having
@@ -29,11 +30,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 valueFactory)
- => serviceCollection.AddScoped(sp => new CascadingValueSource(name, () => valueFactory(sp), isFixed: true));
+ this IServiceCollection serviceCollection, string name, Func initialValueFactory)
+ => serviceCollection.AddScoped(sp => new CascadingValueSource(name, () => initialValueFactory(sp), isFixed: true));
///
/// Adds a cascading value to the . This is equivalent to having
@@ -50,4 +51,59 @@ 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 680c022c71ee..dbf9a4662265 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 valueFactory, bool isFixed) : this(isFixed)
+ public CascadingValueSource(Func initialValueFactory, bool isFixed) : this(isFixed)
{
- _initialValueFactory = valueFactory;
+ _initialValueFactory = initialValueFactory;
}
///
/// 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 valueFactory, bool isFixed) : this(valueFactory, isFixed)
+ public CascadingValueSource(string name, Func initialValueFactory, bool isFixed) : this(initialValueFactory, 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 f892dc2cbd74..df77f9475253 100644
--- a/src/Components/Components/src/PublicAPI.Unshipped.txt
+++ b/src/Components/Components/src/PublicAPI.Unshipped.txt
@@ -1,6 +1,4 @@
#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
@@ -10,9 +8,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! valueFactory, bool isFixed) -> void
+Microsoft.AspNetCore.Components.CascadingValueSource.CascadingValueSource(string! name, System.Func! initialValueFactory, bool isFixed) -> void
Microsoft.AspNetCore.Components.CascadingValueSource.CascadingValueSource(string! name, TValue value, bool isFixed) -> void
-Microsoft.AspNetCore.Components.CascadingValueSource.CascadingValueSource(System.Func! valueFactory, bool isFixed) -> void
+Microsoft.AspNetCore.Components.CascadingValueSource.CascadingValueSource(System.Func! initialValueFactory, 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!
@@ -26,10 +24,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.AddNamedEvent(int sequence, string! eventType, string! assignedName) -> void
+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.RenderTree.ComponentFrameFlags
Microsoft.AspNetCore.Components.RenderTree.ComponentFrameFlags.HasCallerSpecifiedRenderMode = 1 -> Microsoft.AspNetCore.Components.RenderTree.ComponentFrameFlags
Microsoft.AspNetCore.Components.RenderTree.NamedEventChange
@@ -82,25 +80,20 @@ 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) -> void
-*REMOVED*Microsoft.AspNetCore.Components.CascadingParameterAttribute.Name.get -> string?
-*REMOVED*Microsoft.AspNetCore.Components.CascadingParameterAttribute.Name.set -> void
+Microsoft.AspNetCore.Components.StreamRenderingAttribute.StreamRenderingAttribute(bool enabled = true) -> 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! valueFactory) -> 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, System.Func!>! sourceFactory) -> Microsoft.Extensions.DependencyInjection.IServiceCollection!
-static Microsoft.Extensions.DependencyInjection.CascadingValueServiceCollectionExtensions.AddCascadingValue(this Microsoft.Extensions.DependencyInjection.IServiceCollection! serviceCollection, System.Func! valueFactory) -> 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
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 d49cfad7978a..9a0b3cbdfdb4 100644
--- a/src/Components/Components/src/Reflection/ComponentProperties.cs
+++ b/src/Components/Components/src/Reflection/ComponentProperties.cs
@@ -184,8 +184,7 @@ 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 [{nameof(ParameterAttribute)}], [{nameof(CascadingParameterAttribute)}] or " +
- $"[SupplyParameterFromFormAttribute] applied.");
+ $"but it does not have [Parameter], [CascadingParameter], or any other parameter-supplying attribute.");
}
else
{
diff --git a/src/Components/Components/src/RenderModeAttribute.cs b/src/Components/Components/src/RenderModeAttribute.cs
index d9d7ff286270..87fb58c0e9de 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)]
+[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public abstract class RenderModeAttribute : Attribute
{
///
diff --git a/src/Components/Components/src/RenderTree/RenderTreeFrameArrayBuilder.cs b/src/Components/Components/src/RenderTree/RenderTreeFrameArrayBuilder.cs
index 3b946154a254..56cc77e9d966 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(int sequence, IComponentRenderMode renderMode)
+ public void AppendComponentRenderMode(IComponentRenderMode renderMode)
{
if (_itemsInUse == _items.Length)
{
@@ -144,13 +144,13 @@ public void AppendComponentRenderMode(int sequence, IComponentRenderMode renderM
_items[_itemsInUse++] = new RenderTreeFrame
{
- SequenceField = sequence,
+ SequenceField = 0, // We're only interested in one of these, so it's not useful to optimize diffing over multiple
FrameTypeField = RenderTreeFrameType.ComponentRenderMode,
ComponentRenderModeField = renderMode,
};
}
- public void AppendNamedEvent(int sequence, string eventType, string assignedName)
+ public void AppendNamedEvent(string eventType, string assignedName)
{
if (_itemsInUse == _items.Length)
{
@@ -159,7 +159,7 @@ public void AppendNamedEvent(int sequence, string eventType, string assignedName
_items[_itemsInUse++] = new RenderTreeFrame
{
- SequenceField = sequence,
+ SequenceField = 0, // We're only interested in one of these per eventType, so it's not useful to optimize diffing over multiple
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 78f9cd39d57c..5d2cabc1ce4e 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 (int Sequence, string AssignedName)? _pendingNamedSubmitEvent; // TODO: Remove when Razor compiler supports @formname
+ private string? _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 { } pendingNamedSubmitEvent)
+ if (_pendingNamedSubmitEvent is not null)
{
- AddNamedEvent(pendingNamedSubmitEvent.Sequence, "onsubmit", pendingNamedSubmitEvent.AssignedName);
+ AddNamedEvent("onsubmit", _pendingNamedSubmitEvent);
_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 = (sequence, value!);
+ _pendingNamedSubmitEvent = value!;
}
else
{
@@ -623,7 +623,7 @@ public void CloseComponent()
{
if (_pendingComponentCallSiteRenderMode is not null)
{
- AddComponentRenderMode(0, _pendingComponentCallSiteRenderMode);
+ AddComponentRenderMode(_pendingComponentCallSiteRenderMode);
_pendingComponentCallSiteRenderMode = null;
}
@@ -681,9 +681,8 @@ public void AddComponentReferenceCapture(int sequence, Action
- public override string? Name { get; set; }
+ public string? Name { get; set; }
}
diff --git a/src/Components/Components/src/SupplyParameterFromQueryProviderServiceCollectionExtensions.cs b/src/Components/Components/src/SupplyParameterFromQueryProviderServiceCollectionExtensions.cs
index 3de3ab99ee9a..21cb8c98db19 100644
--- a/src/Components/Components/src/SupplyParameterFromQueryProviderServiceCollectionExtensions.cs
+++ b/src/Components/Components/src/SupplyParameterFromQueryProviderServiceCollectionExtensions.cs
@@ -50,7 +50,8 @@ public bool CanSupplyValue(in CascadingParameterInfo parameterInfo)
UpdateQueryParameters();
}
- var queryParameterName = parameterInfo.Attribute.Name ?? parameterInfo.PropertyName;
+ var attribute = (SupplyParameterFromQueryAttribute)parameterInfo.Attribute; // Must be a valid cast because we check in CanSupplyValue
+ var queryParameterName = 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 ed6420fffb25..e055b9c70801 100644
--- a/src/Components/Components/test/CascadingParameterStateTest.cs
+++ b/src/Components/Components/test/CascadingParameterStateTest.cs
@@ -476,8 +476,6 @@ class ComponentWithNamedCascadingParam : TestComponentBase
class SupplyParameterWithSingleDeliveryAttribute : CascadingParameterAttributeBase
{
- public override string Name { get; set; }
-
internal override bool SingleDelivery => true;
}
@@ -523,19 +521,3 @@ 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 9ce74e19708b..a99baeb96833 100644
--- a/src/Components/Components/test/CascadingParameterTest.cs
+++ b/src/Components/Components/test/CascadingParameterTest.cs
@@ -727,6 +727,51 @@ 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;
@@ -734,8 +779,6 @@ private class SingleDeliveryValue(string text)
private class SingleDeliveryCascadingParameterAttribute : CascadingParameterAttributeBase
{
- public override string Name { get; set; }
-
internal override bool SingleDelivery => true;
}
@@ -852,13 +895,11 @@ 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
@@ -904,7 +945,7 @@ void ICascadingValueSupplier.Unsubscribe(ComponentState subscriber, in Cascading
class CustomCascadingValueConsumer1 : AutoRenderComponent
{
- [CustomCascadingParameter1(Name = nameof(Value))]
+ [CustomCascadingParameter1]
public object Value { get; set; }
protected override void BuildRenderTree(RenderTreeBuilder builder)
@@ -915,7 +956,7 @@ protected override void BuildRenderTree(RenderTreeBuilder builder)
class CustomCascadingValueConsumer2 : AutoRenderComponent
{
- [CustomCascadingParameter2(Name = nameof(Value))]
+ [CustomCascadingParameter2]
public object Value { get; set; }
protected override void BuildRenderTree(RenderTreeBuilder builder)
@@ -944,4 +985,7 @@ 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 7eca91d4471a..fd18139905bf 100644
--- a/src/Components/Components/test/ComponentFactoryTest.cs
+++ b/src/Components/Components/test/ComponentFactoryTest.cs
@@ -151,6 +151,29 @@ 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()
{
@@ -290,6 +313,16 @@ 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 262f9584c4e4..f0308c0182a0 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 [{nameof(ParameterAttribute)}], [{nameof(CascadingParameterAttribute)}] or [{nameof(SupplyParameterFromFormAttribute)}] applied.",
+ "but it does not have [Parameter], [CascadingParameter], or any other parameter-supplying attribute.",
ex.Message);
}
diff --git a/src/Components/Components/test/RenderTreeDiffBuilderTest.cs b/src/Components/Components/test/RenderTreeDiffBuilderTest.cs
index 4cce00158c87..f1d0766f1841 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(2, "someevent1", "added to existing element");
+ newTree.AddNamedEvent("someevent1", "added to existing element");
newTree.CloseElement();
- newTree.OpenElement(3, "new element");
- newTree.AddNamedEvent(4, "someevent2", "added with new element");
+ newTree.OpenElement(2, "new element");
+ newTree.AddNamedEvent("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(2, "someevent1", "removing from retained element");
+ oldTree.AddNamedEvent("someevent1", "removing from retained element");
oldTree.CloseElement();
- oldTree.OpenElement(3, "removing");
- oldTree.AddNamedEvent(4, "someevent2", "removed because element was removed");
+ oldTree.OpenElement(2, "removing");
+ oldTree.AddNamedEvent("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(2, "eventname", "assigned name");
+ oldTree.AddNamedEvent("eventname", "assigned name");
oldTree.CloseElement();
newTree.OpenElement(0, "elem");
newTree.AddAttribute(1, "attr1", "unrelated val1");
- newTree.AddNamedEvent(2, "eventname", "assigned name");
+ newTree.AddNamedEvent("eventname", "assigned name");
newTree.CloseElement();
// Act
@@ -2300,13 +2300,13 @@ public void RecognizesNamedEventBeingMoved()
public void RecognizesNamedEventChangingAssignedName()
{
oldTree.OpenElement(0, "elem");
- oldTree.AddNamedEvent(1, "eventname1", "original name");
- oldTree.AddNamedEvent(2, "eventname2", "will be left unchanged");
+ oldTree.AddNamedEvent("eventname1", "original name");
+ oldTree.AddNamedEvent("eventname2", "will be left unchanged");
oldTree.CloseElement();
newTree.OpenElement(0, "elem");
- newTree.AddNamedEvent(1, "eventname1", "changed name");
- newTree.AddNamedEvent(2, "eventname2", "will be left unchanged");
+ newTree.AddNamedEvent("eventname1", "changed name");
+ newTree.AddNamedEvent("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 1b7a91792bad..ea383da376ee 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(1, new ComponentWithUnknownRenderMode.UnknownRenderMode());
+ builder.AddComponentRenderMode(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(2, new SubstituteComponentRenderMode());
+ builder.AddComponentRenderMode(new SubstituteComponentRenderMode());
builder.CloseComponent();
});
diff --git a/src/Components/Components/test/Rendering/RenderTreeBuilderTest.cs b/src/Components/Components/test/Rendering/RenderTreeBuilderTest.cs
index 07f6e5e4554b..8ef9bbea6dac 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(2, renderMode);
+ builder.AddComponentRenderMode(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, 2));
+ frame => AssertFrame.ComponentRenderMode(frame, renderMode));
}
[Fact]
@@ -2135,7 +2135,7 @@ public void CannotAddComponentRenderModeToElement()
// Act/Assert
var ex = Assert.Throws(() =>
{
- builder.AddComponentRenderMode(1, new TestRenderMode());
+ builder.AddComponentRenderMode(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(1, null);
+ builder.AddComponentRenderMode(null);
});
Assert.Equal("renderMode", ex.ParamName);
}
@@ -2161,7 +2161,7 @@ public void CannotAddParametersAfterComponentRenderMode()
// Arrange
var builder = new RenderTreeBuilder();
builder.OpenComponent(0);
- builder.AddComponentRenderMode(1, new TestRenderMode());
+ builder.AddComponentRenderMode(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, 0));
+ frame => AssertFrame.ComponentRenderMode(frame, renderMode));
}
[Fact]
@@ -2214,7 +2214,7 @@ public void CanAddNamedEvent()
// Act
builder.OpenElement(0, "elem");
builder.AddAttribute(1, "attr", 123);
- builder.AddNamedEvent(2, "myeventtype", "my event name");
+ builder.AddNamedEvent("myeventtype", "my event name");
builder.CloseElement();
// Assert
@@ -2235,7 +2235,7 @@ public void CannotAddNamedEventToComponent()
// Act/Assert
var ex = Assert.Throws(() =>
{
- builder.AddNamedEvent(1, "x", "y");
+ builder.AddNamedEvent("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(1, null, "assigned name");
+ builder.AddNamedEvent(null, "assigned name");
});
Assert.Equal("eventType", ex.ParamName);
}
@@ -2265,7 +2265,7 @@ public void CannotAddNamedEventWithNullAssignedName()
// Act/Assert
var ex = Assert.Throws(() =>
{
- builder.AddNamedEvent(1, "eventtype", null);
+ builder.AddNamedEvent("eventtype", null);
});
Assert.Equal("assignedName", ex.ParamName);
}
@@ -2280,7 +2280,7 @@ public void CannotAddNamedEventWithEmptyAssignedName()
// Act/Assert
var ex = Assert.Throws(() =>
{
- builder.AddNamedEvent(1, "eventtype", "");
+ builder.AddNamedEvent("eventtype", "");
});
Assert.Equal("assignedName", ex.ParamName);
}
@@ -2291,7 +2291,7 @@ public void CannotAddAttributesAfterNamedEvent()
// Arrange
var builder = new RenderTreeBuilder();
builder.OpenElement(0, "elem");
- builder.AddNamedEvent(1, "someevent", "somename");
+ builder.AddNamedEvent("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", 2),
+ frame => AssertFrame.NamedEvent(frame, "onsubmit", "some custom name"),
frame => AssertFrame.Element(frame, "other", 1));
}
diff --git a/src/Components/Endpoints/src/Builder/ConfiguredRenderModesMetadata.cs b/src/Components/Endpoints/src/Builder/ConfiguredRenderModesMetadata.cs
new file mode 100644
index 000000000000..65c99a924843
--- /dev/null
+++ b/src/Components/Endpoints/src/Builder/ConfiguredRenderModesMetadata.cs
@@ -0,0 +1,9 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.AspNetCore.Components.Endpoints;
+
+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 eac2bd011495..8013ab54794d 100644
--- a/src/Components/Endpoints/src/Builder/RazorComponentEndpointDataSource.cs
+++ b/src/Components/Endpoints/src/Builder/RazorComponentEndpointDataSource.cs
@@ -24,22 +24,27 @@ internal class RazorComponentEndpointDataSource<[DynamicallyAccessedMembers(Comp
private readonly IApplicationBuilder _applicationBuilder;
private readonly RenderModeEndpointProvider[] _renderModeEndpointProviders;
private readonly RazorComponentEndpointFactory _factory;
-
+ private readonly HotReloadService? _hotReloadService;
private List? _endpoints;
- // TODO: Implement endpoint data source updates https://github.com/dotnet/aspnetcore/issues/47026
- private readonly CancellationTokenSource _cancellationTokenSource;
- private readonly IChangeToken _changeToken;
+ private CancellationTokenSource _cancellationTokenSource;
+ private IChangeToken _changeToken;
+
+ // Internal for testing.
+ internal ComponentApplicationBuilder Builder => _builder;
+ internal List> Conventions => _conventions;
public RazorComponentEndpointDataSource(
ComponentApplicationBuilder builder,
IEnumerable renderModeEndpointProviders,
IApplicationBuilder applicationBuilder,
- RazorComponentEndpointFactory factory)
+ RazorComponentEndpointFactory factory,
+ HotReloadService? hotReloadService = null)
{
_builder = builder;
_applicationBuilder = applicationBuilder;
_renderModeEndpointProviders = renderModeEndpointProviders.ToArray();
_factory = factory;
+ _hotReloadService = hotReloadService;
DefaultBuilder = new RazorComponentsEndpointConventionBuilder(
_lock,
builder,
@@ -62,7 +67,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 acesses the data sources to get the endpoints.
+ // * The first request arrives and the DfaMatcherBuilder accesses the data sources to get the endpoints.
// * The endpoints get created and the conventions get applied.
Initialize();
Debug.Assert(_changeToken != null);
@@ -89,47 +94,63 @@ private void Initialize()
private void UpdateEndpoints()
{
- var endpoints = new List();
- var context = _builder.Build();
-
- foreach (var definition in context.Pages)
+ lock (_lock)
{
- _factory.AddEndpoints(endpoints, typeof(TRootComponent), definition, _conventions, _finallyConventions);
- }
+ var endpoints = new List();
+ var context = _builder.Build();
+ var configuredRenderModesMetadata = new ConfiguredRenderModesMetadata(
+ Options.ConfiguredRenderModes.ToArray());
- ICollection renderModes = Options.ConfiguredRenderModes;
+ foreach (var definition in context.Pages)
+ {
+ _factory.AddEndpoints(endpoints, typeof(TRootComponent), definition, _conventions, _finallyConventions, configuredRenderModesMetadata);
+ }
- foreach (var renderMode in renderModes)
- {
- var found = false;
- foreach (var provider in _renderModeEndpointProviders)
+ ICollection renderModes = Options.ConfiguredRenderModes;
+
+ foreach (var renderMode in renderModes)
{
- if (provider.Supports(renderMode))
+ 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)
{
- found = true;
- RenderModeEndpointProvider.AddEndpoints(
- endpoints,
- typeof(TRootComponent),
- provider.GetEndpointBuilders(renderMode, _applicationBuilder.New()),
- renderMode,
- _conventions,
- _finallyConventions);
+ 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().");
}
}
- if (!found)
+ var oldCancellationTokenSource = _cancellationTokenSource;
+ _endpoints = endpoints;
+ _cancellationTokenSource = new CancellationTokenSource();
+ _changeToken = new CancellationChangeToken(_cancellationTokenSource.Token);
+ oldCancellationTokenSource?.Cancel();
+ if (_hotReloadService is { MetadataUpdateSupported : true })
{
- 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.");
+ ChangeToken.OnChange(_hotReloadService.GetChangeToken, UpdateEndpoints);
}
}
-
- _endpoints = endpoints;
}
+
public override IChangeToken GetChangeToken()
{
- // TODO: Handle updates if necessary (for hot reload).
+ Initialize();
+ Debug.Assert(_changeToken != null);
+ Debug.Assert(_endpoints != null);
return _changeToken;
}
}
diff --git a/src/Components/Endpoints/src/Builder/RazorComponentEndpointDataSourceFactory.cs b/src/Components/Endpoints/src/Builder/RazorComponentEndpointDataSourceFactory.cs
index 3c66aa7264dc..3dcee668151a 100644
--- a/src/Components/Endpoints/src/Builder/RazorComponentEndpointDataSourceFactory.cs
+++ b/src/Components/Endpoints/src/Builder/RazorComponentEndpointDataSourceFactory.cs
@@ -14,13 +14,16 @@ internal class RazorComponentEndpointDataSourceFactory
{
private readonly RazorComponentEndpointFactory _factory;
private readonly IEnumerable _providers;
+ private readonly HotReloadService? _hotReloadService;
public RazorComponentEndpointDataSourceFactory(
RazorComponentEndpointFactory factory,
- IEnumerable providers)
+ IEnumerable providers,
+ HotReloadService? hotReloadService = null)
{
_factory = factory;
_providers = providers;
+ _hotReloadService = hotReloadService;
}
public RazorComponentEndpointDataSource CreateDataSource<[DynamicallyAccessedMembers(Component)] TRootComponent>(IEndpointRouteBuilder endpoints)
@@ -28,6 +31,6 @@ public RazorComponentEndpointDataSourceFactory(
var builder = ComponentApplicationBuilder.GetBuilder() ??
DefaultRazorComponentApplication.Instance.GetBuilder();
- return new RazorComponentEndpointDataSource(builder, _providers, endpoints.CreateApplicationBuilder(), _factory);
+ return new RazorComponentEndpointDataSource(builder, _providers, endpoints.CreateApplicationBuilder(), _factory, _hotReloadService);
}
}
diff --git a/src/Components/Endpoints/src/Builder/RazorComponentEndpointFactory.cs b/src/Components/Endpoints/src/Builder/RazorComponentEndpointFactory.cs
index c14ae30a8f2b..117aaea090f3 100644
--- a/src/Components/Endpoints/src/Builder/RazorComponentEndpointFactory.cs
+++ b/src/Components/Endpoints/src/Builder/RazorComponentEndpointFactory.cs
@@ -24,7 +24,8 @@ internal void AddEndpoints(
[DynamicallyAccessedMembers(Component)] Type rootComponent,
PageComponentInfo pageDefinition,
IReadOnlyList> conventions,
- IReadOnlyList> finallyConventions)
+ IReadOnlyList> finallyConventions,
+ ConfiguredRenderModesMetadata configuredRenderModesMetadata)
{
// 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.
@@ -48,6 +49,7 @@ 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
new file mode 100644
index 000000000000..51680ca61034
--- /dev/null
+++ b/src/Components/Endpoints/src/DependencyInjection/HotReloadService.cs
@@ -0,0 +1,42 @@
+// 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 0da30332d2c4..8098deb3b6ce 100644
--- a/src/Components/Endpoints/src/DependencyInjection/HttpNavigationManager.cs
+++ b/src/Components/Endpoints/src/DependencyInjection/HttpNavigationManager.cs
@@ -11,6 +11,7 @@ internal sealed class HttpNavigationManager : NavigationManager, IHostEnvironmen
protected override void NavigateToCore(string uri, bool forceLoad)
{
- throw new NavigationException(uri);
+ var absoluteUriString = ToAbsoluteUri(uri).ToString();
+ throw new NavigationException(absoluteUriString);
}
}
diff --git a/src/Components/Endpoints/src/DependencyInjection/RazorComponentsServiceCollectionExtensions.cs b/src/Components/Endpoints/src/DependencyInjection/RazorComponentsServiceCollectionExtensions.cs
index 3b143bb6cff2..d361c17687c4 100644
--- a/src/Components/Endpoints/src/DependencyInjection/RazorComponentsServiceCollectionExtensions.cs
+++ b/src/Components/Endpoints/src/DependencyInjection/RazorComponentsServiceCollectionExtensions.cs
@@ -2,6 +2,7 @@
// 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;
@@ -39,12 +40,13 @@ 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
@@ -63,6 +65,7 @@ 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 a4f923605d68..a3ca0d2f9115 100644
--- a/src/Components/Endpoints/src/PublicAPI.Unshipped.txt
+++ b/src/Components/Endpoints/src/PublicAPI.Unshipped.txt
@@ -18,25 +18,6 @@ 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
@@ -54,11 +35,26 @@ 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 979cfaa17e16..9a8663ec5098 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(componentType, renderMode);
+ return new SSRRenderModeBoundary(_httpContext, componentType, renderMode);
}
}
@@ -84,7 +84,7 @@ public async ValueTask PrerenderComponentAsync(
{
var rootComponent = prerenderMode is null
? InstantiateComponent(componentType)
- : new SSRRenderModeBoundary(componentType, prerenderMode);
+ : new SSRRenderModeBoundary(_httpContext, 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 1118dfeb5825..0c46325988a1 100644
--- a/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs
+++ b/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs
@@ -52,6 +52,8 @@ 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 8f1529e3e0a0..f0ac9aaba6db 100644
--- a/src/Components/Endpoints/src/Rendering/SSRRenderModeBoundary.cs
+++ b/src/Components/Endpoints/src/Rendering/SSRRenderModeBoundary.cs
@@ -7,6 +7,7 @@
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;
@@ -31,8 +32,13 @@ internal class SSRRenderModeBoundary : IComponent
private IReadOnlyDictionary? _latestParameters;
private string? _markerKey;
- public SSRRenderModeBoundary([DynamicallyAccessedMembers(Component)] Type componentType, IComponentRenderMode renderMode)
+ public SSRRenderModeBoundary(
+ HttpContext httpContext,
+ [DynamicallyAccessedMembers(Component)] Type componentType,
+ IComponentRenderMode renderMode)
{
+ AssertRenderModeIsConfigured(httpContext, componentType, renderMode);
+
_componentType = componentType;
_renderMode = renderMode;
_prerender = renderMode switch
@@ -44,6 +50,50 @@ public SSRRenderModeBoundary([DynamicallyAccessedMembers(Component)] Type compon
};
}
+ 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 3049313f9a21..0f4e385ac288 100644
--- a/src/Components/Endpoints/src/Results/RazorComponentResult.cs
+++ b/src/Components/Endpoints/src/Results/RazorComponentResult.cs
@@ -1,18 +1,19 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
-using Microsoft.AspNetCore.Http;
-using Microsoft.Extensions.DependencyInjection;
+using Microsoft.AspNetCore.Components;
+using Microsoft.AspNetCore.Components.Endpoints;
using Microsoft.Extensions.Internal;
using static Microsoft.AspNetCore.Internal.LinkerFlags;
-namespace Microsoft.AspNetCore.Components.Endpoints;
+namespace Microsoft.AspNetCore.Http.HttpResults;
///
/// An that renders a Razor Component.
///
-public class RazorComponentResult : IResult
+public class RazorComponentResult : IResult, IStatusCodeHttpResult, IContentTypeHttpResult
{
private static readonly IReadOnlyDictionary EmptyParameters
= new Dictionary().AsReadOnly();
@@ -22,7 +23,7 @@ public class RazorComponentResult : IResult
///
/// The type of the component to render. This must implement .
public RazorComponentResult([DynamicallyAccessedMembers(Component)] Type componentType)
- : this(componentType, null)
+ : this(componentType, ReadOnlyDictionary.Empty)
{
}
@@ -31,7 +32,9 @@ 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, object? parameters)
+ public RazorComponentResult(
+ [DynamicallyAccessedMembers(Component)] Type componentType,
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] object parameters)
: this(componentType, CoerceParametersObjectToDictionary(parameters))
{
}
@@ -41,25 +44,27 @@ 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, 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
- ? null
+ ? throw new ArgumentNullException(nameof(parameters))
: (IReadOnlyDictionary)PropertyHelper.ObjectToDictionary(parameters);
///
/// Gets the component type.
///
- [DynamicallyAccessedMembers(Component)]
public Type ComponentType { get; }
///
@@ -87,15 +92,10 @@ public RazorComponentResult([DynamicallyAccessedMembers(Component)] Type compone
public bool PreventStreamingRendering { get; set; }
///
- /// Requests the service of
- ///
- /// to process itself in the given .
+ /// Processes this result in the given .
///
/// An associated with the current request.
/// A which will complete when execution is completed.
public Task ExecuteAsync(HttpContext httpContext)
- {
- var executor = httpContext.RequestServices.GetRequiredService();
- return executor.ExecuteAsync(httpContext, this);
- }
+ => RazorComponentResultExecutor.ExecuteAsync(httpContext, this);
}
diff --git a/src/Components/Endpoints/src/Results/RazorComponentResultExecutor.cs b/src/Components/Endpoints/src/Results/RazorComponentResultExecutor.cs
index af14fa146f70..5467336e932d 100644
--- a/src/Components/Endpoints/src/Results/RazorComponentResultExecutor.cs
+++ b/src/Components/Endpoints/src/Results/RazorComponentResultExecutor.cs
@@ -9,23 +9,15 @@
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.DependencyInjection;
using static Microsoft.AspNetCore.Internal.LinkerFlags;
+using Microsoft.AspNetCore.Http.HttpResults;
namespace Microsoft.AspNetCore.Components.Endpoints;
-///
-/// Executes a .
-///
-public class RazorComponentResultExecutor
+internal static class RazorComponentResultExecutor
{
- ///
- /// 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 const string DefaultContentType = "text/html; charset=utf-8";
- ///
- /// Executes a asynchronously.
- ///
- public virtual Task ExecuteAsync(HttpContext httpContext, RazorComponentResult result)
+ public static Task ExecuteAsync(HttpContext httpContext, RazorComponentResult result)
{
ArgumentNullException.ThrowIfNull(httpContext);
@@ -44,7 +36,7 @@ public virtual Task ExecuteAsync(HttpContext httpContext, RazorComponentResult r
result.PreventStreamingRendering);
}
- internal static Task RenderComponentToResponse(
+ private 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 3019550b9100..5ab0f23481f0 100644
--- a/src/Components/Endpoints/src/Results/RazorComponentResultOfT.cs
+++ b/src/Components/Endpoints/src/Results/RazorComponentResultOfT.cs
@@ -2,15 +2,16 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics.CodeAnalysis;
-using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Components;
using static Microsoft.AspNetCore.Internal.LinkerFlags;
-namespace Microsoft.AspNetCore.Components.Endpoints;
+namespace Microsoft.AspNetCore.Http.HttpResults;
///
/// 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 .
@@ -23,7 +24,8 @@ public RazorComponentResult() : base(typeof(TComponent))
/// Constructs an instance of .
///
/// Parameters for the component.
- public RazorComponentResult(object parameters) : base(typeof(TComponent), parameters)
+ public RazorComponentResult(
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] object parameters) : base(typeof(TComponent), parameters)
{
}
diff --git a/src/Components/Endpoints/test/EndpointHtmlRendererTest.cs b/src/Components/Endpoints/test/EndpointHtmlRendererTest.cs
index 7b3cda4f7ac4..a2c730f1f21c 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(2, "onsubmit", "my-name");
+ builder.AddNamedEvent("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(2, "onsubmit", firstRender ? "my-name-1" : "my-name-2");
+ builder.AddNamedEvent("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(2, "onsubmit", "default");
+ builder.AddNamedEvent("onsubmit", "default");
builder.CloseElement();
}
}
@@ -1201,12 +1201,12 @@ protected override void BuildRenderTree(RenderTreeBuilder builder)
if (!hasRendered)
{
builder.AddAttribute(1, "onsubmit", () => { });
- builder.AddNamedEvent(2, "onsubmit", "default");
+ builder.AddNamedEvent("onsubmit", "default");
}
else
{
builder.AddAttribute(1, "onsubmit", () => { GC.KeepAlive(new object()); });
- builder.AddNamedEvent(2, "onsubmit", "default");
+ builder.AddNamedEvent("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(2, "onsubmit", "default");
+ builder.AddNamedEvent("onsubmit", "default");
builder.CloseElement();
}
@@ -1248,7 +1248,7 @@ protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.OpenElement(0, "form");
builder.AddAttribute(1, "onsubmit", () => { });
- builder.AddNamedEvent(2, "onsubmit", "default");
+ builder.AddNamedEvent("onsubmit", "default");
builder.CloseElement();
}
}
diff --git a/src/Components/Endpoints/test/HotReloadServiceTests.cs b/src/Components/Endpoints/test/HotReloadServiceTests.cs
new file mode 100644
index 000000000000..afa86e09971e
--- /dev/null
+++ b/src/Components/Endpoints/test/HotReloadServiceTests.cs
@@ -0,0 +1,213 @@
+// 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 07606588ea10..c758d717c8c2 100644
--- a/src/Components/Endpoints/test/RazorComponentEndpointDataSourceTest.cs
+++ b/src/Components/Endpoints/test/RazorComponentEndpointDataSourceTest.cs
@@ -228,7 +228,8 @@ private RazorComponentEndpointDataSource CreateDataSource.Instance.GetBuilder(),
services?.GetService>() ?? Enumerable.Empty(),
new ApplicationBuilder(services ?? new ServiceCollection().BuildServiceProvider()),
- new RazorComponentEndpointFactory());
+ new RazorComponentEndpointFactory(),
+ new HotReloadService() { MetadataUpdateSupported = true });
if (renderModes != null)
{
diff --git a/src/Components/Endpoints/test/RazorComponentEndpointFactoryTest.cs b/src/Components/Endpoints/test/RazorComponentEndpointFactoryTest.cs
index 0e35b75e68f4..a7dbd751bf25 100644
--- a/src/Components/Endpoints/test/RazorComponentEndpointFactoryTest.cs
+++ b/src/Components/Endpoints/test/RazorComponentEndpointFactoryTest.cs
@@ -18,13 +18,16 @@ 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);
+ finallyConventions,
+ configuredRenderModes);
var endpoint = Assert.Single(endpoints);
Assert.Equal("/ (App)", endpoint.DisplayName);
@@ -35,6 +38,8 @@ 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());
@@ -63,7 +68,8 @@ public void AddEndpoints_RunsConventions()
"/",
Array.Empty