dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.36k stars 9.99k forks source link

API review: Components SSR streaming and render modes #50057

Closed SteveSandersonMS closed 1 year ago

SteveSandersonMS commented 1 year ago

Background and Motivation

Server-side rendering for components in .NET 8. This is a pretty immense set of changes accounting for much of the work we've done for the release.

The team decided early on not to attempt to do API review for each piece as it was implemented, because there was too much churn as the designs evolved. As such it's now a huge review-bomb, but I think most of it should be quite understandable with a bit of explanation.

I'm trying to split up the API reviews into thematic areas and this one is meant to focus on rendermodes. However some other smaller, tangential bits have snuck in here too and that's probably fine.

Proposed API

namespace Microsoft.AspNetCore.Components
{
+    /// <summary>
+    /// Represents a render mode for a component.
+    /// </summary>
+    public interface IComponentRenderMode {} // Just a marker. Each renderer assigns its own meanings to these types.

+    /// <summary>
+    /// Specifies a fixed rendering mode for a component type.
+    ///
+    /// Where possible, components should not specify any render mode this way, and should
+    /// 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.
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Class)]
+    public abstract class RenderModeAttribute : Attribute
+    {
+        /// <summary>
+        /// Gets the fixed rendering mode for a component type.
+        /// </summary>
+        public abstract IComponentRenderMode Mode { get; }
+    }

+    /// <summary>
+    /// Supplies a cascading value that can be received by components using
+    /// <see cref="CascadingParameterAttribute"/>.
+    /// </summary>
+    public class CascadingValueSource<TValue> : ICascadingValueSupplier
+    {
+        public CascadingValueSource(TValue value, bool isFixed) {}
+        public CascadingValueSource(string name, TValue value, bool isFixed) {}
+        public CascadingValueSource(Func<TValue> valueFactory, bool isFixed) {}
+
+        /// <summary>
+        /// Notifies subscribers that the value has changed (for example, if it has been mutated).
+        /// </summary>
+        /// <returns>A <see cref="Task"/> that completes when the notifications have been issued.</returns>
+        public Task NotifyChangedAsync();
+
+        /// <summary>
+        /// Notifies subscribers that the value has changed, supplying a new value.
+        /// </summary>
+        /// <param name="newValue"></param>
+        /// <returns>A <see cref="Task"/> that completes when the notifications have been issued.</returns>
+        public Task NotifyChangedAsync(TValue newValue)
+    }

    public readonly struct ParameterView
    {
-        public IReadOnlyDictionary<string, object!> ToDictionary() {}
+        public IReadOnlyDictionary<string, object?> ToDictionary() {}
    }

+    /// <summary>
+    /// An attribute to indicate whether to stream the rendering of a component and its descendants.
+    ///
+    /// This attribute only takes effect within renderers that support streaming rendering (for example,
+    /// server-side HTML rendering from a Razor Component endpoint). In other hosting models it has no effect.
+    ///
+    /// If a component type does not declare this attribute, then instances of that component type will share
+    /// the same streaming rendering mode as their parent component.
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
+    public class StreamRenderingAttribute : Attribute
+    {
+        /// <summary>
+        /// Constructs an instance of <see cref="StreamRenderingAttribute"/>
+        /// </summary>
+        /// <param name="enabled">A flag to indicate whether this component and its descendants should stream their rendering.</param>
+        public StreamRenderingAttribute(bool enabled) {}
+
+        /// <summary>
+        /// Gets a flag indicating whether this component and its descendants should stream their rendering.
+        /// </summary>
+        public bool Enabled { get; }
+    }

    // Note: Our own hosting models call this as needed. Applications don't need to call it manually, and if they do, it's a no-op because it uses TryAddEnumerable.
+    /// <summary>
+    /// Enables component parameters to be supplied from the query string with <see cref="SupplyParameterFromQueryAttribute"/>.
+    /// </summary>
+    public static class SupplyParameterFromQueryProviderServiceCollectionExtensions
+    {
+        /// <summary>
+        /// Enables component parameters to be supplied from the query string with <see cref="SupplyParameterFromQueryAttribute"/>.
+        /// </summary>
+        /// <param name="services">The <see cref="IServiceCollection"/>.</param>
+        /// <returns>The <see cref="IServiceCollection"/>.</returns>
+        public static IServiceCollection AddSupplyValueFromQueryProvider(this IServiceCollection services) {}
+    }

+    /// <summary>
+    /// Extension methods for configuring cascading values on an <see cref="IServiceCollection"/>.
+    /// </summary>
+    public static class CascadingValueServiceCollectionExtensions
+    {
+        /// <summary>
+        /// Adds a cascading value to the <paramref name="serviceCollection"/>. This is equivalent to having
+        /// a fixed <see cref="CascadingValue{TValue}"/> at the root of the component hierarchy.
+        /// </summary>
+        /// <typeparam name="TValue">The value type.</typeparam>
+        /// <param name="serviceCollection">The <see cref="IServiceCollection"/>.</param>
+        /// <param name="valueFactory">A callback that supplies a fixed value within each service provider scope.</param>
+        /// <param name="sourceFactory">A callback that supplies a <see cref="CascadingValueSource{TValue}"/> within each service provider scope.</param>
+        /// <returns>The <see cref="IServiceCollection"/>.</returns>
+        public static IServiceCollection AddCascadingValue<TValue>(
+            this IServiceCollection serviceCollection, Func<IServiceProvider, TValue> valueFactory) {}
+
+        public static IServiceCollection AddCascadingValue<TValue>(
+            this IServiceCollection serviceCollection, string name, Func<IServiceProvider, TValue> valueFactory) {}
+
+        public static IServiceCollection AddCascadingValue<TValue>(
+            this IServiceCollection serviceCollection, Func<IServiceProvider, CascadingValueSource<TValue>> sourceFactory) {}
+    }

}

namespace Microsoft.AspNetCore.Components.Infrastructure
{
    public class ComponentStatePersistenceManager
    {
+        Task PersistStateAsync(IPersistentComponentStateStore store, Dispatcher dispatcher) {}
    }
}

namespace Microsoft.AspNetCore.Components.Rendering
{
    public class Renderer
    {
+        /// <summary>
+        /// Gets the <see cref="ComponentState"/> associated with the specified component.
+        /// </summary>
+        /// <param name="componentId">The component ID</param>
+        /// <returns>The corresponding <see cref="ComponentState"/>.</returns>
+        protected ComponentState GetComponentState(int componentId) {}

+        /// <summary>
+        /// Notifies the renderer that there is a pending task associated with a component. The
+        /// renderer is regarded as quiescent when all such tasks have completed.
+        /// </summary>
+        /// <param name="componentState">The <see cref="ComponentState"/> for the component associated with this pending task, if any.</param>
+        /// <param name="task">The <see cref="Task"/>.</param>
+        protected virtual void AddPendingTask(ComponentState? componentState, Task task) {}

+        /// <summary>
+        /// Creates a <see cref="ComponentState"/> instance to track state associated with a newly-instantiated component.
+        /// This is called before the component is initialized and tracked within the <see cref="Renderer"/>. Subclasses
+        /// may override this method to use their own subclasses of <see cref="ComponentState"/>.
+        /// </summary>
+        /// <param name="componentId">The ID of the newly-created component.</param>
+        /// <param name="component">The component instance.</param>
+        /// <param name="parentComponentState">The <see cref="ComponentState"/> associated with the parent component, or null if this is a root component.</param>
+        /// <returns>A <see cref="ComponentState"/> for the new component.</returns>
+        protected virtual ComponentState CreateComponentState(int componentId, IComponent component, ComponentState? parentComponentState) {}

        // Note: This is a new overload for the existing DispatchEventAsync, adding the waitForQuiescence flag. It's needed for SSR event dispatch.
+        public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo? fieldInfo, EventArgs eventArgs, bool waitForQuiescence)

+        /// <summary>
+        /// Determines how to handle an <see cref="IComponentRenderMode"/> when obtaining a component instance.
+        /// 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="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 renderMode) {}
    }

    // Note: This type already existed before but was internal. The change is that we now made the type public and some of its properties.
    // This is to support renderers that have to be able to inspect the component hierarchy more, e.g., for SSR.
+    public class ComponentState : IAsyncDisposable
+    {
+        /// <summary>
+        /// Constructs an instance of <see cref="ComponentState"/>.
+        /// </summary>
+        public ComponentState(Renderer renderer, int componentId, IComponent component, ComponentState? parentComponentState) {}
+
+        /// <summary>
+        /// Gets the component ID.
+        /// </summary>
+        public int ComponentId { get; }
+
+        /// <summary>
+        /// Gets the component instance.
+        /// </summary>
+        public IComponent Component { get; }
+
+        /// <summary>
+        /// Gets the <see cref="ComponentState"/> of the parent component, or null if this is a root component.
+        /// </summary>
+        public ComponentState? ParentComponentState { get; }
+
+        /// <summary>
+        /// Gets the <see cref="ComponentState"/> of the logical parent component, or null if this is a root component.
+        /// </summary>
+        public ComponentState? LogicalParentComponentState { get; }
+
+        /// <summary>
+        /// Disposes this instance and its associated component.
+        /// </summary>
+        public virtual ValueTask DisposeAsync() {}
+    }

    public sealed class RenderTreeBuilder : IDisposable
    {
+        /// <summary>
+        /// Adds a frame indicating the render mode on the enclosing component frame.
+        /// </summary>
+        /// <param name="sequence">An integer that represents the position of the instruction in the source code.</param>
+        /// <param name="renderMode">The <see cref="IComponentRenderMode"/>.</param>
+        public void AddComponentRenderMode(int sequence, IComponentRenderMode renderMode) {}

+        /// <summary>
+        /// Assigns a name to an event in the enclosing element.
+        /// </summary>
+        /// <param name="sequence">An integer that represents the position of the instruction in the source code.</param>
+        /// <param name="eventType">The event type, e.g., 'onsubmit'.</param>
+        /// <param name="assignedName">The application-assigned name.</param>
+        public void AddNamedEvent(int sequence, string eventType, string assignedName) {}
    }
}

// NOTE: The following namespace is basically pubternal (and has been pubternal since .NET Core 3.0), so I'm not pasting in
// all the many copies of the following XML doc block we put on each of its types.
// The reason for the pubternalness is that we have multiple hosting models (Server, WebAssembly, MAUI) that need to
// work with the renderer's data formats but we don't wish to have a supported public API layer for custom hosting models.
/// <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>
namespace Microsoft.AspNetCore.Components.RenderTree
{
+    [Flags]
+    public enum ComponentFrameFlags : byte
+    {
+        HasCallerSpecifiedRenderMode = 1,
+    }

+    public readonly struct NamedEventChange(NamedEventChangeType changeType, int componentId, int frameIndex, string eventType, string assignedName)
+    {
+        public readonly NamedEventChangeType ChangeType { get; }
+        public readonly int ComponentId { get; }
+        public readonly int FrameIndex { get; }
+        public readonly string EventType { get; }
+        public readonly string AssignedName { get; }
+    }

+    public enum NamedEventChangeType : int
+    {
+        Added,
+        Removed,
+    }

    public readonly struct RenderBatch
    {
+        public ArrayRange<NamedEventChange>? NamedEventChanges { get; }
    }

    public struct RenderTreeFrame
    {
+        public ComponentFrameFlags ComponentFrameFlags { get; }
+        public IComponentRenderMode ComponentRenderMode { get; }
+        public string NamedEventAssignedName { get; }
+        public string NamedEventType { get; }
    }

    public enum RenderTreeFrameType : short
    {
+        ComponentRenderMode = 9,
+        NamedEvent = 10,
    }
}
SteveSandersonMS commented 1 year ago

Also stuff in M.A.C.Web:

namespace Microsoft.AspNetCore.Components.Web
{
+    /// <summary>
+    /// Provides pre-constructed <see cref="IComponentRenderMode"/> instances that may be used during rendering.
+    /// </summary>
+    public static class RenderMode
+    {
+        /// <summary>
+        /// Gets an <see cref="IComponentRenderMode"/> that represents rendering interactively on the server via Blazor Server hosting
+        /// with server-side prerendering.
+        /// </summary>
+        public static ServerRenderMode Server { get; } = new();
+
+        /// <summary>
+        /// Gets an <see cref="IComponentRenderMode"/> that represents rendering interactively on the client via Blazor WebAssembly hosting
+        /// with server-side prerendering.
+        /// </summary>
+        public static WebAssemblyRenderMode WebAssembly { get; } = new();
+
+        /// <summary>
+        /// Gets an <see cref="IComponentRenderMode"/> that means the render mode will be determined automatically based on a policy.
+        /// </summary>
+        public static AutoRenderMode Auto { get; } = new();
+    }

+    /// <summary>
+    /// A <see cref="IComponentRenderMode"/> indicating that the component should be rendered interactively on the server using Blazor Server hosting.
+    /// </summary>
+    public class ServerRenderMode : IComponentRenderMode
+    {
+        /// <summary>
+        /// Constructs an instance of <see cref="ServerRenderMode"/>.
+        /// </summary>
+        public ServerRenderMode() : this(true) {}
+
+        /// <summary>
+        /// Constructs an instance of <see cref="ServerRenderMode"/>
+        /// </summary>
+        /// <param name="prerender">A flag indicating whether the component should first prerender on the server. The default value is true.</param>
+        public ServerRenderMode(bool prerender) {}
+
+        /// <summary>
+        /// A flag indicating whether the component should first prerender on the server. The default value is true.
+        /// </summary>
+        public bool Prerender { get; }
+    }
+
+    /// <summary>
+    /// A <see cref="IComponentRenderMode"/> indicating that the component should be rendered on the client using WebAssembly.
+    /// </summary>
+    public class WebAssemblyRenderMode : IComponentRenderMode
+    {
+        /// <summary>
+        /// Constructs an instance of <see cref="WebAssemblyRenderMode"/>.
+        /// </summary>
+        public WebAssemblyRenderMode() : this(true) {}
+
+        /// <summary>
+        /// Constructs an instance of <see cref="WebAssemblyRenderMode"/>
+        /// </summary>
+        /// <param name="prerender">A flag indicating whether the component should first prerender on the server. The default value is true.</param>
+        public WebAssemblyRenderMode(bool prerender) {}
+
+        /// <summary>
+        /// A flag indicating whether the component should first prerender on the server. The default value is true.
+        /// </summary>
+        public bool Prerender { get; }
+    }
+
+    /// <summary>
+    /// A <see cref="IComponentRenderMode"/> indicating that the component's render mode should be determined automatically based on a policy.
+    /// </summary>
+    public class AutoRenderMode : IComponentRenderMode
+    {
+        /// <summary>
+        /// Constructs an instance of <see cref="AutoRenderMode"/>.
+        /// </summary>
+        public AutoRenderMode() : this(true) {}
+
+        /// <summary>
+        /// Constructs an instance of <see cref="AutoRenderMode"/>
+        /// </summary>
+        /// <param name="prerender">A flag indicating whether the component should first prerender on the server. The default value is true.</param>
+        public AutoRenderMode(bool prerender) {}
+
+        /// <summary>
+        /// A flag indicating whether the component should first prerender on the server. The default value is true.
+        /// </summary>
+        public bool Prerender { get; }
+    }

+    // TEMPORARY
+    public class RenderModeServerAttribute : RenderModeAttribute
+    {
+        public RenderModeServerAttribute() : this(true)
+        public RenderModeServerAttribute(bool prerender) {}
+        public override IComponentRenderMode Mode { get; }
+    }
+
+    // TEMPORARY
+    public class RenderModeWebAssemblyAttribute : RenderModeAttribute
+    {
+        public RenderModeWebAssemblyAttribute() : this(true)
+        public RenderModeWebAssemblyAttribute(bool prerender) {}
+        public override IComponentRenderMode Mode { get; }
+    }
+
+    // TEMPORARY
+    public class RenderModeAutoAttribute : RenderModeAttribute
+    {
+        public RenderModeAutoAttribute() : this(true)
+        public RenderModeAutoAttribute(bool prerender) {}
+        public override IComponentRenderMode Mode { get; }
+    }
}
SteveSandersonMS commented 1 year ago

The following interface enables M.A.Mvc.ViewFeatures' existing tag helper to use the new EndpointHtmlRenderer infrastructure, allowing us to remove all the old implementation.

namespace Microsoft.AspNetCore.Components.Endpoints
{
+    /// <summary>
+    /// A service that can prerender Razor Components as HTML.
+    /// </summary>
+    public interface IComponentPrerenderer
+    {
+        /// <summary>
+        /// Prerenders a Razor Component as HTML.
+        /// </summary>
+        /// <param name="httpContext">The <see cref="HttpContext"/>.</param>
+        /// <param name="componentType">The type of component to prerender. This must implement <see cref="IComponent"/>.</param>
+        /// <param name="renderMode">The mode in which to prerender the component.</param>
+        /// <param name="parameters">Parameters for the component.</param>
+        /// <returns>A task that completes with the prerendered content.</returns>
+        ValueTask<IHtmlAsyncContent> PrerenderComponentAsync(
+            HttpContext httpContext, Type componentType, IComponentRenderMode renderMode, ParameterView parameters);
+
+        /// <summary>
+        /// Prepares a serialized representation of any component state that is persistible within the current request.
+        /// </summary>
+        /// <param name="httpContext">The <see cref="HttpContext"/>.</param>
+        /// <param name="serializationMode">The <see cref="PersistedStateSerializationMode"/>.</param>
+        /// <returns>A task that completes with the prerendered state content.</returns>
+        ValueTask<IHtmlContent> PrerenderPersistedStateAsync(
+            HttpContext httpContext, PersistedStateSerializationMode serializationMode);
+
+        /// <summary>
+        /// Gets a <see cref="Dispatcher"/> that should be used for calls to <see cref="PrerenderComponentAsync(HttpContext, Type, IComponentRenderMode, ParameterView)"/>.
+        /// </summary>
+        Dispatcher Dispatcher { get; }
+    }
}

namespace Microsoft.AspNetCore.Components
{
+    /// <summary>
+    /// Specifies the mode to use when serializing component persistent state.
+    /// </summary>
+    public enum PersistedStateSerializationMode
+    {
+        /// <summary>
+        /// Indicates that the serialization mode should be inferred from the current request context.
+        /// </summary>
+        Infer = 1,
+
+        /// <summary>
+        /// Indicates that the state should be persisted so that execution may resume on Server.
+        /// </summary>
+        Server = 2,
+
+        /// <summary>
+        /// Indicates that the state should be persisted so that execution may resume on WebAssembly.
+        /// </summary>
+        WebAssembly = 3,
+    }
}
SteveSandersonMS commented 1 year ago

Usage examples:

For IComponentRenderMode, RenderModeAttribute, and AddComponentRenderMode, see https://github.com/dotnet/razor/issues/9068

For AddNamedEvent, see https://github.com/dotnet/razor/issues/9077

For CascadingValueSource, see https://github.com/dotnet/aspnetcore/issues/49104

For StreamRenderingAttribute, see https://github.com/dotnet/aspnetcore/pull/47555/files#diff-52f74241bea72a2ece76fe07f0711e951fd79fbaee293a4ca50788b3c1644a4d

The other APIs here are public for layering reasons, e.g., the renderer APIs have to be public because the platform-specific renderers (WebAssembly, Server, WebView) are each in other layers, but we don't expect applications to use them directly. Likewise IComponentPrerenderer exists to expose this functionality to the <component> tag helper in M.A.Mvc.ViewFeatures.

halter73 commented 1 year ago

API Review Notes:

API review to be continued.

halter73 commented 1 year ago

Final API Review Notes:

API Approved!

namespace Microsoft.AspNetCore.Components
{
    public interface IComponentRenderMode {}

    [AttributeUsage(AttributeTargets.Class)]
    public abstract class RenderModeAttribute : Attribute
    {
        public abstract IComponentRenderMode Mode { get; }
    }

    public class CascadingValueSource<TValue> : ICascadingValueSupplier
    {
        public CascadingValueSource(TValue value, bool isFixed) {}
        public CascadingValueSource(string name, TValue value) {}
        public CascadingValueSource(Func<TValue> initialValueFactory) {}

        public Task NotifyChangedAsync();
        public Task NotifyChangedAsync(TValue newValue)
    }

    public class StreamRenderingAttribute : Attribute
    {
        public StreamRenderingAttribute(bool enabled = true) {}

        public bool Enabled { get; }
    }

    // Post Review Note: Shouldn't this be in the Microsoft.Extensions.DependencyInjection instead?
    public static class SupplyParameterFromQueryProviderServiceCollectionExtensions
    {
        public static IServiceCollection AddSupplyValueFromQueryProvider(this IServiceCollection services) {}
    }
}

namespace Microsoft.Extensions.DependencyInjection
{
    public static class CascadingValueServiceCollectionExtensions
    {
        public static IServiceCollection AddCascadingValue<TValue>(
            this IServiceCollection serviceCollection, Func<IServiceProvider, TValue> initialValueFactory) {}

        public static IServiceCollection AddCascadingValue<TValue>(
            this IServiceCollection serviceCollection, string name, Func<IServiceProvider, TValue> initialValueFactory) {}

        public static IServiceCollection AddCascadingValue<TValue>(
            this IServiceCollection serviceCollection, Func<IServiceProvider, CascadingValueSource<TValue>> sourceFactory) {}
    }
}
namespace Microsoft.AspNetCore.Components
{
    public readonly struct ParameterView
    {
-        public IReadOnlyDictionary<string, object!> ToDictionary() {}
+        public IReadOnlyDictionary<string, object?> ToDictionary() {}
    }
}

namespace Microsoft.AspNetCore.Components.Infrastructure
{
    public class ComponentStatePersistenceManager
    {
+        Task PersistStateAsync(IPersistentComponentStateStore store, Dispatcher dispatcher) {}
    }
}

namespace Microsoft.AspNetCore.Components.Rendering
{
    public class Renderer
    {
+        protected ComponentState GetComponentState(int componentId) {}

+        protected virtual void AddPendingTask(ComponentState? componentState, Task task) {}

+        protected virtual ComponentState CreateComponentState(int componentId, IComponent component, ComponentState? parentComponentState) {}

+        public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo? fieldInfo, EventArgs eventArgs, bool waitForQuiescence)

+        protected internal virtual IComponent ResolveComponentForRenderMode(
+            [DynamicallyAccessedMembers(Component)] Type componentType,
+            int? parentComponentId,
+            IComponentActivator componentActivator,
+            IComponentRenderMode renderMode) {}
    }

+    public class ComponentState : IAsyncDisposable
+    {
+        public ComponentState(Renderer renderer, int componentId, IComponent component, ComponentState? parentComponentState) {}
+
+        public int ComponentId { get; }
+        public IComponent Component { get; }
+        public ComponentState? ParentComponentState { get; }
+        public ComponentState? LogicalParentComponentState { get; }
+
+        public virtual ValueTask DisposeAsync() {}
+    }

    public sealed class RenderTreeBuilder : IDisposable
    {
+        public void AddComponentRenderMode(IComponentRenderMode renderMode) {}
+        public void AddNamedEvent(string eventType, string assignedName) {}
    }
}

namespace Microsoft.AspNetCore.Components.RenderTree
{
+    [Flags]
+    public enum ComponentFrameFlags : byte
+    {
+        HasCallerSpecifiedRenderMode = 1,
+    }

+    public readonly struct NamedEventChange(NamedEventChangeType changeType, int componentId, int frameIndex, string eventType, string assignedName)
+    {
+        public readonly NamedEventChangeType ChangeType { get; }
+        public readonly int ComponentId { get; }
+        public readonly int FrameIndex { get; }
+        public readonly string EventType { get; }
+        public readonly string AssignedName { get; }
+    }

+    public enum NamedEventChangeType : int
+    {
+        Added,
+        Removed,
+    }

    public readonly struct RenderBatch
    {
+        public ArrayRange<NamedEventChange>? NamedEventChanges { get; }
    }

    public struct RenderTreeFrame
    {
+        public ComponentFrameFlags ComponentFrameFlags { get; }
+        public IComponentRenderMode ComponentRenderMode { get; }
+        public string NamedEventAssignedName { get; }
+        public string NamedEventType { get; }
    }

    public enum RenderTreeFrameType : short
    {
+        ComponentRenderMode = 9,
+        NamedEvent = 10,
    }
}
SteveSandersonMS commented 1 year ago

RazorComponentResult.StreamingRendering should be renamed to StreamRendering.

I realised the property is called PreventStreamingRendering, not just StreamingRendering. So I'll leave it as-is. Renaming it to PreventStreamRendering would sound weird, since the term is "streaming rendering" not "stream rendering". The reason [StreamRendering] is so-named is because it's a directive to do something.

SteveSandersonMS commented 1 year ago

Should now be implemented by https://github.com/dotnet/aspnetcore/pull/50181