From c12ea9295fb6fd919c3d2b4766923053b0e88ba8 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Fri, 3 Mar 2023 13:11:53 +0000 Subject: [PATCH 01/17] Initial RazorComponentResult implementation --- ...MvcViewFeaturesMvcCoreBuilderExtensions.cs | 1 + .../src/Diagnostics/MvcDiagnostics.cs | 101 ++++++++++++++++++ ...iewFeaturesDiagnosticListenerExtensions.cs | 46 ++++++++ .../src/PublicAPI.Unshipped.txt | 34 +++++- .../RazorComponents/ComponentPrerenderer.cs | 37 +++---- .../src/RazorComponents/IAsyncHtmlContent.cs | 6 +- .../RazorComponents/RazorComponentResult.cs | 65 +++++++++++ .../RazorComponentResultExecutor.cs | 94 ++++++++++++++++ 8 files changed, 363 insertions(+), 21 deletions(-) create mode 100644 src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResult.cs create mode 100644 src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultExecutor.cs diff --git a/src/Mvc/Mvc.ViewFeatures/src/DependencyInjection/MvcViewFeaturesMvcCoreBuilderExtensions.cs b/src/Mvc/Mvc.ViewFeatures/src/DependencyInjection/MvcViewFeaturesMvcCoreBuilderExtensions.cs index d3ed50ef8b96..c78eab07e05b 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/DependencyInjection/MvcViewFeaturesMvcCoreBuilderExtensions.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/DependencyInjection/MvcViewFeaturesMvcCoreBuilderExtensions.cs @@ -212,6 +212,7 @@ internal static void AddViewServices(IServiceCollection services) services.TryAddScoped(); services.TryAddScoped(sp => sp.GetRequiredService().State); services.TryAddScoped(); + services.TryAddScoped(); services.TryAddTransient(); diff --git a/src/Mvc/Mvc.ViewFeatures/src/Diagnostics/MvcDiagnostics.cs b/src/Mvc/Mvc.ViewFeatures/src/Diagnostics/MvcDiagnostics.cs index 58d43ba2399b..040fd5ea3838 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/Diagnostics/MvcDiagnostics.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/Diagnostics/MvcDiagnostics.cs @@ -1,6 +1,7 @@ // 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.Components; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.ViewComponents; @@ -438,3 +439,103 @@ public ViewNotFoundEventData(ActionContext actionContext, bool isMainPage, Actio _ => throw new IndexOutOfRangeException(nameof(index)) }; } + +/// +/// An that occurs before a Razor Component. +/// +public sealed class BeforeRazorComponentEventData : EventData +{ + /// + /// The name of the event. + /// + public const string EventName = EventNamespace + "BeforeRazorComponent"; + + /// + /// Initializes a new instance of . + /// + /// The component type. + /// The component render mode. + /// The . + public BeforeRazorComponentEventData(Type componentType, RenderMode renderMode, ActionContext actionContext) + { + ComponentType = componentType; + RenderMode = renderMode; + ActionContext = actionContext; + } + + /// + /// The component type. + /// + public Type ComponentType { get; } + + /// + /// The component render mode. + /// + public RenderMode RenderMode { get; } + + /// + /// The . + /// + public ActionContext ActionContext { get; } + + /// + protected override int Count => 2; + + /// + protected override KeyValuePair this[int index] => index switch + { + 0 => new KeyValuePair(nameof(ComponentType), ComponentType), + 1 => new KeyValuePair(nameof(RenderMode), RenderMode), + _ => throw new IndexOutOfRangeException(nameof(index)) + }; +} + +/// +/// An that occurs after a Razor Component. +/// +public sealed class AfterRazorComponentEventData : EventData +{ + /// + /// The name of the event. + /// + public const string EventName = EventNamespace + "AfterRazorComponent"; + + /// + /// Initializes a new instance of . + /// + /// The component type. + /// The component render mode. + /// The . + public AfterRazorComponentEventData(Type componentType, RenderMode renderMode, ActionContext actionContext) + { + ComponentType = componentType; + RenderMode = renderMode; + ActionContext = actionContext; + } + + /// + /// The component type. + /// + public Type ComponentType { get; } + + /// + /// The component render mode. + /// + public RenderMode RenderMode { get; } + + /// + /// The . + /// + public ActionContext ActionContext { get; } + + /// + protected override int Count => 2; + + /// + protected override KeyValuePair this[int index] => index switch + { + 0 => new KeyValuePair(nameof(ComponentType), ComponentType), + 1 => new KeyValuePair(nameof(RenderMode), RenderMode), + _ => throw new IndexOutOfRangeException(nameof(index)) + }; +} diff --git a/src/Mvc/Mvc.ViewFeatures/src/MvcViewFeaturesDiagnosticListenerExtensions.cs b/src/Mvc/Mvc.ViewFeatures/src/MvcViewFeaturesDiagnosticListenerExtensions.cs index 755869805a41..7a1adaab7da7 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/MvcViewFeaturesDiagnosticListenerExtensions.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/MvcViewFeaturesDiagnosticListenerExtensions.cs @@ -117,6 +117,52 @@ private static void ViewComponentAfterViewExecuteImpl(DiagnosticListener diagnos } } + public static void BeforeRazorComponent( + this DiagnosticListener diagnosticListener, + Type componentType, + RenderMode renderMode, + ActionContext actionContext) + { + // Inlinable fast-path check if Diagnositcs is enabled + if (diagnosticListener.IsEnabled()) + { + BeforeRazorComponentImpl(diagnosticListener, componentType, renderMode, actionContext); + } + } + + private static void BeforeRazorComponentImpl(DiagnosticListener diagnosticListener, Type componentType, RenderMode renderMode, ActionContext actionContext) + { + if (diagnosticListener.IsEnabled(Diagnostics.BeforeViewEventData.EventName)) + { + diagnosticListener.Write( + Diagnostics.BeforeRazorComponentEventData.EventName, + new BeforeRazorComponentEventData(componentType, renderMode, actionContext)); + } + } + + public static void AfterRazorComponent( + this DiagnosticListener diagnosticListener, + Type componentType, + RenderMode renderMode, + ActionContext actionContext) + { + // Inlinable fast-path check if Diagnositcs is enabled + if (diagnosticListener.IsEnabled()) + { + AfterRazorComponentImpl(diagnosticListener, componentType, renderMode, actionContext); + } + } + + private static void AfterRazorComponentImpl(DiagnosticListener diagnosticListener, Type componentType, RenderMode renderMode, ActionContext actionContext) + { + if (diagnosticListener.IsEnabled(Diagnostics.AfterViewEventData.EventName)) + { + diagnosticListener.Write( + Diagnostics.AfterRazorComponentEventData.EventName, + new AfterRazorComponentEventData(componentType, renderMode, actionContext)); + } + } + public static void BeforeView( this DiagnosticListener diagnosticListener, IView view, diff --git a/src/Mvc/Mvc.ViewFeatures/src/PublicAPI.Unshipped.txt b/src/Mvc/Mvc.ViewFeatures/src/PublicAPI.Unshipped.txt index b7b64d0c74cc..e9e62f9fda7b 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/PublicAPI.Unshipped.txt +++ b/src/Mvc/Mvc.ViewFeatures/src/PublicAPI.Unshipped.txt @@ -1,2 +1,34 @@ #nullable enable -Microsoft.AspNetCore.Mvc.Rendering.FormMethod.Dialog = 2 -> Microsoft.AspNetCore.Mvc.Rendering.FormMethod \ No newline at end of file +Microsoft.AspNetCore.Mvc.Diagnostics.AfterRazorComponentEventData +Microsoft.AspNetCore.Mvc.Diagnostics.AfterRazorComponentEventData.RenderMode.get -> Microsoft.AspNetCore.Mvc.Rendering.RenderMode +Microsoft.AspNetCore.Mvc.Diagnostics.BeforeRazorComponentEventData +Microsoft.AspNetCore.Mvc.Diagnostics.BeforeRazorComponentEventData.RenderMode.get -> Microsoft.AspNetCore.Mvc.Rendering.RenderMode +Microsoft.AspNetCore.Mvc.Rendering.FormMethod.Dialog = 2 -> Microsoft.AspNetCore.Mvc.Rendering.FormMethod +Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult +Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.Parameters.get -> Microsoft.AspNetCore.Components.ParameterView +Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.Parameters.set -> void +Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.RenderMode.get -> Microsoft.AspNetCore.Mvc.Rendering.RenderMode +Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.RenderMode.set -> void +Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.StatusCode.get -> int? +Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.StatusCode.set -> void +Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor +Microsoft.Extensions.DependencyInjection.RazorComponentsServiceCollectionExtensions +~const Microsoft.AspNetCore.Mvc.Diagnostics.AfterRazorComponentEventData.EventName = "Microsoft.AspNetCore.Mvc.AfterRazorComponent" -> string +~const Microsoft.AspNetCore.Mvc.Diagnostics.BeforeRazorComponentEventData.EventName = "Microsoft.AspNetCore.Mvc.BeforeRazorComponent" -> string +~Microsoft.AspNetCore.Mvc.Diagnostics.AfterRazorComponentEventData.ActionContext.get -> Microsoft.AspNetCore.Mvc.ActionContext +~Microsoft.AspNetCore.Mvc.Diagnostics.AfterRazorComponentEventData.AfterRazorComponentEventData(System.Type componentType, Microsoft.AspNetCore.Mvc.Rendering.RenderMode renderMode, Microsoft.AspNetCore.Mvc.ActionContext actionContext) -> void +~Microsoft.AspNetCore.Mvc.Diagnostics.AfterRazorComponentEventData.ComponentType.get -> System.Type +~Microsoft.AspNetCore.Mvc.Diagnostics.BeforeRazorComponentEventData.ActionContext.get -> Microsoft.AspNetCore.Mvc.ActionContext +~Microsoft.AspNetCore.Mvc.Diagnostics.BeforeRazorComponentEventData.BeforeRazorComponentEventData(System.Type componentType, Microsoft.AspNetCore.Mvc.Rendering.RenderMode renderMode, Microsoft.AspNetCore.Mvc.ActionContext actionContext) -> void +~Microsoft.AspNetCore.Mvc.Diagnostics.BeforeRazorComponentEventData.ComponentType.get -> System.Type +~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.ComponentType.get -> System.Type +~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.ContentType.get -> string +~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.ContentType.set -> void +~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.RazorComponentResult(System.Type componentType) -> void +~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.DiagnosticListener.get -> System.Diagnostics.DiagnosticListener +~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.RazorComponentResultExecutor(Microsoft.AspNetCore.Mvc.Infrastructure.IHttpResponseStreamWriterFactory writerFactory, System.Diagnostics.DiagnosticListener diagnosticListener) -> void +~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.WriterFactory.get -> Microsoft.AspNetCore.Mvc.Infrastructure.IHttpResponseStreamWriterFactory +~override Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.ExecuteResultAsync(Microsoft.AspNetCore.Mvc.ActionContext context) -> System.Threading.Tasks.Task +~static Microsoft.Extensions.DependencyInjection.RazorComponentsServiceCollectionExtensions.AddRazorComponents(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) -> void +~static readonly Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.DefaultContentType -> string +~virtual Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.ExecuteAsync(Microsoft.AspNetCore.Mvc.ActionContext actionContext, Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult result) -> System.Threading.Tasks.Task \ No newline at end of file diff --git a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/ComponentPrerenderer.cs b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/ComponentPrerenderer.cs index cac3ca8fdf0e..ee0dd77559c8 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/ComponentPrerenderer.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/ComponentPrerenderer.cs @@ -38,13 +38,13 @@ public ComponentPrerenderer( public Dispatcher Dispatcher => _htmlRenderer.Dispatcher; - public async ValueTask PrerenderComponentAsync( - ViewContext viewContext, + public async ValueTask PrerenderComponentAsync( + ActionContext actionContext, Type componentType, RenderMode prerenderMode, ParameterView parameters) { - ArgumentNullException.ThrowIfNull(viewContext); + ArgumentNullException.ThrowIfNull(actionContext); ArgumentNullException.ThrowIfNull(componentType); if (!typeof(IComponent).IsAssignableFrom(componentType)) @@ -53,19 +53,19 @@ public async ValueTask PrerenderComponentAsync( } // Make sure we only initialize the services once, but on every call we wait for that process to complete - var httpContext = viewContext.HttpContext; + var httpContext = actionContext.HttpContext; lock (_servicesInitializedLock) { _servicesInitializedTask ??= InitializeStandardComponentServicesAsync(httpContext); } await _servicesInitializedTask; - UpdateSaveStateRenderMode(viewContext, prerenderMode); + UpdateSaveStateRenderMode(actionContext, prerenderMode); return prerenderMode switch { - RenderMode.Server => NonPrerenderedServerComponent(httpContext, GetOrCreateInvocationId(viewContext), componentType, parameters), - RenderMode.ServerPrerendered => await PrerenderedServerComponentAsync(httpContext, GetOrCreateInvocationId(viewContext), componentType, parameters), + RenderMode.Server => NonPrerenderedServerComponent(httpContext, GetOrCreateInvocationId(actionContext), componentType, parameters), + RenderMode.ServerPrerendered => await PrerenderedServerComponentAsync(httpContext, GetOrCreateInvocationId(actionContext), componentType, parameters), RenderMode.Static => await StaticComponentAsync(httpContext, componentType, parameters), RenderMode.WebAssembly => NonPrerenderedWebAssemblyComponent(componentType, parameters), RenderMode.WebAssemblyPrerendered => await PrerenderedWebAssemblyComponentAsync(httpContext, componentType, parameters), @@ -101,29 +101,30 @@ private async ValueTask PrerenderComponentCoreAsync( } } - private static ServerComponentInvocationSequence GetOrCreateInvocationId(ViewContext viewContext) + private static ServerComponentInvocationSequence GetOrCreateInvocationId(ActionContext actionContext) { - if (!viewContext.Items.TryGetValue(ComponentSequenceKey, out var result)) + if (!actionContext.HttpContext.Items.TryGetValue(ComponentSequenceKey, out var result)) { result = new ServerComponentInvocationSequence(); - viewContext.Items[ComponentSequenceKey] = result; + actionContext.HttpContext.Items[ComponentSequenceKey] = result; } return (ServerComponentInvocationSequence)result; } // Internal for test only - internal static void UpdateSaveStateRenderMode(ViewContext viewContext, RenderMode mode) + internal static void UpdateSaveStateRenderMode(ActionContext actionContext, RenderMode mode) { + // TODO: This will all have to change when we support multiple render modes in the same response if (mode == RenderMode.ServerPrerendered || mode == RenderMode.WebAssemblyPrerendered) { - if (!viewContext.Items.TryGetValue(InvokedRenderModesKey, out var result)) + if (!actionContext.HttpContext.Items.TryGetValue(InvokedRenderModesKey, out var result)) { result = new InvokedRenderModes(mode is RenderMode.ServerPrerendered ? InvokedRenderModes.Mode.Server : InvokedRenderModes.Mode.WebAssembly); - viewContext.Items[InvokedRenderModesKey] = result; + actionContext.HttpContext.Items[InvokedRenderModesKey] = result; } else { @@ -152,7 +153,7 @@ internal static InvokedRenderModes.Mode GetPersistStateRenderMode(ViewContext vi } } - private async ValueTask StaticComponentAsync(HttpContext context, Type type, ParameterView parametersCollection) + private async ValueTask StaticComponentAsync(HttpContext context, Type type, ParameterView parametersCollection) { var htmlComponent = await PrerenderComponentCoreAsync( parametersCollection, @@ -161,7 +162,7 @@ private async ValueTask StaticComponentAsync(HttpContext context, return new PrerenderedComponentHtmlContent(_htmlRenderer.Dispatcher, htmlComponent, null, null); } - private async Task PrerenderedServerComponentAsync(HttpContext context, ServerComponentInvocationSequence invocationId, Type type, ParameterView parametersCollection) + private async Task PrerenderedServerComponentAsync(HttpContext context, ServerComponentInvocationSequence invocationId, Type type, ParameterView parametersCollection) { if (!context.Response.HasStarted) { @@ -182,7 +183,7 @@ private async Task PrerenderedServerComponentAsync(HttpContext con return new PrerenderedComponentHtmlContent(_htmlRenderer.Dispatcher, htmlComponent, marker, null); } - private async ValueTask PrerenderedWebAssemblyComponentAsync(HttpContext context, Type type, ParameterView parametersCollection) + private async ValueTask PrerenderedWebAssemblyComponentAsync(HttpContext context, Type type, ParameterView parametersCollection) { var marker = WebAssemblyComponentSerializer.SerializeInvocation( type, @@ -197,7 +198,7 @@ private async ValueTask PrerenderedWebAssemblyComponentAsync(HttpC return new PrerenderedComponentHtmlContent(_htmlRenderer.Dispatcher, htmlComponent, null, marker); } - private IHtmlContent NonPrerenderedServerComponent(HttpContext context, ServerComponentInvocationSequence invocationId, Type type, ParameterView parametersCollection) + private IAsyncHtmlContent NonPrerenderedServerComponent(HttpContext context, ServerComponentInvocationSequence invocationId, Type type, ParameterView parametersCollection) { if (!context.Response.HasStarted) { @@ -208,7 +209,7 @@ private IHtmlContent NonPrerenderedServerComponent(HttpContext context, ServerCo return new PrerenderedComponentHtmlContent(null, null, marker, null); } - private static IHtmlContent NonPrerenderedWebAssemblyComponent(Type type, ParameterView parametersCollection) + private static IAsyncHtmlContent NonPrerenderedWebAssemblyComponent(Type type, ParameterView parametersCollection) { var marker = WebAssemblyComponentSerializer.SerializeInvocation(type, parametersCollection, prerendered: false); return new PrerenderedComponentHtmlContent(null, null, null, marker); diff --git a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/IAsyncHtmlContent.cs b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/IAsyncHtmlContent.cs index faaff366f841..52fb500c6179 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/IAsyncHtmlContent.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/IAsyncHtmlContent.cs @@ -1,11 +1,13 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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.Mvc.ViewFeatures; // For prerendered components, we can't use IHtmlComponent directly because it has no asynchrony and // hence can't dispatch to the renderer's sync context. -internal interface IAsyncHtmlContent +internal interface IAsyncHtmlContent : IHtmlContent { ValueTask WriteToAsync(TextWriter writer); } diff --git a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResult.cs b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResult.cs new file mode 100644 index 000000000000..731e1053a064 --- /dev/null +++ b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResult.cs @@ -0,0 +1,65 @@ +// 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.Components; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Mvc.ViewFeatures; + +/// +/// An that renders a Razor Component. +/// +public class RazorComponentResult : ActionResult +{ + /// + /// Constructs an instance of . + /// + /// The type of the component to render. This must implement . + public RazorComponentResult(Type componentType) + { + // Note that the Blazor renderer will validate that componentType implements IComponent and throws a suitable + // exception if not, so we don't need to duplicate that logic here. + + ArgumentNullException.ThrowIfNull(componentType); + ComponentType = componentType; + } + + /// + /// Gets the component type. + /// + public Type ComponentType { get; } + + /// + /// Gets or sets the Content-Type header for the response. + /// + public string ContentType { get; set; } + + /// + /// Gets or sets the HTTP status code. + /// + public int? StatusCode { get; set; } + + /// + /// Gets or sets the parameters for the component. + /// + public ParameterView Parameters { get; set; } = ParameterView.Empty; + + /// + /// Gets or sets the rendering mode. + /// + public RenderMode RenderMode { get; set; } = RenderMode.Static; + + /// + /// Requests the service of + /// + /// to process itself in the given . + /// + /// An associated with the current request for a Razor Component. + /// A which will complete when execution is completed. + public override Task ExecuteResultAsync(ActionContext context) + { + var executor = context.HttpContext.RequestServices.GetRequiredService(); + return executor.ExecuteAsync(context, this); + } +} diff --git a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultExecutor.cs b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultExecutor.cs new file mode 100644 index 000000000000..48875e0f2d0d --- /dev/null +++ b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultExecutor.cs @@ -0,0 +1,94 @@ +// 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; +using System.Text; +using Microsoft.AspNetCore.Internal; +using Microsoft.AspNetCore.Mvc.Formatters; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Mvc.ViewFeatures; + +/// +/// Executes a Razor Component. +/// +public class RazorComponentResultExecutor +{ + /// + /// The default content-type header value for Razor Components, text/html; charset=utf-8. + /// + public static readonly string DefaultContentType = "text/html; charset=utf-8"; + + /// + /// Constructs an instance of . + /// + /// The . + /// The . + public RazorComponentResultExecutor( + IHttpResponseStreamWriterFactory writerFactory, + DiagnosticListener diagnosticListener) + { + ArgumentNullException.ThrowIfNull(writerFactory); + + WriterFactory = writerFactory; + DiagnosticListener = diagnosticListener; + } + + /// + /// Gets the . + /// + protected DiagnosticListener DiagnosticListener { get; } + + /// + /// Gets the . + /// + protected IHttpResponseStreamWriterFactory WriterFactory { get; } + + /// + /// Executes a Razor Component asynchronously. + /// + public virtual async Task ExecuteAsync(ActionContext actionContext, RazorComponentResult result) + { + ArgumentNullException.ThrowIfNull(actionContext); + + var response = actionContext.HttpContext.Response; + + ResponseContentTypeHelper.ResolveContentTypeAndEncoding( + result.ContentType, + response.ContentType, + (DefaultContentType, Encoding.UTF8), + MediaType.GetEncoding, + out var resolvedContentType, + out var resolvedContentTypeEncoding); + + response.ContentType = resolvedContentType; + + if (result.StatusCode != null) + { + response.StatusCode = result.StatusCode.Value; + } + + await using var writer = WriterFactory.CreateWriter(response.Body, resolvedContentTypeEncoding); + + var componentPrerenderer = actionContext.HttpContext.RequestServices.GetRequiredService(); + + DiagnosticListener.BeforeRazorComponent(result.ComponentType, result.RenderMode, actionContext); + await componentPrerenderer.Dispatcher.InvokeAsync(async () => + { + var htmlContent = await componentPrerenderer.PrerenderComponentAsync( + actionContext, + result.ComponentType, + result.RenderMode, + result.Parameters); + await htmlContent.WriteToAsync(writer); + }); + + DiagnosticListener.AfterRazorComponent(result.ComponentType, result.RenderMode, actionContext); + + // Perf: 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 writer.FlushAsync(); + } +} From 92d532544798f3f2ed77725284968332ebb7ed6d Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Mon, 6 Mar 2023 09:56:25 +0000 Subject: [PATCH 02/17] Support layouts and anonymously-typed parameter objects --- .../src/PublicAPI.Unshipped.txt | 4 +- .../RazorComponents/RazorComponentResult.cs | 2 +- .../RazorComponentResultExecutor.cs | 34 +++++--- .../RazorComponentResultHost.cs | 77 +++++++++++++++++++ 4 files changed, 102 insertions(+), 15 deletions(-) create mode 100644 src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultHost.cs diff --git a/src/Mvc/Mvc.ViewFeatures/src/PublicAPI.Unshipped.txt b/src/Mvc/Mvc.ViewFeatures/src/PublicAPI.Unshipped.txt index e9e62f9fda7b..5cf86356f26c 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/PublicAPI.Unshipped.txt +++ b/src/Mvc/Mvc.ViewFeatures/src/PublicAPI.Unshipped.txt @@ -5,8 +5,8 @@ Microsoft.AspNetCore.Mvc.Diagnostics.BeforeRazorComponentEventData Microsoft.AspNetCore.Mvc.Diagnostics.BeforeRazorComponentEventData.RenderMode.get -> Microsoft.AspNetCore.Mvc.Rendering.RenderMode Microsoft.AspNetCore.Mvc.Rendering.FormMethod.Dialog = 2 -> Microsoft.AspNetCore.Mvc.Rendering.FormMethod Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult -Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.Parameters.get -> Microsoft.AspNetCore.Components.ParameterView -Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.Parameters.set -> void +~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.Parameters.get -> object +~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.Parameters.set -> void Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.RenderMode.get -> Microsoft.AspNetCore.Mvc.Rendering.RenderMode Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.RenderMode.set -> void Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.StatusCode.get -> int? diff --git a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResult.cs b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResult.cs index 731e1053a064..d85b0df7b2ed 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResult.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResult.cs @@ -43,7 +43,7 @@ public RazorComponentResult(Type componentType) /// /// Gets or sets the parameters for the component. /// - public ParameterView Parameters { get; set; } = ParameterView.Empty; + public object Parameters { get; set; } /// /// Gets or sets the rendering mode. diff --git a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultExecutor.cs b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultExecutor.cs index 48875e0f2d0d..816550e8e590 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultExecutor.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultExecutor.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Text; +using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.AspNetCore.Mvc.Infrastructure; @@ -70,25 +71,34 @@ public virtual async Task ExecuteAsync(ActionContext actionContext, RazorCompone } await using var writer = WriterFactory.CreateWriter(response.Body, resolvedContentTypeEncoding); + DiagnosticListener.BeforeRazorComponent(result.ComponentType, result.RenderMode, actionContext); + await RenderComponentToResponse(actionContext, result, writer); + DiagnosticListener.AfterRazorComponent(result.ComponentType, result.RenderMode, actionContext); - var componentPrerenderer = actionContext.HttpContext.RequestServices.GetRequiredService(); + // Perf: 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 writer.FlushAsync(); + } - DiagnosticListener.BeforeRazorComponent(result.ComponentType, result.RenderMode, actionContext); - await componentPrerenderer.Dispatcher.InvokeAsync(async () => + private static Task RenderComponentToResponse(ActionContext actionContext, RazorComponentResult result, TextWriter writer) + { + var componentPrerenderer = actionContext.HttpContext.RequestServices.GetRequiredService(); + return componentPrerenderer.Dispatcher.InvokeAsync(async () => { + // We could pool these dictionary instances if we wanted. We could even skip the dictionary + // phase entirely if we added some new ParameterView.FromSingleValue(name, value) API. + var hostParameters = ParameterView.FromDictionary(new Dictionary + { + { nameof(RazorComponentResultHost.RazorComponentResult), result }, + }); + var htmlContent = await componentPrerenderer.PrerenderComponentAsync( actionContext, - result.ComponentType, + typeof(RazorComponentResultHost), result.RenderMode, - result.Parameters); + hostParameters); await htmlContent.WriteToAsync(writer); }); - - DiagnosticListener.AfterRazorComponent(result.ComponentType, result.RenderMode, actionContext); - - // Perf: 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 writer.FlushAsync(); } } diff --git a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultHost.cs b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultHost.cs new file mode 100644 index 000000000000..bcb869d0871a --- /dev/null +++ b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultHost.cs @@ -0,0 +1,77 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Reflection; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Rendering; +using Microsoft.Extensions.Internal; + +namespace Microsoft.AspNetCore.Mvc.ViewFeatures; + +/// +/// Internal component type that acts as a root component when executing a RazorComponentResult. It takes +/// care of rendering the component inside its hierarchy of layouts (via LayoutView) as well as converting +/// any information we want from RazorComponentResult into component parameters. We could also use this to +/// supply other data from the original HttpContext as component parameters, e.g., for model binding. +/// +/// It happens to be almost the same as RouteView except it doesn't supply any query parameters. We can +/// resolve that at the same time we implement support for form posts. +/// +internal class RazorComponentResultHost : IComponent +{ + private RenderHandle _renderHandle; + + public RazorComponentResult RazorComponentResult { get; private set; } + + public void Attach(RenderHandle renderHandle) + => _renderHandle = renderHandle; + + public Task SetParametersAsync(ParameterView parameters) + { + foreach (var kvp in parameters) + { + switch (kvp.Name) + { + case nameof(RazorComponentResult): + RazorComponentResult = (RazorComponentResult)kvp.Value; + break; + default: + throw new ArgumentException($"Unknown parameter '{kvp.Name}'"); + } + } + + if (RazorComponentResult is null) + { + throw new InvalidOperationException($"Failed to supply nonnull value for required parameter '{nameof(RazorComponentResult)}'"); + } + + _renderHandle.Render(BuildRenderTree); + return Task.CompletedTask; + } + + private void BuildRenderTree(RenderTreeBuilder builder) + { + var pageLayoutType = RazorComponentResult.ComponentType.GetCustomAttribute()?.LayoutType; + + builder.OpenComponent(0); + builder.AddComponentParameter(1, nameof(LayoutView.Layout), pageLayoutType); + builder.AddComponentParameter(2, nameof(LayoutView.ChildContent), (RenderFragment)RenderPageWithParameters); + builder.CloseComponent(); + } + + private void RenderPageWithParameters(RenderTreeBuilder builder) + { + builder.OpenComponent(0, RazorComponentResult.ComponentType); + + if (RazorComponentResult.Parameters is not null) + { + var dict = PropertyHelper.ObjectToDictionary(RazorComponentResult.Parameters); + foreach (var kvp in dict) + { + builder.AddComponentParameter(1, kvp.Key, kvp.Value); + } + } + + builder.CloseComponent(); + } +} From d5fa2c0d85213e639683528ec6833f13f811baf6 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Mon, 6 Mar 2023 10:09:57 +0000 Subject: [PATCH 03/17] Update RazorComponentResultHost.cs --- .../src/RazorComponents/RazorComponentResultHost.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultHost.cs b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultHost.cs index bcb869d0871a..76384f868fd4 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultHost.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultHost.cs @@ -61,6 +61,9 @@ private void BuildRenderTree(RenderTreeBuilder builder) private void RenderPageWithParameters(RenderTreeBuilder builder) { + // If we wanted to support rendering pre-instantiated component objects, it would probably go here + // as either a whole new RenderTreeFrame type or as some mechanism to attach the instance onto a + // regular Component frame (perhaps using a dummy ComponentState that represents 'not yet initialized') builder.OpenComponent(0, RazorComponentResult.ComponentType); if (RazorComponentResult.Parameters is not null) From 8770ebc73092cadd7922948b1878ce2126f41d8e Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Tue, 7 Mar 2023 14:57:49 +0000 Subject: [PATCH 04/17] Fix other render modes --- .../RazorComponents/RazorComponentResultExecutor.cs | 6 +++++- .../src/RazorComponents/RazorComponentResultHost.cs | 12 +++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultExecutor.cs b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultExecutor.cs index 816550e8e590..5361598546ba 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultExecutor.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultExecutor.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNetCore.Mvc.ViewFeatures; @@ -93,10 +94,13 @@ private static Task RenderComponentToResponse(ActionContext actionContext, Razor { nameof(RazorComponentResultHost.RazorComponentResult), result }, }); + // Note that we always use Static 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 = await componentPrerenderer.PrerenderComponentAsync( actionContext, typeof(RazorComponentResultHost), - result.RenderMode, + RenderMode.Static, hostParameters); await htmlContent.WriteToAsync(writer); }); diff --git a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultHost.cs b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultHost.cs index 76384f868fd4..415eedc63d3d 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultHost.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultHost.cs @@ -61,9 +61,15 @@ private void BuildRenderTree(RenderTreeBuilder builder) private void RenderPageWithParameters(RenderTreeBuilder builder) { - // If we wanted to support rendering pre-instantiated component objects, it would probably go here - // as either a whole new RenderTreeFrame type or as some mechanism to attach the instance onto a - // regular Component frame (perhaps using a dummy ComponentState that represents 'not yet initialized') + // TODO: Once we support rendering Server/WebAssembly components into the page, implementation will + // go here. We need to switch into the rendermode given by RazorComponentResult.RenderMode for this + // child component. That will cause the developer-supplied parameters to be serialized into a marker + // but not attempt to serialize the RenderFragment that causes this to be hosted in its layout. + + // TODO: If we wanted to support rendering pre-instantiated component objects, it would probably go here + // as either a whole new RenderTreeFrame type or as some mechanism to attach the instance onto a regular + // Component frame (perhaps using a dummy ComponentState that represents 'not yet initialized') + builder.OpenComponent(0, RazorComponentResult.ComponentType); if (RazorComponentResult.Parameters is not null) From 793f88f9774e8268e020b733b1ca638583b40edd Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Tue, 7 Mar 2023 14:58:16 +0000 Subject: [PATCH 05/17] Add Results.Extensions helper --- ...crosoft.AspNetCore.Mvc.ViewFeatures.csproj | 1 + .../src/PublicAPI.Unshipped.txt | 4 ++ .../RazorComponentResultExtensions.cs | 44 +++++++++++++++++++ 3 files changed, 49 insertions(+) create mode 100644 src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultExtensions.cs diff --git a/src/Mvc/Mvc.ViewFeatures/src/Microsoft.AspNetCore.Mvc.ViewFeatures.csproj b/src/Mvc/Mvc.ViewFeatures/src/Microsoft.AspNetCore.Mvc.ViewFeatures.csproj index 3bfae6b23b80..7f52a490e0ae 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/Microsoft.AspNetCore.Mvc.ViewFeatures.csproj +++ b/src/Mvc/Mvc.ViewFeatures/src/Microsoft.AspNetCore.Mvc.ViewFeatures.csproj @@ -25,6 +25,7 @@ + diff --git a/src/Mvc/Mvc.ViewFeatures/src/PublicAPI.Unshipped.txt b/src/Mvc/Mvc.ViewFeatures/src/PublicAPI.Unshipped.txt index 5cf86356f26c..1ed7cc045b67 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/PublicAPI.Unshipped.txt +++ b/src/Mvc/Mvc.ViewFeatures/src/PublicAPI.Unshipped.txt @@ -5,6 +5,7 @@ Microsoft.AspNetCore.Mvc.Diagnostics.BeforeRazorComponentEventData Microsoft.AspNetCore.Mvc.Diagnostics.BeforeRazorComponentEventData.RenderMode.get -> Microsoft.AspNetCore.Mvc.Rendering.RenderMode Microsoft.AspNetCore.Mvc.Rendering.FormMethod.Dialog = 2 -> Microsoft.AspNetCore.Mvc.Rendering.FormMethod Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult +Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExtensions ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.Parameters.get -> object ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.Parameters.set -> void Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.RenderMode.get -> Microsoft.AspNetCore.Mvc.Rendering.RenderMode @@ -29,6 +30,9 @@ Microsoft.Extensions.DependencyInjection.RazorComponentsServiceCollectionExtensi ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.RazorComponentResultExecutor(Microsoft.AspNetCore.Mvc.Infrastructure.IHttpResponseStreamWriterFactory writerFactory, System.Diagnostics.DiagnosticListener diagnosticListener) -> void ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.WriterFactory.get -> Microsoft.AspNetCore.Mvc.Infrastructure.IHttpResponseStreamWriterFactory ~override Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.ExecuteResultAsync(Microsoft.AspNetCore.Mvc.ActionContext context) -> System.Threading.Tasks.Task +~static Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExtensions.RazorComponent(this Microsoft.AspNetCore.Http.IResultExtensions resultExtensions) -> Microsoft.AspNetCore.Mvc.IActionResult +~static Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExtensions.RazorComponent(this Microsoft.AspNetCore.Http.IResultExtensions resultExtensions, Microsoft.AspNetCore.Mvc.Rendering.RenderMode renderMode, object componentParameters) -> Microsoft.AspNetCore.Mvc.IActionResult +~static Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExtensions.RazorComponent(this Microsoft.AspNetCore.Http.IResultExtensions resultExtensions, object componentParameters) -> Microsoft.AspNetCore.Mvc.IActionResult ~static Microsoft.Extensions.DependencyInjection.RazorComponentsServiceCollectionExtensions.AddRazorComponents(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) -> void ~static readonly Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.DefaultContentType -> string ~virtual Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.ExecuteAsync(Microsoft.AspNetCore.Mvc.ActionContext actionContext, Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult result) -> System.Threading.Tasks.Task \ No newline at end of file diff --git a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultExtensions.cs b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultExtensions.cs new file mode 100644 index 000000000000..966baa46752f --- /dev/null +++ b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultExtensions.cs @@ -0,0 +1,44 @@ +// 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.Components; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Rendering; + +namespace Microsoft.AspNetCore.Mvc.ViewFeatures; + +/// +/// Holds extension methods on . +/// +public static class RazorComponentResultExtensions +{ + /// + /// Returns an that renders a Razor Component. + /// + /// The type of component to render. + /// The . + /// An that renders a Razor Component. + public static IActionResult RazorComponent(this IResultExtensions resultExtensions) where TComponent : IComponent + => new RazorComponentResult(typeof(TComponent)); + + /// + /// Returns an that renders a Razor Component. + /// + /// The type of component to render. + /// The . + /// Parameters for the component. + /// An that renders a Razor Component. + public static IActionResult RazorComponent(this IResultExtensions resultExtensions, object componentParameters) where TComponent : IComponent + => new RazorComponentResult(typeof(TComponent)) { Parameters = componentParameters }; + + /// + /// Returns an that renders a Razor Component. + /// + /// The type of component to render. + /// The . + /// Parameters for the component. + /// A value that specifies how to render the component. + /// An that renders a Razor Component. + public static IActionResult RazorComponent(this IResultExtensions resultExtensions, RenderMode renderMode, object componentParameters) where TComponent: IComponent + => new RazorComponentResult(typeof(TComponent)) { RenderMode = renderMode, Parameters = componentParameters }; +} From 5cb2bda55880407c30ae55b1135f11c2b25baad8 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Tue, 7 Mar 2023 14:58:19 +0000 Subject: [PATCH 06/17] Revert "Add Results.Extensions helper" This reverts commit 793f88f9774e8268e020b733b1ca638583b40edd. --- ...crosoft.AspNetCore.Mvc.ViewFeatures.csproj | 1 - .../src/PublicAPI.Unshipped.txt | 4 -- .../RazorComponentResultExtensions.cs | 44 ------------------- 3 files changed, 49 deletions(-) delete mode 100644 src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultExtensions.cs diff --git a/src/Mvc/Mvc.ViewFeatures/src/Microsoft.AspNetCore.Mvc.ViewFeatures.csproj b/src/Mvc/Mvc.ViewFeatures/src/Microsoft.AspNetCore.Mvc.ViewFeatures.csproj index 7f52a490e0ae..3bfae6b23b80 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/Microsoft.AspNetCore.Mvc.ViewFeatures.csproj +++ b/src/Mvc/Mvc.ViewFeatures/src/Microsoft.AspNetCore.Mvc.ViewFeatures.csproj @@ -25,7 +25,6 @@ - diff --git a/src/Mvc/Mvc.ViewFeatures/src/PublicAPI.Unshipped.txt b/src/Mvc/Mvc.ViewFeatures/src/PublicAPI.Unshipped.txt index 1ed7cc045b67..5cf86356f26c 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/PublicAPI.Unshipped.txt +++ b/src/Mvc/Mvc.ViewFeatures/src/PublicAPI.Unshipped.txt @@ -5,7 +5,6 @@ Microsoft.AspNetCore.Mvc.Diagnostics.BeforeRazorComponentEventData Microsoft.AspNetCore.Mvc.Diagnostics.BeforeRazorComponentEventData.RenderMode.get -> Microsoft.AspNetCore.Mvc.Rendering.RenderMode Microsoft.AspNetCore.Mvc.Rendering.FormMethod.Dialog = 2 -> Microsoft.AspNetCore.Mvc.Rendering.FormMethod Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult -Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExtensions ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.Parameters.get -> object ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.Parameters.set -> void Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.RenderMode.get -> Microsoft.AspNetCore.Mvc.Rendering.RenderMode @@ -30,9 +29,6 @@ Microsoft.Extensions.DependencyInjection.RazorComponentsServiceCollectionExtensi ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.RazorComponentResultExecutor(Microsoft.AspNetCore.Mvc.Infrastructure.IHttpResponseStreamWriterFactory writerFactory, System.Diagnostics.DiagnosticListener diagnosticListener) -> void ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.WriterFactory.get -> Microsoft.AspNetCore.Mvc.Infrastructure.IHttpResponseStreamWriterFactory ~override Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.ExecuteResultAsync(Microsoft.AspNetCore.Mvc.ActionContext context) -> System.Threading.Tasks.Task -~static Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExtensions.RazorComponent(this Microsoft.AspNetCore.Http.IResultExtensions resultExtensions) -> Microsoft.AspNetCore.Mvc.IActionResult -~static Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExtensions.RazorComponent(this Microsoft.AspNetCore.Http.IResultExtensions resultExtensions, Microsoft.AspNetCore.Mvc.Rendering.RenderMode renderMode, object componentParameters) -> Microsoft.AspNetCore.Mvc.IActionResult -~static Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExtensions.RazorComponent(this Microsoft.AspNetCore.Http.IResultExtensions resultExtensions, object componentParameters) -> Microsoft.AspNetCore.Mvc.IActionResult ~static Microsoft.Extensions.DependencyInjection.RazorComponentsServiceCollectionExtensions.AddRazorComponents(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) -> void ~static readonly Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.DefaultContentType -> string ~virtual Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.ExecuteAsync(Microsoft.AspNetCore.Mvc.ActionContext actionContext, Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult result) -> System.Threading.Tasks.Task \ No newline at end of file diff --git a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultExtensions.cs b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultExtensions.cs deleted file mode 100644 index 966baa46752f..000000000000 --- a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultExtensions.cs +++ /dev/null @@ -1,44 +0,0 @@ -// 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.Components; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc.Rendering; - -namespace Microsoft.AspNetCore.Mvc.ViewFeatures; - -/// -/// Holds extension methods on . -/// -public static class RazorComponentResultExtensions -{ - /// - /// Returns an that renders a Razor Component. - /// - /// The type of component to render. - /// The . - /// An that renders a Razor Component. - public static IActionResult RazorComponent(this IResultExtensions resultExtensions) where TComponent : IComponent - => new RazorComponentResult(typeof(TComponent)); - - /// - /// Returns an that renders a Razor Component. - /// - /// The type of component to render. - /// The . - /// Parameters for the component. - /// An that renders a Razor Component. - public static IActionResult RazorComponent(this IResultExtensions resultExtensions, object componentParameters) where TComponent : IComponent - => new RazorComponentResult(typeof(TComponent)) { Parameters = componentParameters }; - - /// - /// Returns an that renders a Razor Component. - /// - /// The type of component to render. - /// The . - /// Parameters for the component. - /// A value that specifies how to render the component. - /// An that renders a Razor Component. - public static IActionResult RazorComponent(this IResultExtensions resultExtensions, RenderMode renderMode, object componentParameters) where TComponent: IComponent - => new RazorComponentResult(typeof(TComponent)) { RenderMode = renderMode, Parameters = componentParameters }; -} From 2c0452b31a7aa7b9918cc9a828944f79d11ec21d Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Tue, 7 Mar 2023 15:07:50 +0000 Subject: [PATCH 07/17] Fix unrelated build issue from different work item --- src/Mvc/Mvc.ViewFeatures/src/PublicAPI.Unshipped.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Mvc/Mvc.ViewFeatures/src/PublicAPI.Unshipped.txt b/src/Mvc/Mvc.ViewFeatures/src/PublicAPI.Unshipped.txt index 5cf86356f26c..dc2a71de148a 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/PublicAPI.Unshipped.txt +++ b/src/Mvc/Mvc.ViewFeatures/src/PublicAPI.Unshipped.txt @@ -12,7 +12,6 @@ Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.RenderMode.set -> voi Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.StatusCode.get -> int? Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.StatusCode.set -> void Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor -Microsoft.Extensions.DependencyInjection.RazorComponentsServiceCollectionExtensions ~const Microsoft.AspNetCore.Mvc.Diagnostics.AfterRazorComponentEventData.EventName = "Microsoft.AspNetCore.Mvc.AfterRazorComponent" -> string ~const Microsoft.AspNetCore.Mvc.Diagnostics.BeforeRazorComponentEventData.EventName = "Microsoft.AspNetCore.Mvc.BeforeRazorComponent" -> string ~Microsoft.AspNetCore.Mvc.Diagnostics.AfterRazorComponentEventData.ActionContext.get -> Microsoft.AspNetCore.Mvc.ActionContext @@ -29,6 +28,5 @@ Microsoft.Extensions.DependencyInjection.RazorComponentsServiceCollectionExtensi ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.RazorComponentResultExecutor(Microsoft.AspNetCore.Mvc.Infrastructure.IHttpResponseStreamWriterFactory writerFactory, System.Diagnostics.DiagnosticListener diagnosticListener) -> void ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.WriterFactory.get -> Microsoft.AspNetCore.Mvc.Infrastructure.IHttpResponseStreamWriterFactory ~override Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.ExecuteResultAsync(Microsoft.AspNetCore.Mvc.ActionContext context) -> System.Threading.Tasks.Task -~static Microsoft.Extensions.DependencyInjection.RazorComponentsServiceCollectionExtensions.AddRazorComponents(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) -> void ~static readonly Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.DefaultContentType -> string ~virtual Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.ExecuteAsync(Microsoft.AspNetCore.Mvc.ActionContext actionContext, Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult result) -> System.Threading.Tasks.Task \ No newline at end of file From 57638d2537c21ff81a7f5d13d9b27792f6c15713 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Tue, 7 Mar 2023 15:08:18 +0000 Subject: [PATCH 08/17] Add RazorComponentResult --- .../src/Diagnostics/MvcDiagnostics.cs | 1 - .../src/PublicAPI.Unshipped.txt | 4 +++- .../RazorComponentResultOfT.cs | 19 +++++++++++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultOfT.cs diff --git a/src/Mvc/Mvc.ViewFeatures/src/Diagnostics/MvcDiagnostics.cs b/src/Mvc/Mvc.ViewFeatures/src/Diagnostics/MvcDiagnostics.cs index 040fd5ea3838..3e834ad9f5b9 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/Diagnostics/MvcDiagnostics.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/Diagnostics/MvcDiagnostics.cs @@ -1,7 +1,6 @@ // 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.Components; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.ViewComponents; diff --git a/src/Mvc/Mvc.ViewFeatures/src/PublicAPI.Unshipped.txt b/src/Mvc/Mvc.ViewFeatures/src/PublicAPI.Unshipped.txt index dc2a71de148a..b78252eaf52b 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/PublicAPI.Unshipped.txt +++ b/src/Mvc/Mvc.ViewFeatures/src/PublicAPI.Unshipped.txt @@ -5,6 +5,7 @@ Microsoft.AspNetCore.Mvc.Diagnostics.BeforeRazorComponentEventData Microsoft.AspNetCore.Mvc.Diagnostics.BeforeRazorComponentEventData.RenderMode.get -> Microsoft.AspNetCore.Mvc.Rendering.RenderMode Microsoft.AspNetCore.Mvc.Rendering.FormMethod.Dialog = 2 -> Microsoft.AspNetCore.Mvc.Rendering.FormMethod Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult +Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.RazorComponentResult() -> void ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.Parameters.get -> object ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.Parameters.set -> void Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.RenderMode.get -> Microsoft.AspNetCore.Mvc.Rendering.RenderMode @@ -24,9 +25,10 @@ Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.ContentType.get -> string ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.ContentType.set -> void ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.RazorComponentResult(System.Type componentType) -> void +~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.DiagnosticListener.get -> System.Diagnostics.DiagnosticListener ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.RazorComponentResultExecutor(Microsoft.AspNetCore.Mvc.Infrastructure.IHttpResponseStreamWriterFactory writerFactory, System.Diagnostics.DiagnosticListener diagnosticListener) -> void ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.WriterFactory.get -> Microsoft.AspNetCore.Mvc.Infrastructure.IHttpResponseStreamWriterFactory ~override Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.ExecuteResultAsync(Microsoft.AspNetCore.Mvc.ActionContext context) -> System.Threading.Tasks.Task ~static readonly Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.DefaultContentType -> string -~virtual Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.ExecuteAsync(Microsoft.AspNetCore.Mvc.ActionContext actionContext, Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult result) -> System.Threading.Tasks.Task \ No newline at end of file +~virtual Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.ExecuteAsync(Microsoft.AspNetCore.Mvc.ActionContext actionContext, Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult result) -> System.Threading.Tasks.Task diff --git a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultOfT.cs b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultOfT.cs new file mode 100644 index 000000000000..7ad55e338946 --- /dev/null +++ b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultOfT.cs @@ -0,0 +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.Components; + +namespace Microsoft.AspNetCore.Mvc.ViewFeatures; + +/// +/// An that renders a Razor Component. +/// +public class RazorComponentResult : RazorComponentResult where TComponent: IComponent +{ + /// + /// Constructs an instance of . + /// + public RazorComponentResult() : base(typeof(TComponent)) + { + } +} From 71e6b82e662de1ffad8836e5a3016cf6fc175843 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Tue, 7 Mar 2023 15:58:29 +0000 Subject: [PATCH 09/17] Make clear which render mode is supported currently --- .../src/RazorComponents/RazorComponentResultHost.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultHost.cs b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultHost.cs index 415eedc63d3d..b95c7afabfbd 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultHost.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultHost.cs @@ -4,6 +4,7 @@ using System.Reflection; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Rendering; +using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.Extensions.Internal; namespace Microsoft.AspNetCore.Mvc.ViewFeatures; @@ -65,6 +66,11 @@ private void RenderPageWithParameters(RenderTreeBuilder builder) // go here. We need to switch into the rendermode given by RazorComponentResult.RenderMode for this // child component. That will cause the developer-supplied parameters to be serialized into a marker // but not attempt to serialize the RenderFragment that causes this to be hosted in its layout. + if (RazorComponentResult.RenderMode != Rendering.RenderMode.Static) + { + // Tracked by #46353 and #46354 + throw new NotSupportedException($"Currently, {nameof(RazorComponentResult)} only supports the {RenderMode.Static} render mode."); + } // TODO: If we wanted to support rendering pre-instantiated component objects, it would probably go here // as either a whole new RenderTreeFrame type or as some mechanism to attach the instance onto a regular From 7b3d4b0f6e0f166483d91fc0c36db6e279d3605a Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Tue, 7 Mar 2023 16:46:29 +0000 Subject: [PATCH 10/17] Add unit tests --- .../RazorComponentResultHost.cs | 2 +- .../RazorComponentResultTest.cs | 149 ++++++++++++++++++ 2 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 src/Mvc/Mvc.ViewFeatures/test/RazorComponents/RazorComponentResultTest.cs diff --git a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultHost.cs b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultHost.cs index b95c7afabfbd..d26f88beccd0 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultHost.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultHost.cs @@ -66,7 +66,7 @@ private void RenderPageWithParameters(RenderTreeBuilder builder) // go here. We need to switch into the rendermode given by RazorComponentResult.RenderMode for this // child component. That will cause the developer-supplied parameters to be serialized into a marker // but not attempt to serialize the RenderFragment that causes this to be hosted in its layout. - if (RazorComponentResult.RenderMode != Rendering.RenderMode.Static) + if (RazorComponentResult.RenderMode != RenderMode.Static) { // Tracked by #46353 and #46354 throw new NotSupportedException($"Currently, {nameof(RazorComponentResult)} only supports the {RenderMode.Static} render mode."); diff --git a/src/Mvc/Mvc.ViewFeatures/test/RazorComponents/RazorComponentResultTest.cs b/src/Mvc/Mvc.ViewFeatures/test/RazorComponents/RazorComponentResultTest.cs new file mode 100644 index 000000000000..ed62312ec35f --- /dev/null +++ b/src/Mvc/Mvc.ViewFeatures/test/RazorComponents/RazorComponentResultTest.cs @@ -0,0 +1,149 @@ +// 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; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Infrastructure; +using Microsoft.AspNetCore.Components.Rendering; +using Microsoft.AspNetCore.Components.Routing; +using Microsoft.AspNetCore.Components.Web; +using Microsoft.AspNetCore.DataProtection; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Mvc.ViewFeatures; + +public class RazorComponentResultTest +{ + [Fact] + public async Task CanRenderComponentStatically() + { + // Arrange + var result = new RazorComponentResult(); + var httpContext = GetTestHttpContext(); + var responseBody = new MemoryStream(); + httpContext.Response.Body = responseBody; + var actionContext = new ActionContext(httpContext, new AspNetCore.Routing.RouteData(), new Abstractions.ActionDescriptor()); + + // Act + await result.ExecuteResultAsync(actionContext); + + // Assert + responseBody.Position = 0; + var responseHtml = new StreamReader(responseBody).ReadToEnd(); + Assert.Equal("

Hello from SimpleComponent

", responseHtml); + Assert.Equal("text/html; charset=utf-8", httpContext.Response.ContentType); + Assert.Equal(200, httpContext.Response.StatusCode); + } + + [Fact] + public async Task CanSetStatusCodeAndContentType() + { + // Arrange + var result = new RazorComponentResult + { + StatusCode = 123, + ContentType = "application/test-content-type", + }; + var httpContext = GetTestHttpContext(); + var responseBody = new MemoryStream(); + httpContext.Response.Body = responseBody; + var actionContext = new ActionContext(httpContext, new AspNetCore.Routing.RouteData(), new Abstractions.ActionDescriptor()); + + // Act + await result.ExecuteResultAsync(actionContext); + + // Assert + responseBody.Position = 0; + var responseHtml = new StreamReader(responseBody).ReadToEnd(); + Assert.Equal("

Hello from SimpleComponent

", responseHtml); + Assert.Equal("application/test-content-type", httpContext.Response.ContentType); + Assert.Equal(123, httpContext.Response.StatusCode); + } + + [Fact] + public async Task WaitsForQuiescence() + { + // Arrange + var tcs = new TaskCompletionSource(); + var result = new RazorComponentResult + { + Parameters = new { LoadingTask = tcs.Task } + }; + var httpContext = GetTestHttpContext(); + var responseBody = new MemoryStream(); + httpContext.Response.Body = responseBody; + var actionContext = new ActionContext(httpContext, new AspNetCore.Routing.RouteData(), new Abstractions.ActionDescriptor()); + + // Act/Assert: Doesn't complete until loading finishes + var completionTask = result.ExecuteResultAsync(actionContext); + await Task.Yield(); + Assert.False(completionTask.IsCompleted); + + // Act/Assert: Does complete when loading finishes + tcs.SetResult(); + await completionTask; + responseBody.Position = 0; + var responseHtml = new StreamReader(responseBody).ReadToEnd(); + Assert.Equal("Loading task status: RanToCompletion", responseHtml); + } + + private static DefaultHttpContext GetTestHttpContext() + { + var serviceCollection = new ServiceCollection() + .AddSingleton(new DiagnosticListener("test")) + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddLogging(); + return new DefaultHttpContext { RequestServices = serviceCollection.BuildServiceProvider() }; + } + + class SimpleComponent : ComponentBase + { + protected override void BuildRenderTree(RenderTreeBuilder builder) + { + builder.OpenElement(0, "h1"); + builder.AddContent(1, "Hello from SimpleComponent"); + builder.CloseElement(); + } + } + + class AsyncLoadingComponent : ComponentBase + { + [Parameter] public Task LoadingTask { get; set; } + + protected override async Task OnInitializedAsync() + { + await LoadingTask; + await Task.Delay(100); // Doesn't strictly make any difference to the test, but clarifies that arbitrary async stuff could happen here + } + + protected override void BuildRenderTree(RenderTreeBuilder builder) + => builder.AddContent(0, $"Loading task status: {LoadingTask.Status}"); + } + + class FakeDataProtectionProvider : IDataProtectionProvider + { + public IDataProtector CreateProtector(string purpose) + => new FakeDataProtector(); + + class FakeDataProtector : IDataProtector + { + public IDataProtector CreateProtector(string purpose) => throw new NotImplementedException(); + public byte[] Protect(byte[] plaintext) => throw new NotImplementedException(); + public byte[] Unprotect(byte[] protectedData) => throw new NotImplementedException(); + } + } + + class FakeNavigationManager : NavigationManager, IHostEnvironmentNavigationManager + { + public new void Initialize(string baseUri, string uri) { } + } +} From cc733e63e45cdaf23ed94fa2558d887c26144a3a Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Tue, 7 Mar 2023 17:07:17 +0000 Subject: [PATCH 11/17] Make it an IResult rather than IActionResult --- .../Mvc.TagHelpers/src/ComponentTagHelper.cs | 2 +- .../src/Diagnostics/MvcDiagnostics.cs | 21 +-- ...iewFeaturesDiagnosticListenerExtensions.cs | 19 +-- .../src/PublicAPI.Unshipped.txt | 12 +- .../RazorComponents/ComponentPrerenderer.cs | 23 ++-- .../RazorComponents/RazorComponentResult.cs | 17 +-- .../RazorComponentResultExecutor.cs | 19 +-- .../HtmlHelperComponentExtensions.cs | 6 +- .../ComponentPrerendererTest.cs | 123 +++++++++--------- .../RazorComponentResultTest.cs | 9 +- 10 files changed, 125 insertions(+), 126 deletions(-) diff --git a/src/Mvc/Mvc.TagHelpers/src/ComponentTagHelper.cs b/src/Mvc/Mvc.TagHelpers/src/ComponentTagHelper.cs index 448c6d2c9875..b72d354f08c4 100644 --- a/src/Mvc/Mvc.TagHelpers/src/ComponentTagHelper.cs +++ b/src/Mvc/Mvc.TagHelpers/src/ComponentTagHelper.cs @@ -94,7 +94,7 @@ public override async Task ProcessAsync(TagHelperContext context, TagHelperOutpu var requestServices = ViewContext.HttpContext.RequestServices; var componentPrerenderer = requestServices.GetRequiredService(); var parameters = _parameters is null || _parameters.Count == 0 ? ParameterView.Empty : ParameterView.FromDictionary(_parameters); - var result = await componentPrerenderer.PrerenderComponentAsync(ViewContext, ComponentType, RenderMode, parameters); + var result = await componentPrerenderer.PrerenderComponentAsync(ViewContext.HttpContext, ComponentType, RenderMode, parameters); // Reset the TagName. We don't want `component` to render. output.TagName = null; diff --git a/src/Mvc/Mvc.ViewFeatures/src/Diagnostics/MvcDiagnostics.cs b/src/Mvc/Mvc.ViewFeatures/src/Diagnostics/MvcDiagnostics.cs index 3e834ad9f5b9..47bffbf20d19 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/Diagnostics/MvcDiagnostics.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/Diagnostics/MvcDiagnostics.cs @@ -1,6 +1,7 @@ // 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.Http; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.ViewComponents; @@ -454,12 +455,12 @@ public sealed class BeforeRazorComponentEventData : EventData ///
/// The component type. /// The component render mode. - /// The . - public BeforeRazorComponentEventData(Type componentType, RenderMode renderMode, ActionContext actionContext) + /// The . + public BeforeRazorComponentEventData(Type componentType, RenderMode renderMode, HttpContext httpContext) { ComponentType = componentType; RenderMode = renderMode; - ActionContext = actionContext; + HttpContext = httpContext; } /// @@ -473,9 +474,9 @@ public BeforeRazorComponentEventData(Type componentType, RenderMode renderMode, public RenderMode RenderMode { get; } /// - /// The . + /// The . /// - public ActionContext ActionContext { get; } + public HttpContext HttpContext { get; } /// protected override int Count => 2; @@ -504,12 +505,12 @@ public sealed class AfterRazorComponentEventData : EventData /// /// The component type. /// The component render mode. - /// The . - public AfterRazorComponentEventData(Type componentType, RenderMode renderMode, ActionContext actionContext) + /// The . + public AfterRazorComponentEventData(Type componentType, RenderMode renderMode, HttpContext httpContext) { ComponentType = componentType; RenderMode = renderMode; - ActionContext = actionContext; + HttpContext = httpContext; } /// @@ -523,9 +524,9 @@ public AfterRazorComponentEventData(Type componentType, RenderMode renderMode, A public RenderMode RenderMode { get; } /// - /// The . + /// The . /// - public ActionContext ActionContext { get; } + public HttpContext HttpContext { get; } /// protected override int Count => 2; diff --git a/src/Mvc/Mvc.ViewFeatures/src/MvcViewFeaturesDiagnosticListenerExtensions.cs b/src/Mvc/Mvc.ViewFeatures/src/MvcViewFeaturesDiagnosticListenerExtensions.cs index 7a1adaab7da7..2c704e6095a7 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/MvcViewFeaturesDiagnosticListenerExtensions.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/MvcViewFeaturesDiagnosticListenerExtensions.cs @@ -1,7 +1,8 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Diagnostics; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.ViewComponents; @@ -121,22 +122,22 @@ public static void BeforeRazorComponent( this DiagnosticListener diagnosticListener, Type componentType, RenderMode renderMode, - ActionContext actionContext) + HttpContext httpContext) { // Inlinable fast-path check if Diagnositcs is enabled if (diagnosticListener.IsEnabled()) { - BeforeRazorComponentImpl(diagnosticListener, componentType, renderMode, actionContext); + BeforeRazorComponentImpl(diagnosticListener, componentType, renderMode, httpContext); } } - private static void BeforeRazorComponentImpl(DiagnosticListener diagnosticListener, Type componentType, RenderMode renderMode, ActionContext actionContext) + private static void BeforeRazorComponentImpl(DiagnosticListener diagnosticListener, Type componentType, RenderMode renderMode, HttpContext httpContext) { if (diagnosticListener.IsEnabled(Diagnostics.BeforeViewEventData.EventName)) { diagnosticListener.Write( Diagnostics.BeforeRazorComponentEventData.EventName, - new BeforeRazorComponentEventData(componentType, renderMode, actionContext)); + new BeforeRazorComponentEventData(componentType, renderMode, httpContext)); } } @@ -144,22 +145,22 @@ public static void AfterRazorComponent( this DiagnosticListener diagnosticListener, Type componentType, RenderMode renderMode, - ActionContext actionContext) + HttpContext httpContext) { // Inlinable fast-path check if Diagnositcs is enabled if (diagnosticListener.IsEnabled()) { - AfterRazorComponentImpl(diagnosticListener, componentType, renderMode, actionContext); + AfterRazorComponentImpl(diagnosticListener, componentType, renderMode, httpContext); } } - private static void AfterRazorComponentImpl(DiagnosticListener diagnosticListener, Type componentType, RenderMode renderMode, ActionContext actionContext) + private static void AfterRazorComponentImpl(DiagnosticListener diagnosticListener, Type componentType, RenderMode renderMode, HttpContext httpContext) { if (diagnosticListener.IsEnabled(Diagnostics.AfterViewEventData.EventName)) { diagnosticListener.Write( Diagnostics.AfterRazorComponentEventData.EventName, - new AfterRazorComponentEventData(componentType, renderMode, actionContext)); + new AfterRazorComponentEventData(componentType, renderMode, httpContext)); } } diff --git a/src/Mvc/Mvc.ViewFeatures/src/PublicAPI.Unshipped.txt b/src/Mvc/Mvc.ViewFeatures/src/PublicAPI.Unshipped.txt index b78252eaf52b..d1ccbb2417de 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/PublicAPI.Unshipped.txt +++ b/src/Mvc/Mvc.ViewFeatures/src/PublicAPI.Unshipped.txt @@ -6,6 +6,11 @@ Microsoft.AspNetCore.Mvc.Diagnostics.BeforeRazorComponentEventData.RenderMode.ge Microsoft.AspNetCore.Mvc.Rendering.FormMethod.Dialog = 2 -> Microsoft.AspNetCore.Mvc.Rendering.FormMethod Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.RazorComponentResult() -> void +~Microsoft.AspNetCore.Mvc.Diagnostics.AfterRazorComponentEventData.AfterRazorComponentEventData(System.Type componentType, Microsoft.AspNetCore.Mvc.Rendering.RenderMode renderMode, Microsoft.AspNetCore.Http.HttpContext httpContext) -> void +~Microsoft.AspNetCore.Mvc.Diagnostics.AfterRazorComponentEventData.HttpContext.get -> Microsoft.AspNetCore.Http.HttpContext +~Microsoft.AspNetCore.Mvc.Diagnostics.BeforeRazorComponentEventData.BeforeRazorComponentEventData(System.Type componentType, Microsoft.AspNetCore.Mvc.Rendering.RenderMode renderMode, Microsoft.AspNetCore.Http.HttpContext httpContext) -> void +~Microsoft.AspNetCore.Mvc.Diagnostics.BeforeRazorComponentEventData.HttpContext.get -> Microsoft.AspNetCore.Http.HttpContext +~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext httpContext) -> System.Threading.Tasks.Task ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.Parameters.get -> object ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.Parameters.set -> void Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.RenderMode.get -> Microsoft.AspNetCore.Mvc.Rendering.RenderMode @@ -15,11 +20,7 @@ Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.StatusCode.set -> voi Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor ~const Microsoft.AspNetCore.Mvc.Diagnostics.AfterRazorComponentEventData.EventName = "Microsoft.AspNetCore.Mvc.AfterRazorComponent" -> string ~const Microsoft.AspNetCore.Mvc.Diagnostics.BeforeRazorComponentEventData.EventName = "Microsoft.AspNetCore.Mvc.BeforeRazorComponent" -> string -~Microsoft.AspNetCore.Mvc.Diagnostics.AfterRazorComponentEventData.ActionContext.get -> Microsoft.AspNetCore.Mvc.ActionContext -~Microsoft.AspNetCore.Mvc.Diagnostics.AfterRazorComponentEventData.AfterRazorComponentEventData(System.Type componentType, Microsoft.AspNetCore.Mvc.Rendering.RenderMode renderMode, Microsoft.AspNetCore.Mvc.ActionContext actionContext) -> void ~Microsoft.AspNetCore.Mvc.Diagnostics.AfterRazorComponentEventData.ComponentType.get -> System.Type -~Microsoft.AspNetCore.Mvc.Diagnostics.BeforeRazorComponentEventData.ActionContext.get -> Microsoft.AspNetCore.Mvc.ActionContext -~Microsoft.AspNetCore.Mvc.Diagnostics.BeforeRazorComponentEventData.BeforeRazorComponentEventData(System.Type componentType, Microsoft.AspNetCore.Mvc.Rendering.RenderMode renderMode, Microsoft.AspNetCore.Mvc.ActionContext actionContext) -> void ~Microsoft.AspNetCore.Mvc.Diagnostics.BeforeRazorComponentEventData.ComponentType.get -> System.Type ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.ComponentType.get -> System.Type ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.ContentType.get -> string @@ -29,6 +30,5 @@ Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.DiagnosticListener.get -> System.Diagnostics.DiagnosticListener ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.RazorComponentResultExecutor(Microsoft.AspNetCore.Mvc.Infrastructure.IHttpResponseStreamWriterFactory writerFactory, System.Diagnostics.DiagnosticListener diagnosticListener) -> void ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.WriterFactory.get -> Microsoft.AspNetCore.Mvc.Infrastructure.IHttpResponseStreamWriterFactory -~override Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.ExecuteResultAsync(Microsoft.AspNetCore.Mvc.ActionContext context) -> System.Threading.Tasks.Task ~static readonly Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.DefaultContentType -> string -~virtual Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.ExecuteAsync(Microsoft.AspNetCore.Mvc.ActionContext actionContext, Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult result) -> System.Threading.Tasks.Task +~virtual Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext httpContext, Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult result) -> System.Threading.Tasks.Task diff --git a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/ComponentPrerenderer.cs b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/ComponentPrerenderer.cs index ee0dd77559c8..1c0e23c3c70d 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/ComponentPrerenderer.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/ComponentPrerenderer.cs @@ -39,12 +39,12 @@ public ComponentPrerenderer( public Dispatcher Dispatcher => _htmlRenderer.Dispatcher; public async ValueTask PrerenderComponentAsync( - ActionContext actionContext, + HttpContext httpContext, Type componentType, RenderMode prerenderMode, ParameterView parameters) { - ArgumentNullException.ThrowIfNull(actionContext); + ArgumentNullException.ThrowIfNull(httpContext); ArgumentNullException.ThrowIfNull(componentType); if (!typeof(IComponent).IsAssignableFrom(componentType)) @@ -53,19 +53,18 @@ public async ValueTask PrerenderComponentAsync( } // Make sure we only initialize the services once, but on every call we wait for that process to complete - var httpContext = actionContext.HttpContext; lock (_servicesInitializedLock) { _servicesInitializedTask ??= InitializeStandardComponentServicesAsync(httpContext); } await _servicesInitializedTask; - UpdateSaveStateRenderMode(actionContext, prerenderMode); + UpdateSaveStateRenderMode(httpContext, prerenderMode); return prerenderMode switch { - RenderMode.Server => NonPrerenderedServerComponent(httpContext, GetOrCreateInvocationId(actionContext), componentType, parameters), - RenderMode.ServerPrerendered => await PrerenderedServerComponentAsync(httpContext, GetOrCreateInvocationId(actionContext), componentType, parameters), + RenderMode.Server => NonPrerenderedServerComponent(httpContext, GetOrCreateInvocationId(httpContext), componentType, parameters), + RenderMode.ServerPrerendered => await PrerenderedServerComponentAsync(httpContext, GetOrCreateInvocationId(httpContext), componentType, parameters), RenderMode.Static => await StaticComponentAsync(httpContext, componentType, parameters), RenderMode.WebAssembly => NonPrerenderedWebAssemblyComponent(componentType, parameters), RenderMode.WebAssemblyPrerendered => await PrerenderedWebAssemblyComponentAsync(httpContext, componentType, parameters), @@ -101,30 +100,30 @@ private async ValueTask PrerenderComponentCoreAsync( } } - private static ServerComponentInvocationSequence GetOrCreateInvocationId(ActionContext actionContext) + private static ServerComponentInvocationSequence GetOrCreateInvocationId(HttpContext httpContext) { - if (!actionContext.HttpContext.Items.TryGetValue(ComponentSequenceKey, out var result)) + if (!httpContext.Items.TryGetValue(ComponentSequenceKey, out var result)) { result = new ServerComponentInvocationSequence(); - actionContext.HttpContext.Items[ComponentSequenceKey] = result; + httpContext.Items[ComponentSequenceKey] = result; } return (ServerComponentInvocationSequence)result; } // Internal for test only - internal static void UpdateSaveStateRenderMode(ActionContext actionContext, RenderMode mode) + internal static void UpdateSaveStateRenderMode(HttpContext httpContext, RenderMode mode) { // TODO: This will all have to change when we support multiple render modes in the same response if (mode == RenderMode.ServerPrerendered || mode == RenderMode.WebAssemblyPrerendered) { - if (!actionContext.HttpContext.Items.TryGetValue(InvokedRenderModesKey, out var result)) + if (!httpContext.Items.TryGetValue(InvokedRenderModesKey, out var result)) { result = new InvokedRenderModes(mode is RenderMode.ServerPrerendered ? InvokedRenderModes.Mode.Server : InvokedRenderModes.Mode.WebAssembly); - actionContext.HttpContext.Items[InvokedRenderModesKey] = result; + httpContext.Items[InvokedRenderModesKey] = result; } else { diff --git a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResult.cs b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResult.cs index d85b0df7b2ed..e1e120d33741 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResult.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResult.cs @@ -2,15 +2,16 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNetCore.Mvc.ViewFeatures; /// -/// An that renders a Razor Component. +/// An that renders a Razor Component. /// -public class RazorComponentResult : ActionResult +public class RazorComponentResult : IResult { /// /// Constructs an instance of . @@ -52,14 +53,14 @@ public RazorComponentResult(Type componentType) /// /// Requests the service of - /// - /// to process itself in the given . + /// + /// to process itself in the given . /// - /// An associated with the current request for a Razor Component. + /// An associated with the current request. /// A which will complete when execution is completed. - public override Task ExecuteResultAsync(ActionContext context) + public Task ExecuteAsync(HttpContext httpContext) { - var executor = context.HttpContext.RequestServices.GetRequiredService(); - return executor.ExecuteAsync(context, this); + var executor = httpContext.RequestServices.GetRequiredService(); + return executor.ExecuteAsync(httpContext, this); } } diff --git a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultExecutor.cs b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultExecutor.cs index 5361598546ba..8c4284556ec7 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultExecutor.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultExecutor.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Text; using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.AspNetCore.Mvc.Infrastructure; @@ -50,11 +51,11 @@ public RazorComponentResultExecutor( /// /// Executes a Razor Component asynchronously. /// - public virtual async Task ExecuteAsync(ActionContext actionContext, RazorComponentResult result) + public virtual async Task ExecuteAsync(HttpContext httpContext, RazorComponentResult result) { - ArgumentNullException.ThrowIfNull(actionContext); + ArgumentNullException.ThrowIfNull(httpContext); - var response = actionContext.HttpContext.Response; + var response = httpContext.Response; ResponseContentTypeHelper.ResolveContentTypeAndEncoding( result.ContentType, @@ -72,9 +73,9 @@ public virtual async Task ExecuteAsync(ActionContext actionContext, RazorCompone } await using var writer = WriterFactory.CreateWriter(response.Body, resolvedContentTypeEncoding); - DiagnosticListener.BeforeRazorComponent(result.ComponentType, result.RenderMode, actionContext); - await RenderComponentToResponse(actionContext, result, writer); - DiagnosticListener.AfterRazorComponent(result.ComponentType, result.RenderMode, actionContext); + DiagnosticListener.BeforeRazorComponent(result.ComponentType, result.RenderMode, httpContext); + await RenderComponentToResponse(httpContext, result, writer); + DiagnosticListener.AfterRazorComponent(result.ComponentType, result.RenderMode, httpContext); // Perf: 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 @@ -82,9 +83,9 @@ public virtual async Task ExecuteAsync(ActionContext actionContext, RazorCompone await writer.FlushAsync(); } - private static Task RenderComponentToResponse(ActionContext actionContext, RazorComponentResult result, TextWriter writer) + private static Task RenderComponentToResponse(HttpContext httpContext, RazorComponentResult result, TextWriter writer) { - var componentPrerenderer = actionContext.HttpContext.RequestServices.GetRequiredService(); + var componentPrerenderer = httpContext.RequestServices.GetRequiredService(); return componentPrerenderer.Dispatcher.InvokeAsync(async () => { // We could pool these dictionary instances if we wanted. We could even skip the dictionary @@ -98,7 +99,7 @@ private static Task RenderComponentToResponse(ActionContext actionContext, Razor // 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 = await componentPrerenderer.PrerenderComponentAsync( - actionContext, + httpContext, typeof(RazorComponentResultHost), RenderMode.Static, hostParameters); diff --git a/src/Mvc/Mvc.ViewFeatures/src/Rendering/HtmlHelperComponentExtensions.cs b/src/Mvc/Mvc.ViewFeatures/src/Rendering/HtmlHelperComponentExtensions.cs index a6f3078d2b09..f75e058525e3 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/Rendering/HtmlHelperComponentExtensions.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/Rendering/HtmlHelperComponentExtensions.cs @@ -57,8 +57,8 @@ public static async Task RenderComponentAsync( ParameterView.Empty : ParameterView.FromDictionary(HtmlHelper.ObjectToDictionary(parameters)); - var viewContext = htmlHelper.ViewContext; - var componentRenderer = viewContext.HttpContext.RequestServices.GetRequiredService(); - return await componentRenderer.PrerenderComponentAsync(viewContext, componentType, renderMode, parameterView); + var httpContext = htmlHelper.ViewContext.HttpContext; + var componentRenderer = httpContext.RequestServices.GetRequiredService(); + return await componentRenderer.PrerenderComponentAsync(httpContext, componentType, renderMode, parameterView); } } diff --git a/src/Mvc/Mvc.ViewFeatures/test/RazorComponents/ComponentPrerendererTest.cs b/src/Mvc/Mvc.ViewFeatures/test/RazorComponents/ComponentPrerendererTest.cs index e644578d0260..9d285bce0f4a 100644 --- a/src/Mvc/Mvc.ViewFeatures/test/RazorComponents/ComponentPrerendererTest.cs +++ b/src/Mvc/Mvc.ViewFeatures/test/RazorComponents/ComponentPrerendererTest.cs @@ -12,7 +12,6 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Mvc.Rendering; -using Microsoft.AspNetCore.Mvc.ViewFeatures.Buffers; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; @@ -43,11 +42,11 @@ public ComponentPrerendererTest() public async Task CanRender_ParameterlessComponent_ClientMode() { // Arrange - var viewContext = GetViewContext(); + var httpContext = GetHttpContext(); var writer = new StringWriter(); // Act - var result = await renderer.PrerenderComponentAsync(viewContext, typeof(TestComponent), RenderMode.WebAssembly, ParameterView.Empty); + var result = await renderer.PrerenderComponentAsync(httpContext, typeof(TestComponent), RenderMode.WebAssembly, ParameterView.Empty); result.WriteTo(writer, HtmlEncoder.Default); var content = writer.ToString(); var match = Regex.Match(content, ComponentPattern); @@ -59,18 +58,18 @@ public async Task CanRender_ParameterlessComponent_ClientMode() Assert.Equal("webassembly", marker.Type); Assert.Equal(typeof(TestComponent).Assembly.GetName().Name, marker.Assembly); Assert.Equal(typeof(TestComponent).FullName, marker.TypeName); - Assert.Empty(viewContext.Items); + Assert.Empty(httpContext.Items); } [Fact] public async Task CanPrerender_ParameterlessComponent_ClientMode() { // Arrange - var viewContext = GetViewContext(); + var httpContext = GetHttpContext(); var writer = new StringWriter(); // Act - var result = await renderer.PrerenderComponentAsync(viewContext, typeof(TestComponent), RenderMode.WebAssemblyPrerendered, ParameterView.Empty); + var result = await renderer.PrerenderComponentAsync(httpContext, typeof(TestComponent), RenderMode.WebAssemblyPrerendered, ParameterView.Empty); await renderer.Dispatcher.InvokeAsync(() => result.WriteTo(writer, HtmlEncoder.Default)); var content = writer.ToString(); var match = Regex.Match(content, PrerenderedComponentPattern, RegexOptions.Multiline); @@ -95,7 +94,7 @@ public async Task CanPrerender_ParameterlessComponent_ClientMode() Assert.Null(epilogueMarker.Type); Assert.Null(epilogueMarker.ParameterDefinitions); Assert.Null(epilogueMarker.ParameterValues); - var (_, mode) = Assert.Single(viewContext.Items); + var (_, mode) = Assert.Single(httpContext.Items); var invoked = Assert.IsType(mode); Assert.Equal(InvokedRenderModes.Mode.WebAssembly, invoked.Value); } @@ -104,11 +103,11 @@ public async Task CanPrerender_ParameterlessComponent_ClientMode() public async Task CanRender_ComponentWithParameters_ClientMode() { // Arrange - var viewContext = GetViewContext(); + var httpContext = GetHttpContext(); var writer = new StringWriter(); // Act - var result = await renderer.PrerenderComponentAsync(viewContext, typeof(GreetingComponent), + var result = await renderer.PrerenderComponentAsync(httpContext, typeof(GreetingComponent), RenderMode.WebAssembly, ParameterView.FromDictionary(new Dictionary { @@ -141,11 +140,11 @@ public async Task CanRender_ComponentWithParameters_ClientMode() public async Task CanRender_ComponentWithNullParameters_ClientMode() { // Arrange - var viewContext = GetViewContext(); + var httpContext = GetHttpContext(); var writer = new StringWriter(); // Act - var result = await renderer.PrerenderComponentAsync(viewContext, typeof(GreetingComponent), + var result = await renderer.PrerenderComponentAsync(httpContext, typeof(GreetingComponent), RenderMode.WebAssembly, ParameterView.FromDictionary(new Dictionary { @@ -176,11 +175,11 @@ public async Task CanRender_ComponentWithNullParameters_ClientMode() public async Task CanPrerender_ComponentWithParameters_ClientMode() { // Arrange - var viewContext = GetViewContext(); + var httpContext = GetHttpContext(); var writer = new StringWriter(); // Act - var result = await renderer.PrerenderComponentAsync(viewContext, typeof(GreetingComponent), + var result = await renderer.PrerenderComponentAsync(httpContext, typeof(GreetingComponent), RenderMode.WebAssemblyPrerendered, ParameterView.FromDictionary(new Dictionary { @@ -225,11 +224,11 @@ public async Task CanPrerender_ComponentWithParameters_ClientMode() public async Task CanPrerender_ComponentWithNullParameters_ClientMode() { // Arrange - var viewContext = GetViewContext(); + var httpContext = GetHttpContext(); var writer = new StringWriter(); // Act - var result = await renderer.PrerenderComponentAsync(viewContext, typeof(GreetingComponent), + var result = await renderer.PrerenderComponentAsync(httpContext, typeof(GreetingComponent), RenderMode.WebAssemblyPrerendered, ParameterView.FromDictionary(new Dictionary { @@ -273,11 +272,11 @@ public async Task CanPrerender_ComponentWithNullParameters_ClientMode() public async Task CanRender_ParameterlessComponent() { // Arrange - var viewContext = GetViewContext(); + var httpContext = GetHttpContext(); var writer = new StringWriter(); // Act - var result = await renderer.PrerenderComponentAsync(viewContext, typeof(TestComponent), RenderMode.Static, ParameterView.Empty); + var result = await renderer.PrerenderComponentAsync(httpContext, typeof(TestComponent), RenderMode.Static, ParameterView.Empty); await renderer.Dispatcher.InvokeAsync(() => result.WriteTo(writer, HtmlEncoder.Default)); var content = writer.ToString(); @@ -289,12 +288,12 @@ public async Task CanRender_ParameterlessComponent() public async Task CanRender_ParameterlessComponent_ServerMode() { // Arrange - var viewContext = GetViewContext(); + var httpContext = GetHttpContext(); var protector = _dataprotectorProvider.CreateProtector(ServerComponentSerializationSettings.DataProtectionProviderPurpose) .ToTimeLimitedDataProtector(); // Act - var result = await renderer.PrerenderComponentAsync(viewContext, typeof(TestComponent), RenderMode.Server, ParameterView.Empty); + var result = await renderer.PrerenderComponentAsync(httpContext, typeof(TestComponent), RenderMode.Server, ParameterView.Empty); var content = HtmlContentUtilities.HtmlContentToString(result, HtmlEncoder.Default); var match = Regex.Match(content, ComponentPattern); @@ -313,20 +312,20 @@ public async Task CanRender_ParameterlessComponent_ServerMode() Assert.Equal(typeof(TestComponent).FullName, serverComponent.TypeName); Assert.NotEqual(Guid.Empty, serverComponent.InvocationId); - Assert.Equal("no-cache, no-store, max-age=0", viewContext.HttpContext.Response.Headers.CacheControl); - Assert.DoesNotContain(viewContext.Items.Values, value => value is InvokedRenderModes); + Assert.Equal("no-cache, no-store, max-age=0", httpContext.Response.Headers.CacheControl); + Assert.DoesNotContain(httpContext.Items.Values, value => value is InvokedRenderModes); } [Fact] public async Task CanPrerender_ParameterlessComponent_ServerMode() { // Arrange - var viewContext = GetViewContext(); + var httpContext = GetHttpContext(); var protector = _dataprotectorProvider.CreateProtector(ServerComponentSerializationSettings.DataProtectionProviderPurpose) .ToTimeLimitedDataProtector(); // Act - var result = await renderer.PrerenderComponentAsync(viewContext, typeof(TestComponent), RenderMode.ServerPrerendered, ParameterView.Empty); + var result = await renderer.PrerenderComponentAsync(httpContext, typeof(TestComponent), RenderMode.ServerPrerendered, ParameterView.Empty); var content = await renderer.Dispatcher.InvokeAsync(() => HtmlContentUtilities.HtmlContentToString(result, HtmlEncoder.Default)); var match = Regex.Match(content, PrerenderedComponentPattern, RegexOptions.Multiline); @@ -357,8 +356,8 @@ public async Task CanPrerender_ParameterlessComponent_ServerMode() Assert.Null(epilogueMarker.Descriptor); Assert.Null(epilogueMarker.Type); - Assert.Equal("no-cache, no-store, max-age=0", viewContext.HttpContext.Response.Headers.CacheControl); - var (_, mode) = Assert.Single(viewContext.Items, (kvp) => kvp.Value is InvokedRenderModes); + Assert.Equal("no-cache, no-store, max-age=0", httpContext.Response.Headers.CacheControl); + var (_, mode) = Assert.Single(httpContext.Items, (kvp) => kvp.Value is InvokedRenderModes); Assert.Equal(InvokedRenderModes.Mode.Server, ((InvokedRenderModes)mode).Value); } @@ -366,15 +365,15 @@ public async Task CanPrerender_ParameterlessComponent_ServerMode() public async Task Prerender_ServerAndClientComponentUpdatesInvokedPrerenderModes() { // Arrange - var viewContext = GetViewContext(); + var httpContext = GetHttpContext(); // Act var parameters = ParameterView.FromDictionary(new Dictionary { { "Name", "SomeName" } }); - var server = await renderer.PrerenderComponentAsync(viewContext, typeof(GreetingComponent), RenderMode.ServerPrerendered, parameters); - var client = await renderer.PrerenderComponentAsync(viewContext, typeof(GreetingComponent), RenderMode.WebAssemblyPrerendered, parameters); + var server = await renderer.PrerenderComponentAsync(httpContext, typeof(GreetingComponent), RenderMode.ServerPrerendered, parameters); + var client = await renderer.PrerenderComponentAsync(httpContext, typeof(GreetingComponent), RenderMode.WebAssemblyPrerendered, parameters); // Assert - var (_, mode) = Assert.Single(viewContext.Items, (kvp) => kvp.Value is InvokedRenderModes); + var (_, mode) = Assert.Single(httpContext.Items, (kvp) => kvp.Value is InvokedRenderModes); Assert.Equal(InvokedRenderModes.Mode.ServerAndWebAssembly, ((InvokedRenderModes)mode).Value); } @@ -382,16 +381,16 @@ public async Task Prerender_ServerAndClientComponentUpdatesInvokedPrerenderModes public async Task CanRenderMultipleServerComponents() { // Arrange - var viewContext = GetViewContext(); + var httpContext = GetHttpContext(); var protector = _dataprotectorProvider.CreateProtector(ServerComponentSerializationSettings.DataProtectionProviderPurpose) .ToTimeLimitedDataProtector(); // Act - var firstResult = await renderer.PrerenderComponentAsync(viewContext, typeof(TestComponent), RenderMode.ServerPrerendered, ParameterView.Empty); + var firstResult = await renderer.PrerenderComponentAsync(httpContext, typeof(TestComponent), RenderMode.ServerPrerendered, ParameterView.Empty); var firstComponent = await renderer.Dispatcher.InvokeAsync(() => HtmlContentUtilities.HtmlContentToString(firstResult)); var firstMatch = Regex.Match(firstComponent, PrerenderedComponentPattern, RegexOptions.Multiline); - var secondResult = await renderer.PrerenderComponentAsync(viewContext, typeof(TestComponent), RenderMode.Server, ParameterView.Empty); + var secondResult = await renderer.PrerenderComponentAsync(httpContext, typeof(TestComponent), RenderMode.Server, ParameterView.Empty); var secondComponent = await renderer.Dispatcher.InvokeAsync(() => HtmlContentUtilities.HtmlContentToString(secondResult)); var secondMatch = Regex.Match(secondComponent, ComponentPattern); @@ -424,11 +423,11 @@ public async Task CanRenderMultipleServerComponents() public async Task CanRender_ComponentWithParametersObject() { // Arrange - var viewContext = GetViewContext(); + var httpContext = GetHttpContext(); // Act var parameters = ParameterView.FromDictionary(new Dictionary { { "Name", "SomeName" } }); - var result = await renderer.PrerenderComponentAsync(viewContext, typeof(GreetingComponent), RenderMode.Static, parameters); + var result = await renderer.PrerenderComponentAsync(httpContext, typeof(GreetingComponent), RenderMode.Static, parameters); // Assert var content = await renderer.Dispatcher.InvokeAsync(() => HtmlContentUtilities.HtmlContentToString(result, HtmlEncoder.Default)); @@ -439,13 +438,13 @@ public async Task CanRender_ComponentWithParametersObject() public async Task CanRender_ComponentWithParameters_ServerMode() { // Arrange - var viewContext = GetViewContext(); + var httpContext = GetHttpContext(); var protector = _dataprotectorProvider.CreateProtector(ServerComponentSerializationSettings.DataProtectionProviderPurpose) .ToTimeLimitedDataProtector(); // Act var parameters = ParameterView.FromDictionary(new Dictionary { { "Name", "SomeName" } }); - var result = await renderer.PrerenderComponentAsync(viewContext, typeof(GreetingComponent), RenderMode.Server, parameters); + var result = await renderer.PrerenderComponentAsync(httpContext, typeof(GreetingComponent), RenderMode.Server, parameters); var content = HtmlContentUtilities.HtmlContentToString(result, HtmlEncoder.Default); var match = Regex.Match(content, ComponentPattern); @@ -478,13 +477,13 @@ public async Task CanRender_ComponentWithParameters_ServerMode() public async Task CanRender_ComponentWithNullParameters_ServerMode() { // Arrange - var viewContext = GetViewContext(); + var httpContext = GetHttpContext(); var protector = _dataprotectorProvider.CreateProtector(ServerComponentSerializationSettings.DataProtectionProviderPurpose) .ToTimeLimitedDataProtector(); // Act var parameters = ParameterView.FromDictionary(new Dictionary { { "Name", null } }); - var result = await renderer.PrerenderComponentAsync(viewContext, typeof(GreetingComponent), RenderMode.Server, parameters); + var result = await renderer.PrerenderComponentAsync(httpContext, typeof(GreetingComponent), RenderMode.Server, parameters); var content = HtmlContentUtilities.HtmlContentToString(result, HtmlEncoder.Default); var match = Regex.Match(content, ComponentPattern); @@ -517,13 +516,13 @@ public async Task CanRender_ComponentWithNullParameters_ServerMode() public async Task CanPrerender_ComponentWithParameters_ServerPrerenderedMode() { // Arrange - var viewContext = GetViewContext(); + var httpContext = GetHttpContext(); var protector = _dataprotectorProvider.CreateProtector(ServerComponentSerializationSettings.DataProtectionProviderPurpose) .ToTimeLimitedDataProtector(); // Act var parameters = ParameterView.FromDictionary(new Dictionary { { "Name", "SomeName" } }); - var result = await renderer.PrerenderComponentAsync(viewContext, typeof(GreetingComponent), RenderMode.ServerPrerendered, parameters); + var result = await renderer.PrerenderComponentAsync(httpContext, typeof(GreetingComponent), RenderMode.ServerPrerendered, parameters); var content = await renderer.Dispatcher.InvokeAsync(() => HtmlContentUtilities.HtmlContentToString(result, HtmlEncoder.Default)); var match = Regex.Match(content, PrerenderedComponentPattern, RegexOptions.Multiline); @@ -568,13 +567,13 @@ public async Task CanPrerender_ComponentWithParameters_ServerPrerenderedMode() public async Task CanPrerender_ComponentWithNullParameters_ServerPrerenderedMode() { // Arrange - var viewContext = GetViewContext(); + var httpContext = GetHttpContext(); var protector = _dataprotectorProvider.CreateProtector(ServerComponentSerializationSettings.DataProtectionProviderPurpose) .ToTimeLimitedDataProtector(); // Act var parameters = ParameterView.FromDictionary(new Dictionary { { "Name", null } }); - var result = await renderer.PrerenderComponentAsync(viewContext, typeof(GreetingComponent), RenderMode.ServerPrerendered, parameters); + var result = await renderer.PrerenderComponentAsync(httpContext, typeof(GreetingComponent), RenderMode.ServerPrerendered, parameters); var content = await renderer.Dispatcher.InvokeAsync(() => HtmlContentUtilities.HtmlContentToString(result, HtmlEncoder.Default)); var match = Regex.Match(content, PrerenderedComponentPattern, RegexOptions.Multiline); @@ -619,12 +618,12 @@ public async Task CanPrerender_ComponentWithNullParameters_ServerPrerenderedMode public async Task ComponentWithInvalidRenderMode_Throws() { // Arrange - var viewContext = GetViewContext(); + var httpContext = GetHttpContext(); // Act & Assert var parameters = ParameterView.FromDictionary(new Dictionary { { "Name", "SomeName" } }); var ex = await ExceptionAssert.ThrowsArgumentAsync( - async () => await renderer.PrerenderComponentAsync(viewContext, typeof(GreetingComponent), default, parameters), + async () => await renderer.PrerenderComponentAsync(httpContext, typeof(GreetingComponent), default, parameters), "prerenderMode", $"Unsupported RenderMode '{(RenderMode)default}'"); } @@ -633,12 +632,12 @@ public async Task ComponentWithInvalidRenderMode_Throws() public async Task RenderComponent_DoesNotInvokeOnAfterRenderInComponent() { // Arrange - var viewContext = GetViewContext(); + var httpContext = GetHttpContext(); // Act var state = new OnAfterRenderState(); var parameters = ParameterView.FromDictionary(new Dictionary { { "state", state } }); - var result = await renderer.PrerenderComponentAsync(viewContext, typeof(OnAfterRenderComponent), RenderMode.Static, parameters); + var result = await renderer.PrerenderComponentAsync(httpContext, typeof(OnAfterRenderComponent), RenderMode.Static, parameters); // Assert var content = await renderer.Dispatcher.InvokeAsync(() => HtmlContentUtilities.HtmlContentToString(result, HtmlEncoder.Default)); @@ -663,13 +662,13 @@ public async Task DisposableComponents_GetDisposedAfterScopeCompletes() var scope = provider.GetRequiredService().CreateScope(); var scopedProvider = scope.ServiceProvider; var context = new DefaultHttpContext() { RequestServices = scopedProvider }; - var viewContext = GetViewContext(context); + var httpContext = GetHttpContext(context); var renderer = scopedProvider.GetRequiredService(); // Act var state = new AsyncDisposableState(); var parameters = ParameterView.FromDictionary(new Dictionary { { "state", state } }); - var result = await renderer.PrerenderComponentAsync(viewContext, typeof(AsyncDisposableComponent), RenderMode.Static, parameters); + var result = await renderer.PrerenderComponentAsync(httpContext, typeof(AsyncDisposableComponent), RenderMode.Static, parameters); // Assert var content = await renderer.Dispatcher.InvokeAsync(() => HtmlContentUtilities.HtmlContentToString(result, HtmlEncoder.Default)); @@ -683,11 +682,11 @@ public async Task DisposableComponents_GetDisposedAfterScopeCompletes() public async Task CanCatch_ComponentWithSynchronousException() { // Arrange - var viewContext = GetViewContext(); + var httpContext = GetHttpContext(); // Act & Assert var exception = await Assert.ThrowsAsync(async () => await renderer.PrerenderComponentAsync( - viewContext, + httpContext, typeof(ExceptionComponent), RenderMode.Static, ParameterView.FromDictionary(new Dictionary @@ -703,11 +702,11 @@ public async Task CanCatch_ComponentWithSynchronousException() public async Task CanCatch_ComponentWithAsynchronousException() { // Arrange - var viewContext = GetViewContext(); + var httpContext = GetHttpContext(); // Act & Assert var exception = await Assert.ThrowsAsync(async () => await renderer.PrerenderComponentAsync( - viewContext, + httpContext, typeof(ExceptionComponent), RenderMode.Static, ParameterView.FromDictionary(new Dictionary @@ -723,11 +722,11 @@ public async Task CanCatch_ComponentWithAsynchronousException() public async Task Rendering_ComponentWithJsInteropThrows() { // Arrange - var viewContext = GetViewContext(); + var httpContext = GetHttpContext(); // Act & Assert var exception = await Assert.ThrowsAsync(async () => await renderer.PrerenderComponentAsync( - viewContext, + httpContext, typeof(ExceptionComponent), RenderMode.Static, ParameterView.FromDictionary(new Dictionary @@ -755,11 +754,11 @@ public async Task UriHelperRedirect_ThrowsInvalidOperationException_WhenResponse var responseMock = new Mock(); responseMock.Setup(r => r.HasStarted).Returns(true); ctx.Features.Set(responseMock.Object); - var viewContext = GetViewContext(ctx); + var httpContext = GetHttpContext(ctx); // Act var exception = await Assert.ThrowsAsync(async () => await renderer.PrerenderComponentAsync( - viewContext, + httpContext, typeof(RedirectComponent), RenderMode.Static, ParameterView.FromDictionary(new Dictionary @@ -783,11 +782,11 @@ public async Task HtmlHelper_Redirects_WhenComponentNavigates() ctx.Request.PathBase = "/base"; ctx.Request.Path = "/path"; ctx.Request.QueryString = new QueryString("?query=value"); - var viewContext = GetViewContext(ctx); + var httpContext = GetHttpContext(ctx); // Act await renderer.PrerenderComponentAsync( - viewContext, + httpContext, typeof(RedirectComponent), RenderMode.Static, ParameterView.FromDictionary(new Dictionary @@ -804,7 +803,7 @@ await renderer.PrerenderComponentAsync( public async Task CanRender_AsyncComponent() { // Arrange - var viewContext = GetViewContext(); + var httpContext = GetHttpContext(); var expectedContent = @" @@ -849,7 +848,7 @@ public async Task CanRender_AsyncComponent()
"; // Act - var result = await renderer.PrerenderComponentAsync(viewContext, typeof(AsyncComponent), RenderMode.Static, ParameterView.Empty); + var result = await renderer.PrerenderComponentAsync(httpContext, typeof(AsyncComponent), RenderMode.Static, ParameterView.Empty); var content = await renderer.Dispatcher.InvokeAsync(() => HtmlContentUtilities.HtmlContentToString(result, HtmlEncoder.Default)); // Assert @@ -863,7 +862,7 @@ private ComponentPrerenderer GetComponentRenderer(IServiceProvider services = nu new ServerComponentSerializer(_dataprotectorProvider)); } - private ViewContext GetViewContext(HttpContext context = null) + private HttpContext GetHttpContext(HttpContext context = null) { context ??= new DefaultHttpContext(); context.RequestServices ??= _services; @@ -873,7 +872,7 @@ private ViewContext GetViewContext(HttpContext context = null) context.Request.Path = "/path"; context.Request.QueryString = QueryString.FromUriComponent("?query=value"); - return new ViewContext { HttpContext = context }; + return context; } private static ServiceCollection CreateDefaultServiceCollection() diff --git a/src/Mvc/Mvc.ViewFeatures/test/RazorComponents/RazorComponentResultTest.cs b/src/Mvc/Mvc.ViewFeatures/test/RazorComponents/RazorComponentResultTest.cs index ed62312ec35f..464983a23bd7 100644 --- a/src/Mvc/Mvc.ViewFeatures/test/RazorComponents/RazorComponentResultTest.cs +++ b/src/Mvc/Mvc.ViewFeatures/test/RazorComponents/RazorComponentResultTest.cs @@ -24,10 +24,9 @@ public async Task CanRenderComponentStatically() var httpContext = GetTestHttpContext(); var responseBody = new MemoryStream(); httpContext.Response.Body = responseBody; - var actionContext = new ActionContext(httpContext, new AspNetCore.Routing.RouteData(), new Abstractions.ActionDescriptor()); // Act - await result.ExecuteResultAsync(actionContext); + await result.ExecuteAsync(httpContext); // Assert responseBody.Position = 0; @@ -49,10 +48,9 @@ public async Task CanSetStatusCodeAndContentType() var httpContext = GetTestHttpContext(); var responseBody = new MemoryStream(); httpContext.Response.Body = responseBody; - var actionContext = new ActionContext(httpContext, new AspNetCore.Routing.RouteData(), new Abstractions.ActionDescriptor()); // Act - await result.ExecuteResultAsync(actionContext); + await result.ExecuteAsync(httpContext); // Assert responseBody.Position = 0; @@ -74,10 +72,9 @@ public async Task WaitsForQuiescence() var httpContext = GetTestHttpContext(); var responseBody = new MemoryStream(); httpContext.Response.Body = responseBody; - var actionContext = new ActionContext(httpContext, new AspNetCore.Routing.RouteData(), new Abstractions.ActionDescriptor()); // Act/Assert: Doesn't complete until loading finishes - var completionTask = result.ExecuteResultAsync(actionContext); + var completionTask = result.ExecuteAsync(httpContext); await Task.Yield(); Assert.False(completionTask.IsCompleted); From 314173cb7a4c8ec9d9f9e2f407ff837740cc3639 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Tue, 7 Mar 2023 17:14:29 +0000 Subject: [PATCH 12/17] More testing --- .../RazorComponentResultExecutor.cs | 2 +- .../RazorComponentResultTest.cs | 62 ++++++++++++++++--- 2 files changed, 54 insertions(+), 10 deletions(-) diff --git a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultExecutor.cs b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultExecutor.cs index 8c4284556ec7..74860fbf6fc7 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultExecutor.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultExecutor.cs @@ -61,7 +61,7 @@ public virtual async Task ExecuteAsync(HttpContext httpContext, RazorComponentRe result.ContentType, response.ContentType, (DefaultContentType, Encoding.UTF8), - MediaType.GetEncoding, + MediaType.GetEncoding, out var resolvedContentType, out var resolvedContentTypeEncoding); diff --git a/src/Mvc/Mvc.ViewFeatures/test/RazorComponents/RazorComponentResultTest.cs b/src/Mvc/Mvc.ViewFeatures/test/RazorComponents/RazorComponentResultTest.cs index 464983a23bd7..9f7e06ac7136 100644 --- a/src/Mvc/Mvc.ViewFeatures/test/RazorComponents/RazorComponentResultTest.cs +++ b/src/Mvc/Mvc.ViewFeatures/test/RazorComponents/RazorComponentResultTest.cs @@ -29,9 +29,7 @@ public async Task CanRenderComponentStatically() await result.ExecuteAsync(httpContext); // Assert - responseBody.Position = 0; - var responseHtml = new StreamReader(responseBody).ReadToEnd(); - Assert.Equal("

Hello from SimpleComponent

", responseHtml); + Assert.Equal("

Hello from SimpleComponent

", GetStringContent(responseBody)); Assert.Equal("text/html; charset=utf-8", httpContext.Response.ContentType); Assert.Equal(200, httpContext.Response.StatusCode); } @@ -53,9 +51,7 @@ public async Task CanSetStatusCodeAndContentType() await result.ExecuteAsync(httpContext); // Assert - responseBody.Position = 0; - var responseHtml = new StreamReader(responseBody).ReadToEnd(); - Assert.Equal("

Hello from SimpleComponent

", responseHtml); + Assert.Equal("

Hello from SimpleComponent

", GetStringContent(responseBody)); Assert.Equal("application/test-content-type", httpContext.Response.ContentType); Assert.Equal(123, httpContext.Response.StatusCode); } @@ -81,9 +77,29 @@ public async Task WaitsForQuiescence() // Act/Assert: Does complete when loading finishes tcs.SetResult(); await completionTask; - responseBody.Position = 0; - var responseHtml = new StreamReader(responseBody).ReadToEnd(); - Assert.Equal("Loading task status: RanToCompletion", responseHtml); + Assert.Equal("Loading task status: RanToCompletion", GetStringContent(responseBody)); + } + + [Fact] + public async Task SupportsLayouts() + { + // Arrange + var result = new RazorComponentResult(); + var httpContext = GetTestHttpContext(); + var responseBody = new MemoryStream(); + httpContext.Response.Body = responseBody; + + // Act + await result.ExecuteAsync(httpContext); + + // Assert + Assert.Equal("[TestParentLayout with content: [TestLayout with content: Page]]", GetStringContent(responseBody)); + } + + private static string GetStringContent(MemoryStream stream) + { + stream.Position = 0; + return new StreamReader(stream).ReadToEnd(); } private static DefaultHttpContext GetTestHttpContext() @@ -126,6 +142,34 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) => builder.AddContent(0, $"Loading task status: {LoadingTask.Status}"); } + [Layout(typeof(TestLayout))] + class ComponentWithLayout : ComponentBase + { + protected override void BuildRenderTree(RenderTreeBuilder builder) + => builder.AddContent(0, $"Page"); + } + + [Layout(typeof(TestParentLayout))] + class TestLayout : LayoutComponentBase + { + protected override void BuildRenderTree(RenderTreeBuilder builder) + { + builder.AddContent(0, $"[{nameof(TestLayout)} with content: "); + builder.AddContent(1, Body); + builder.AddContent(2, "]"); + } + } + + class TestParentLayout : LayoutComponentBase + { + protected override void BuildRenderTree(RenderTreeBuilder builder) + { + builder.AddContent(0, $"[{nameof(TestParentLayout)} with content: "); + builder.AddContent(1, Body); + builder.AddContent(2, "]"); + } + } + class FakeDataProtectionProvider : IDataProtectionProvider { public IDataProtector CreateProtector(string purpose) From 8b91b7ce7bf1c68e23c86cc0b400272976d71e5e Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Tue, 7 Mar 2023 18:23:44 +0000 Subject: [PATCH 13/17] Expose parameters as dictionary for unit tests --- .../src/PublicAPI.Unshipped.txt | 7 ++-- .../RazorComponents/RazorComponentResult.cs | 34 +++++++++++++++++-- .../RazorComponentResultOfT.cs | 14 ++++++++ .../RazorComponentResultTest.cs | 32 ++++++++++++++--- 4 files changed, 79 insertions(+), 8 deletions(-) diff --git a/src/Mvc/Mvc.ViewFeatures/src/PublicAPI.Unshipped.txt b/src/Mvc/Mvc.ViewFeatures/src/PublicAPI.Unshipped.txt index d1ccbb2417de..4afb2981b60c 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/PublicAPI.Unshipped.txt +++ b/src/Mvc/Mvc.ViewFeatures/src/PublicAPI.Unshipped.txt @@ -11,8 +11,7 @@ Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.RazorComp ~Microsoft.AspNetCore.Mvc.Diagnostics.BeforeRazorComponentEventData.BeforeRazorComponentEventData(System.Type componentType, Microsoft.AspNetCore.Mvc.Rendering.RenderMode renderMode, Microsoft.AspNetCore.Http.HttpContext httpContext) -> void ~Microsoft.AspNetCore.Mvc.Diagnostics.BeforeRazorComponentEventData.HttpContext.get -> Microsoft.AspNetCore.Http.HttpContext ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext httpContext) -> System.Threading.Tasks.Task -~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.Parameters.get -> object -~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.Parameters.set -> void +~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.Parameters.get -> System.Collections.Generic.IReadOnlyDictionary Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.RenderMode.get -> Microsoft.AspNetCore.Mvc.Rendering.RenderMode Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.RenderMode.set -> void Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.StatusCode.get -> int? @@ -26,7 +25,11 @@ Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.ContentType.get -> string ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.ContentType.set -> void ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.RazorComponentResult(System.Type componentType) -> void +~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.RazorComponentResult(System.Type componentType, object parameters) -> void +~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.RazorComponentResult(System.Type componentType, System.Collections.Generic.IReadOnlyDictionary parameters) -> void ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult +~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.RazorComponentResult(object parameters) -> void +~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.RazorComponentResult(System.Collections.Generic.IReadOnlyDictionary parameters) -> void ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.DiagnosticListener.get -> System.Diagnostics.DiagnosticListener ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.RazorComponentResultExecutor(Microsoft.AspNetCore.Mvc.Infrastructure.IHttpResponseStreamWriterFactory writerFactory, System.Diagnostics.DiagnosticListener diagnosticListener) -> void ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.WriterFactory.get -> Microsoft.AspNetCore.Mvc.Infrastructure.IHttpResponseStreamWriterFactory diff --git a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResult.cs b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResult.cs index e1e120d33741..87a0c1764890 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResult.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResult.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Internal; namespace Microsoft.AspNetCore.Mvc.ViewFeatures; @@ -13,19 +14,48 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures; ///
public class RazorComponentResult : IResult { + private static readonly IReadOnlyDictionary EmptyParameters + = new Dictionary().AsReadOnly(); + /// /// Constructs an instance of . /// /// The type of the component to render. This must implement . public RazorComponentResult(Type componentType) + : this(componentType, null) + { + } + + /// + /// Constructs an instance of . + /// + /// The type of the component to render. This must implement . + /// Parameters for the component. + public RazorComponentResult(Type componentType, object parameters) + : this(componentType, CoerceParametersObjectToDictionary(parameters)) + { + } + + /// + /// Constructs an instance of . + /// + /// The type of the component to render. This must implement . + /// Parameters for the component. + public RazorComponentResult(Type componentType, IReadOnlyDictionary parameters) { // Note that the Blazor renderer will validate that componentType implements IComponent and throws a suitable // exception if not, so we don't need to duplicate that logic here. ArgumentNullException.ThrowIfNull(componentType); ComponentType = componentType; + Parameters = parameters ?? EmptyParameters; } + private static IReadOnlyDictionary CoerceParametersObjectToDictionary(object parameters) + => parameters is null + ? null + : (IReadOnlyDictionary)PropertyHelper.ObjectToDictionary(parameters); + /// /// Gets the component type. /// @@ -42,9 +72,9 @@ public RazorComponentResult(Type componentType) public int? StatusCode { get; set; } /// - /// Gets or sets the parameters for the component. + /// Gets the parameters for the component. /// - public object Parameters { get; set; } + public IReadOnlyDictionary Parameters { get; } /// /// Gets or sets the rendering mode. diff --git a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultOfT.cs b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultOfT.cs index 7ad55e338946..64737915e054 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultOfT.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultOfT.cs @@ -16,4 +16,18 @@ public class RazorComponentResult : RazorComponentResult where TComp public RazorComponentResult() : base(typeof(TComponent)) { } + + /// + /// Constructs an instance of . + /// + public RazorComponentResult(object parameters) : base(typeof(TComponent), parameters) + { + } + + /// + /// Constructs an instance of . + /// + public RazorComponentResult(IReadOnlyDictionary parameters) : base(typeof(TComponent), parameters) + { + } } diff --git a/src/Mvc/Mvc.ViewFeatures/test/RazorComponents/RazorComponentResultTest.cs b/src/Mvc/Mvc.ViewFeatures/test/RazorComponents/RazorComponentResultTest.cs index 9f7e06ac7136..9ebdbce6cfc2 100644 --- a/src/Mvc/Mvc.ViewFeatures/test/RazorComponents/RazorComponentResultTest.cs +++ b/src/Mvc/Mvc.ViewFeatures/test/RazorComponents/RazorComponentResultTest.cs @@ -16,6 +16,33 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures; public class RazorComponentResultTest { + [Fact] + public void AcceptsNullParameters() + { + var result = new RazorComponentResult(typeof(SimpleComponent), null); + Assert.NotNull(result.Parameters); + Assert.Empty(result.Parameters); + } + + [Fact] + public void AcceptsDictionaryParameters() + { + var paramsDict = new Dictionary { { "First", 123 } }; + var result = new RazorComponentResult(typeof(SimpleComponent), paramsDict); + Assert.Equal(1, result.Parameters.Count); + Assert.Equal(123, result.Parameters["First"]); + Assert.Same(paramsDict, result.Parameters); + } + + [Fact] + public void AcceptsObjectParameters() + { + var result = new RazorComponentResult(typeof(SimpleComponent), new { Param1 = 123, Param2 = "Another" }); + Assert.Equal(2, result.Parameters.Count); + Assert.Equal(123, result.Parameters["Param1"]); + Assert.Equal("Another", result.Parameters["Param2"]); + } + [Fact] public async Task CanRenderComponentStatically() { @@ -61,10 +88,7 @@ public async Task WaitsForQuiescence() { // Arrange var tcs = new TaskCompletionSource(); - var result = new RazorComponentResult - { - Parameters = new { LoadingTask = tcs.Task } - }; + var result = new RazorComponentResult(new { LoadingTask = tcs.Task }); var httpContext = GetTestHttpContext(); var responseBody = new MemoryStream(); httpContext.Response.Body = responseBody; From 7daf0aa19ffd3906595f792aa2244a65198aa2fb Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Wed, 8 Mar 2023 15:13:01 +0000 Subject: [PATCH 14/17] Test fixes --- .../Mvc.TagHelpers/src/PersistComponentStateTagHelper.cs | 2 +- .../test/PersistComponentStateTagHelperTest.cs | 8 ++++---- .../src/RazorComponents/ComponentPrerenderer.cs | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Mvc/Mvc.TagHelpers/src/PersistComponentStateTagHelper.cs b/src/Mvc/Mvc.TagHelpers/src/PersistComponentStateTagHelper.cs index bf832e1aaa0f..99dac7e83ac1 100644 --- a/src/Mvc/Mvc.TagHelpers/src/PersistComponentStateTagHelper.cs +++ b/src/Mvc/Mvc.TagHelpers/src/PersistComponentStateTagHelper.cs @@ -52,7 +52,7 @@ public override async Task ProcessAsync(TagHelperContext context, TagHelperOutpu var renderer = services.GetRequiredService(); var store = PersistenceMode switch { - null => ComponentPrerenderer.GetPersistStateRenderMode(ViewContext) switch + null => ComponentPrerenderer.GetPersistStateRenderMode(ViewContext.HttpContext) switch { InvokedRenderModes.Mode.None => null, diff --git a/src/Mvc/Mvc.TagHelpers/test/PersistComponentStateTagHelperTest.cs b/src/Mvc/Mvc.TagHelpers/test/PersistComponentStateTagHelperTest.cs index 12dcff2797e9..34853e13cf17 100644 --- a/src/Mvc/Mvc.TagHelpers/test/PersistComponentStateTagHelperTest.cs +++ b/src/Mvc/Mvc.TagHelpers/test/PersistComponentStateTagHelperTest.cs @@ -80,7 +80,7 @@ public async Task ExecuteAsync_RendersWebAssemblyStateImplicitlyWhenAWebAssembly ViewContext = GetViewContext() }; - ComponentPrerenderer.UpdateSaveStateRenderMode(tagHelper.ViewContext, RenderMode.WebAssemblyPrerendered); + ComponentPrerenderer.UpdateSaveStateRenderMode(tagHelper.ViewContext.HttpContext, RenderMode.WebAssemblyPrerendered); var context = GetTagHelperContext(); var output = GetTagHelperOutput(); @@ -128,7 +128,7 @@ public async Task ExecuteAsync_RendersServerStateImplicitlyWhenAServerComponentW ViewContext = GetViewContext() }; - ComponentPrerenderer.UpdateSaveStateRenderMode(tagHelper.ViewContext, RenderMode.ServerPrerendered); + ComponentPrerenderer.UpdateSaveStateRenderMode(tagHelper.ViewContext.HttpContext, RenderMode.ServerPrerendered); var context = GetTagHelperContext(); var output = GetTagHelperOutput(); @@ -153,8 +153,8 @@ public async Task ExecuteAsync_ThrowsIfItCantInferThePersistMode() ViewContext = GetViewContext() }; - ComponentPrerenderer.UpdateSaveStateRenderMode(tagHelper.ViewContext, RenderMode.ServerPrerendered); - ComponentPrerenderer.UpdateSaveStateRenderMode(tagHelper.ViewContext, RenderMode.WebAssemblyPrerendered); + ComponentPrerenderer.UpdateSaveStateRenderMode(tagHelper.ViewContext.HttpContext, RenderMode.ServerPrerendered); + ComponentPrerenderer.UpdateSaveStateRenderMode(tagHelper.ViewContext.HttpContext, RenderMode.WebAssemblyPrerendered); var context = GetTagHelperContext(); var output = GetTagHelperOutput(); diff --git a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/ComponentPrerenderer.cs b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/ComponentPrerenderer.cs index 1c0e23c3c70d..68ccaa29138d 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/ComponentPrerenderer.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/ComponentPrerenderer.cs @@ -140,9 +140,9 @@ internal static void UpdateSaveStateRenderMode(HttpContext httpContext, RenderMo } } - internal static InvokedRenderModes.Mode GetPersistStateRenderMode(ViewContext viewContext) + internal static InvokedRenderModes.Mode GetPersistStateRenderMode(HttpContext httpContext) { - if (viewContext.Items.TryGetValue(InvokedRenderModesKey, out var result)) + if (httpContext.Items.TryGetValue(InvokedRenderModesKey, out var result)) { return ((InvokedRenderModes)result).Value; } From 83b3e61c13f940148b2f2db434fcbda4edf8d5f3 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Wed, 8 Mar 2023 15:59:10 +0000 Subject: [PATCH 15/17] Fix XML docs --- .../src/RazorComponents/RazorComponentResultOfT.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultOfT.cs b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultOfT.cs index 64737915e054..525b491145f5 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultOfT.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultOfT.cs @@ -2,11 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Http; namespace Microsoft.AspNetCore.Mvc.ViewFeatures; /// -/// An that renders a Razor Component. +/// An that renders a Razor Component. /// public class RazorComponentResult : RazorComponentResult where TComponent: IComponent { @@ -20,6 +21,7 @@ public RazorComponentResult() : base(typeof(TComponent)) /// /// Constructs an instance of . /// + /// Parameters for the component. public RazorComponentResult(object parameters) : base(typeof(TComponent), parameters) { } @@ -27,6 +29,7 @@ public RazorComponentResult(object parameters) : base(typeof(TComponent), parame /// /// Constructs an instance of . /// + /// Parameters for the component. public RazorComponentResult(IReadOnlyDictionary parameters) : base(typeof(TComponent), parameters) { } From 4a53208118ac279ffb101f390fffac98b0315230 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Thu, 9 Mar 2023 11:32:58 +0000 Subject: [PATCH 16/17] Clean up some unnecessary stuff based on what's coming in later work --- .../src/Buffers/ViewBuffer.cs | 2 +- .../src/Diagnostics/MvcDiagnostics.cs | 101 ------------------ ...iewFeaturesDiagnosticListenerExtensions.cs | 47 -------- .../src/PublicAPI.Unshipped.txt | 14 +-- .../RazorComponents/ComponentPrerenderer.cs | 14 +-- ...yncHtmlContent.cs => IHtmlAsyncContent.cs} | 2 +- .../RazorComponentResultExecutor.cs | 8 +- 7 files changed, 11 insertions(+), 177 deletions(-) rename src/Mvc/Mvc.ViewFeatures/src/RazorComponents/{IAsyncHtmlContent.cs => IHtmlAsyncContent.cs} (89%) diff --git a/src/Mvc/Mvc.ViewFeatures/src/Buffers/ViewBuffer.cs b/src/Mvc/Mvc.ViewFeatures/src/Buffers/ViewBuffer.cs index e082d16b0558..ebcd7a0c934d 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/Buffers/ViewBuffer.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/Buffers/ViewBuffer.cs @@ -232,7 +232,7 @@ public async Task WriteToAsync(TextWriter writer, HtmlEncoder encoder) continue; } - if (value.Value is IAsyncHtmlContent valueAsAsyncHtmlContent) + if (value.Value is IHtmlAsyncContent valueAsAsyncHtmlContent) { await valueAsAsyncHtmlContent.WriteToAsync(writer); await writer.FlushAsync(); diff --git a/src/Mvc/Mvc.ViewFeatures/src/Diagnostics/MvcDiagnostics.cs b/src/Mvc/Mvc.ViewFeatures/src/Diagnostics/MvcDiagnostics.cs index 47bffbf20d19..58d43ba2399b 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/Diagnostics/MvcDiagnostics.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/Diagnostics/MvcDiagnostics.cs @@ -1,7 +1,6 @@ // 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.Http; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.ViewComponents; @@ -439,103 +438,3 @@ public ViewNotFoundEventData(ActionContext actionContext, bool isMainPage, Actio _ => throw new IndexOutOfRangeException(nameof(index)) }; } - -/// -/// An that occurs before a Razor Component. -/// -public sealed class BeforeRazorComponentEventData : EventData -{ - /// - /// The name of the event. - /// - public const string EventName = EventNamespace + "BeforeRazorComponent"; - - /// - /// Initializes a new instance of . - /// - /// The component type. - /// The component render mode. - /// The . - public BeforeRazorComponentEventData(Type componentType, RenderMode renderMode, HttpContext httpContext) - { - ComponentType = componentType; - RenderMode = renderMode; - HttpContext = httpContext; - } - - /// - /// The component type. - /// - public Type ComponentType { get; } - - /// - /// The component render mode. - /// - public RenderMode RenderMode { get; } - - /// - /// The . - /// - public HttpContext HttpContext { get; } - - /// - protected override int Count => 2; - - /// - protected override KeyValuePair this[int index] => index switch - { - 0 => new KeyValuePair(nameof(ComponentType), ComponentType), - 1 => new KeyValuePair(nameof(RenderMode), RenderMode), - _ => throw new IndexOutOfRangeException(nameof(index)) - }; -} - -/// -/// An that occurs after a Razor Component. -/// -public sealed class AfterRazorComponentEventData : EventData -{ - /// - /// The name of the event. - /// - public const string EventName = EventNamespace + "AfterRazorComponent"; - - /// - /// Initializes a new instance of . - /// - /// The component type. - /// The component render mode. - /// The . - public AfterRazorComponentEventData(Type componentType, RenderMode renderMode, HttpContext httpContext) - { - ComponentType = componentType; - RenderMode = renderMode; - HttpContext = httpContext; - } - - /// - /// The component type. - /// - public Type ComponentType { get; } - - /// - /// The component render mode. - /// - public RenderMode RenderMode { get; } - - /// - /// The . - /// - public HttpContext HttpContext { get; } - - /// - protected override int Count => 2; - - /// - protected override KeyValuePair this[int index] => index switch - { - 0 => new KeyValuePair(nameof(ComponentType), ComponentType), - 1 => new KeyValuePair(nameof(RenderMode), RenderMode), - _ => throw new IndexOutOfRangeException(nameof(index)) - }; -} diff --git a/src/Mvc/Mvc.ViewFeatures/src/MvcViewFeaturesDiagnosticListenerExtensions.cs b/src/Mvc/Mvc.ViewFeatures/src/MvcViewFeaturesDiagnosticListenerExtensions.cs index 2c704e6095a7..b456ac30fddf 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/MvcViewFeaturesDiagnosticListenerExtensions.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/MvcViewFeaturesDiagnosticListenerExtensions.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Diagnostics; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.ViewComponents; @@ -118,52 +117,6 @@ private static void ViewComponentAfterViewExecuteImpl(DiagnosticListener diagnos } } - public static void BeforeRazorComponent( - this DiagnosticListener diagnosticListener, - Type componentType, - RenderMode renderMode, - HttpContext httpContext) - { - // Inlinable fast-path check if Diagnositcs is enabled - if (diagnosticListener.IsEnabled()) - { - BeforeRazorComponentImpl(diagnosticListener, componentType, renderMode, httpContext); - } - } - - private static void BeforeRazorComponentImpl(DiagnosticListener diagnosticListener, Type componentType, RenderMode renderMode, HttpContext httpContext) - { - if (diagnosticListener.IsEnabled(Diagnostics.BeforeViewEventData.EventName)) - { - diagnosticListener.Write( - Diagnostics.BeforeRazorComponentEventData.EventName, - new BeforeRazorComponentEventData(componentType, renderMode, httpContext)); - } - } - - public static void AfterRazorComponent( - this DiagnosticListener diagnosticListener, - Type componentType, - RenderMode renderMode, - HttpContext httpContext) - { - // Inlinable fast-path check if Diagnositcs is enabled - if (diagnosticListener.IsEnabled()) - { - AfterRazorComponentImpl(diagnosticListener, componentType, renderMode, httpContext); - } - } - - private static void AfterRazorComponentImpl(DiagnosticListener diagnosticListener, Type componentType, RenderMode renderMode, HttpContext httpContext) - { - if (diagnosticListener.IsEnabled(Diagnostics.AfterViewEventData.EventName)) - { - diagnosticListener.Write( - Diagnostics.AfterRazorComponentEventData.EventName, - new AfterRazorComponentEventData(componentType, renderMode, httpContext)); - } - } - public static void BeforeView( this DiagnosticListener diagnosticListener, IView view, diff --git a/src/Mvc/Mvc.ViewFeatures/src/PublicAPI.Unshipped.txt b/src/Mvc/Mvc.ViewFeatures/src/PublicAPI.Unshipped.txt index 4afb2981b60c..60ecc69db485 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/PublicAPI.Unshipped.txt +++ b/src/Mvc/Mvc.ViewFeatures/src/PublicAPI.Unshipped.txt @@ -1,15 +1,7 @@ #nullable enable -Microsoft.AspNetCore.Mvc.Diagnostics.AfterRazorComponentEventData -Microsoft.AspNetCore.Mvc.Diagnostics.AfterRazorComponentEventData.RenderMode.get -> Microsoft.AspNetCore.Mvc.Rendering.RenderMode -Microsoft.AspNetCore.Mvc.Diagnostics.BeforeRazorComponentEventData -Microsoft.AspNetCore.Mvc.Diagnostics.BeforeRazorComponentEventData.RenderMode.get -> Microsoft.AspNetCore.Mvc.Rendering.RenderMode Microsoft.AspNetCore.Mvc.Rendering.FormMethod.Dialog = 2 -> Microsoft.AspNetCore.Mvc.Rendering.FormMethod Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.RazorComponentResult() -> void -~Microsoft.AspNetCore.Mvc.Diagnostics.AfterRazorComponentEventData.AfterRazorComponentEventData(System.Type componentType, Microsoft.AspNetCore.Mvc.Rendering.RenderMode renderMode, Microsoft.AspNetCore.Http.HttpContext httpContext) -> void -~Microsoft.AspNetCore.Mvc.Diagnostics.AfterRazorComponentEventData.HttpContext.get -> Microsoft.AspNetCore.Http.HttpContext -~Microsoft.AspNetCore.Mvc.Diagnostics.BeforeRazorComponentEventData.BeforeRazorComponentEventData(System.Type componentType, Microsoft.AspNetCore.Mvc.Rendering.RenderMode renderMode, Microsoft.AspNetCore.Http.HttpContext httpContext) -> void -~Microsoft.AspNetCore.Mvc.Diagnostics.BeforeRazorComponentEventData.HttpContext.get -> Microsoft.AspNetCore.Http.HttpContext ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext httpContext) -> System.Threading.Tasks.Task ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.Parameters.get -> System.Collections.Generic.IReadOnlyDictionary Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.RenderMode.get -> Microsoft.AspNetCore.Mvc.Rendering.RenderMode @@ -17,10 +9,6 @@ Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.RenderMode.set -> voi Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.StatusCode.get -> int? Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.StatusCode.set -> void Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor -~const Microsoft.AspNetCore.Mvc.Diagnostics.AfterRazorComponentEventData.EventName = "Microsoft.AspNetCore.Mvc.AfterRazorComponent" -> string -~const Microsoft.AspNetCore.Mvc.Diagnostics.BeforeRazorComponentEventData.EventName = "Microsoft.AspNetCore.Mvc.BeforeRazorComponent" -> string -~Microsoft.AspNetCore.Mvc.Diagnostics.AfterRazorComponentEventData.ComponentType.get -> System.Type -~Microsoft.AspNetCore.Mvc.Diagnostics.BeforeRazorComponentEventData.ComponentType.get -> System.Type ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.ComponentType.get -> System.Type ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.ContentType.get -> string ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.ContentType.set -> void @@ -31,7 +19,7 @@ Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.RazorComponentResult(object parameters) -> void ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.RazorComponentResult(System.Collections.Generic.IReadOnlyDictionary parameters) -> void ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.DiagnosticListener.get -> System.Diagnostics.DiagnosticListener -~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.RazorComponentResultExecutor(Microsoft.AspNetCore.Mvc.Infrastructure.IHttpResponseStreamWriterFactory writerFactory, System.Diagnostics.DiagnosticListener diagnosticListener) -> void +~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.RazorComponentResultExecutor(Microsoft.AspNetCore.Mvc.Infrastructure.IHttpResponseStreamWriterFactory writerFactory) -> void ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.WriterFactory.get -> Microsoft.AspNetCore.Mvc.Infrastructure.IHttpResponseStreamWriterFactory ~static readonly Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.DefaultContentType -> string ~virtual Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext httpContext, Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult result) -> System.Threading.Tasks.Task diff --git a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/ComponentPrerenderer.cs b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/ComponentPrerenderer.cs index 68ccaa29138d..d490e9c54ba4 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/ComponentPrerenderer.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/ComponentPrerenderer.cs @@ -38,7 +38,7 @@ public ComponentPrerenderer( public Dispatcher Dispatcher => _htmlRenderer.Dispatcher; - public async ValueTask PrerenderComponentAsync( + public async ValueTask PrerenderComponentAsync( HttpContext httpContext, Type componentType, RenderMode prerenderMode, @@ -152,7 +152,7 @@ internal static InvokedRenderModes.Mode GetPersistStateRenderMode(HttpContext ht } } - private async ValueTask StaticComponentAsync(HttpContext context, Type type, ParameterView parametersCollection) + private async ValueTask StaticComponentAsync(HttpContext context, Type type, ParameterView parametersCollection) { var htmlComponent = await PrerenderComponentCoreAsync( parametersCollection, @@ -161,7 +161,7 @@ private async ValueTask StaticComponentAsync(HttpContext cont return new PrerenderedComponentHtmlContent(_htmlRenderer.Dispatcher, htmlComponent, null, null); } - private async Task PrerenderedServerComponentAsync(HttpContext context, ServerComponentInvocationSequence invocationId, Type type, ParameterView parametersCollection) + private async Task PrerenderedServerComponentAsync(HttpContext context, ServerComponentInvocationSequence invocationId, Type type, ParameterView parametersCollection) { if (!context.Response.HasStarted) { @@ -182,7 +182,7 @@ private async Task PrerenderedServerComponentAsync(HttpContex return new PrerenderedComponentHtmlContent(_htmlRenderer.Dispatcher, htmlComponent, marker, null); } - private async ValueTask PrerenderedWebAssemblyComponentAsync(HttpContext context, Type type, ParameterView parametersCollection) + private async ValueTask PrerenderedWebAssemblyComponentAsync(HttpContext context, Type type, ParameterView parametersCollection) { var marker = WebAssemblyComponentSerializer.SerializeInvocation( type, @@ -197,7 +197,7 @@ private async ValueTask PrerenderedWebAssemblyComponentAsync( return new PrerenderedComponentHtmlContent(_htmlRenderer.Dispatcher, htmlComponent, null, marker); } - private IAsyncHtmlContent NonPrerenderedServerComponent(HttpContext context, ServerComponentInvocationSequence invocationId, Type type, ParameterView parametersCollection) + private IHtmlAsyncContent NonPrerenderedServerComponent(HttpContext context, ServerComponentInvocationSequence invocationId, Type type, ParameterView parametersCollection) { if (!context.Response.HasStarted) { @@ -208,7 +208,7 @@ private IAsyncHtmlContent NonPrerenderedServerComponent(HttpContext context, Ser return new PrerenderedComponentHtmlContent(null, null, marker, null); } - private static IAsyncHtmlContent NonPrerenderedWebAssemblyComponent(Type type, ParameterView parametersCollection) + private static IHtmlAsyncContent NonPrerenderedWebAssemblyComponent(Type type, ParameterView parametersCollection) { var marker = WebAssemblyComponentSerializer.SerializeInvocation(type, parametersCollection, prerendered: false); return new PrerenderedComponentHtmlContent(null, null, null, marker); @@ -216,7 +216,7 @@ private static IAsyncHtmlContent NonPrerenderedWebAssemblyComponent(Type type, P // An implementation of IHtmlContent that holds a reference to a component until we're ready to emit it as HTML to the response. // We don't construct the actual HTML until we receive the call to WriteTo. - private class PrerenderedComponentHtmlContent : IHtmlContent, IAsyncHtmlContent + private class PrerenderedComponentHtmlContent : IHtmlContent, IHtmlAsyncContent { private readonly Dispatcher _dispatcher; private readonly HtmlComponent _htmlToEmitOrNull; diff --git a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/IAsyncHtmlContent.cs b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/IHtmlAsyncContent.cs similarity index 89% rename from src/Mvc/Mvc.ViewFeatures/src/RazorComponents/IAsyncHtmlContent.cs rename to src/Mvc/Mvc.ViewFeatures/src/RazorComponents/IHtmlAsyncContent.cs index 52fb500c6179..679c9b0c4b9e 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/IAsyncHtmlContent.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/IHtmlAsyncContent.cs @@ -7,7 +7,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures; // For prerendered components, we can't use IHtmlComponent directly because it has no asynchrony and // hence can't dispatch to the renderer's sync context. -internal interface IAsyncHtmlContent : IHtmlContent +internal interface IHtmlAsyncContent : IHtmlContent { ValueTask WriteToAsync(TextWriter writer); } diff --git a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultExecutor.cs b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultExecutor.cs index 74860fbf6fc7..95002d8f07fd 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultExecutor.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultExecutor.cs @@ -27,15 +27,11 @@ public class RazorComponentResultExecutor /// Constructs an instance of . /// /// The . - /// The . public RazorComponentResultExecutor( - IHttpResponseStreamWriterFactory writerFactory, - DiagnosticListener diagnosticListener) + IHttpResponseStreamWriterFactory writerFactory) { ArgumentNullException.ThrowIfNull(writerFactory); - WriterFactory = writerFactory; - DiagnosticListener = diagnosticListener; } /// @@ -73,9 +69,7 @@ public virtual async Task ExecuteAsync(HttpContext httpContext, RazorComponentRe } await using var writer = WriterFactory.CreateWriter(response.Body, resolvedContentTypeEncoding); - DiagnosticListener.BeforeRazorComponent(result.ComponentType, result.RenderMode, httpContext); await RenderComponentToResponse(httpContext, result, writer); - DiagnosticListener.AfterRazorComponent(result.ComponentType, result.RenderMode, httpContext); // Perf: 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 From 3148d0f1319bfb0f73943854ec9a5c8ab59c8d60 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Thu, 9 Mar 2023 11:48:19 +0000 Subject: [PATCH 17/17] A bit more cleanup --- src/Mvc/Mvc.ViewFeatures/src/PublicAPI.Unshipped.txt | 1 - .../src/RazorComponents/RazorComponentResultExecutor.cs | 6 ------ 2 files changed, 7 deletions(-) diff --git a/src/Mvc/Mvc.ViewFeatures/src/PublicAPI.Unshipped.txt b/src/Mvc/Mvc.ViewFeatures/src/PublicAPI.Unshipped.txt index 60ecc69db485..5f27b8d359b5 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/PublicAPI.Unshipped.txt +++ b/src/Mvc/Mvc.ViewFeatures/src/PublicAPI.Unshipped.txt @@ -18,7 +18,6 @@ Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.RazorComponentResult(object parameters) -> void ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.RazorComponentResult(System.Collections.Generic.IReadOnlyDictionary parameters) -> void -~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.DiagnosticListener.get -> System.Diagnostics.DiagnosticListener ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.RazorComponentResultExecutor(Microsoft.AspNetCore.Mvc.Infrastructure.IHttpResponseStreamWriterFactory writerFactory) -> void ~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.WriterFactory.get -> Microsoft.AspNetCore.Mvc.Infrastructure.IHttpResponseStreamWriterFactory ~static readonly Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.DefaultContentType -> string diff --git a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultExecutor.cs b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultExecutor.cs index 95002d8f07fd..70c0a3a74e91 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultExecutor.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/RazorComponentResultExecutor.cs @@ -1,7 +1,6 @@ // 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; using System.Text; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Http; @@ -34,11 +33,6 @@ public RazorComponentResultExecutor( WriterFactory = writerFactory; } - /// - /// Gets the . - /// - protected DiagnosticListener DiagnosticListener { get; } - /// /// Gets the . ///