Skip to content

Commit

Permalink
Modify HtmlRenderer to update content when a component re-renders (#4…
Browse files Browse the repository at this point in the history
…0512)

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
  • Loading branch information
pranavkm committed Mar 3, 2022
1 parent 3817558 commit e9dddd6
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 56 deletions.
2 changes: 1 addition & 1 deletion src/Mvc/Mvc.ViewFeatures/src/Buffers/ViewBuffer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Buffers;
/// An <see cref="IHtmlContentBuilder"/> that is backed by a buffer provided by <see cref="IViewBufferScope"/>.
/// </summary>
[DebuggerDisplay("{DebuggerToString()}")]
internal class ViewBuffer : IHtmlContentBuilder
internal sealed class ViewBuffer : IHtmlContentBuilder
{
public const int PartialViewPageSize = 32;
public const int TagHelperPageSize = 32;
Expand Down
54 changes: 41 additions & 13 deletions src/Mvc/Mvc.ViewFeatures/src/RazorComponents/HtmlRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@

using System.Diagnostics;
using System.Runtime.ExceptionServices;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Components.RenderTree;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Buffers;
using Microsoft.Extensions.Logging;

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

public async Task<ComponentRenderedText> RenderComponentAsync(Type componentType, ParameterView initialParameters)
{
var (componentId, frames) = await CreateInitialRenderAsync(componentType, initialParameters);

var viewBuffer = new ViewBuffer(_viewBufferScope, nameof(HtmlRenderer), ViewBuffer.ViewPageSize);
var component = InstantiateComponent(componentType);
var componentId = AssignRootComponentId(component);

var context = new HtmlRenderingContext { HtmlContentBuilder = viewBuffer, };
var newPosition = RenderFrames(context, frames, 0, frames.Count);
Debug.Assert(newPosition == frames.Count);
return new ComponentRenderedText(componentId, context.HtmlContentBuilder);
await RenderRootComponentAsync(componentId, initialParameters);
return new ComponentRenderedText(componentId, new ComponentHtmlContent(this, componentId));
}

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

private async Task<(int, ArrayRange<RenderTreeFrame>)> CreateInitialRenderAsync(Type componentType, ParameterView initialParameters)
private ViewBuffer GetRenderedHtmlContent(int componentId)
{
var component = InstantiateComponent(componentType);
var componentId = AssignRootComponentId(component);
var viewBuffer = new ViewBuffer(_viewBufferScope, nameof(HtmlRenderer), ViewBuffer.ViewPageSize);
var context = new HtmlRenderingContext(viewBuffer);

await RenderRootComponentAsync(componentId, initialParameters);
var frames = GetCurrentRenderTreeFrames(componentId);
var newPosition = RenderFrames(context, frames, 0, frames.Count);
Debug.Assert(newPosition == frames.Count);

return (componentId, GetCurrentRenderTreeFrames(componentId));
return viewBuffer;
}

private sealed class HtmlRenderingContext
{
public ViewBuffer HtmlContentBuilder { get; init; }
public HtmlRenderingContext(ViewBuffer viewBuffer)
{
HtmlContentBuilder = viewBuffer;
}

public ViewBuffer HtmlContentBuilder { get; }

public string ClosestSelectValueAsString { get; set; }
}

/// <summary>
/// A <see cref="IHtmlContent"/> that defers rendering component markup until <see cref="IHtmlContent.WriteTo(TextWriter, HtmlEncoder)"/>
/// is called.
/// </summary>
private sealed class ComponentHtmlContent : IHtmlContent
{
private readonly HtmlRenderer _renderer;
private readonly int _componentId;

public ComponentHtmlContent(HtmlRenderer renderer, int componentId)
{
_renderer = renderer;
_componentId = componentId;
}

public void WriteTo(TextWriter writer, HtmlEncoder encoder)
{
var actualHtmlContent = _renderer.GetRenderedHtmlContent(_componentId);
actualHtmlContent.WriteTo(writer, encoder);
}
}
}

Original file line number Diff line number Diff line change
@@ -1,8 +1,33 @@
@page "/"
@using Microsoft.AspNetCore.Components.Web
@namespace BlazorServerWeb_CSharp.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
Layout = "_Layout";
}

<component type="typeof(App)" render-mode="ServerPrerendered" />
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<base href="~/" />
<link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
<link href="css/site.css" rel="stylesheet" />
<link href="BlazorServerWeb-CSharp.styles.css" rel="stylesheet" />
<component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" />
</head>
<body>
<component type="typeof(App)" render-mode="ServerPrerendered" />

<div id="blazor-error-ui">
<environment include="Staging,Production">
An error has occurred. This application may no longer respond until reloaded.
</environment>
<environment include="Development">
An unhandled exception has occurred. See browser dev tools for details.
</environment>
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>

<script src="_framework/blazor.server.js"></script>
</body>
</html>

This file was deleted.

6 changes: 0 additions & 6 deletions src/ProjectTemplates/test/template-baselines.json
Original file line number Diff line number Diff line change
Expand Up @@ -1262,7 +1262,6 @@
"Pages/FetchData.razor",
"Pages/Index.razor",
"Pages/_Host.cshtml",
"Pages/_Layout.cshtml",
"Properties/launchSettings.json",
"Shared/LoginDisplay.razor",
"Shared/MainLayout.razor",
Expand Down Expand Up @@ -1302,7 +1301,6 @@
"Pages/FetchData.razor",
"Pages/Index.razor",
"Pages/_Host.cshtml",
"Pages/_Layout.cshtml",
"Properties/launchSettings.json",
"Shared/LoginDisplay.razor",
"Shared/MainLayout.razor",
Expand Down Expand Up @@ -1342,7 +1340,6 @@
"Pages/FetchData.razor",
"Pages/Index.razor",
"Pages/_Host.cshtml",
"Pages/_Layout.cshtml",
"Properties/launchSettings.json",
"Shared/LoginDisplay.razor",
"Shared/MainLayout.razor",
Expand Down Expand Up @@ -1382,7 +1379,6 @@
"Pages/FetchData.razor",
"Pages/Index.razor",
"Pages/_Host.cshtml",
"Pages/_Layout.cshtml",
"Properties/launchSettings.json",
"Shared/MainLayout.razor",
"Shared/MainLayout.razor.css",
Expand Down Expand Up @@ -1421,7 +1417,6 @@
"Pages/FetchData.razor",
"Pages/Index.razor",
"Pages/_Host.cshtml",
"Pages/_Layout.cshtml",
"Properties/launchSettings.json",
"Shared/LoginDisplay.razor",
"Shared/MainLayout.razor",
Expand Down Expand Up @@ -1461,7 +1456,6 @@
"Pages/FetchData.razor",
"Pages/Index.razor",
"Pages/_Host.cshtml",
"Pages/_Layout.cshtml",
"Properties/launchSettings.json",
"Shared/LoginDisplay.razor",
"Shared/MainLayout.razor",
Expand Down

0 comments on commit e9dddd6

Please sign in to comment.