Skip to content

Commit

Permalink
Use view buffers during pre-rendering (#39465)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
pranavkm authored Jan 18, 2022
1 parent 2325e12 commit 1ca0709
Show file tree
Hide file tree
Showing 16 changed files with 210 additions and 233 deletions.
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
</PropertyGroup>

<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Components.Server" />
<Reference Include="Microsoft.AspNetCore.Html.Abstractions" />
</ItemGroup>

<PropertyGroup>
Expand Down
2 changes: 1 addition & 1 deletion src/Mvc/Mvc.TagHelpers/test/ComponentTagHelperTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ private ViewContext GetViewContext()
{
var htmlContent = new HtmlContentBuilder().AppendHtml("Hello world");
var renderer = Mock.Of<IComponentRenderer>(c =>
c.RenderComponentAsync(It.IsAny<ViewContext>(), It.IsAny<Type>(), It.IsAny<RenderMode>(), It.IsAny<object>()) == Task.FromResult<IHtmlContent>(htmlContent));
c.RenderComponentAsync(It.IsAny<ViewContext>(), It.IsAny<Type>(), It.IsAny<RenderMode>(), It.IsAny<object>()) == new ValueTask<IHtmlContent>(htmlContent));

var httpContext = new DefaultHttpContext
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Buffers;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
Expand Down Expand Up @@ -180,7 +181,7 @@ private ViewContext GetViewContext()
{
var htmlContent = new HtmlContentBuilder().AppendHtml("Hello world");
var renderer = Mock.Of<IComponentRenderer>(c =>
c.RenderComponentAsync(It.IsAny<ViewContext>(), It.IsAny<Type>(), It.IsAny<RenderMode>(), It.IsAny<object>()) == Task.FromResult<IHtmlContent>(htmlContent));
c.RenderComponentAsync(It.IsAny<ViewContext>(), It.IsAny<Type>(), It.IsAny<RenderMode>(), It.IsAny<object>()) == new ValueTask<IHtmlContent>(htmlContent));

var httpContext = new DefaultHttpContext
{
Expand All @@ -191,6 +192,7 @@ private ViewContext GetViewContext()
.AddSingleton(_ephemeralProvider)
.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance)
.AddSingleton(HtmlEncoder.Default)
.AddScoped<IViewBufferScope, TestViewBufferScope>()
.BuildServiceProvider(),
};

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.AspNetCore.Html;

namespace Microsoft.AspNetCore.Components.Rendering;

internal readonly struct ComponentRenderedText
{
public ComponentRenderedText(int componentId, IEnumerable<string> tokens)
public ComponentRenderedText(int componentId, IHtmlContent htmlContent)
{
ComponentId = componentId;
Tokens = tokens;
HtmlContent = htmlContent;
}

public int ComponentId { get; }

public IEnumerable<string> Tokens { get; }
public IHtmlContent HtmlContent { get; }
}
68 changes: 28 additions & 40 deletions src/Mvc/Mvc.ViewFeatures/src/RazorComponents/ComponentRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,30 @@
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Buffers;

namespace Microsoft.AspNetCore.Mvc.ViewFeatures;

internal class ComponentRenderer : IComponentRenderer
internal sealed class ComponentRenderer : IComponentRenderer
{
private static readonly object ComponentSequenceKey = new object();
private static readonly object InvokedRenderModesKey = new object();

private readonly StaticComponentRenderer _staticComponentRenderer;
private readonly ServerComponentSerializer _serverComponentSerializer;
private readonly WebAssemblyComponentSerializer _WebAssemblyComponentSerializer;
private readonly IViewBufferScope _viewBufferScope;

public ComponentRenderer(
StaticComponentRenderer staticComponentRenderer,
ServerComponentSerializer serverComponentSerializer,
WebAssemblyComponentSerializer WebAssemblyComponentSerializer)
IViewBufferScope viewBufferScope)
{
_staticComponentRenderer = staticComponentRenderer;
_serverComponentSerializer = serverComponentSerializer;
_WebAssemblyComponentSerializer = WebAssemblyComponentSerializer;
_viewBufferScope = viewBufferScope;
}

public async Task<IHtmlContent> RenderComponentAsync(
public async ValueTask<IHtmlContent> RenderComponentAsync(
ViewContext viewContext,
Type componentType,
RenderMode renderMode,
Expand Down Expand Up @@ -117,14 +118,12 @@ internal static InvokedRenderModes.Mode GetPersistStateRenderMode(ViewContext vi
}
}

private async Task<IHtmlContent> StaticComponentAsync(HttpContext context, Type type, ParameterView parametersCollection)
private ValueTask<IHtmlContent> StaticComponentAsync(HttpContext context, Type type, ParameterView parametersCollection)
{
var result = await _staticComponentRenderer.PrerenderComponentAsync(
return _staticComponentRenderer.PrerenderComponentAsync(
parametersCollection,
context,
type);

return new ComponentHtmlContent(result);
}

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

return new ComponentHtmlContent(
ServerComponentSerializer.GetPreamble(currentInvocation),
result,
ServerComponentSerializer.GetEpilogue(currentInvocation));
var viewBuffer = new ViewBuffer(_viewBufferScope, nameof(ComponentRenderer), ViewBuffer.ViewPageSize);
ServerComponentSerializer.AppendPreamble(viewBuffer, currentInvocation);
viewBuffer.AppendHtml(result);
ServerComponentSerializer.AppendEpilogue(viewBuffer, currentInvocation);

return viewBuffer;
}

private async Task<IHtmlContent> PrerenderedWebAssemblyComponentAsync(HttpContext context, Type type, ParameterView parametersCollection)
private async ValueTask<IHtmlContent> PrerenderedWebAssemblyComponentAsync(HttpContext context, Type type, ParameterView parametersCollection)
{
var currentInvocation = WebAssemblyComponentSerializer.SerializeInvocation(
type,
Expand All @@ -163,10 +164,12 @@ private async Task<IHtmlContent> PrerenderedWebAssemblyComponentAsync(HttpContex
context,
type);

return new ComponentHtmlContent(
WebAssemblyComponentSerializer.GetPreamble(currentInvocation),
result,
WebAssemblyComponentSerializer.GetEpilogue(currentInvocation));
var viewBuffer = new ViewBuffer(_viewBufferScope, nameof(ComponentRenderer), ViewBuffer.ViewPageSize);
WebAssemblyComponentSerializer.AppendPreamble(viewBuffer, currentInvocation);
viewBuffer.AppendHtml(result);
WebAssemblyComponentSerializer.AppendEpilogue(viewBuffer, currentInvocation);

return viewBuffer;
}

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

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

return new ComponentHtmlContent(ServerComponentSerializer.GetPreamble(currentInvocation));
var viewBuffer = new ViewBuffer(_viewBufferScope, nameof(ComponentRenderer), ServerComponentSerializer.PreambleBufferSize);
ServerComponentSerializer.AppendPreamble(viewBuffer, currentInvocation);
return viewBuffer;
}

private static IHtmlContent NonPrerenderedWebAssemblyComponent(HttpContext context, Type type, ParameterView parametersCollection)
private IHtmlContent NonPrerenderedWebAssemblyComponent(HttpContext context, Type type, ParameterView parametersCollection)
{
var currentInvocation = WebAssemblyComponentSerializer.SerializeInvocation(type, parametersCollection, prerendered: false);

return new ComponentHtmlContent(WebAssemblyComponentSerializer.GetPreamble(currentInvocation));
}
}

internal class InvokedRenderModes
{
public InvokedRenderModes(Mode mode)
{
Value = mode;
}

public Mode Value { get; set; }

internal enum Mode
{
None,
Server,
WebAssembly,
ServerAndWebAssembly
var viewBuffer = new ViewBuffer(_viewBufferScope, nameof(ComponentRenderer), ServerComponentSerializer.PreambleBufferSize);
WebAssemblyComponentSerializer.AppendPreamble(viewBuffer, currentInvocation);
return viewBuffer;
}
}
Loading

0 comments on commit 1ca0709

Please sign in to comment.