Skip to content

Commit e9dddd6

Browse files
authored
Modify HtmlRenderer to update content when a component re-renders (#40512)
Prior to this change HtmlRenderer would return a snapshot of a component immediately after it was initially rendered. This meant any updates to a component as a result of other root components rendering were lost. In this change, we update the HtmlRenderer to return a placeholder `IHtmlContent` that retrieves render frames only when `WriteTo` is invoked. Typically MVC will defer calling WriteTo until the content is about to be sent to the wire, which means any updates to a component (e.g. HeadOutlet being updated) are picked up. Also updates the template to consolidate `_Host` and `_Layout`. Fixes #38400
1 parent 3817558 commit e9dddd6

File tree

5 files changed

+71
-56
lines changed

5 files changed

+71
-56
lines changed

src/Mvc/Mvc.ViewFeatures/src/Buffers/ViewBuffer.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Buffers;
1212
/// An <see cref="IHtmlContentBuilder"/> that is backed by a buffer provided by <see cref="IViewBufferScope"/>.
1313
/// </summary>
1414
[DebuggerDisplay("{DebuggerToString()}")]
15-
internal class ViewBuffer : IHtmlContentBuilder
15+
internal sealed class ViewBuffer : IHtmlContentBuilder
1616
{
1717
public const int PartialViewPageSize = 32;
1818
public const int TagHelperPageSize = 32;

src/Mvc/Mvc.ViewFeatures/src/RazorComponents/HtmlRenderer.cs

+41-13
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33

44
using System.Diagnostics;
55
using System.Runtime.ExceptionServices;
6+
using System.Text.Encodings.Web;
67
using Microsoft.AspNetCore.Components.RenderTree;
8+
using Microsoft.AspNetCore.Html;
79
using Microsoft.AspNetCore.Mvc.ViewFeatures.Buffers;
810
using Microsoft.Extensions.Logging;
911

@@ -47,14 +49,11 @@ protected override Task UpdateDisplayAsync(in RenderBatch renderBatch)
4749

4850
public async Task<ComponentRenderedText> RenderComponentAsync(Type componentType, ParameterView initialParameters)
4951
{
50-
var (componentId, frames) = await CreateInitialRenderAsync(componentType, initialParameters);
51-
52-
var viewBuffer = new ViewBuffer(_viewBufferScope, nameof(HtmlRenderer), ViewBuffer.ViewPageSize);
52+
var component = InstantiateComponent(componentType);
53+
var componentId = AssignRootComponentId(component);
5354

54-
var context = new HtmlRenderingContext { HtmlContentBuilder = viewBuffer, };
55-
var newPosition = RenderFrames(context, frames, 0, frames.Count);
56-
Debug.Assert(newPosition == frames.Count);
57-
return new ComponentRenderedText(componentId, context.HtmlContentBuilder);
55+
await RenderRootComponentAsync(componentId, initialParameters);
56+
return new ComponentRenderedText(componentId, new ComponentHtmlContent(this, componentId));
5857
}
5958

6059
public Task<ComponentRenderedText> RenderComponentAsync<TComponent>(ParameterView initialParameters) where TComponent : IComponent
@@ -248,21 +247,50 @@ private static int RenderAttributes(
248247
return position + maxElements;
249248
}
250249

251-
private async Task<(int, ArrayRange<RenderTreeFrame>)> CreateInitialRenderAsync(Type componentType, ParameterView initialParameters)
250+
private ViewBuffer GetRenderedHtmlContent(int componentId)
252251
{
253-
var component = InstantiateComponent(componentType);
254-
var componentId = AssignRootComponentId(component);
252+
var viewBuffer = new ViewBuffer(_viewBufferScope, nameof(HtmlRenderer), ViewBuffer.ViewPageSize);
253+
var context = new HtmlRenderingContext(viewBuffer);
255254

256-
await RenderRootComponentAsync(componentId, initialParameters);
255+
var frames = GetCurrentRenderTreeFrames(componentId);
256+
var newPosition = RenderFrames(context, frames, 0, frames.Count);
257+
Debug.Assert(newPosition == frames.Count);
257258

258-
return (componentId, GetCurrentRenderTreeFrames(componentId));
259+
return viewBuffer;
259260
}
260261

261262
private sealed class HtmlRenderingContext
262263
{
263-
public ViewBuffer HtmlContentBuilder { get; init; }
264+
public HtmlRenderingContext(ViewBuffer viewBuffer)
265+
{
266+
HtmlContentBuilder = viewBuffer;
267+
}
268+
269+
public ViewBuffer HtmlContentBuilder { get; }
264270

265271
public string ClosestSelectValueAsString { get; set; }
266272
}
273+
274+
/// <summary>
275+
/// A <see cref="IHtmlContent"/> that defers rendering component markup until <see cref="IHtmlContent.WriteTo(TextWriter, HtmlEncoder)"/>
276+
/// is called.
277+
/// </summary>
278+
private sealed class ComponentHtmlContent : IHtmlContent
279+
{
280+
private readonly HtmlRenderer _renderer;
281+
private readonly int _componentId;
282+
283+
public ComponentHtmlContent(HtmlRenderer renderer, int componentId)
284+
{
285+
_renderer = renderer;
286+
_componentId = componentId;
287+
}
288+
289+
public void WriteTo(TextWriter writer, HtmlEncoder encoder)
290+
{
291+
var actualHtmlContent = _renderer.GetRenderedHtmlContent(_componentId);
292+
actualHtmlContent.WriteTo(writer, encoder);
293+
}
294+
}
267295
}
268296

Original file line numberDiff line numberDiff line change
@@ -1,8 +1,33 @@
11
@page "/"
2+
@using Microsoft.AspNetCore.Components.Web
23
@namespace BlazorServerWeb_CSharp.Pages
34
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
4-
@{
5-
Layout = "_Layout";
6-
}
75

8-
<component type="typeof(App)" render-mode="ServerPrerendered" />
6+
<!DOCTYPE html>
7+
<html lang="en">
8+
<head>
9+
<meta charset="utf-8" />
10+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
11+
<base href="~/" />
12+
<link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
13+
<link href="css/site.css" rel="stylesheet" />
14+
<link href="BlazorServerWeb-CSharp.styles.css" rel="stylesheet" />
15+
<component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" />
16+
</head>
17+
<body>
18+
<component type="typeof(App)" render-mode="ServerPrerendered" />
19+
20+
<div id="blazor-error-ui">
21+
<environment include="Staging,Production">
22+
An error has occurred. This application may no longer respond until reloaded.
23+
</environment>
24+
<environment include="Development">
25+
An unhandled exception has occurred. See browser dev tools for details.
26+
</environment>
27+
<a href="" class="reload">Reload</a>
28+
<a class="dismiss">🗙</a>
29+
</div>
30+
31+
<script src="_framework/blazor.server.js"></script>
32+
</body>
33+
</html>

src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Pages/_Layout.cshtml

-32
This file was deleted.

src/ProjectTemplates/test/template-baselines.json

-6
Original file line numberDiff line numberDiff line change
@@ -1262,7 +1262,6 @@
12621262
"Pages/FetchData.razor",
12631263
"Pages/Index.razor",
12641264
"Pages/_Host.cshtml",
1265-
"Pages/_Layout.cshtml",
12661265
"Properties/launchSettings.json",
12671266
"Shared/LoginDisplay.razor",
12681267
"Shared/MainLayout.razor",
@@ -1302,7 +1301,6 @@
13021301
"Pages/FetchData.razor",
13031302
"Pages/Index.razor",
13041303
"Pages/_Host.cshtml",
1305-
"Pages/_Layout.cshtml",
13061304
"Properties/launchSettings.json",
13071305
"Shared/LoginDisplay.razor",
13081306
"Shared/MainLayout.razor",
@@ -1342,7 +1340,6 @@
13421340
"Pages/FetchData.razor",
13431341
"Pages/Index.razor",
13441342
"Pages/_Host.cshtml",
1345-
"Pages/_Layout.cshtml",
13461343
"Properties/launchSettings.json",
13471344
"Shared/LoginDisplay.razor",
13481345
"Shared/MainLayout.razor",
@@ -1382,7 +1379,6 @@
13821379
"Pages/FetchData.razor",
13831380
"Pages/Index.razor",
13841381
"Pages/_Host.cshtml",
1385-
"Pages/_Layout.cshtml",
13861382
"Properties/launchSettings.json",
13871383
"Shared/MainLayout.razor",
13881384
"Shared/MainLayout.razor.css",
@@ -1421,7 +1417,6 @@
14211417
"Pages/FetchData.razor",
14221418
"Pages/Index.razor",
14231419
"Pages/_Host.cshtml",
1424-
"Pages/_Layout.cshtml",
14251420
"Properties/launchSettings.json",
14261421
"Shared/LoginDisplay.razor",
14271422
"Shared/MainLayout.razor",
@@ -1461,7 +1456,6 @@
14611456
"Pages/FetchData.razor",
14621457
"Pages/Index.razor",
14631458
"Pages/_Host.cshtml",
1464-
"Pages/_Layout.cshtml",
14651459
"Properties/launchSettings.json",
14661460
"Shared/LoginDisplay.razor",
14671461
"Shared/MainLayout.razor",

0 commit comments

Comments
 (0)