-
Notifications
You must be signed in to change notification settings - Fork 10.4k
[Blazor] Improvements to the interaction between SSR and interactive rendering #49238
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 13 commits
acaba4d
7666f56
5a7c33f
674cf33
95b262f
2c0bfa8
3145735
62d8dd4
531d296
8319aca
f9b688d
fea2dcb
4949da5
18829c8
f8e38de
95cce82
7373384
abe2fb4
dadb3cc
8bb4182
d339790
582517e
b162b3b
b3a01fa
43bb43f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,15 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System.Diagnostics.CodeAnalysis; | ||
|
||
namespace Microsoft.AspNetCore.Components.Server; | ||
|
||
internal interface IServerComponentDeserializer | ||
{ | ||
bool TryDeserializeComponentDescriptorCollection( | ||
string serializedComponentRecords, | ||
out List<ComponentDescriptor> descriptors); | ||
|
||
bool TryDeserializeSingleComponentDescriptor(ServerComponentMarker record, [NotNullWhen(true)] out ComponentDescriptor? result); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,7 +3,9 @@ | |
|
||
using System.Collections.Concurrent; | ||
using System.Diagnostics.CodeAnalysis; | ||
using System.Globalization; | ||
using System.Linq; | ||
using System.Text.Json; | ||
using Microsoft.AspNetCore.Components.RenderTree; | ||
using Microsoft.AspNetCore.Components.Web; | ||
using Microsoft.AspNetCore.SignalR; | ||
|
@@ -21,6 +23,7 @@ internal partial class RemoteRenderer : WebRenderer | |
|
||
private readonly CircuitClientProxy _client; | ||
private readonly CircuitOptions _options; | ||
private readonly IServerComponentDeserializer _serverComponentDeserializer; | ||
private readonly ILogger _logger; | ||
internal readonly ConcurrentQueue<UnacknowledgedRenderBatch> _unacknowledgedRenderBatches = new ConcurrentQueue<UnacknowledgedRenderBatch>(); | ||
private long _nextRenderId = 1; | ||
|
@@ -39,13 +42,15 @@ public RemoteRenderer( | |
ILoggerFactory loggerFactory, | ||
CircuitOptions options, | ||
CircuitClientProxy client, | ||
IServerComponentDeserializer serverComponentDeserializer, | ||
ILogger logger, | ||
RemoteJSRuntime jsRuntime, | ||
CircuitJSComponentInterop jsComponentInterop) | ||
: base(serviceProvider, loggerFactory, jsRuntime.ReadJsonSerializerOptions(), jsComponentInterop) | ||
{ | ||
_client = client; | ||
_options = options; | ||
_serverComponentDeserializer = serverComponentDeserializer; | ||
_logger = logger; | ||
|
||
ElementReferenceContext = jsRuntime.ElementReferenceContext; | ||
|
@@ -59,12 +64,90 @@ public Task AddComponentAsync(Type componentType, ParameterView parameters, stri | |
return RenderRootComponentAsync(componentId, parameters); | ||
} | ||
|
||
protected override int GetWebRendererId() => (int)WebRendererId.Server; | ||
|
||
protected override void AttachRootComponentToBrowser(int componentId, string domElementSelector) | ||
{ | ||
var attachComponentTask = _client.SendAsync("JS.AttachComponent", componentId, domElementSelector); | ||
_ = CaptureAsyncExceptions(attachComponentTask); | ||
} | ||
|
||
protected override void UpdateRootComponents(string operationsJson) | ||
{ | ||
var operations = JsonSerializer.Deserialize<IEnumerable<RootComponentOperation<ServerComponentMarker>>>( | ||
operationsJson, | ||
ServerComponentSerializationSettings.JsonSerializationOptions); | ||
|
||
foreach (var operation in operations) | ||
{ | ||
switch (operation.Type) | ||
{ | ||
case RootComponentOperationType.Add: | ||
AddRootComponent(operation); | ||
break; | ||
case RootComponentOperationType.Update: | ||
UpdateRootComponent(operation); | ||
break; | ||
case RootComponentOperationType.Remove: | ||
RemoveRootComponent(operation); | ||
break; | ||
} | ||
} | ||
|
||
return; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was intentional to make it clear that there isn't some code hiding after the local function declarations. I can remove it if you feel that it adds more confusion than it eliminates! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe you can consider not putting these local functions if their "combined size" is so big that makes you lose track of what's going on in the method. They might be better off as separate members. |
||
|
||
void AddRootComponent(RootComponentOperation<ServerComponentMarker> operation) | ||
{ | ||
if (operation.SelectorId is not { } selectorId) | ||
{ | ||
Log.InvalidRootComponentOperation(_logger, operation.Type, message: "Missing selector ID."); | ||
return; | ||
} | ||
|
||
if (!_serverComponentDeserializer.TryDeserializeSingleComponentDescriptor(operation.Marker, out var descriptor)) | ||
{ | ||
throw new InvalidOperationException("Failed to deserialize a component descriptor when adding a new root component."); | ||
} | ||
|
||
_ = AddComponentAsync(descriptor.ComponentType, descriptor.Parameters, selectorId.ToString(CultureInfo.InvariantCulture)); | ||
} | ||
|
||
void UpdateRootComponent(RootComponentOperation<ServerComponentMarker> operation) | ||
{ | ||
if (operation.ComponentId is not { } componentId) | ||
{ | ||
Log.InvalidRootComponentOperation(_logger, operation.Type, message: "Missing component ID."); | ||
return; | ||
} | ||
|
||
var componentState = GetComponentState(componentId); | ||
|
||
if (!_serverComponentDeserializer.TryDeserializeSingleComponentDescriptor(operation.Marker, out var descriptor)) | ||
{ | ||
throw new InvalidOperationException("Failed to deserialize a component descriptor when updating an existing root component."); | ||
} | ||
|
||
if (descriptor.ComponentType != componentState.Component.GetType()) | ||
{ | ||
Log.InvalidRootComponentOperation(_logger, operation.Type, message: "Component type mismatch."); | ||
return; | ||
} | ||
|
||
_ = RenderRootComponentAsync(componentId, descriptor.Parameters); | ||
SteveSandersonMS marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
void RemoveRootComponent(RootComponentOperation<ServerComponentMarker> operation) | ||
{ | ||
if (operation.ComponentId is not { } componentId) | ||
{ | ||
Log.InvalidRootComponentOperation(_logger, operation.Type, message: "Missing component ID."); | ||
return; | ||
} | ||
|
||
this.RemoveRootComponent(componentId); | ||
} | ||
} | ||
|
||
protected override void ProcessPendingRender() | ||
{ | ||
if (_unacknowledgedRenderBatches.Count >= _options.MaxBufferedUnacknowledgedRenderBatches) | ||
|
@@ -388,6 +471,9 @@ public static void CompletingBatchWithoutError(ILogger logger, long batchId, Tim | |
|
||
[LoggerMessage(107, LogLevel.Debug, "The queue of unacknowledged render batches is full.", EventName = "FullUnacknowledgedRenderBatchesQueue")] | ||
public static partial void FullUnacknowledgedRenderBatchesQueue(ILogger logger); | ||
|
||
[LoggerMessage(108, LogLevel.Debug, "The root component operation of type '{OperationType}' was invalid: {Message}", EventName = "InvalidRootComponentOperation")] | ||
public static partial void InvalidRootComponentOperation(ILogger logger, RootComponentOperationType operationType, string message); | ||
} | ||
} | ||
|
||
|
Uh oh!
There was an error while loading. Please reload this page.