Skip to content

Commit 46bd83e

Browse files
Going interactive (#8)
* Define a sample use case * Support setting a rendermode attribute that's converted into a new RenderTreeFrame field * Render markers for interactive components * Have an actual interactive server component
1 parent 9097110 commit 46bd83e

File tree

14 files changed

+194
-11
lines changed

14 files changed

+194
-11
lines changed

src/Components/Components/src/ParameterView.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,15 @@ public static ParameterView FromDictionary(IDictionary<string, object?> paramete
297297
return builder.ToParameterView();
298298
}
299299

300+
/// <summary>
301+
/// TODO: This should not be public. It's safe when used like HtmlRenderer does (just to immediately
302+
/// serialize out the data, not holding it across renders) but would be dangerous in more general cases.
303+
/// </summary>
304+
public static ParameterView DangerouslyCaptureUnboundComponentParameters(ArrayRange<RenderTreeFrame> frames, int ownerIndex)
305+
{
306+
return new ParameterView(ParameterViewLifetime.Unbound, frames.Array, ownerIndex);
307+
}
308+
300309
/// <summary>
301310
/// For each parameter property on <paramref name="target"/>, updates its value to
302311
/// match the corresponding entry in the <see cref="ParameterView"/>.

src/Components/Components/src/PublicAPI.Unshipped.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
#nullable enable
2+
Microsoft.AspNetCore.Components.ComponentRenderMode
3+
Microsoft.AspNetCore.Components.ComponentRenderMode.ComponentRenderMode(byte numericValue) -> void
24
Microsoft.AspNetCore.Components.NavigationManager.HistoryEntryState.get -> string?
35
Microsoft.AspNetCore.Components.NavigationManager.HistoryEntryState.set -> void
46
Microsoft.AspNetCore.Components.NavigationManager.NotifyLocationChangingAsync(string! uri, string? state, bool isNavigationIntercepted) -> System.Threading.Tasks.ValueTask<bool>
57
Microsoft.AspNetCore.Components.NavigationManager.RegisterLocationChangingHandler(System.Func<Microsoft.AspNetCore.Components.Routing.LocationChangingContext!, System.Threading.Tasks.ValueTask>! locationChangingHandler) -> System.IDisposable!
68
Microsoft.AspNetCore.Components.NavigationOptions.HistoryEntryState.get -> string?
79
Microsoft.AspNetCore.Components.NavigationOptions.HistoryEntryState.init -> void
10+
Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder.AddAttribute(int sequence, string! name, Microsoft.AspNetCore.Components.ComponentRenderMode! renderMode) -> void
811
Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder.AddContent(int sequence, Microsoft.AspNetCore.Components.MarkupString? markupContent) -> void
12+
Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrame.ComponentRenderMode.get -> byte
913
Microsoft.AspNetCore.Components.RouteData.FormValues.get -> System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string!, Microsoft.Extensions.Primitives.StringValues>>?
1014
Microsoft.AspNetCore.Components.RouteData.FormValues.init -> void
1115
Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs.HistoryEntryState.get -> string?
@@ -24,6 +28,7 @@ Microsoft.AspNetCore.Components.SupplyParameterFromFormAttribute
2428
Microsoft.AspNetCore.Components.SupplyParameterFromFormAttribute.Name.get -> string?
2529
Microsoft.AspNetCore.Components.SupplyParameterFromFormAttribute.Name.set -> void
2630
Microsoft.AspNetCore.Components.SupplyParameterFromFormAttribute.SupplyParameterFromFormAttribute() -> void
31+
readonly Microsoft.AspNetCore.Components.ComponentRenderMode.NumericValue -> byte
2732
static Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.CreateInferredEventCallback<T>(object! receiver, Microsoft.AspNetCore.Components.EventCallback<T> callback, T value) -> Microsoft.AspNetCore.Components.EventCallback<T>
2833
static Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.InvokeAsynchronousDelegate(System.Action! callback) -> System.Threading.Tasks.Task!
2934
static Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.InvokeAsynchronousDelegate(System.Func<System.Threading.Tasks.Task!>! callback) -> System.Threading.Tasks.Task!
@@ -60,6 +65,8 @@ static Microsoft.AspNetCore.Components.EventCallbackFactoryBinderExtensions.Crea
6065
static Microsoft.AspNetCore.Components.EventCallbackFactoryBinderExtensions.CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory! factory, object! receiver, Microsoft.AspNetCore.Components.EventCallback<short?> setter, short? existingValue, System.Globalization.CultureInfo? culture = null) -> Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.ChangeEventArgs!>
6166
static Microsoft.AspNetCore.Components.EventCallbackFactoryBinderExtensions.CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory! factory, object! receiver, Microsoft.AspNetCore.Components.EventCallback<string?> setter, string! existingValue, System.Globalization.CultureInfo? culture = null) -> Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.ChangeEventArgs!>
6267
static Microsoft.AspNetCore.Components.EventCallbackFactoryBinderExtensions.CreateBinder<T>(this Microsoft.AspNetCore.Components.EventCallbackFactory! factory, object! receiver, Microsoft.AspNetCore.Components.EventCallback<T> setter, T existingValue, System.Globalization.CultureInfo? culture = null) -> Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.ChangeEventArgs!>
68+
static Microsoft.AspNetCore.Components.ParameterView.DangerouslyCaptureUnboundComponentParameters(Microsoft.AspNetCore.Components.RenderTree.ArrayRange<Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrame> frames, int ownerIndex) -> Microsoft.AspNetCore.Components.ParameterView
69+
static readonly Microsoft.AspNetCore.Components.ComponentRenderMode.Unspecified -> Microsoft.AspNetCore.Components.ComponentRenderMode!
6370
virtual Microsoft.AspNetCore.Components.NavigationManager.HandleLocationChangingHandlerException(System.Exception! ex, Microsoft.AspNetCore.Components.Routing.LocationChangingContext! context) -> void
6471
virtual Microsoft.AspNetCore.Components.NavigationManager.SetNavigationLockState(bool value) -> void
6572
*REMOVED*Microsoft.AspNetCore.Components.NavigationManager.ToAbsoluteUri(string! relativeUri) -> System.Uri!
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.AspNetCore.Components;
5+
6+
/// <summary>
7+
/// Indicates how a component should be rendered.
8+
/// </summary>
9+
public class ComponentRenderMode
10+
{
11+
/// <summary>
12+
/// Indicates that no render mode was specified, so the component should continue rendering
13+
/// in the same way as its parent.
14+
/// </summary>
15+
public static readonly ComponentRenderMode Unspecified = new ComponentRenderMode(0);
16+
17+
/// <summary>
18+
/// Represents the render mode as a numeric value.
19+
/// </summary>
20+
public readonly byte NumericValue;
21+
22+
/// <summary>
23+
/// Constructs an instance of <see cref="ComponentRenderMode"/>.
24+
/// </summary>
25+
/// <param name="numericValue">A unique numeric value.</param>
26+
protected ComponentRenderMode(byte numericValue)
27+
=> NumericValue = numericValue;
28+
}

src/Components/Components/src/RenderTree/RenderTreeFrame.cs

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ public struct RenderTreeFrame
136136
// RenderTreeFrameType.Component
137137
// --------------------------------------------------------------------------------
138138

139+
[FieldOffset(6)] internal byte ComponentRenderModeField;
139140
[FieldOffset(8)] internal int ComponentSubtreeLengthField;
140141
[FieldOffset(12)] internal int ComponentIdField;
141142
[FieldOffset(16)]
@@ -144,6 +145,12 @@ public struct RenderTreeFrame
144145
[FieldOffset(24)] internal ComponentState ComponentStateField;
145146
[FieldOffset(32)] internal object ComponentKeyField;
146147

148+
/// <summary>
149+
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Component"/>
150+
/// gets the component render mode's numeric value.
151+
/// </summary>
152+
public byte ComponentRenderMode => ComponentRenderModeField;
153+
147154
/// <summary>
148155
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Component"/>
149156
/// gets the number of frames in the subtree for which this frame is the root.
@@ -262,14 +269,15 @@ private RenderTreeFrame(int sequence, int elementSubtreeLength, string elementNa
262269
}
263270

264271
// Component constructor
265-
private RenderTreeFrame(int sequence, int componentSubtreeLength, [DynamicallyAccessedMembers(LinkerFlags.Component)] Type componentType, ComponentState componentState, object componentKey)
272+
private RenderTreeFrame(int sequence, int componentSubtreeLength, [DynamicallyAccessedMembers(LinkerFlags.Component)] Type componentType, ComponentState componentState, object componentKey, byte componentRenderMode)
266273
: this()
267274
{
268275
SequenceField = sequence;
269276
FrameTypeField = RenderTreeFrameType.Component;
270277
ComponentSubtreeLengthField = componentSubtreeLength;
271278
ComponentTypeField = componentType;
272279
ComponentKeyField = componentKey;
280+
ComponentRenderModeField = componentRenderMode;
273281

274282
if (componentState != null)
275283
{
@@ -349,10 +357,10 @@ internal static RenderTreeFrame Attribute(int sequence, string name, object valu
349357
=> new RenderTreeFrame(sequence, attributeName: name, attributeValue: value, attributeEventHandlerId: 0, attributeEventUpdatesAttributeName: null);
350358

351359
internal static RenderTreeFrame ChildComponent(int sequence, [DynamicallyAccessedMembers(LinkerFlags.Component)] Type componentType)
352-
=> new RenderTreeFrame(sequence, componentSubtreeLength: 0, componentType, null, null);
360+
=> new RenderTreeFrame(sequence, componentSubtreeLength: 0, componentType, null, null, default);
353361

354362
internal static RenderTreeFrame PlaceholderChildComponentWithSubtreeLength(int subtreeLength)
355-
=> new RenderTreeFrame(0, componentSubtreeLength: subtreeLength, typeof(IComponent), null, null);
363+
=> new RenderTreeFrame(0, componentSubtreeLength: subtreeLength, typeof(IComponent), null, null, default);
356364

357365
internal static RenderTreeFrame Region(int sequence)
358366
=> new RenderTreeFrame(sequence, regionSubtreeLength: 0);
@@ -367,13 +375,16 @@ internal RenderTreeFrame WithElementSubtreeLength(int elementSubtreeLength)
367375
=> new RenderTreeFrame(SequenceField, elementSubtreeLength: elementSubtreeLength, ElementNameField, ElementKeyField);
368376

369377
internal RenderTreeFrame WithComponentSubtreeLength(int componentSubtreeLength)
370-
=> new RenderTreeFrame(SequenceField, componentSubtreeLength: componentSubtreeLength, ComponentTypeField, ComponentStateField, ComponentKeyField);
378+
=> new RenderTreeFrame(SequenceField, componentSubtreeLength: componentSubtreeLength, ComponentTypeField, ComponentStateField, ComponentKeyField, ComponentRenderModeField);
371379

372380
internal RenderTreeFrame WithAttributeSequence(int sequence)
373381
=> new RenderTreeFrame(sequence, attributeName: AttributeNameField, AttributeValueField, AttributeEventHandlerIdField, AttributeEventUpdatesAttributeNameField);
374382

375383
internal RenderTreeFrame WithComponent(ComponentState componentState)
376-
=> new RenderTreeFrame(SequenceField, componentSubtreeLength: ComponentSubtreeLengthField, ComponentTypeField, componentState, ComponentKeyField);
384+
=> new RenderTreeFrame(SequenceField, componentSubtreeLength: ComponentSubtreeLengthField, ComponentTypeField, componentState, ComponentKeyField, ComponentRenderModeField);
385+
386+
internal RenderTreeFrame WithRenderMode(ComponentRenderMode componentRenderMode)
387+
=> new RenderTreeFrame(SequenceField, componentSubtreeLength: ComponentSubtreeLengthField, ComponentTypeField, ComponentStateField, ComponentKeyField, componentRenderMode.NumericValue);
377388

378389
internal RenderTreeFrame WithAttributeEventHandlerId(ulong eventHandlerId)
379390
=> new RenderTreeFrame(SequenceField, attributeName: AttributeNameField, AttributeValueField, eventHandlerId, AttributeEventUpdatesAttributeNameField);
@@ -394,7 +405,7 @@ internal RenderTreeFrame WithElementKey(object elementKey)
394405
=> new RenderTreeFrame(SequenceField, elementSubtreeLength: ElementSubtreeLengthField, ElementNameField, elementKey);
395406

396407
internal RenderTreeFrame WithComponentKey(object componentKey)
397-
=> new RenderTreeFrame(SequenceField, componentSubtreeLength: ComponentSubtreeLengthField, ComponentTypeField, ComponentStateField, componentKey);
408+
=> new RenderTreeFrame(SequenceField, componentSubtreeLength: ComponentSubtreeLengthField, ComponentTypeField, ComponentStateField, componentKey, ComponentRenderModeField);
398409

399410
/// <inheritdoc />
400411
// Just to be nice for debugging and unit tests.

src/Components/Components/src/Rendering/RenderTreeBuilder.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,34 @@ public void AddAttribute(int sequence, RenderTreeFrame frame)
429429
_entries.Append(frame);
430430
}
431431

432+
/// <summary>
433+
/// Adds an attribute specifying the render mode of the closest component frame.
434+
///
435+
/// TEMPORARY. Longer term, we should make the Razor compiler have a native @rendermode directive attribute,
436+
/// which will produce a call to some distinct API instead of relying on an AddAttribute overload.
437+
/// </summary>
438+
public void AddAttribute(int sequence, string name, ComponentRenderMode renderMode)
439+
{
440+
if (string.Equals(name, "rendermode"))
441+
{
442+
AssertCanAddAttribute();
443+
444+
var componentFrameIndex = GetCurrentParentFrameIndex()!;
445+
ref var componentFrame = ref _entries.Buffer[componentFrameIndex.Value];
446+
if (componentFrame.FrameType != RenderTreeFrameType.Component)
447+
{
448+
throw new InvalidOperationException("'rendermode' can only be specified on components.");
449+
}
450+
451+
componentFrame = componentFrame.WithRenderMode(renderMode);
452+
}
453+
else
454+
{
455+
// Fall back to treating as a normal attribute
456+
AddAttribute(sequence, name, (object)renderMode);
457+
}
458+
}
459+
432460
/// <summary>
433461
/// Adds frames representing multiple attributes with the same sequence number.
434462
/// </summary>

src/Components/Samples/BlazorUnitedApp/Pages/Counter.razor renamed to src/Components/Samples/BlazorUnitedApp/Pages/CounterPassive.razor

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
@page "/counter"
1+
@page "/counter-passive"
22

33
<PageTitle>Counter</PageTitle>
44

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
@page "/counter-server"
2+
3+
<PageTitle>Counter</PageTitle>
4+
5+
<h1>Counter</h1>
6+
7+
<CounterUI InitialCount="100" rendermode="@WebComponentRenderMode.Server" />
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<p>Current count: @currentCount</p>
2+
3+
<button @onclick="@IncrementCount" class="btn btn-primary">Increment</button>
4+
5+
@code {
6+
int currentCount;
7+
8+
[Parameter] public int InitialCount { get; set; }
9+
10+
protected override void OnInitialized()
11+
{
12+
currentCount = InitialCount;
13+
}
14+
15+
void IncrementCount()
16+
{
17+
currentCount++;
18+
}
19+
}

src/Components/Samples/BlazorUnitedApp/Program.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
// Add services to the container.
1010
builder.Services.AddSingleton<WeatherForecastService>();
1111
builder.Services.AddRazorComponents();
12+
builder.Services.AddServerSideBlazor();
1213

1314
var app = builder.Build();
1415

@@ -26,5 +27,6 @@
2627
app.UseRouting();
2728

2829
app.MapRazorComponents();
30+
app.MapBlazorHub();
2931

3032
app.Run();

src/Components/Samples/BlazorUnitedApp/Shared/MainLayout.razor

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,7 @@
3232
<a class="reload">Reload</a>
3333
<a class="dismiss">X</a>
3434
</div>
35+
36+
<script src="_framework/blazor.server.js" suppress-error="BL9992"></script>
3537
</body>
3638
</html>

0 commit comments

Comments
 (0)