Skip to content

Commit 128b9d0

Browse files
committed
Changes per PR comments
1 parent c14f2ab commit 128b9d0

File tree

9 files changed

+271
-166
lines changed

9 files changed

+271
-166
lines changed

src/Components/Blazor/Blazor/src/Hosting/WebAssemblyBlazorApplicationBuilder.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Threading.Tasks;
67
using Microsoft.AspNetCore.Blazor.Rendering;
78
using Microsoft.AspNetCore.Components.Builder;
89

@@ -35,13 +36,13 @@ public void AddComponent(Type componentType, string domElementSelector)
3536
Entries.Add((componentType, domElementSelector));
3637
}
3738

38-
public WebAssemblyRenderer CreateRenderer()
39+
public async Task<WebAssemblyRenderer> CreateRendererAsync()
3940
{
4041
var renderer = new WebAssemblyRenderer(Services);
4142
for (var i = 0; i < Entries.Count; i++)
4243
{
43-
var entry = Entries[i];
44-
renderer.AddComponent(entry.componentType, entry.domElementSelector);
44+
var (componentType, domElementSelector) = Entries[i];
45+
await renderer.AddComponentAsync(componentType, domElementSelector);
4546
}
4647

4748
return renderer;

src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHost.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ public Task StartAsync(CancellationToken cancellationToken = default)
4242
JSRuntime.SetCurrentJSRuntime(_runtime);
4343
SetBrowserHttpMessageHandlerAsDefault();
4444

45+
return StartAsyncAwaited();
46+
}
47+
48+
private async Task StartAsyncAwaited()
49+
{
4550
var scopeFactory = Services.GetRequiredService<IServiceScopeFactory>();
4651
_scope = scopeFactory.CreateScope();
4752

@@ -61,7 +66,7 @@ public Task StartAsync(CancellationToken cancellationToken = default)
6166
var builder = new WebAssemblyBlazorApplicationBuilder(_scope.ServiceProvider);
6267
startup.Configure(builder, _scope.ServiceProvider);
6368

64-
_renderer = builder.CreateRenderer();
69+
_renderer = await builder.CreateRendererAsync();
6570
}
6671
catch
6772
{
@@ -76,9 +81,6 @@ public Task StartAsync(CancellationToken cancellationToken = default)
7681

7782
throw;
7883
}
79-
80-
81-
return Task.CompletedTask;
8284
}
8385

8486
public Task StopAsync(CancellationToken cancellationToken = default)

src/Components/Blazor/Blazor/src/Rendering/WebAssemblyRenderer.cs

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,19 +37,26 @@ public WebAssemblyRenderer(IServiceProvider serviceProvider): base(serviceProvid
3737
/// </summary>
3838
/// <typeparam name="TComponent">The type of the component.</typeparam>
3939
/// <param name="domElementSelector">A CSS selector that uniquely identifies a DOM element.</param>
40-
public void AddComponent<TComponent>(string domElementSelector)
41-
where TComponent: IComponent
42-
{
43-
AddComponent(typeof(TComponent), domElementSelector);
44-
}
40+
/// <returns>A <see cref="Task"/> that represents the asynchronous rendering of the added component.</returns>
41+
/// <remarks>
42+
/// Callers of this method may choose to ignore the returned <see cref="Task"/> if they do not
43+
/// want to await the rendering of the added component.
44+
/// </remarks>
45+
public Task AddComponentAsync<TComponent>(string domElementSelector) where TComponent : IComponent
46+
=> AddComponentAsync(typeof(TComponent), domElementSelector);
4547

4648
/// <summary>
4749
/// Associates the <see cref="IComponent"/> with the <see cref="WebAssemblyRenderer"/>,
4850
/// causing it to be displayed in the specified DOM element.
4951
/// </summary>
5052
/// <param name="componentType">The type of the component.</param>
5153
/// <param name="domElementSelector">A CSS selector that uniquely identifies a DOM element.</param>
52-
public async void AddComponent(Type componentType, string domElementSelector)
54+
/// <returns>A <see cref="Task"/> that represents the asynchronous rendering of the added component.</returns>
55+
/// <remarks>
56+
/// Callers of this method may choose to ignore the returned <see cref="Task"/> if they do not
57+
/// want to await the rendering of the added component.
58+
/// </remarks>
59+
public Task AddComponentAsync(Type componentType, string domElementSelector)
5360
{
5461
var component = InstantiateComponent(componentType);
5562
var componentId = AssignRootComponentId(component);
@@ -65,8 +72,7 @@ public async void AddComponent(Type componentType, string domElementSelector)
6572
domElementSelector,
6673
componentId);
6774

68-
// Dispatch the rendering. In the most common cases, this will finish synchronously
69-
await RenderRootComponentAsync(componentId);
75+
return RenderRootComponentAsync(componentId);
7076
}
7177

7278
/// <inheritdoc />

src/Components/Components/src/ComponentBase.cs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,16 @@ private async Task RunInitAndSetParametersAsync()
203203
// async work is to be performed.
204204
StateHasChanged();
205205

206-
await ProcessLifecycleTaskAsync(task);
206+
try
207+
{
208+
await task;
209+
}
210+
catch when (task.IsCanceled)
211+
{
212+
// Ignore exceptions from task cancelletions.
213+
}
214+
215+
// Don't call StateHasChanged here. CallOnParametersSetAsync should handle that for us.
207216
}
208217

209218
await CallOnParametersSetAsync();
@@ -224,19 +233,19 @@ private Task CallOnParametersSetAsync()
224233
StateHasChanged();
225234

226235
return shouldAwaitTask ?
227-
ProcessLifecycleTaskAsync(task) :
236+
CallStateHasChangedOnAsyncCompletion(task) :
228237
Task.CompletedTask;
229238
}
230239

231-
private async Task ProcessLifecycleTaskAsync(Task task)
240+
private async Task CallStateHasChangedOnAsyncCompletion(Task task)
232241
{
233242
try
234243
{
235244
await task;
236245
}
237246
catch when (task.IsCanceled)
238247
{
239-
// Ignore task cancelation but don't bother issuing a state change.
248+
// Ignore exceptions from task cancelletions, but don't bother issuing a state change.
240249
return;
241250
}
242251

@@ -255,7 +264,7 @@ Task IHandleEvent.HandleEventAsync(EventHandlerInvoker binding, UIEventArgs args
255264
StateHasChanged();
256265

257266
return shouldAwaitTask ?
258-
ProcessLifecycleTaskAsync(task) :
267+
CallStateHasChangedOnAsyncCompletion(task) :
259268
Task.CompletedTask;
260269
}
261270

src/Components/Components/src/Rendering/HtmlRenderer.cs

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -40,28 +40,6 @@ protected override Task UpdateDisplayAsync(in RenderBatch renderBatch)
4040
return Task.CompletedTask;
4141
}
4242

43-
/// <summary>
44-
/// Renders a component into a sequence of <see cref="string"/> fragments that represent the textual representation
45-
/// of the HTML produced by the component.
46-
/// </summary>
47-
/// <typeparam name="TComponent">The type of the <see cref="IComponent"/>.</typeparam>
48-
/// <param name="initialParameters">A <see cref="ParameterCollection"/> with the initial parameters to render the component.</param>
49-
/// <returns>A sequence of <see cref="string"/> fragments that represent the HTML text of the component.</returns>
50-
public IEnumerable<string> RenderComponent<TComponent>(ParameterCollection initialParameters) where TComponent : IComponent
51-
{
52-
return RenderComponent(typeof(TComponent), initialParameters);
53-
}
54-
55-
/// <summary>
56-
/// Renders a component into a sequence of <see cref="string"/> fragments that represent the textual representation
57-
/// of the HTML produced by the component.
58-
/// </summary>
59-
/// <param name="componentType">The type of the <see cref="IComponent"/>.</param>
60-
/// <param name="initialParameters">A <see cref="ParameterCollection"/> with the initial parameters to render the component.</param>
61-
/// <returns>A sequence of <see cref="string"/> fragments that represent the HTML text of the component.</returns>
62-
private IEnumerable<string> RenderComponent(Type componentType, ParameterCollection initialParameters)
63-
=> RenderComponentAsync(componentType, initialParameters).GetAwaiter().GetResult();
64-
6543
/// <summary>
6644
/// Renders a component into a sequence of <see cref="string"/> fragments that represent the textual representation
6745
/// of the HTML produced by the component.

src/Components/Components/src/Rendering/Renderer.cs

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,10 @@ protected async Task RenderRootComponentAsync(int componentId, ParameterCollecti
150150
try
151151
{
152152
await ProcessAsynchronousWork();
153+
if (_pendingTasks.Count != 0)
154+
{
155+
throw new Exception();
156+
}
153157
Debug.Assert(_pendingTasks.Count == 0);
154158
}
155159
finally
@@ -170,26 +174,15 @@ private async Task ProcessAsynchronousWork()
170174
// which might trigger further renders.
171175
while (_pendingTasks.Count > 0)
172176
{
173-
Task pendingWork;
174177
// Create a Task that represents the remaining ongoing work for the rendering process
175-
pendingWork = Task.WhenAll(_pendingTasks);
178+
var pendingWork = Task.WhenAll(_pendingTasks);
176179

177180
// Clear all pending work.
178181
_pendingTasks.Clear();
179182

180183
// new work might be added before we check again as a result of waiting for all
181184
// the child components to finish executing SetParametersAsync
182-
183-
try
184-
{
185-
await pendingWork;
186-
}
187-
catch when (!pendingWork.IsCanceled)
188-
{
189-
// await will unwrap an AggregateException and throw exactly one inner exception.
190-
// Using pendingWork.Exception gives us access to all of the exception
191-
HandleException(pendingWork.Exception);
192-
}
185+
await pendingWork;
193186
}
194187
}
195188

@@ -343,7 +336,8 @@ internal void AddToPendingTasks(Task task)
343336
{
344337
return;
345338
}
346-
_pendingTasks.Add(task);
339+
340+
_pendingTasks.Add(GetErrorHandledTask(task));
347341
break;
348342
}
349343
}
@@ -471,21 +465,13 @@ private async void InvokeRenderCompletedCalls(ArrayRange<RenderTreeDiff> updated
471465
// The Task is incomplete.
472466
// Queue up the task and we can inspect it later.
473467
batch = batch ?? new List<Task>();
474-
batch.Add(task);
468+
batch.Add(GetErrorHandledTask(task));
475469
}
476470
}
477471

478472
if (batch != null)
479473
{
480-
var aggregateTask = Task.WhenAll(batch);
481-
try
482-
{
483-
await aggregateTask;
484-
}
485-
catch when (!aggregateTask.IsCanceled)
486-
{
487-
HandleException(aggregateTask.Exception);
488-
}
474+
await Task.WhenAll(batch);
489475
}
490476
}
491477

@@ -532,6 +518,22 @@ private void RemoveEventHandlerIds(ArrayRange<int> eventHandlerIds, Task afterTa
532518
}
533519
}
534520

521+
private async Task GetErrorHandledTask(Task taskToHandle)
522+
{
523+
try
524+
{
525+
await taskToHandle;
526+
}
527+
catch (Exception ex)
528+
{
529+
if (!taskToHandle.IsCanceled)
530+
{
531+
// Ignore errors due to task cancellations.
532+
HandleException(ex);
533+
}
534+
}
535+
}
536+
535537
/// <summary>
536538
/// Releases all resources currently used by this <see cref="Renderer"/> instance.
537539
/// </summary>

src/Components/Components/test/ComponentBaseTest.cs

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -168,10 +168,10 @@ public async Task RendersAfterParametersSetAsyncTaskIsCompleted()
168168
component.Counter = 2;
169169
parametersSetTask.SetResult(true);
170170

171+
await renderTask;
172+
171173
// Component should be rendered again
172174
Assert.Equal(2, renderer.Batches.Count);
173-
174-
await renderTask;
175175
}
176176

177177
[Fact]
@@ -194,26 +194,25 @@ public async Task RendersAfterParametersSetAndInitAsyncTasksAreCompleted()
194194
var renderTask = renderer.RenderRootComponentAsync(componentId);
195195

196196
// Assert
197+
// A rendering should have happened after the synchronous execution of Init
197198
Assert.Single(renderer.Batches);
198199

199200
// Completes task started by OnInitAsync
200201
component.Counter = 2;
201202
initTask.SetResult(true);
202203

203-
// Component should be rendered again 2 times
204-
// after on init async
205-
// after set parameters
206-
Assert.Equal(3, renderer.Batches.Count);
204+
// Component should be rendered once, after set parameters
205+
Assert.Equal(2, renderer.Batches.Count);
207206

208207
// Completes task started by OnParametersSetAsync
209208
component.Counter = 3;
210209
parametersSetTask.SetResult(false);
211210

211+
await renderTask;
212+
212213
// Component should be rendered again
213214
// after the async part of onparameterssetasync completes
214-
Assert.Equal(4, renderer.Batches.Count);
215-
216-
await renderTask;
215+
Assert.Equal(3, renderer.Batches.Count);
217216
}
218217

219218
[Fact]
@@ -230,17 +229,18 @@ public async Task DoesNotRenderAfterOnInitAsyncTaskIsCancelled()
230229
var renderTask = renderer.RenderRootComponentAsync(componentId);
231230

232231
// Assert
232+
Assert.False(renderTask.IsCompleted);
233233
Assert.Single(renderer.Batches);
234234

235235
// Cancel task started by OnInitAsync
236236
component.Counter = 2;
237237
initTask.SetCanceled();
238238

239+
await renderTask;
240+
239241
// Component should only be rendered again due to
240242
// the call to StateHasChanged after SetParametersAsync
241243
Assert.Equal(2, renderer.Batches.Count);
242-
243-
await renderTask;
244244
}
245245

246246
[Fact]
@@ -287,10 +287,11 @@ public async Task DoesNotRenderAfterOnParametersSetAsyncTaskIsCanceled()
287287
component.Counter = 2;
288288
onParametersSetTask.SetCanceled();
289289

290+
await renderTask;
291+
290292
// Component should not be rendered again
291293
Assert.Single(renderer.Batches);
292294

293-
await renderTask;
294295
}
295296

296297
[Fact]

0 commit comments

Comments
 (0)