Skip to content

Commit 1ca0709

Browse files
authored
Use view buffers during pre-rendering (#39465)
* Use view buffers during rendering This change removes about 8kb of string[] allocations per request during pre-rendering and replaces them with a ViewBuffer that uses array pooling. The allocations come from list resizing as part of HtmlRenderer operations (such as https://github.com/dotnet/aspnetcore/blob/main/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/HtmlRenderer.cs#L133-L134). Also includes a couple of other clean up items: * Uses ValueTask instead of `Task<T>` * Moves top-level types to a separate file. * Some formatting cleanup
1 parent 2325e12 commit 1ca0709

16 files changed

+210
-233
lines changed

src/Components/Server/test/Microsoft.AspNetCore.Components.Server.Tests.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
55
</PropertyGroup>
66

77
<ItemGroup>
88
<Reference Include="Microsoft.AspNetCore.Components.Server" />
9+
<Reference Include="Microsoft.AspNetCore.Html.Abstractions" />
910
</ItemGroup>
1011

1112
<PropertyGroup>

src/Mvc/Mvc.TagHelpers/test/ComponentTagHelperTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ private ViewContext GetViewContext()
7575
{
7676
var htmlContent = new HtmlContentBuilder().AppendHtml("Hello world");
7777
var renderer = Mock.Of<IComponentRenderer>(c =>
78-
c.RenderComponentAsync(It.IsAny<ViewContext>(), It.IsAny<Type>(), It.IsAny<RenderMode>(), It.IsAny<object>()) == Task.FromResult<IHtmlContent>(htmlContent));
78+
c.RenderComponentAsync(It.IsAny<ViewContext>(), It.IsAny<Type>(), It.IsAny<RenderMode>(), It.IsAny<object>()) == new ValueTask<IHtmlContent>(htmlContent));
7979

8080
var httpContext = new DefaultHttpContext
8181
{

src/Mvc/Mvc.TagHelpers/test/PersistComponentStateTagHelperTest.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using Microsoft.AspNetCore.Http;
1010
using Microsoft.AspNetCore.Mvc.Rendering;
1111
using Microsoft.AspNetCore.Mvc.ViewFeatures;
12+
using Microsoft.AspNetCore.Mvc.ViewFeatures.Buffers;
1213
using Microsoft.AspNetCore.Razor.TagHelpers;
1314
using Microsoft.Extensions.DependencyInjection;
1415
using Microsoft.Extensions.Logging;
@@ -180,7 +181,7 @@ private ViewContext GetViewContext()
180181
{
181182
var htmlContent = new HtmlContentBuilder().AppendHtml("Hello world");
182183
var renderer = Mock.Of<IComponentRenderer>(c =>
183-
c.RenderComponentAsync(It.IsAny<ViewContext>(), It.IsAny<Type>(), It.IsAny<RenderMode>(), It.IsAny<object>()) == Task.FromResult<IHtmlContent>(htmlContent));
184+
c.RenderComponentAsync(It.IsAny<ViewContext>(), It.IsAny<Type>(), It.IsAny<RenderMode>(), It.IsAny<object>()) == new ValueTask<IHtmlContent>(htmlContent));
184185

185186
var httpContext = new DefaultHttpContext
186187
{
@@ -191,6 +192,7 @@ private ViewContext GetViewContext()
191192
.AddSingleton(_ephemeralProvider)
192193
.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance)
193194
.AddSingleton(HtmlEncoder.Default)
195+
.AddScoped<IViewBufferScope, TestViewBufferScope>()
194196
.BuildServiceProvider(),
195197
};
196198

src/Mvc/Mvc.ViewFeatures/src/Infrastructure/ComponentHtmlContent.cs

Lines changed: 0 additions & 29 deletions
This file was deleted.
Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using Microsoft.AspNetCore.Html;
5+
46
namespace Microsoft.AspNetCore.Components.Rendering;
57

68
internal readonly struct ComponentRenderedText
79
{
8-
public ComponentRenderedText(int componentId, IEnumerable<string> tokens)
10+
public ComponentRenderedText(int componentId, IHtmlContent htmlContent)
911
{
1012
ComponentId = componentId;
11-
Tokens = tokens;
13+
HtmlContent = htmlContent;
1214
}
1315

1416
public int ComponentId { get; }
1517

16-
public IEnumerable<string> Tokens { get; }
18+
public IHtmlContent HtmlContent { get; }
1719
}

src/Mvc/Mvc.ViewFeatures/src/RazorComponents/ComponentRenderer.cs

Lines changed: 28 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,30 @@
55
using Microsoft.AspNetCore.Html;
66
using Microsoft.AspNetCore.Http;
77
using Microsoft.AspNetCore.Mvc.Rendering;
8+
using Microsoft.AspNetCore.Mvc.ViewFeatures.Buffers;
89

910
namespace Microsoft.AspNetCore.Mvc.ViewFeatures;
1011

11-
internal class ComponentRenderer : IComponentRenderer
12+
internal sealed class ComponentRenderer : IComponentRenderer
1213
{
1314
private static readonly object ComponentSequenceKey = new object();
1415
private static readonly object InvokedRenderModesKey = new object();
1516

1617
private readonly StaticComponentRenderer _staticComponentRenderer;
1718
private readonly ServerComponentSerializer _serverComponentSerializer;
18-
private readonly WebAssemblyComponentSerializer _WebAssemblyComponentSerializer;
19+
private readonly IViewBufferScope _viewBufferScope;
1920

2021
public ComponentRenderer(
2122
StaticComponentRenderer staticComponentRenderer,
2223
ServerComponentSerializer serverComponentSerializer,
23-
WebAssemblyComponentSerializer WebAssemblyComponentSerializer)
24+
IViewBufferScope viewBufferScope)
2425
{
2526
_staticComponentRenderer = staticComponentRenderer;
2627
_serverComponentSerializer = serverComponentSerializer;
27-
_WebAssemblyComponentSerializer = WebAssemblyComponentSerializer;
28+
_viewBufferScope = viewBufferScope;
2829
}
2930

30-
public async Task<IHtmlContent> RenderComponentAsync(
31+
public async ValueTask<IHtmlContent> RenderComponentAsync(
3132
ViewContext viewContext,
3233
Type componentType,
3334
RenderMode renderMode,
@@ -117,14 +118,12 @@ internal static InvokedRenderModes.Mode GetPersistStateRenderMode(ViewContext vi
117118
}
118119
}
119120

120-
private async Task<IHtmlContent> StaticComponentAsync(HttpContext context, Type type, ParameterView parametersCollection)
121+
private ValueTask<IHtmlContent> StaticComponentAsync(HttpContext context, Type type, ParameterView parametersCollection)
121122
{
122-
var result = await _staticComponentRenderer.PrerenderComponentAsync(
123+
return _staticComponentRenderer.PrerenderComponentAsync(
123124
parametersCollection,
124125
context,
125126
type);
126-
127-
return new ComponentHtmlContent(result);
128127
}
129128

130129
private async Task<IHtmlContent> PrerenderedServerComponentAsync(HttpContext context, ServerComponentInvocationSequence invocationId, Type type, ParameterView parametersCollection)
@@ -145,13 +144,15 @@ private async Task<IHtmlContent> PrerenderedServerComponentAsync(HttpContext con
145144
context,
146145
type);
147146

148-
return new ComponentHtmlContent(
149-
ServerComponentSerializer.GetPreamble(currentInvocation),
150-
result,
151-
ServerComponentSerializer.GetEpilogue(currentInvocation));
147+
var viewBuffer = new ViewBuffer(_viewBufferScope, nameof(ComponentRenderer), ViewBuffer.ViewPageSize);
148+
ServerComponentSerializer.AppendPreamble(viewBuffer, currentInvocation);
149+
viewBuffer.AppendHtml(result);
150+
ServerComponentSerializer.AppendEpilogue(viewBuffer, currentInvocation);
151+
152+
return viewBuffer;
152153
}
153154

154-
private async Task<IHtmlContent> PrerenderedWebAssemblyComponentAsync(HttpContext context, Type type, ParameterView parametersCollection)
155+
private async ValueTask<IHtmlContent> PrerenderedWebAssemblyComponentAsync(HttpContext context, Type type, ParameterView parametersCollection)
155156
{
156157
var currentInvocation = WebAssemblyComponentSerializer.SerializeInvocation(
157158
type,
@@ -163,10 +164,12 @@ private async Task<IHtmlContent> PrerenderedWebAssemblyComponentAsync(HttpContex
163164
context,
164165
type);
165166

166-
return new ComponentHtmlContent(
167-
WebAssemblyComponentSerializer.GetPreamble(currentInvocation),
168-
result,
169-
WebAssemblyComponentSerializer.GetEpilogue(currentInvocation));
167+
var viewBuffer = new ViewBuffer(_viewBufferScope, nameof(ComponentRenderer), ViewBuffer.ViewPageSize);
168+
WebAssemblyComponentSerializer.AppendPreamble(viewBuffer, currentInvocation);
169+
viewBuffer.AppendHtml(result);
170+
WebAssemblyComponentSerializer.AppendEpilogue(viewBuffer, currentInvocation);
171+
172+
return viewBuffer;
170173
}
171174

172175
private IHtmlContent NonPrerenderedServerComponent(HttpContext context, ServerComponentInvocationSequence invocationId, Type type, ParameterView parametersCollection)
@@ -178,31 +181,16 @@ private IHtmlContent NonPrerenderedServerComponent(HttpContext context, ServerCo
178181

179182
var currentInvocation = _serverComponentSerializer.SerializeInvocation(invocationId, type, parametersCollection, prerendered: false);
180183

181-
return new ComponentHtmlContent(ServerComponentSerializer.GetPreamble(currentInvocation));
184+
var viewBuffer = new ViewBuffer(_viewBufferScope, nameof(ComponentRenderer), ServerComponentSerializer.PreambleBufferSize);
185+
ServerComponentSerializer.AppendPreamble(viewBuffer, currentInvocation);
186+
return viewBuffer;
182187
}
183188

184-
private static IHtmlContent NonPrerenderedWebAssemblyComponent(HttpContext context, Type type, ParameterView parametersCollection)
189+
private IHtmlContent NonPrerenderedWebAssemblyComponent(HttpContext context, Type type, ParameterView parametersCollection)
185190
{
186191
var currentInvocation = WebAssemblyComponentSerializer.SerializeInvocation(type, parametersCollection, prerendered: false);
187-
188-
return new ComponentHtmlContent(WebAssemblyComponentSerializer.GetPreamble(currentInvocation));
189-
}
190-
}
191-
192-
internal class InvokedRenderModes
193-
{
194-
public InvokedRenderModes(Mode mode)
195-
{
196-
Value = mode;
197-
}
198-
199-
public Mode Value { get; set; }
200-
201-
internal enum Mode
202-
{
203-
None,
204-
Server,
205-
WebAssembly,
206-
ServerAndWebAssembly
192+
var viewBuffer = new ViewBuffer(_viewBufferScope, nameof(ComponentRenderer), ServerComponentSerializer.PreambleBufferSize);
193+
WebAssemblyComponentSerializer.AppendPreamble(viewBuffer, currentInvocation);
194+
return viewBuffer;
207195
}
208196
}

0 commit comments

Comments
 (0)