Skip to content

Commit

Permalink
Callsite rendermodes (#48967) (#49055)
Browse files Browse the repository at this point in the history
  • Loading branch information
SteveSandersonMS authored Jun 29, 2023
1 parent 541b49e commit 8564dfe
Show file tree
Hide file tree
Showing 23 changed files with 638 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public AuthorizeRouteViewTest()
var services = serviceCollection.BuildServiceProvider();
_renderer = new TestRenderer(services);
var componentFactory = new ComponentFactory(new DefaultComponentActivator(), _renderer);
_authorizeRouteViewComponent = (AuthorizeRouteView)componentFactory.InstantiateComponent(services, typeof(AuthorizeRouteView), null);
_authorizeRouteViewComponent = (AuthorizeRouteView)componentFactory.InstantiateComponent(services, typeof(AuthorizeRouteView), null, null);
_authorizeRouteViewComponentId = _renderer.AssignRootComponentId(_authorizeRouteViewComponent);
}

Expand Down
26 changes: 20 additions & 6 deletions src/Components/Components/src/ComponentFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,26 @@ private static ComponentTypeInfoCacheEntry GetComponentTypeInfo([DynamicallyAcce
return cacheEntry;
}

public IComponent InstantiateComponent(IServiceProvider serviceProvider, [DynamicallyAccessedMembers(Component)] Type componentType, int? parentComponentId)
public IComponent InstantiateComponent(IServiceProvider serviceProvider, [DynamicallyAccessedMembers(Component)] Type componentType, IComponentRenderMode? callerSpecifiedRenderMode, int? parentComponentId)
{
var componentTypeInfo = GetComponentTypeInfo(componentType);
var component = componentTypeInfo.ComponentTypeRenderMode is null
? _componentActivator.CreateInstance(componentType)
: _renderer.ResolveComponentForRenderMode(componentType, parentComponentId, _componentActivator, componentTypeInfo.ComponentTypeRenderMode);
var (componentTypeRenderMode, propertyInjector) = GetComponentTypeInfo(componentType);
IComponent component;

if (componentTypeRenderMode is null && callerSpecifiedRenderMode is null)
{
// Typical case where no rendermode is specified in either location. We don't call ResolveComponentForRenderMode in this case.
component = _componentActivator.CreateInstance(componentType);
}
else
{
// At least one rendermode is specified. We require that it's exactly one, and use ResolveComponentForRenderMode with it.
var effectiveRenderMode = callerSpecifiedRenderMode is null
? componentTypeRenderMode!
: componentTypeRenderMode is null
? callerSpecifiedRenderMode
: throw new InvalidOperationException($"The component type '{componentType}' has a fixed rendermode of '{componentTypeRenderMode}', so it is not valid to specify any rendermode when using this component.");
component = _renderer.ResolveComponentForRenderMode(componentType, parentComponentId, _componentActivator, effectiveRenderMode);
}

if (component is null)
{
Expand All @@ -61,7 +75,7 @@ public IComponent InstantiateComponent(IServiceProvider serviceProvider, [Dynami
if (component.GetType() == componentType)
{
// Fast, common case: use the cached data we already looked up
componentTypeInfo.PerformPropertyInjection(serviceProvider, component);
propertyInjector(serviceProvider, component);
}
else
{
Expand Down
9 changes: 8 additions & 1 deletion src/Components/Components/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,15 @@ Microsoft.AspNetCore.Components.RenderHandle.DispatchExceptionAsync(System.Excep
*REMOVED*Microsoft.AspNetCore.Components.NavigationManager.ToAbsoluteUri(string! relativeUri) -> System.Uri!
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
Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder.SetEventHandlerName(string! eventHandlerName) -> void
*REMOVED*Microsoft.AspNetCore.Components.RouteData.RouteData(System.Type! pageType, System.Collections.Generic.IReadOnlyDictionary<string!, object!>! routeValues) -> void
*REMOVED*Microsoft.AspNetCore.Components.RouteData.RouteValues.get -> System.Collections.Generic.IReadOnlyDictionary<string!, object!>!
Microsoft.AspNetCore.Components.RenderTree.ComponentFrameFlags
Microsoft.AspNetCore.Components.RenderTree.ComponentFrameFlags.HasCallerSpecifiedRenderMode = 1 -> Microsoft.AspNetCore.Components.RenderTree.ComponentFrameFlags
Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrame.ComponentFrameFlags.get -> Microsoft.AspNetCore.Components.RenderTree.ComponentFrameFlags
Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrameType.ComponentRenderMode = 9 -> Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrameType
Microsoft.AspNetCore.Components.RouteData.RouteData(System.Type! pageType, System.Collections.Generic.IReadOnlyDictionary<string!, object?>! routeValues) -> void
Microsoft.AspNetCore.Components.RouteData.RouteValues.get -> System.Collections.Generic.IReadOnlyDictionary<string!, object?>!
Microsoft.AspNetCore.Components.Routing.IRoutingStateProvider
Expand Down Expand Up @@ -119,6 +125,7 @@ virtual Microsoft.AspNetCore.Components.Rendering.ComponentState.DisposeAsync()
virtual Microsoft.AspNetCore.Components.RenderTree.Renderer.AddPendingTask(Microsoft.AspNetCore.Components.Rendering.ComponentState? componentState, System.Threading.Tasks.Task! task) -> void
virtual Microsoft.AspNetCore.Components.RenderTree.Renderer.CreateComponentState(int componentId, Microsoft.AspNetCore.Components.IComponent! component, Microsoft.AspNetCore.Components.Rendering.ComponentState? parentComponentState) -> Microsoft.AspNetCore.Components.Rendering.ComponentState!
virtual Microsoft.AspNetCore.Components.RenderTree.Renderer.DispatchEventAsync(ulong eventHandlerId, Microsoft.AspNetCore.Components.RenderTree.EventFieldInfo? fieldInfo, System.EventArgs! eventArgs, bool quiesce) -> System.Threading.Tasks.Task!
virtual Microsoft.AspNetCore.Components.RenderTree.Renderer.ResolveComponentForRenderMode(System.Type! componentType, int? parentComponentId, Microsoft.AspNetCore.Components.IComponentActivator! componentActivator, Microsoft.AspNetCore.Components.IComponentRenderMode! componentTypeRenderMode) -> Microsoft.AspNetCore.Components.IComponent!
virtual Microsoft.AspNetCore.Components.RenderTree.Renderer.ResolveComponentForRenderMode(System.Type! componentType, int? parentComponentId, Microsoft.AspNetCore.Components.IComponentActivator! componentActivator, Microsoft.AspNetCore.Components.IComponentRenderMode! renderMode) -> Microsoft.AspNetCore.Components.IComponent!
virtual Microsoft.AspNetCore.Components.RenderTree.Renderer.ShouldTrackNamedEventHandlers() -> bool
virtual Microsoft.AspNetCore.Components.RenderTree.Renderer.TrackNamedEventId(ulong eventHandlerId, int componentId, string! eventHandlerName) -> void
~Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrame.ComponentRenderMode.get -> Microsoft.AspNetCore.Components.IComponentRenderMode
17 changes: 17 additions & 0 deletions src/Components/Components/src/RenderTree/ComponentFrameFlags.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.AspNetCore.Components.RenderTree;

/// <summary>
/// Types in the Microsoft.AspNetCore.Components.RenderTree namespace are not recommended for use outside
/// of the Blazor framework. These types will change in future release.
/// </summary>
[Flags]
public enum ComponentFrameFlags : byte
{
/// <summary>
/// Indicates that the caller has specified a render mode.
/// </summary>
HasCallerSpecifiedRenderMode = 1,
}
Original file line number Diff line number Diff line change
Expand Up @@ -953,7 +953,7 @@ private static void InitializeNewComponentFrame(ref DiffContext diffContext, int
var frames = diffContext.NewTree;
ref var frame = ref frames[frameIndex];
var parentComponentId = diffContext.ComponentId;
var childComponentState = diffContext.Renderer.InstantiateChildComponentOnFrame(ref frame, parentComponentId);
var childComponentState = diffContext.Renderer.InstantiateChildComponentOnFrame(frames, frameIndex, parentComponentId);

// Set initial parameters
var initialParametersLifetime = new ParameterViewLifetime(diffContext.BatchBuilder);
Expand Down
32 changes: 32 additions & 0 deletions src/Components/Components/src/RenderTree/RenderTreeFrame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ public struct RenderTreeFrame
// RenderTreeFrameType.Component
// --------------------------------------------------------------------------------

[FieldOffset(6)] internal ComponentFrameFlags ComponentFrameFlagsField;
[FieldOffset(8)] internal int ComponentSubtreeLengthField;
[FieldOffset(12)] internal int ComponentIdField;
[FieldOffset(16)]
Expand All @@ -144,6 +145,12 @@ public struct RenderTreeFrame
[FieldOffset(24)] internal ComponentState ComponentStateField;
[FieldOffset(32)] internal object ComponentKeyField;

/// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Component"/>
/// gets the <see cref="ComponentFrameFlags"/> for the component frame.
/// </summary>
public ComponentFrameFlags ComponentFrameFlags => ComponentFrameFlagsField;

/// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Component"/>
/// gets the number of frames in the subtree for which this frame is the root.
Expand Down Expand Up @@ -250,6 +257,31 @@ public struct RenderTreeFrame
/// </summary>
public string MarkupContent => MarkupContentField;

// --------------------------------------------------------------------------------
// RenderTreeFrameType.ComponentRenderMode
// --------------------------------------------------------------------------------

[FieldOffset(16)] internal IComponentRenderMode ComponentRenderModeField;

/// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.ComponentRenderMode"/>,
/// gets the specified <see cref="IComponentRenderMode"/>. Otherwise, the value is undefined.
/// </summary>
public IComponentRenderMode ComponentRenderMode
{
get
{
// Normally we don't check the frame type matches, and leave it to the caller to be responsible for only evaluating the correct properties.
// However the name "ComponentRenderMode" sounds so much like it would be a field on Component frames that we'll explicitly check to avoid mistakes.
if (FrameType != RenderTreeFrameType.ComponentRenderMode)
{
throw new InvalidOperationException($"The {nameof(ComponentRenderMode)} field only exists on frames of type {nameof(RenderTreeFrameType.ComponentRenderMode)}.");
}

return ComponentRenderModeField;
}
}

// Element constructor
private RenderTreeFrame(int sequence, int elementSubtreeLength, string elementName, object elementKey)
: this()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,4 +134,19 @@ public void AppendRegion(int sequence)
FrameTypeField = RenderTreeFrameType.Region,
};
}

public void AppendComponentRenderMode(int sequence, IComponentRenderMode renderMode)
{
if (_itemsInUse == _items.Length)
{
GrowBuffer(_items.Length * 2);
}

_items[_itemsInUse++] = new RenderTreeFrame
{
SequenceField = sequence,
FrameTypeField = RenderTreeFrameType.ComponentRenderMode,
ComponentRenderModeField = renderMode,
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,9 @@ public enum RenderTreeFrameType : short
/// Represents a block of markup content.
/// </summary>
Markup = 8,

/// <summary>
/// Represents an instruction to use a specified render mode for the component.
/// </summary>
ComponentRenderMode = 9,
}
50 changes: 40 additions & 10 deletions src/Components/Components/src/RenderTree/Renderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ await Dispatcher.InvokeAsync(() =>
/// <param name="componentType">The type of the component to instantiate.</param>
/// <returns>The component instance.</returns>
protected IComponent InstantiateComponent([DynamicallyAccessedMembers(Component)] Type componentType)
=> _componentFactory.InstantiateComponent(_serviceProvider, componentType, null);
=> _componentFactory.InstantiateComponent(_serviceProvider, componentType, null, null);

/// <summary>
/// Associates the <see cref="IComponent"/> with the <see cref="Renderer"/>, assigning
Expand Down Expand Up @@ -475,26 +475,55 @@ public Type GetEventArgsType(ulong eventHandlerId)
: EventArgsTypeCache.GetEventArgsType(methodInfo);
}

internal ComponentState InstantiateChildComponentOnFrame(ref RenderTreeFrame frame, int parentComponentId)
internal ComponentState InstantiateChildComponentOnFrame(RenderTreeFrame[] frames, int frameIndex, int parentComponentId)
{
ref var frame = ref frames[frameIndex];
if (frame.FrameTypeField != RenderTreeFrameType.Component)
{
throw new ArgumentException($"The frame's {nameof(RenderTreeFrame.FrameType)} property must equal {RenderTreeFrameType.Component}", nameof(frame));
throw new ArgumentException($"The frame's {nameof(RenderTreeFrame.FrameType)} property must equal {RenderTreeFrameType.Component}", nameof(frameIndex));
}

if (frame.ComponentStateField != null)
{
throw new ArgumentException($"The frame already has a non-null component instance", nameof(frame));
throw new ArgumentException($"The frame already has a non-null component instance", nameof(frameIndex));
}

var newComponent = _componentFactory.InstantiateComponent(_serviceProvider, frame.ComponentTypeField, parentComponentId);
var callerSpecifiedRenderMode = frame.ComponentFrameFlags.HasFlag(ComponentFrameFlags.HasCallerSpecifiedRenderMode)
? FindCallerSpecifiedRenderMode(frames, frameIndex)
: null;

var newComponent = _componentFactory.InstantiateComponent(_serviceProvider, frame.ComponentTypeField, callerSpecifiedRenderMode, parentComponentId);
var newComponentState = AttachAndInitComponent(newComponent, parentComponentId);
frame.ComponentStateField = newComponentState;
frame.ComponentIdField = newComponentState.ComponentId;

return newComponentState;
}

private static IComponentRenderMode? FindCallerSpecifiedRenderMode(RenderTreeFrame[] frames, int componentFrameIndex)
{
// ComponentRenderMode frames are immediate children of Component frames. So, they have to appear after any parameter
// attributes (since attributes must always immediately follow Component frames), but before anything that would
// represent a different child node, such as text/element or another component. It's OK to do this linear scan
// because we consider it uncommon to specify a rendermode, and none of this happens if you don't.
var endIndex = componentFrameIndex + frames[componentFrameIndex].ComponentSubtreeLengthField;
for (var index = componentFrameIndex + 1; index <= endIndex; index++)
{
ref var frame = ref frames[index];
switch (frame.FrameType)
{
case RenderTreeFrameType.Attribute:
continue;
case RenderTreeFrameType.ComponentRenderMode:
return frame.ComponentRenderMode;
default:
break;
}
}

return null;
}

internal void AddToPendingTasksWithErrorHandling(Task task, ComponentState? owningComponentState)
{
switch (task == null ? TaskStatus.RanToCompletion : task.Status)
Expand Down Expand Up @@ -1146,23 +1175,24 @@ void NotifyExceptions(List<Exception> exceptions)

/// <summary>
/// Determines how to handle an <see cref="IComponentRenderMode"/> when obtaining a component instance.
/// This is only called for components that have specified a render mode. Subclasses may override this
/// method to return a component of a different type, or throw, depending on whether the renderer
/// This is only called when a render mode is specified either at the call site or on the component type.
///
/// Subclasses may override this method to return a component of a different type, or throw, depending on whether the renderer
/// supports the render mode and how it implements that support.
/// </summary>
/// <param name="componentType">The type of component that was requested.</param>
/// <param name="parentComponentId">The parent component ID, or null if it is a root component.</param>
/// <param name="componentActivator">An <see cref="IComponentActivator"/> that should be used when instantiating component objects.</param>
/// <param name="componentTypeRenderMode">The <see cref="IComponentRenderMode"/> declared on <paramref name="componentType"/>.</param>
/// <param name="renderMode">The <see cref="IComponentRenderMode"/> declared on <paramref name="componentType"/> or at the call site (for example, by the parent component).</param>
/// <returns>An <see cref="IComponent"/> instance.</returns>
protected internal virtual IComponent ResolveComponentForRenderMode(
[DynamicallyAccessedMembers(Component)] Type componentType,
int? parentComponentId,
IComponentActivator componentActivator,
IComponentRenderMode componentTypeRenderMode)
IComponentRenderMode renderMode)
{
// Nothing is supported by default. Subclasses must override this to opt into supporting specific render modes.
throw new NotSupportedException($"Cannot supply a component of type '{componentType}' because the current platform does not support the render mode '{componentTypeRenderMode}'.");
throw new NotSupportedException($"Cannot supply a component of type '{componentType}' because the current platform does not support the render mode '{renderMode}'.");
}

/// <summary>
Expand Down
Loading

0 comments on commit 8564dfe

Please sign in to comment.