| File: Results\RazorComponentResultExecutor.cs | Web Access |
| Project: src\aspnetcore\src\Components\Endpoints\src\Microsoft.AspNetCore.Components.Endpoints.csproj (Microsoft.AspNetCore.Components.Endpoints) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.CodeAnalysis; using System.Buffers; using System.Text; using System.Text.Encodings.Web; using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.DependencyInjection; using static Microsoft.AspNetCore.Internal.LinkerFlags; using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Components.Endpoints.Rendering; namespace Microsoft.AspNetCore.Components.Endpoints; internal static class RazorComponentResultExecutor { public const string DefaultContentType = "text/html; charset=utf-8"; public static Task ExecuteAsync(HttpContext httpContext, RazorComponentResult result) { ArgumentNullException.ThrowIfNull(httpContext); var response = httpContext.Response; response.ContentType = result.ContentType ?? DefaultContentType; if (result.StatusCode != null) { response.StatusCode = result.StatusCode.Value; } return RenderComponentToResponse( httpContext, result.ComponentType, result.Parameters, result.PreventStreamingRendering); } private static Task RenderComponentToResponse( HttpContext httpContext, [DynamicallyAccessedMembers(Component)] Type componentType, IReadOnlyDictionary<string, object?>? componentParameters, bool preventStreamingRendering) { var endpointHtmlRenderer = httpContext.RequestServices.GetRequiredService<EndpointHtmlRenderer>(); return endpointHtmlRenderer.Dispatcher.InvokeAsync(async () => { var isErrorHandler = httpContext.Features.Get<IExceptionHandlerFeature>() is not null; var isReExecuted = httpContext.Features.Get<IStatusCodeReExecuteFeature>() is not null; endpointHtmlRenderer.InitializeStreamingRenderingFraming(httpContext, isErrorHandler, isReExecuted); EndpointHtmlRenderer.MarkAsAllowingEnhancedNavigation(httpContext); // We could pool these dictionary instances if we wanted, and possibly even the ParameterView // backing buffers could come from a pool like they do during rendering. var hostParameters = ParameterView.FromDictionary(new Dictionary<string, object?> { { nameof(RazorComponentEndpointHost.ComponentType), componentType }, { nameof(RazorComponentEndpointHost.ComponentParameters), componentParameters }, }); // Matches MVC's MemoryPoolHttpResponseStreamWriterFactory.DefaultBufferSize var defaultBufferSize = 16 * 1024; await using var writer = new HttpResponseStreamWriter(httpContext.Response.Body, Encoding.UTF8, defaultBufferSize, ArrayPool<byte>.Shared, ArrayPool<char>.Shared); using var bufferWriter = new BufferedTextWriter(writer); // Note that we don't set any interactive rendering mode for the top-level output from a RazorComponentResult, // because you never want to serialize the invocation of RazorComponentResultHost. Instead, that host // component takes care of switching into your desired render mode when it produces its own output. var htmlContent = (EndpointHtmlRenderer.PrerenderedComponentHtmlContent)(await endpointHtmlRenderer.PrerenderComponentAsync( httpContext, typeof(RazorComponentEndpointHost), null, hostParameters, waitForQuiescence: preventStreamingRendering)); // Importantly, we must not yield this thread (which holds exclusive access to the renderer sync context) // in between the first call to htmlContent.WriteTo and the point where we start listening for subsequent // streaming SSR batches (inside SendStreamingUpdatesAsync). Otherwise some other code might dispatch to the // renderer sync context and cause a batch that would get missed. htmlContent.WriteTo(bufferWriter, HtmlEncoder.Default); // Don't use WriteToAsync, as per the comment above if (!htmlContent.QuiescenceTask.IsCompletedSuccessfully) { await endpointHtmlRenderer.SendStreamingUpdatesAsync(httpContext, htmlContent.QuiescenceTask, bufferWriter); } // Invoke FlushAsync to ensure any buffered content is asynchronously written to the underlying // response asynchronously. In the absence of this line, the buffer gets synchronously written to the // response as part of the Dispose which has a perf impact. await bufferWriter.FlushAsync(); }); } }