Skip to content

Commit f2a26e3

Browse files
Initial RazorComponentResult implementation
1 parent d189018 commit f2a26e3

File tree

8 files changed

+363
-21
lines changed

8 files changed

+363
-21
lines changed

src/Mvc/Mvc.ViewFeatures/src/DependencyInjection/MvcViewFeaturesMvcCoreBuilderExtensions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ internal static void AddViewServices(IServiceCollection services)
212212
services.TryAddScoped<ComponentStatePersistenceManager>();
213213
services.TryAddScoped<PersistentComponentState>(sp => sp.GetRequiredService<ComponentStatePersistenceManager>().State);
214214
services.TryAddScoped<IErrorBoundaryLogger, PrerenderingErrorBoundaryLogger>();
215+
services.TryAddScoped<RazorComponentResultExecutor>();
215216

216217
services.TryAddTransient<ControllerSaveTempDataPropertyFilter>();
217218

src/Mvc/Mvc.ViewFeatures/src/Diagnostics/MvcDiagnostics.cs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using Microsoft.AspNetCore.Components;
45
using Microsoft.AspNetCore.Mvc.Abstractions;
56
using Microsoft.AspNetCore.Mvc.Rendering;
67
using Microsoft.AspNetCore.Mvc.ViewComponents;
@@ -438,3 +439,103 @@ public ViewNotFoundEventData(ActionContext actionContext, bool isMainPage, Actio
438439
_ => throw new IndexOutOfRangeException(nameof(index))
439440
};
440441
}
442+
443+
/// <summary>
444+
/// An <see cref="EventData"/> that occurs before a Razor Component.
445+
/// </summary>
446+
public sealed class BeforeRazorComponentEventData : EventData
447+
{
448+
/// <summary>
449+
/// The name of the event.
450+
/// </summary>
451+
public const string EventName = EventNamespace + "BeforeRazorComponent";
452+
453+
/// <summary>
454+
/// Initializes a new instance of <see cref="BeforeRazorComponentEventData"/>.
455+
/// </summary>
456+
/// <param name="componentType">The component type.</param>
457+
/// <param name="renderMode">The component render mode.</param>
458+
/// <param name="actionContext">The <see cref="ActionContext"/>.</param>
459+
public BeforeRazorComponentEventData(Type componentType, RenderMode renderMode, ActionContext actionContext)
460+
{
461+
ComponentType = componentType;
462+
RenderMode = renderMode;
463+
ActionContext = actionContext;
464+
}
465+
466+
/// <summary>
467+
/// The component type.
468+
/// </summary>
469+
public Type ComponentType { get; }
470+
471+
/// <summary>
472+
/// The component render mode.
473+
/// </summary>
474+
public RenderMode RenderMode { get; }
475+
476+
/// <summary>
477+
/// The <see cref="ActionContext"/>.
478+
/// </summary>
479+
public ActionContext ActionContext { get; }
480+
481+
/// <inheritdoc/>
482+
protected override int Count => 2;
483+
484+
/// <inheritdoc/>
485+
protected override KeyValuePair<string, object> this[int index] => index switch
486+
{
487+
0 => new KeyValuePair<string, object>(nameof(ComponentType), ComponentType),
488+
1 => new KeyValuePair<string, object>(nameof(RenderMode), RenderMode),
489+
_ => throw new IndexOutOfRangeException(nameof(index))
490+
};
491+
}
492+
493+
/// <summary>
494+
/// An <see cref="EventData"/> that occurs after a Razor Component.
495+
/// </summary>
496+
public sealed class AfterRazorComponentEventData : EventData
497+
{
498+
/// <summary>
499+
/// The name of the event.
500+
/// </summary>
501+
public const string EventName = EventNamespace + "AfterRazorComponent";
502+
503+
/// <summary>
504+
/// Initializes a new instance of <see cref="BeforeRazorComponentEventData"/>.
505+
/// </summary>
506+
/// <param name="componentType">The component type.</param>
507+
/// <param name="renderMode">The component render mode.</param>
508+
/// <param name="actionContext">The <see cref="ActionContext"/>.</param>
509+
public AfterRazorComponentEventData(Type componentType, RenderMode renderMode, ActionContext actionContext)
510+
{
511+
ComponentType = componentType;
512+
RenderMode = renderMode;
513+
ActionContext = actionContext;
514+
}
515+
516+
/// <summary>
517+
/// The component type.
518+
/// </summary>
519+
public Type ComponentType { get; }
520+
521+
/// <summary>
522+
/// The component render mode.
523+
/// </summary>
524+
public RenderMode RenderMode { get; }
525+
526+
/// <summary>
527+
/// The <see cref="ActionContext"/>.
528+
/// </summary>
529+
public ActionContext ActionContext { get; }
530+
531+
/// <inheritdoc/>
532+
protected override int Count => 2;
533+
534+
/// <inheritdoc/>
535+
protected override KeyValuePair<string, object> this[int index] => index switch
536+
{
537+
0 => new KeyValuePair<string, object>(nameof(ComponentType), ComponentType),
538+
1 => new KeyValuePair<string, object>(nameof(RenderMode), RenderMode),
539+
_ => throw new IndexOutOfRangeException(nameof(index))
540+
};
541+
}

src/Mvc/Mvc.ViewFeatures/src/MvcViewFeaturesDiagnosticListenerExtensions.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,52 @@ private static void ViewComponentAfterViewExecuteImpl(DiagnosticListener diagnos
117117
}
118118
}
119119

120+
public static void BeforeRazorComponent(
121+
this DiagnosticListener diagnosticListener,
122+
Type componentType,
123+
RenderMode renderMode,
124+
ActionContext actionContext)
125+
{
126+
// Inlinable fast-path check if Diagnositcs is enabled
127+
if (diagnosticListener.IsEnabled())
128+
{
129+
BeforeRazorComponentImpl(diagnosticListener, componentType, renderMode, actionContext);
130+
}
131+
}
132+
133+
private static void BeforeRazorComponentImpl(DiagnosticListener diagnosticListener, Type componentType, RenderMode renderMode, ActionContext actionContext)
134+
{
135+
if (diagnosticListener.IsEnabled(Diagnostics.BeforeViewEventData.EventName))
136+
{
137+
diagnosticListener.Write(
138+
Diagnostics.BeforeRazorComponentEventData.EventName,
139+
new BeforeRazorComponentEventData(componentType, renderMode, actionContext));
140+
}
141+
}
142+
143+
public static void AfterRazorComponent(
144+
this DiagnosticListener diagnosticListener,
145+
Type componentType,
146+
RenderMode renderMode,
147+
ActionContext actionContext)
148+
{
149+
// Inlinable fast-path check if Diagnositcs is enabled
150+
if (diagnosticListener.IsEnabled())
151+
{
152+
AfterRazorComponentImpl(diagnosticListener, componentType, renderMode, actionContext);
153+
}
154+
}
155+
156+
private static void AfterRazorComponentImpl(DiagnosticListener diagnosticListener, Type componentType, RenderMode renderMode, ActionContext actionContext)
157+
{
158+
if (diagnosticListener.IsEnabled(Diagnostics.AfterViewEventData.EventName))
159+
{
160+
diagnosticListener.Write(
161+
Diagnostics.AfterRazorComponentEventData.EventName,
162+
new AfterRazorComponentEventData(componentType, renderMode, actionContext));
163+
}
164+
}
165+
120166
public static void BeforeView(
121167
this DiagnosticListener diagnosticListener,
122168
IView view,
Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,34 @@
11
#nullable enable
2-
Microsoft.AspNetCore.Mvc.Rendering.FormMethod.Dialog = 2 -> Microsoft.AspNetCore.Mvc.Rendering.FormMethod
2+
Microsoft.AspNetCore.Mvc.Diagnostics.AfterRazorComponentEventData
3+
Microsoft.AspNetCore.Mvc.Diagnostics.AfterRazorComponentEventData.RenderMode.get -> Microsoft.AspNetCore.Mvc.Rendering.RenderMode
4+
Microsoft.AspNetCore.Mvc.Diagnostics.BeforeRazorComponentEventData
5+
Microsoft.AspNetCore.Mvc.Diagnostics.BeforeRazorComponentEventData.RenderMode.get -> Microsoft.AspNetCore.Mvc.Rendering.RenderMode
6+
Microsoft.AspNetCore.Mvc.Rendering.FormMethod.Dialog = 2 -> Microsoft.AspNetCore.Mvc.Rendering.FormMethod
7+
Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult
8+
Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.Parameters.get -> Microsoft.AspNetCore.Components.ParameterView
9+
Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.Parameters.set -> void
10+
Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.RenderMode.get -> Microsoft.AspNetCore.Mvc.Rendering.RenderMode
11+
Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.RenderMode.set -> void
12+
Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.StatusCode.get -> int?
13+
Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.StatusCode.set -> void
14+
Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor
15+
Microsoft.Extensions.DependencyInjection.RazorComponentsServiceCollectionExtensions
16+
~const Microsoft.AspNetCore.Mvc.Diagnostics.AfterRazorComponentEventData.EventName = "Microsoft.AspNetCore.Mvc.AfterRazorComponent" -> string
17+
~const Microsoft.AspNetCore.Mvc.Diagnostics.BeforeRazorComponentEventData.EventName = "Microsoft.AspNetCore.Mvc.BeforeRazorComponent" -> string
18+
~Microsoft.AspNetCore.Mvc.Diagnostics.AfterRazorComponentEventData.ActionContext.get -> Microsoft.AspNetCore.Mvc.ActionContext
19+
~Microsoft.AspNetCore.Mvc.Diagnostics.AfterRazorComponentEventData.AfterRazorComponentEventData(System.Type componentType, Microsoft.AspNetCore.Mvc.Rendering.RenderMode renderMode, Microsoft.AspNetCore.Mvc.ActionContext actionContext) -> void
20+
~Microsoft.AspNetCore.Mvc.Diagnostics.AfterRazorComponentEventData.ComponentType.get -> System.Type
21+
~Microsoft.AspNetCore.Mvc.Diagnostics.BeforeRazorComponentEventData.ActionContext.get -> Microsoft.AspNetCore.Mvc.ActionContext
22+
~Microsoft.AspNetCore.Mvc.Diagnostics.BeforeRazorComponentEventData.BeforeRazorComponentEventData(System.Type componentType, Microsoft.AspNetCore.Mvc.Rendering.RenderMode renderMode, Microsoft.AspNetCore.Mvc.ActionContext actionContext) -> void
23+
~Microsoft.AspNetCore.Mvc.Diagnostics.BeforeRazorComponentEventData.ComponentType.get -> System.Type
24+
~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.ComponentType.get -> System.Type
25+
~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.ContentType.get -> string
26+
~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.ContentType.set -> void
27+
~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.RazorComponentResult(System.Type componentType) -> void
28+
~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.DiagnosticListener.get -> System.Diagnostics.DiagnosticListener
29+
~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.RazorComponentResultExecutor(Microsoft.AspNetCore.Mvc.Infrastructure.IHttpResponseStreamWriterFactory writerFactory, System.Diagnostics.DiagnosticListener diagnosticListener) -> void
30+
~Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.WriterFactory.get -> Microsoft.AspNetCore.Mvc.Infrastructure.IHttpResponseStreamWriterFactory
31+
~override Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult.ExecuteResultAsync(Microsoft.AspNetCore.Mvc.ActionContext context) -> System.Threading.Tasks.Task
32+
~static Microsoft.Extensions.DependencyInjection.RazorComponentsServiceCollectionExtensions.AddRazorComponents(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) -> void
33+
~static readonly Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.DefaultContentType -> string
34+
~virtual Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResultExecutor.ExecuteAsync(Microsoft.AspNetCore.Mvc.ActionContext actionContext, Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponentResult result) -> System.Threading.Tasks.Task

src/Mvc/Mvc.ViewFeatures/src/RazorComponents/ComponentPrerenderer.cs

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,13 @@ public ComponentPrerenderer(
3838

3939
public Dispatcher Dispatcher => _htmlRenderer.Dispatcher;
4040

41-
public async ValueTask<IHtmlContent> PrerenderComponentAsync(
42-
ViewContext viewContext,
41+
public async ValueTask<IAsyncHtmlContent> PrerenderComponentAsync(
42+
ActionContext actionContext,
4343
Type componentType,
4444
RenderMode prerenderMode,
4545
ParameterView parameters)
4646
{
47-
ArgumentNullException.ThrowIfNull(viewContext);
47+
ArgumentNullException.ThrowIfNull(actionContext);
4848
ArgumentNullException.ThrowIfNull(componentType);
4949

5050
if (!typeof(IComponent).IsAssignableFrom(componentType))
@@ -53,19 +53,19 @@ public async ValueTask<IHtmlContent> PrerenderComponentAsync(
5353
}
5454

5555
// Make sure we only initialize the services once, but on every call we wait for that process to complete
56-
var httpContext = viewContext.HttpContext;
56+
var httpContext = actionContext.HttpContext;
5757
lock (_servicesInitializedLock)
5858
{
5959
_servicesInitializedTask ??= InitializeStandardComponentServicesAsync(httpContext);
6060
}
6161
await _servicesInitializedTask;
6262

63-
UpdateSaveStateRenderMode(viewContext, prerenderMode);
63+
UpdateSaveStateRenderMode(actionContext, prerenderMode);
6464

6565
return prerenderMode switch
6666
{
67-
RenderMode.Server => NonPrerenderedServerComponent(httpContext, GetOrCreateInvocationId(viewContext), componentType, parameters),
68-
RenderMode.ServerPrerendered => await PrerenderedServerComponentAsync(httpContext, GetOrCreateInvocationId(viewContext), componentType, parameters),
67+
RenderMode.Server => NonPrerenderedServerComponent(httpContext, GetOrCreateInvocationId(actionContext), componentType, parameters),
68+
RenderMode.ServerPrerendered => await PrerenderedServerComponentAsync(httpContext, GetOrCreateInvocationId(actionContext), componentType, parameters),
6969
RenderMode.Static => await StaticComponentAsync(httpContext, componentType, parameters),
7070
RenderMode.WebAssembly => NonPrerenderedWebAssemblyComponent(componentType, parameters),
7171
RenderMode.WebAssemblyPrerendered => await PrerenderedWebAssemblyComponentAsync(httpContext, componentType, parameters),
@@ -101,29 +101,30 @@ private async ValueTask<HtmlComponent> PrerenderComponentCoreAsync(
101101
}
102102
}
103103

104-
private static ServerComponentInvocationSequence GetOrCreateInvocationId(ViewContext viewContext)
104+
private static ServerComponentInvocationSequence GetOrCreateInvocationId(ActionContext actionContext)
105105
{
106-
if (!viewContext.Items.TryGetValue(ComponentSequenceKey, out var result))
106+
if (!actionContext.HttpContext.Items.TryGetValue(ComponentSequenceKey, out var result))
107107
{
108108
result = new ServerComponentInvocationSequence();
109-
viewContext.Items[ComponentSequenceKey] = result;
109+
actionContext.HttpContext.Items[ComponentSequenceKey] = result;
110110
}
111111

112112
return (ServerComponentInvocationSequence)result;
113113
}
114114

115115
// Internal for test only
116-
internal static void UpdateSaveStateRenderMode(ViewContext viewContext, RenderMode mode)
116+
internal static void UpdateSaveStateRenderMode(ActionContext actionContext, RenderMode mode)
117117
{
118+
// TODO: This will all have to change when we support multiple render modes in the same response
118119
if (mode == RenderMode.ServerPrerendered || mode == RenderMode.WebAssemblyPrerendered)
119120
{
120-
if (!viewContext.Items.TryGetValue(InvokedRenderModesKey, out var result))
121+
if (!actionContext.HttpContext.Items.TryGetValue(InvokedRenderModesKey, out var result))
121122
{
122123
result = new InvokedRenderModes(mode is RenderMode.ServerPrerendered ?
123124
InvokedRenderModes.Mode.Server :
124125
InvokedRenderModes.Mode.WebAssembly);
125126

126-
viewContext.Items[InvokedRenderModesKey] = result;
127+
actionContext.HttpContext.Items[InvokedRenderModesKey] = result;
127128
}
128129
else
129130
{
@@ -152,7 +153,7 @@ internal static InvokedRenderModes.Mode GetPersistStateRenderMode(ViewContext vi
152153
}
153154
}
154155

155-
private async ValueTask<IHtmlContent> StaticComponentAsync(HttpContext context, Type type, ParameterView parametersCollection)
156+
private async ValueTask<IAsyncHtmlContent> StaticComponentAsync(HttpContext context, Type type, ParameterView parametersCollection)
156157
{
157158
var htmlComponent = await PrerenderComponentCoreAsync(
158159
parametersCollection,
@@ -161,7 +162,7 @@ private async ValueTask<IHtmlContent> StaticComponentAsync(HttpContext context,
161162
return new PrerenderedComponentHtmlContent(_htmlRenderer.Dispatcher, htmlComponent, null, null);
162163
}
163164

164-
private async Task<IHtmlContent> PrerenderedServerComponentAsync(HttpContext context, ServerComponentInvocationSequence invocationId, Type type, ParameterView parametersCollection)
165+
private async Task<IAsyncHtmlContent> PrerenderedServerComponentAsync(HttpContext context, ServerComponentInvocationSequence invocationId, Type type, ParameterView parametersCollection)
165166
{
166167
if (!context.Response.HasStarted)
167168
{
@@ -182,7 +183,7 @@ private async Task<IHtmlContent> PrerenderedServerComponentAsync(HttpContext con
182183
return new PrerenderedComponentHtmlContent(_htmlRenderer.Dispatcher, htmlComponent, marker, null);
183184
}
184185

185-
private async ValueTask<IHtmlContent> PrerenderedWebAssemblyComponentAsync(HttpContext context, Type type, ParameterView parametersCollection)
186+
private async ValueTask<IAsyncHtmlContent> PrerenderedWebAssemblyComponentAsync(HttpContext context, Type type, ParameterView parametersCollection)
186187
{
187188
var marker = WebAssemblyComponentSerializer.SerializeInvocation(
188189
type,
@@ -197,7 +198,7 @@ private async ValueTask<IHtmlContent> PrerenderedWebAssemblyComponentAsync(HttpC
197198
return new PrerenderedComponentHtmlContent(_htmlRenderer.Dispatcher, htmlComponent, null, marker);
198199
}
199200

200-
private IHtmlContent NonPrerenderedServerComponent(HttpContext context, ServerComponentInvocationSequence invocationId, Type type, ParameterView parametersCollection)
201+
private IAsyncHtmlContent NonPrerenderedServerComponent(HttpContext context, ServerComponentInvocationSequence invocationId, Type type, ParameterView parametersCollection)
201202
{
202203
if (!context.Response.HasStarted)
203204
{
@@ -208,7 +209,7 @@ private IHtmlContent NonPrerenderedServerComponent(HttpContext context, ServerCo
208209
return new PrerenderedComponentHtmlContent(null, null, marker, null);
209210
}
210211

211-
private static IHtmlContent NonPrerenderedWebAssemblyComponent(Type type, ParameterView parametersCollection)
212+
private static IAsyncHtmlContent NonPrerenderedWebAssemblyComponent(Type type, ParameterView parametersCollection)
212213
{
213214
var marker = WebAssemblyComponentSerializer.SerializeInvocation(type, parametersCollection, prerendered: false);
214215
return new PrerenderedComponentHtmlContent(null, null, null, marker);
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
// Licensed to the .NET Foundation under one or more agreements.
1+
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using Microsoft.AspNetCore.Html;
5+
46
namespace Microsoft.AspNetCore.Mvc.ViewFeatures;
57

68
// For prerendered components, we can't use IHtmlComponent directly because it has no asynchrony and
79
// hence can't dispatch to the renderer's sync context.
8-
internal interface IAsyncHtmlContent
10+
internal interface IAsyncHtmlContent : IHtmlContent
911
{
1012
ValueTask WriteToAsync(TextWriter writer);
1113
}

0 commit comments

Comments
 (0)