File: Results\RazorComponentResultExecutor.cs
Web Access
Project: src\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;
            endpointHtmlRenderer.InitializeStreamingRenderingFraming(httpContext, isErrorHandler);
            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();
        });
    }
}