(ParameterView.Empty)));
-
- // Assert
- AssertHtmlContentEquals(expectedHtml, result);
+ // Assert
+ AssertHtmlContentEquals(expectedHtml, result);
+ });
}
[Fact]
- public void RenderComponentAsync_MarksSelectedOptionsAsSelected()
+ public async Task RenderComponentAsync_MarksSelectedOptionsAsSelected()
{
// Arrange
var expectedHtml = "" +
@@ -308,16 +359,18 @@ public void RenderComponentAsync_MarksSelectedOptionsAsSelected()
})).BuildServiceProvider();
var htmlRenderer = GetHtmlRenderer(serviceProvider);
+ await htmlRenderer.Dispatcher.InvokeAsync(async () =>
+ {
+ // Act
+ var result = await htmlRenderer.RenderComponentAsync();
- // Act
- var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterView.Empty)));
-
- // Assert
- AssertHtmlContentEquals(expectedHtml, result);
+ // Assert
+ AssertHtmlContentEquals(expectedHtml, result);
+ });
}
[Fact]
- public void RenderComponentAsync_RendersValueAttributeAsTextContentOfTextareaElement()
+ public async Task RenderComponentAsync_RendersValueAttributeAsTextContentOfTextareaElement()
{
// Arrange
var expectedHtml = "";
@@ -329,17 +382,20 @@ public void RenderComponentAsync_RendersValueAttributeAsTextContentOfTextareaEle
rtb.AddAttribute(3, "cols", "20");
rtb.CloseElement();
})).BuildServiceProvider();
- var htmlRenderer = GetHtmlRenderer(serviceProvider);
- // Act
- var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterView.Empty)));
+ var htmlRenderer = GetHtmlRenderer(serviceProvider);
+ await htmlRenderer.Dispatcher.InvokeAsync(async () =>
+ {
+ // Act
+ var result = await htmlRenderer.RenderComponentAsync();
- // Assert
- AssertHtmlContentEquals(expectedHtml, result);
+ // Assert
+ AssertHtmlContentEquals(expectedHtml, result);
+ });
}
[Fact]
- public void RenderComponentAsync_RendersTextareaElementWithoutValueAttribute()
+ public async Task RenderComponentAsync_RendersTextareaElementWithoutValueAttribute()
{
// Arrange
var expectedHtml = "";
@@ -351,17 +407,20 @@ public void RenderComponentAsync_RendersTextareaElementWithoutValueAttribute()
rtb.AddContent(3, "Hello -encoded content!");
rtb.CloseElement();
})).BuildServiceProvider();
- var htmlRenderer = GetHtmlRenderer(serviceProvider);
- // Act
- var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterView.Empty)));
+ var htmlRenderer = GetHtmlRenderer(serviceProvider);
+ await htmlRenderer.Dispatcher.InvokeAsync(async () =>
+ {
+ // Act
+ var result = await htmlRenderer.RenderComponentAsync();
- // Assert
- AssertHtmlContentEquals(expectedHtml, result);
+ // Assert
+ AssertHtmlContentEquals(expectedHtml, result);
+ });
}
[Fact]
- public void RenderComponentAsync_RendersTextareaElementWithoutValueAttributeOrTextContent()
+ public async Task RenderComponentAsync_RendersTextareaElementWithoutValueAttributeOrTextContent()
{
// Arrange
var expectedHtml = "";
@@ -372,17 +431,20 @@ public void RenderComponentAsync_RendersTextareaElementWithoutValueAttributeOrTe
rtb.AddAttribute(2, "cols", "20");
rtb.CloseElement();
})).BuildServiceProvider();
- var htmlRenderer = GetHtmlRenderer(serviceProvider);
- // Act
- var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterView.Empty)));
+ var htmlRenderer = GetHtmlRenderer(serviceProvider);
+ await htmlRenderer.Dispatcher.InvokeAsync(async () =>
+ {
+ // Act
+ var result = await htmlRenderer.RenderComponentAsync();
- // Assert
- AssertHtmlContentEquals(expectedHtml, result);
+ // Assert
+ AssertHtmlContentEquals(expectedHtml, result);
+ });
}
[Fact]
- public void RenderComponentAsync_ValueAttributeOfTextareaElementOverridesTextContent()
+ public async Task RenderComponentAsync_ValueAttributeOfTextareaElementOverridesTextContent()
{
// Arrange
var expectedHtml = "";
@@ -393,17 +455,20 @@ public void RenderComponentAsync_ValueAttributeOfTextareaElementOverridesTextCon
rtb.AddContent(3, "Some content");
rtb.CloseElement();
})).BuildServiceProvider();
- var htmlRenderer = GetHtmlRenderer(serviceProvider);
- // Act
- var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterView.Empty)));
+ var htmlRenderer = GetHtmlRenderer(serviceProvider);
+ await htmlRenderer.Dispatcher.InvokeAsync(async () =>
+ {
+ // Act
+ var result = await htmlRenderer.RenderComponentAsync();
- // Assert
- AssertHtmlContentEquals(expectedHtml, result);
+ // Assert
+ AssertHtmlContentEquals(expectedHtml, result);
+ });
}
[Fact]
- public void RenderComponentAsync_RendersSelfClosingElement()
+ public async Task RenderComponentAsync_RendersSelfClosingElement()
{
// Arrange
var expectedHtml = " ";
@@ -414,17 +479,20 @@ public void RenderComponentAsync_RendersSelfClosingElement()
rtb.AddAttribute(2, "id", "Test");
rtb.CloseElement();
})).BuildServiceProvider();
- var htmlRenderer = GetHtmlRenderer(serviceProvider);
- // Act
- var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterView.Empty)));
+ var htmlRenderer = GetHtmlRenderer(serviceProvider);
+ await htmlRenderer.Dispatcher.InvokeAsync(async () =>
+ {
+ // Act
+ var result = await htmlRenderer.RenderComponentAsync();
- // Assert
- AssertHtmlContentEquals(expectedHtml, result);
- }
+ // Assert
+ AssertHtmlContentEquals(expectedHtml, result);
+ });
+ }
[Fact]
- public void RenderComponentAsync_RendersSelfClosingElementWithTextComponentAsNormalElement()
+ public async Task RenderComponentAsync_RendersSelfClosingElementWithTextComponentAsNormalElement()
{
// Arrange
var expectedHtml = " Something";
@@ -434,17 +502,20 @@ public void RenderComponentAsync_RendersSelfClosingElementWithTextComponentAsNor
rtb.AddContent(1, "Something");
rtb.CloseElement();
})).BuildServiceProvider();
- var htmlRenderer = GetHtmlRenderer(serviceProvider);
- // Act
- var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterView.Empty)));
+ var htmlRenderer = GetHtmlRenderer(serviceProvider);
+ await htmlRenderer.Dispatcher.InvokeAsync(async () =>
+ {
+ // Act
+ var result = await htmlRenderer.RenderComponentAsync();
- // Assert
- AssertHtmlContentEquals(expectedHtml, result);
+ // Assert
+ AssertHtmlContentEquals(expectedHtml, result);
+ });
}
[Fact]
- public void RenderComponentAsync_RendersSelfClosingElementBySkippingElementReferenceCapture()
+ public async Task RenderComponentAsync_RendersSelfClosingElementBySkippingElementReferenceCapture()
{
// Arrange
var expectedHtml = " ";
@@ -456,17 +527,20 @@ public void RenderComponentAsync_RendersSelfClosingElementBySkippingElementRefer
rtb.AddElementReferenceCapture(3, inputReference => _ = inputReference);
rtb.CloseElement();
})).BuildServiceProvider();
- var htmlRenderer = GetHtmlRenderer(serviceProvider);
- // Act
- var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterView.Empty)));
+ var htmlRenderer = GetHtmlRenderer(serviceProvider);
+ await htmlRenderer.Dispatcher.InvokeAsync(async () =>
+ {
+ // Act
+ var result = await htmlRenderer.RenderComponentAsync();
- // Assert
- AssertHtmlContentEquals(expectedHtml, result);
+ // Assert
+ AssertHtmlContentEquals(expectedHtml, result);
+ });
}
[Fact]
- public void RenderComponentAsync_MarksSelectedOptionsAsSelected_WithOptGroups()
+ public async Task RenderComponentAsync_MarksSelectedOptionsAsSelected_WithOptGroups()
{
// Arrange
var expectedHtml =
@@ -494,16 +568,18 @@ public void RenderComponentAsync_MarksSelectedOptionsAsSelected_WithOptGroups()
})).BuildServiceProvider();
var htmlRenderer = GetHtmlRenderer(serviceProvider);
+ await htmlRenderer.Dispatcher.InvokeAsync(async () =>
+ {
+ // Act
+ var result = await htmlRenderer.RenderComponentAsync();
- // Act
- var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterView.Empty)));
-
- // Assert
- AssertHtmlContentEquals(expectedHtml, result);
+ // Assert
+ AssertHtmlContentEquals(expectedHtml, result);
+ });
}
[Fact]
- public void RenderComponentAsync_CanRenderComponentAsyncWithChildrenComponents()
+ public async Task RenderComponentAsync_CanRenderComponentAsyncWithChildrenComponents()
{
// Arrange
var expectedHtml = new[] {
@@ -523,16 +599,18 @@ public void RenderComponentAsync_CanRenderComponentAsyncWithChildrenComponents()
})).BuildServiceProvider();
var htmlRenderer = GetHtmlRenderer(serviceProvider);
+ await htmlRenderer.Dispatcher.InvokeAsync(async () =>
+ {
+ // Act
+ var result = await htmlRenderer.RenderComponentAsync();
- // Act
- var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterView.Empty)));
-
- // Assert
- AssertHtmlContentEquals(expectedHtml, result);
+ // Assert
+ AssertHtmlContentEquals(expectedHtml, result);
+ });
}
[Fact]
- public void RenderComponentAsync_ComponentReferenceNoops()
+ public async Task RenderComponentAsync_ComponentReferenceNoops()
{
// Arrange
var expectedHtml = new[] {
@@ -553,16 +631,18 @@ public void RenderComponentAsync_ComponentReferenceNoops()
})).BuildServiceProvider();
var htmlRenderer = GetHtmlRenderer(serviceProvider);
+ await htmlRenderer.Dispatcher.InvokeAsync(async () =>
+ {
+ // Act
+ var result = await htmlRenderer.RenderComponentAsync();
- // Act
- var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterView.Empty)));
-
- // Assert
- AssertHtmlContentEquals(expectedHtml, result);
+ // Assert
+ AssertHtmlContentEquals(expectedHtml, result);
+ });
}
[Fact]
- public void RenderComponentAsync_CanPassParameters()
+ public async Task RenderComponentAsync_CanPassParameters()
{
// Arrange
var expectedHtml = new[] {
@@ -581,24 +661,26 @@ public void RenderComponentAsync_CanPassParameters()
var serviceProvider = new ServiceCollection()
.AddSingleton(new Func(Content))
.BuildServiceProvider();
-
- var htmlRenderer = GetHtmlRenderer(serviceProvider);
Action change = (ChangeEventArgs changeArgs) => throw new InvalidOperationException();
- // Act
- var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(
- ParameterView.FromDictionary(new Dictionary
- {
+ var htmlRenderer = GetHtmlRenderer(serviceProvider);
+ await htmlRenderer.Dispatcher.InvokeAsync(async () =>
+ {
+ // Act
+ var result = await htmlRenderer.RenderComponentAsync(
+ ParameterView.FromDictionary(new Dictionary
+ {
{ "update", change },
{ "value", 5 }
- }))));
+ }));
- // Assert
- AssertHtmlContentEquals(expectedHtml, result);
+ // Assert
+ AssertHtmlContentEquals(expectedHtml, result);
+ });
}
[Fact]
- public void RenderComponentAsync_CanRenderComponentAsyncWithRenderFragmentContent()
+ public async Task RenderComponentAsync_CanRenderComponentAsyncWithRenderFragmentContent()
{
// Arrange
var expectedHtml = new[] {
@@ -615,16 +697,18 @@ public void RenderComponentAsync_CanRenderComponentAsyncWithRenderFragmentConten
})).BuildServiceProvider();
var htmlRenderer = GetHtmlRenderer(serviceProvider);
+ await htmlRenderer.Dispatcher.InvokeAsync(async () =>
+ {
+ // Act
+ var result = await htmlRenderer.RenderComponentAsync();
- // Act
- var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterView.Empty)));
-
- // Assert
- AssertHtmlContentEquals(expectedHtml, result);
+ // Assert
+ AssertHtmlContentEquals(expectedHtml, result);
+ });
}
[Fact]
- public void RenderComponentAsync_ElementRefsNoops()
+ public async Task RenderComponentAsync_ElementRefsNoops()
{
// Arrange
var expectedHtml = new[]
@@ -644,37 +728,14 @@ public void RenderComponentAsync_ElementRefsNoops()
})).BuildServiceProvider();
var htmlRenderer = GetHtmlRenderer(serviceProvider);
-
- // Act
- var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterView.Empty)));
-
- // Assert
- AssertHtmlContentEquals(expectedHtml, result);
- }
-
- private IHtmlContent GetResult(Task task)
- {
- Assert.True(task.IsCompleted);
- if (task.IsCompletedSuccessfully)
- {
- return task.Result.HtmlContent;
- }
- else
+ await htmlRenderer.Dispatcher.InvokeAsync(async () =>
{
- ExceptionDispatchInfo.Capture(task.Exception).Throw();
- throw new InvalidOperationException("We will never hit this line");
- }
- }
-
- private void AssertHtmlContentEquals(IEnumerable expected, IHtmlContent actual)
- {
- var expectedString = string.Concat(expected);
- AssertHtmlContentEquals(expectedString, actual);
- }
+ // Act
+ var result = await htmlRenderer.RenderComponentAsync();
- private void AssertHtmlContentEquals(string expected, IHtmlContent actual)
- {
- Assert.Equal(expected, HtmlContentUtilities.HtmlContentToString(actual, _encoder));
+ // Assert
+ AssertHtmlContentEquals(expectedHtml, result);
+ });
}
private class ComponentWithParameters : IComponent
@@ -705,15 +766,17 @@ public async Task CanRender_AsyncComponent()
var serviceProvider = new ServiceCollection().AddSingleton().BuildServiceProvider();
var htmlRenderer = GetHtmlRenderer(serviceProvider);
-
- // Act
- var result = await htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterView.FromDictionary(new Dictionary
+ await htmlRenderer.Dispatcher.InvokeAsync(async () =>
{
- ["Value"] = 10
- })));
+ // Act
+ var result = await htmlRenderer.RenderComponentAsync(ParameterView.FromDictionary(new Dictionary
+ {
+ ["Value"] = 10
+ }));
- // Assert
- AssertHtmlContentEquals(expectedHtml, result.HtmlContent);
+ // Assert
+ AssertHtmlContentEquals(expectedHtml, result);
+ });
}
[Fact]
@@ -729,42 +792,203 @@ public async Task CanRender_NestedAsyncComponents()
var serviceProvider = new ServiceCollection().AddSingleton().BuildServiceProvider();
var htmlRenderer = GetHtmlRenderer(serviceProvider);
+ await htmlRenderer.Dispatcher.InvokeAsync(async () =>
+ {
+ // Act
+ var result = await htmlRenderer.RenderComponentAsync(ParameterView.FromDictionary(new Dictionary
+ {
+ ["Nested"] = false,
+ ["Value"] = 10
+ }));
- // Act
- var result = await htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterView.FromDictionary(new Dictionary
+ // Assert
+ AssertHtmlContentEquals(expectedHtml, result);
+ });
+ }
+
+ [Fact]
+ public async Task RenderComponentAsync_CanCauseRerenderingOfEarlierComponents()
+ {
+ // This scenario is important when there are multiple root components. The default project
+ // template relies on this - HeadOutlet re-renders when a later PageTitle component is rendered,
+ // even though they are not within the same root component.
+
+ var htmlRenderer = GetHtmlRenderer();
+ await htmlRenderer.Dispatcher.InvokeAsync(async () =>
{
- ["Nested"] = false,
- ["Value"] = 10
- })));
+ // Arrange/Act/Assert 1: initially get some empty output
+ var first = await htmlRenderer.RenderComponentAsync(ParameterView.FromDictionary(new Dictionary
+ {
+ { nameof(SectionOutlet.Name), "testsection" }
+ }));
- // Assert
- AssertHtmlContentEquals(expectedHtml, result.HtmlContent);
+ Assert.Empty(first.ToHtmlString());
+
+ // Act/Assert 2: cause it to be updated
+ var second = await htmlRenderer.RenderComponentAsync(ParameterView.FromDictionary(new Dictionary
+ {
+ { nameof(SectionContent.Name), "testsection" },
+ { nameof(SectionContent.ChildContent), (RenderFragment)(builder =>
+ {
+ builder.AddContent(0, "Hello from the section content provider");
+ })
+ }
+ }));
+
+ Assert.Empty(second.ToHtmlString());
+ Assert.Equal("Hello from the section content provider", first.ToHtmlString());
+ });
}
[Fact]
- public async Task PrerendersMultipleComponentsSuccessfully()
+ public async Task RenderComponentAsync_CanOutputToTextWriter()
{
// Arrange
- var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb =>
+ var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(builder =>
{
- rtb.OpenElement(0, "p");
- rtb.AddMarkupContent(1, "Hello world! ");
- rtb.CloseElement();
+ builder.OpenElement(0, "p");
+ builder.AddContent(1, "Hey!");
+ builder.CloseElement();
})).BuildServiceProvider();
- var renderer = GetHtmlRenderer(serviceProvider);
+ var htmlRenderer = GetHtmlRenderer(serviceProvider);
+ using var ms = new MemoryStream();
+ using var writer = new StreamWriter(ms, new UTF8Encoding(false));
- // Act
- var first = await renderer.Dispatcher.InvokeAsync(() => renderer.RenderComponentAsync(ParameterView.Empty));
- var second = await renderer.Dispatcher.InvokeAsync(() => renderer.RenderComponentAsync(ParameterView.Empty));
+ await htmlRenderer.Dispatcher.InvokeAsync(async () =>
+ {
+ // Act
+ var result = await htmlRenderer.RenderComponentAsync();
+ result.WriteHtmlTo(writer);
+ writer.Flush();
+
+ // Assert
+ var actual = Encoding.UTF8.GetString(ms.ToArray());
+ Assert.Equal("Hey!
", actual);
+ });
+ }
+
+ [Fact]
+ public async Task BeginRenderingComponent_CanObserveStateBeforeAndAfterQuiescence()
+ {
+ // Arrange
+ var completionTcs = new TaskCompletionSource();
+ var services = new ServiceCollection();
+ services.AddSingleton(new AsyncLoadingComponentCompletion { Task = completionTcs.Task });
- // Assert
- Assert.Equal(0, first.ComponentId);
- Assert.Equal(1, second.ComponentId);
+ var htmlRenderer = GetHtmlRenderer(services.BuildServiceProvider());
+ await htmlRenderer.Dispatcher.InvokeAsync(async () =>
+ {
+ // Act/Assert: state before quiescence
+ var result = htmlRenderer.BeginRenderingComponent();
+ var quiescenceTask = result.WaitForQuiescenceAsync();
+ Assert.False(quiescenceTask.IsCompleted);
+ Assert.Equal("Loading...", result.ToHtmlString());
+
+ // Act/Assert: state after quiescence
+ completionTcs.SetResult();
+ await quiescenceTask;
+ Assert.Equal("Finished loading", result.ToHtmlString());
+ });
}
- private HtmlRenderer GetHtmlRenderer(IServiceProvider serviceProvider)
+ [Fact]
+ public async Task RenderComponentAsync_ThrowsSync()
{
- return new HtmlRenderer(serviceProvider, NullLoggerFactory.Instance, new TestViewBufferScope());
+ // Arrange
+ var services = new ServiceCollection();
+ services.AddSingleton(new AsyncLoadingComponentCompletion { Task = new TaskCompletionSource().Task });
+
+ var htmlRenderer = GetHtmlRenderer(services.BuildServiceProvider());
+ await htmlRenderer.Dispatcher.InvokeAsync(async () =>
+ {
+ // Act/Assert
+ var ex = await Assert.ThrowsAsync(async () =>
+ {
+ await htmlRenderer.RenderComponentAsync(ParameterView.FromDictionary(new Dictionary
+ {
+ { nameof(ErrorThrowingComponent.ThrowSync), true }
+ }));
+ });
+ Assert.Equal("sync", ex.Message);
+ });
+ }
+
+ [Fact]
+ public async Task RenderComponentAsync_ThrowsAsync()
+ {
+ // Arrange
+ var completionTcs = new TaskCompletionSource();
+ var services = new ServiceCollection();
+ services.AddSingleton(new AsyncLoadingComponentCompletion { Task = Task.Delay(0) });
+
+ var htmlRenderer = GetHtmlRenderer(services.BuildServiceProvider());
+ await htmlRenderer.Dispatcher.InvokeAsync(async () =>
+ {
+ // Act/Assert
+ var ex = await Assert.ThrowsAsync(() =>
+ htmlRenderer.RenderComponentAsync(ParameterView.FromDictionary(new Dictionary
+ {
+ { nameof(ErrorThrowingComponent.ThrowAsync), true }
+ })));
+ Assert.Equal("async", ex.Message);
+ });
+ }
+
+ [Fact]
+ public async Task BeginRenderingComponent_ThrowsSync()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ services.AddSingleton(new AsyncLoadingComponentCompletion { Task = new TaskCompletionSource().Task });
+
+ var htmlRenderer = GetHtmlRenderer(services.BuildServiceProvider());
+ await htmlRenderer.Dispatcher.InvokeAsync(() =>
+ {
+ // Act/Assert
+ var ex = Assert.Throws(() =>
+ {
+ htmlRenderer.BeginRenderingComponent(ParameterView.FromDictionary(new Dictionary
+ {
+ { nameof(ErrorThrowingComponent.ThrowSync), true }
+ }));
+ });
+ Assert.Equal("sync", ex.Message);
+ });
+ }
+
+ [Fact]
+ public async Task BeginRenderingComponent_ThrowsAsyncDuringWaitForQuiescenceAsync()
+ {
+ // Arrange
+ var completionTcs = new TaskCompletionSource();
+ var services = new ServiceCollection();
+ services.AddSingleton(new AsyncLoadingComponentCompletion { Task = completionTcs.Task });
+
+ var htmlRenderer = GetHtmlRenderer(services.BuildServiceProvider());
+ await htmlRenderer.Dispatcher.InvokeAsync(async () =>
+ {
+ // Act/Assert
+ var content = htmlRenderer.BeginRenderingComponent(ParameterView.FromDictionary(new Dictionary
+ {
+ { nameof(ErrorThrowingComponent.ThrowAsync), true }
+ }));
+
+ var ex = await Assert.ThrowsAsync(() =>
+ {
+ completionTcs.SetResult();
+ return content.WaitForQuiescenceAsync();
+ });
+ Assert.Equal("async", ex.Message);
+ });
+ }
+
+ void AssertHtmlContentEquals(IEnumerable expected, HtmlComponent actual)
+ => AssertHtmlContentEquals(string.Join(string.Empty, expected), actual);
+
+ void AssertHtmlContentEquals(string expected, HtmlComponent actual)
+ {
+ var actualHtml = actual.ToHtmlString();
+ Assert.Equal(expected, actualHtml);
}
private class NestedAsyncComponent : ComponentBase
@@ -863,4 +1087,73 @@ public Task SetParametersAsync(ParameterView parameters)
return Task.CompletedTask;
}
}
+
+ private class AsyncLoadingComponent : ComponentBase
+ {
+ string status;
+
+ [Inject]
+ public AsyncLoadingComponentCompletion Completion { get; set; }
+
+ protected override async Task OnInitializedAsync()
+ {
+ status = "Loading...";
+ await Completion.Task;
+ await Task.Yield(); // So that the test has to await the quiescence task to observe the final outcome
+ status = "Finished loading";
+ }
+
+ protected override void BuildRenderTree(RenderTreeBuilder builder)
+ => builder.AddContent(0, status);
+ }
+
+ private class ErrorThrowingComponent : ComponentBase
+ {
+ [Parameter] public bool ThrowSync { get; set; }
+ [Parameter] public bool ThrowAsync { get; set; }
+
+ [Inject]
+ public AsyncLoadingComponentCompletion Completion { get; set; }
+
+ protected override async Task OnParametersSetAsync()
+ {
+ await Completion.Task;
+ await Task.Yield();
+
+ if (ThrowAsync)
+ {
+ throw new InvalidTimeZoneException("async");
+ }
+ }
+
+ protected override void BuildRenderTree(RenderTreeBuilder builder)
+ {
+ builder.AddContent(0, "Hello");
+
+ if (ThrowSync)
+ {
+ throw new InvalidTimeZoneException("sync");
+ }
+
+ builder.AddContent(1, "Goodbye");
+ }
+ }
+
+ private class AsyncLoadingComponentCompletion
+ {
+ public Task Task { get; init; }
+ }
+
+ HtmlRenderer GetHtmlRenderer(IServiceProvider serviceProvider = null)
+ {
+ if (serviceProvider is null)
+ {
+ var services = new ServiceCollection();
+ services.AddLogging();
+
+ serviceProvider = services.BuildServiceProvider();
+ }
+
+ return new HtmlRenderer(serviceProvider, NullLoggerFactory.Instance);
+ }
}
diff --git a/src/Mvc/Mvc.TagHelpers/src/ComponentTagHelper.cs b/src/Mvc/Mvc.TagHelpers/src/ComponentTagHelper.cs
index 6583986e405d..448c6d2c9875 100644
--- a/src/Mvc/Mvc.TagHelpers/src/ComponentTagHelper.cs
+++ b/src/Mvc/Mvc.TagHelpers/src/ComponentTagHelper.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.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
@@ -91,8 +92,9 @@ public override async Task ProcessAsync(TagHelperContext context, TagHelperOutpu
}
var requestServices = ViewContext.HttpContext.RequestServices;
- var componentRenderer = requestServices.GetRequiredService();
- var result = await componentRenderer.RenderComponentAsync(ViewContext, ComponentType, RenderMode, _parameters);
+ 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);
// Reset the TagName. We don't want `component` to render.
output.TagName = null;
diff --git a/src/Mvc/Mvc.TagHelpers/src/PersistComponentStateTagHelper.cs b/src/Mvc/Mvc.TagHelpers/src/PersistComponentStateTagHelper.cs
index bd48227d2179..bf832e1aaa0f 100644
--- a/src/Mvc/Mvc.TagHelpers/src/PersistComponentStateTagHelper.cs
+++ b/src/Mvc/Mvc.TagHelpers/src/PersistComponentStateTagHelper.cs
@@ -4,7 +4,7 @@
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Infrastructure;
-using Microsoft.AspNetCore.Components.Rendering;
+using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Rendering;
@@ -52,7 +52,7 @@ public override async Task ProcessAsync(TagHelperContext context, TagHelperOutpu
var renderer = services.GetRequiredService();
var store = PersistenceMode switch
{
- null => ComponentRenderer.GetPersistStateRenderMode(ViewContext) switch
+ null => ComponentPrerenderer.GetPersistStateRenderMode(ViewContext) switch
{
InvokedRenderModes.Mode.None =>
null,
@@ -76,7 +76,7 @@ public override async Task ProcessAsync(TagHelperContext context, TagHelperOutpu
output.TagName = null;
if (store != null)
{
- await manager.PersistStateAsync(store, renderer);
+ await manager.PersistStateAsync(store, renderer.Dispatcher);
output.Content.SetHtmlContent(
new HtmlContentBuilder()
.AppendHtml("");
+ writer.Write("");
}
- internal static void AppendEpilogue(IHtmlContentBuilder htmlContentBuilder, ServerComponentMarker record)
+ internal static void AppendEpilogue(TextWriter writer, ServerComponentMarker record)
{
var endRecord = JsonSerializer.Serialize(
record.GetEndRecord(),
ServerComponentSerializationSettings.JsonSerializationOptions);
- htmlContentBuilder.AppendHtml("");
+ writer.Write("");
}
}
diff --git a/src/Mvc/Mvc.ViewFeatures/src/WebAssemblyComponentSerializer.cs b/src/Mvc/Mvc.ViewFeatures/src/WebAssemblyComponentSerializer.cs
index cb69a462ed18..7f1c490abf88 100644
--- a/src/Mvc/Mvc.ViewFeatures/src/WebAssemblyComponentSerializer.cs
+++ b/src/Mvc/Mvc.ViewFeatures/src/WebAssemblyComponentSerializer.cs
@@ -3,7 +3,6 @@
using System.Text.Json;
using Microsoft.AspNetCore.Components;
-using Microsoft.AspNetCore.Mvc.ViewFeatures.Buffers;
namespace Microsoft.AspNetCore.Mvc.ViewFeatures;
@@ -25,25 +24,25 @@ public static WebAssemblyComponentMarker SerializeInvocation(Type type, Paramete
WebAssemblyComponentMarker.NonPrerendered(assembly, typeFullName, serializedDefinitions, serializedValues);
}
- internal static void AppendPreamble(ViewBuffer viewBuffer, WebAssemblyComponentMarker record)
+ internal static void AppendPreamble(TextWriter writer, WebAssemblyComponentMarker record)
{
var serializedStartRecord = JsonSerializer.Serialize(
record,
WebAssemblyComponentSerializationSettings.JsonSerializationOptions);
- viewBuffer.AppendHtml("");
+ writer.Write("");
}
- internal static void AppendEpilogue(ViewBuffer viewBuffer, WebAssemblyComponentMarker record)
+ internal static void AppendEpilogue(TextWriter writer, WebAssemblyComponentMarker record)
{
var endRecord = JsonSerializer.Serialize(
record.GetEndRecord(),
WebAssemblyComponentSerializationSettings.JsonSerializationOptions);
- viewBuffer.AppendHtml("");
+ writer.Write("");
}
}
diff --git a/src/Mvc/Mvc.ViewFeatures/test/RazorComponents/ComponentRendererTest.cs b/src/Mvc/Mvc.ViewFeatures/test/RazorComponents/ComponentPrerendererTest.cs
similarity index 84%
rename from src/Mvc/Mvc.ViewFeatures/test/RazorComponents/ComponentRendererTest.cs
rename to src/Mvc/Mvc.ViewFeatures/test/RazorComponents/ComponentPrerendererTest.cs
index ef8848075e1b..e644578d0260 100644
--- a/src/Mvc/Mvc.ViewFeatures/test/RazorComponents/ComponentRendererTest.cs
+++ b/src/Mvc/Mvc.ViewFeatures/test/RazorComponents/ComponentPrerendererTest.cs
@@ -7,6 +7,7 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Infrastructure;
using Microsoft.AspNetCore.Components.Rendering;
+using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
@@ -19,10 +20,11 @@
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.JSInterop;
using Moq;
+using static Microsoft.Extensions.Internal.ObjectMethodExecutorAwaitable;
namespace Microsoft.AspNetCore.Mvc.ViewFeatures;
-public class ComponentRendererTest
+public class ComponentPrerendererTest
{
private const string PrerenderedComponentPattern = "^(?.+?)$";
private const string ComponentPattern = "^$";
@@ -30,9 +32,9 @@ public class ComponentRendererTest
private static readonly IDataProtectionProvider _dataprotectorProvider = new EphemeralDataProtectionProvider();
private readonly IServiceProvider _services = CreateDefaultServiceCollection().BuildServiceProvider();
- private readonly ComponentRenderer renderer;
+ private readonly ComponentPrerenderer renderer;
- public ComponentRendererTest()
+ public ComponentPrerendererTest()
{
renderer = GetComponentRenderer();
}
@@ -45,7 +47,7 @@ public async Task CanRender_ParameterlessComponent_ClientMode()
var writer = new StringWriter();
// Act
- var result = await renderer.RenderComponentAsync(viewContext, typeof(TestComponent), RenderMode.WebAssembly, null);
+ var result = await renderer.PrerenderComponentAsync(viewContext, typeof(TestComponent), RenderMode.WebAssembly, ParameterView.Empty);
result.WriteTo(writer, HtmlEncoder.Default);
var content = writer.ToString();
var match = Regex.Match(content, ComponentPattern);
@@ -68,8 +70,8 @@ public async Task CanPrerender_ParameterlessComponent_ClientMode()
var writer = new StringWriter();
// Act
- var result = await renderer.RenderComponentAsync(viewContext, typeof(TestComponent), RenderMode.WebAssemblyPrerendered, null);
- result.WriteTo(writer, HtmlEncoder.Default);
+ var result = await renderer.PrerenderComponentAsync(viewContext, 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);
@@ -106,12 +108,12 @@ public async Task CanRender_ComponentWithParameters_ClientMode()
var writer = new StringWriter();
// Act
- var result = await renderer.RenderComponentAsync(viewContext, typeof(GreetingComponent),
+ var result = await renderer.PrerenderComponentAsync(viewContext, typeof(GreetingComponent),
RenderMode.WebAssembly,
- new
+ ParameterView.FromDictionary(new Dictionary
{
- Name = "Daniel"
- });
+ { "Name", "Daniel" }
+ }));
result.WriteTo(writer, HtmlEncoder.Default);
var content = writer.ToString();
var match = Regex.Match(content, ComponentPattern);
@@ -143,12 +145,12 @@ public async Task CanRender_ComponentWithNullParameters_ClientMode()
var writer = new StringWriter();
// Act
- var result = await renderer.RenderComponentAsync(viewContext, typeof(GreetingComponent),
+ var result = await renderer.PrerenderComponentAsync(viewContext, typeof(GreetingComponent),
RenderMode.WebAssembly,
- new
+ ParameterView.FromDictionary(new Dictionary
{
- Name = (string)null
- });
+ { "Name", null }
+ }));
result.WriteTo(writer, HtmlEncoder.Default);
var content = writer.ToString();
var match = Regex.Match(content, ComponentPattern);
@@ -178,13 +180,13 @@ public async Task CanPrerender_ComponentWithParameters_ClientMode()
var writer = new StringWriter();
// Act
- var result = await renderer.RenderComponentAsync(viewContext, typeof(GreetingComponent),
+ var result = await renderer.PrerenderComponentAsync(viewContext, typeof(GreetingComponent),
RenderMode.WebAssemblyPrerendered,
- new
+ ParameterView.FromDictionary(new Dictionary
{
- Name = "Daniel"
- });
- result.WriteTo(writer, HtmlEncoder.Default);
+ { "Name", "Daniel" }
+ }));
+ await renderer.Dispatcher.InvokeAsync(() => result.WriteTo(writer, HtmlEncoder.Default));
var content = writer.ToString();
var match = Regex.Match(content, PrerenderedComponentPattern, RegexOptions.Multiline);
@@ -227,13 +229,13 @@ public async Task CanPrerender_ComponentWithNullParameters_ClientMode()
var writer = new StringWriter();
// Act
- var result = await renderer.RenderComponentAsync(viewContext, typeof(GreetingComponent),
+ var result = await renderer.PrerenderComponentAsync(viewContext, typeof(GreetingComponent),
RenderMode.WebAssemblyPrerendered,
- new
+ ParameterView.FromDictionary(new Dictionary
{
- Name = (string)null
- });
- result.WriteTo(writer, HtmlEncoder.Default);
+ { "Name", null }
+ }));
+ await renderer.Dispatcher.InvokeAsync(() => result.WriteTo(writer, HtmlEncoder.Default));
var content = writer.ToString();
var match = Regex.Match(content, PrerenderedComponentPattern, RegexOptions.Multiline);
@@ -275,8 +277,8 @@ public async Task CanRender_ParameterlessComponent()
var writer = new StringWriter();
// Act
- var result = await renderer.RenderComponentAsync(viewContext, typeof(TestComponent), RenderMode.Static, null);
- result.WriteTo(writer, HtmlEncoder.Default);
+ var result = await renderer.PrerenderComponentAsync(viewContext, typeof(TestComponent), RenderMode.Static, ParameterView.Empty);
+ await renderer.Dispatcher.InvokeAsync(() => result.WriteTo(writer, HtmlEncoder.Default));
var content = writer.ToString();
// Assert
@@ -292,7 +294,7 @@ public async Task CanRender_ParameterlessComponent_ServerMode()
.ToTimeLimitedDataProtector();
// Act
- var result = await renderer.RenderComponentAsync(viewContext, typeof(TestComponent), RenderMode.Server, null);
+ var result = await renderer.PrerenderComponentAsync(viewContext, typeof(TestComponent), RenderMode.Server, ParameterView.Empty);
var content = HtmlContentUtilities.HtmlContentToString(result, HtmlEncoder.Default);
var match = Regex.Match(content, ComponentPattern);
@@ -324,8 +326,8 @@ public async Task CanPrerender_ParameterlessComponent_ServerMode()
.ToTimeLimitedDataProtector();
// Act
- var result = await renderer.RenderComponentAsync(viewContext, typeof(TestComponent), RenderMode.ServerPrerendered, null);
- var content = HtmlContentUtilities.HtmlContentToString(result, HtmlEncoder.Default);
+ var result = await renderer.PrerenderComponentAsync(viewContext, 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);
// Assert
@@ -367,8 +369,9 @@ public async Task Prerender_ServerAndClientComponentUpdatesInvokedPrerenderModes
var viewContext = GetViewContext();
// Act
- var server = await renderer.RenderComponentAsync(viewContext, typeof(GreetingComponent), RenderMode.ServerPrerendered, new { Name = "Steve" });
- var client = await renderer.RenderComponentAsync(viewContext, typeof(GreetingComponent), RenderMode.WebAssemblyPrerendered, new { Name = "Steve" });
+ 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);
// Assert
var (_, mode) = Assert.Single(viewContext.Items, (kvp) => kvp.Value is InvokedRenderModes);
@@ -384,12 +387,12 @@ public async Task CanRenderMultipleServerComponents()
.ToTimeLimitedDataProtector();
// Act
- var firstResult = await renderer.RenderComponentAsync(viewContext, typeof(TestComponent), RenderMode.ServerPrerendered, null);
- var firstComponent = HtmlContentUtilities.HtmlContentToString(firstResult);
+ var firstResult = await renderer.PrerenderComponentAsync(viewContext, 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.RenderComponentAsync(viewContext, typeof(TestComponent), RenderMode.Server, null);
- var secondComponent = HtmlContentUtilities.HtmlContentToString(secondResult);
+ var secondResult = await renderer.PrerenderComponentAsync(viewContext, typeof(TestComponent), RenderMode.Server, ParameterView.Empty);
+ var secondComponent = await renderer.Dispatcher.InvokeAsync(() => HtmlContentUtilities.HtmlContentToString(secondResult));
var secondMatch = Regex.Match(secondComponent, ComponentPattern);
// Assert
@@ -424,11 +427,12 @@ public async Task CanRender_ComponentWithParametersObject()
var viewContext = GetViewContext();
// Act
- var result = await renderer.RenderComponentAsync(viewContext, typeof(GreetingComponent), RenderMode.Static, new { Name = "Steve" });
+ var parameters = ParameterView.FromDictionary(new Dictionary { { "Name", "SomeName" } });
+ var result = await renderer.PrerenderComponentAsync(viewContext, typeof(GreetingComponent), RenderMode.Static, parameters);
// Assert
- var content = HtmlContentUtilities.HtmlContentToString(result, HtmlEncoder.Default);
- Assert.Equal("Hello Steve!
", content);
+ var content = await renderer.Dispatcher.InvokeAsync(() => HtmlContentUtilities.HtmlContentToString(result, HtmlEncoder.Default));
+ Assert.Equal("Hello SomeName!
", content);
}
[Fact]
@@ -440,7 +444,8 @@ public async Task CanRender_ComponentWithParameters_ServerMode()
.ToTimeLimitedDataProtector();
// Act
- var result = await renderer.RenderComponentAsync(viewContext, typeof(GreetingComponent), RenderMode.Server, new { Name = "Daniel" });
+ var parameters = ParameterView.FromDictionary(new Dictionary { { "Name", "SomeName" } });
+ var result = await renderer.PrerenderComponentAsync(viewContext, typeof(GreetingComponent), RenderMode.Server, parameters);
var content = HtmlContentUtilities.HtmlContentToString(result, HtmlEncoder.Default);
var match = Regex.Match(content, ComponentPattern);
@@ -466,7 +471,7 @@ public async Task CanRender_ComponentWithParameters_ServerMode()
var value = Assert.Single(serverComponent.ParameterValues);
var rawValue = Assert.IsType(value);
- Assert.Equal("Daniel", rawValue.GetString());
+ Assert.Equal("SomeName", rawValue.GetString());
}
[Fact]
@@ -478,8 +483,8 @@ public async Task CanRender_ComponentWithNullParameters_ServerMode()
.ToTimeLimitedDataProtector();
// Act
-
- var result = await renderer.RenderComponentAsync(viewContext, typeof(GreetingComponent), RenderMode.Server, new { Name = (string)null });
+ var parameters = ParameterView.FromDictionary(new Dictionary { { "Name", null } });
+ var result = await renderer.PrerenderComponentAsync(viewContext, typeof(GreetingComponent), RenderMode.Server, parameters);
var content = HtmlContentUtilities.HtmlContentToString(result, HtmlEncoder.Default);
var match = Regex.Match(content, ComponentPattern);
@@ -513,13 +518,13 @@ public async Task CanPrerender_ComponentWithParameters_ServerPrerenderedMode()
{
// Arrange
var viewContext = GetViewContext();
- var writer = new StringWriter();
var protector = _dataprotectorProvider.CreateProtector(ServerComponentSerializationSettings.DataProtectionProviderPurpose)
.ToTimeLimitedDataProtector();
// Act
- var result = await renderer.RenderComponentAsync(viewContext, typeof(GreetingComponent), RenderMode.ServerPrerendered, new { Name = "Daniel" });
- var content = HtmlContentUtilities.HtmlContentToString(result, HtmlEncoder.Default);
+ var parameters = ParameterView.FromDictionary(new Dictionary { { "Name", "SomeName" } });
+ var result = await renderer.PrerenderComponentAsync(viewContext, typeof(GreetingComponent), RenderMode.ServerPrerendered, parameters);
+ var content = await renderer.Dispatcher.InvokeAsync(() => HtmlContentUtilities.HtmlContentToString(result, HtmlEncoder.Default));
var match = Regex.Match(content, PrerenderedComponentPattern, RegexOptions.Multiline);
// Assert
@@ -546,10 +551,10 @@ public async Task CanPrerender_ComponentWithParameters_ServerPrerenderedMode()
var value = Assert.Single(serverComponent.ParameterValues);
var rawValue = Assert.IsType(value);
- Assert.Equal("Daniel", rawValue.GetString());
+ Assert.Equal("SomeName", rawValue.GetString());
var prerenderedContent = match.Groups["content"].Value;
- Assert.Equal("Hello Daniel!
", prerenderedContent);
+ Assert.Equal("Hello SomeName!
", prerenderedContent);
var epilogue = match.Groups["epilogue"].Value;
var epilogueMarker = JsonSerializer.Deserialize(epilogue, ServerComponentSerializationSettings.JsonSerializationOptions);
@@ -564,13 +569,13 @@ public async Task CanPrerender_ComponentWithNullParameters_ServerPrerenderedMode
{
// Arrange
var viewContext = GetViewContext();
- var writer = new StringWriter();
var protector = _dataprotectorProvider.CreateProtector(ServerComponentSerializationSettings.DataProtectionProviderPurpose)
.ToTimeLimitedDataProtector();
// Act
- var result = await renderer.RenderComponentAsync(viewContext, typeof(GreetingComponent), RenderMode.ServerPrerendered, new { Name = (string)null });
- var content = HtmlContentUtilities.HtmlContentToString(result, HtmlEncoder.Default);
+ var parameters = ParameterView.FromDictionary(new Dictionary { { "Name", null } });
+ var result = await renderer.PrerenderComponentAsync(viewContext, typeof(GreetingComponent), RenderMode.ServerPrerendered, parameters);
+ var content = await renderer.Dispatcher.InvokeAsync(() => HtmlContentUtilities.HtmlContentToString(result, HtmlEncoder.Default));
var match = Regex.Match(content, PrerenderedComponentPattern, RegexOptions.Multiline);
// Assert
@@ -617,9 +622,10 @@ public async Task ComponentWithInvalidRenderMode_Throws()
var viewContext = GetViewContext();
// Act & Assert
+ var parameters = ParameterView.FromDictionary(new Dictionary { { "Name", "SomeName" } });
var ex = await ExceptionAssert.ThrowsArgumentAsync(
- async () => await renderer.RenderComponentAsync(viewContext, typeof(GreetingComponent), default, new { Name = "Daniel" }),
- "renderMode",
+ async () => await renderer.PrerenderComponentAsync(viewContext, typeof(GreetingComponent), default, parameters),
+ "prerenderMode",
$"Unsupported RenderMode '{(RenderMode)default}'");
}
@@ -631,10 +637,11 @@ public async Task RenderComponent_DoesNotInvokeOnAfterRenderInComponent()
// Act
var state = new OnAfterRenderState();
- var result = await renderer.RenderComponentAsync(viewContext, typeof(OnAfterRenderComponent), RenderMode.Static, new { state });
+ var parameters = ParameterView.FromDictionary(new Dictionary { { "state", state } });
+ var result = await renderer.PrerenderComponentAsync(viewContext, typeof(OnAfterRenderComponent), RenderMode.Static, parameters);
// Assert
- var content = HtmlContentUtilities.HtmlContentToString(result, HtmlEncoder.Default);
+ var content = await renderer.Dispatcher.InvokeAsync(() => HtmlContentUtilities.HtmlContentToString(result, HtmlEncoder.Default));
Assert.Equal("Hello
", content);
Assert.False(state.OnAfterRenderRan);
}
@@ -644,29 +651,28 @@ public async Task DisposableComponents_GetDisposedAfterScopeCompletes()
{
// Arrange
var collection = CreateDefaultServiceCollection();
- collection.TryAddScoped();
- collection.TryAddScoped();
+ collection.TryAddScoped();
collection.TryAddScoped();
collection.TryAddSingleton(HtmlEncoder.Default);
collection.TryAddSingleton(NullLoggerFactory.Instance);
collection.TryAddSingleton();
collection.TryAddSingleton(_dataprotectorProvider);
collection.TryAddSingleton();
- collection.TryAddScoped();
var provider = collection.BuildServiceProvider();
var scope = provider.GetRequiredService().CreateScope();
var scopedProvider = scope.ServiceProvider;
var context = new DefaultHttpContext() { RequestServices = scopedProvider };
var viewContext = GetViewContext(context);
- var renderer = scopedProvider.GetRequiredService();
+ var renderer = scopedProvider.GetRequiredService();
// Act
var state = new AsyncDisposableState();
- var result = await renderer.RenderComponentAsync(viewContext, typeof(AsyncDisposableComponent), RenderMode.Static, new { state });
+ var parameters = ParameterView.FromDictionary(new Dictionary { { "state", state } });
+ var result = await renderer.PrerenderComponentAsync(viewContext, typeof(AsyncDisposableComponent), RenderMode.Static, parameters);
// Assert
- var content = HtmlContentUtilities.HtmlContentToString(result, HtmlEncoder.Default);
+ var content = await renderer.Dispatcher.InvokeAsync(() => HtmlContentUtilities.HtmlContentToString(result, HtmlEncoder.Default));
Assert.Equal("Hello
", content);
await ((IAsyncDisposable)scope).DisposeAsync();
@@ -680,14 +686,14 @@ public async Task CanCatch_ComponentWithSynchronousException()
var viewContext = GetViewContext();
// Act & Assert
- var exception = await Assert.ThrowsAsync(async () => await renderer.RenderComponentAsync(
+ var exception = await Assert.ThrowsAsync(async () => await renderer.PrerenderComponentAsync(
viewContext,
typeof(ExceptionComponent),
RenderMode.Static,
- new
+ ParameterView.FromDictionary(new Dictionary
{
- IsAsync = false
- }));
+ { "IsAsync", false }
+ })));
// Assert
Assert.Equal("Threw an exception synchronously", exception.Message);
@@ -700,14 +706,14 @@ public async Task CanCatch_ComponentWithAsynchronousException()
var viewContext = GetViewContext();
// Act & Assert
- var exception = await Assert.ThrowsAsync(async () => await renderer.RenderComponentAsync(
+ var exception = await Assert.ThrowsAsync(async () => await renderer.PrerenderComponentAsync(
viewContext,
typeof(ExceptionComponent),
RenderMode.Static,
- new
+ ParameterView.FromDictionary(new Dictionary
{
- IsAsync = true
- }));
+ { "IsAsync", true }
+ })));
// Assert
Assert.Equal("Threw an exception asynchronously", exception.Message);
@@ -720,15 +726,14 @@ public async Task Rendering_ComponentWithJsInteropThrows()
var viewContext = GetViewContext();
// Act & Assert
- var exception = await Assert.ThrowsAsync(async () => await renderer.RenderComponentAsync(
+ var exception = await Assert.ThrowsAsync(async () => await renderer.PrerenderComponentAsync(
viewContext,
typeof(ExceptionComponent),
RenderMode.Static,
- new
+ ParameterView.FromDictionary(new Dictionary
{
- JsInterop = true
- }
- ));
+ { "JsInterop", true }
+ })));
// Assert
Assert.Equal("JavaScript interop calls cannot be issued during server-side prerendering, " +
@@ -753,14 +758,14 @@ public async Task UriHelperRedirect_ThrowsInvalidOperationException_WhenResponse
var viewContext = GetViewContext(ctx);
// Act
- var exception = await Assert.ThrowsAsync(async () => await renderer.RenderComponentAsync(
+ var exception = await Assert.ThrowsAsync(async () => await renderer.PrerenderComponentAsync(
viewContext,
typeof(RedirectComponent),
RenderMode.Static,
- new
+ ParameterView.FromDictionary(new Dictionary
{
- RedirectUri = "http://localhost/redirect"
- }));
+ { "RedirectUri", "http://localhost/redirect" }
+ })));
Assert.Equal("A navigation command was attempted during prerendering after the server already started sending the response. " +
"Navigation commands can not be issued during server-side prerendering after the response from the server has started. Applications must buffer the" +
@@ -781,14 +786,14 @@ public async Task HtmlHelper_Redirects_WhenComponentNavigates()
var viewContext = GetViewContext(ctx);
// Act
- await renderer.RenderComponentAsync(
+ await renderer.PrerenderComponentAsync(
viewContext,
typeof(RedirectComponent),
RenderMode.Static,
- new
+ ParameterView.FromDictionary(new Dictionary
{
- RedirectUri = "http://localhost/redirect"
- });
+ { "RedirectUri", "http://localhost/redirect" }
+ }));
// Assert
Assert.Equal(302, ctx.Response.StatusCode);
@@ -844,20 +849,18 @@ public async Task CanRender_AsyncComponent()
";
// Act
- var result = await renderer.RenderComponentAsync(viewContext, typeof(AsyncComponent), RenderMode.Static, null);
- var content = HtmlContentUtilities.HtmlContentToString(result, HtmlEncoder.Default);
+ var result = await renderer.PrerenderComponentAsync(viewContext, typeof(AsyncComponent), RenderMode.Static, ParameterView.Empty);
+ var content = await renderer.Dispatcher.InvokeAsync(() => HtmlContentUtilities.HtmlContentToString(result, HtmlEncoder.Default));
// Assert
Assert.Equal(expectedContent.Replace("\r\n", "\n"), content);
}
- private ComponentRenderer GetComponentRenderer(IServiceProvider services = null)
+ private ComponentPrerenderer GetComponentRenderer(IServiceProvider services = null)
{
- var viewBufferScope = new TestViewBufferScope();
- return new ComponentRenderer(
- new StaticComponentRenderer(new HtmlRenderer(services ?? _services, NullLoggerFactory.Instance, viewBufferScope)),
- new ServerComponentSerializer(_dataprotectorProvider),
- viewBufferScope);
+ return new ComponentPrerenderer(
+ new HtmlRenderer(services ?? _services, NullLoggerFactory.Instance),
+ new ServerComponentSerializer(_dataprotectorProvider));
}
private ViewContext GetViewContext(HttpContext context = null)
diff --git a/src/Mvc/Mvc.ViewFeatures/test/Rendering/HtmlHelperComponentExtensionsTest.cs b/src/Mvc/Mvc.ViewFeatures/test/Rendering/HtmlHelperComponentExtensionsTest.cs
deleted file mode 100644
index 4e6b65a13b1a..000000000000
--- a/src/Mvc/Mvc.ViewFeatures/test/Rendering/HtmlHelperComponentExtensionsTest.cs
+++ /dev/null
@@ -1,52 +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.Html;
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc.ViewFeatures;
-using Microsoft.Extensions.DependencyInjection;
-using Moq;
-
-namespace Microsoft.AspNetCore.Mvc.Rendering;
-
-public class HtmlHelperComponentExtensionsTest
-{
- [Fact]
- public async Task RenderComponentAsync_Works()
- {
- // Arrange
- var viewContext = GetViewContext();
- var htmlHelper = Mock.Of(h => h.ViewContext == viewContext);
-
- // Act
- var result = await HtmlHelperComponentExtensions.RenderComponentAsync(htmlHelper, RenderMode.Static);
-
- // Assert
- Assert.Equal("Hello world", HtmlContentUtilities.HtmlContentToString(result));
- }
-
- private static ViewContext GetViewContext()
- {
- var htmlContent = new HtmlContentBuilder().AppendHtml("Hello world");
- var renderer = Mock.Of(c =>
- c.RenderComponentAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()) == new ValueTask(htmlContent));
-
- var httpContext = new DefaultHttpContext
- {
- RequestServices = new ServiceCollection().AddSingleton(renderer).BuildServiceProvider(),
- };
-
- var viewContext = new ViewContext { HttpContext = httpContext };
- return viewContext;
- }
-
- private class TestComponent : IComponent
- {
- public void Attach(RenderHandle renderHandle)
- {
- }
-
- public Task SetParametersAsync(ParameterView parameters) => null;
- }
-}