Closed SteveSandersonMS closed 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; }
+ }
}
The following interface enables M.A.Mvc.ViewFeatures' existing
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,
+ }
}
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
.
API Review Notes:
Client
or WebAssembly
?
WebAssembly
is less ambiguous than Client
. Even blazor server has client-side pieces to support interop.public bool Prerender { get; }
on IComponentRenderMode
or a derived interface?
RenderModeXAttribute
's are temporary until the compiler adds @rendermode
support.Prerender
ICascadingValueSupplier
is internalvalueFactory
ctor parameter to the CascadingValueSource
to initialValueFactory
to make it clear that it does not get rerun on NotifyStateChangedAsync
.isFixed
indicates whether or not the value may change.
isFixed
ctor parameters, because if you have a fixed value, you can just return the value directly rather than the source.StreamRenderingAttribute
be two attributes? One to enable one to disable?
public StreamRenderingAttribute(bool enabled = true)
to true since that's the primary use case.RazorComponentResult.StreamingRendering
should be renamed to StreamRendering
.API review to be continued.
Final API Review Notes:
ComponentState
needs to be unsealed to support EndpointComponentState
which is why DisposeAsync
is virtual.LogicalParentComponentState
not a ctor parameter?
ParentComponentState
and we don't think we need flexibility to do something completely custom. It should only be different from the normal parent component for section outlets.int sequence
parameters from AddComponentRenderMode
and AddNamedEvent
since there can only be one of each.byte
, int
and short
based enums in Microsoft.AspNetCore.Components.RenderTree? If we change these in the future, it will be binary breaking.
RenderTreeFrame
is heavily optimized, so using shorter-than-default enums is genuinely helpful.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,
}
}
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.
Should now be implemented by https://github.com/dotnet/aspnetcore/pull/50181
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