Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 127 additions & 0 deletions src/Components/Endpoints/test/EndpointHtmlRendererTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,133 @@ public async Task CanPrerender_ComponentWithNullParameters_ServerPrerenderedMode
Assert.Null(epilogueMarker.Type);
}

[Fact]
public async Task CanRender_ClosedGenericComponent()
{
// Arrange
var httpContext = GetHttpContext();
var writer = new StringWriter();

// Act
var parameters = ParameterView.FromDictionary(new Dictionary<string, object> { { "Value", 42 } });
var result = await renderer.PrerenderComponentAsync(httpContext, typeof(GenericComponent<int>), null, parameters);
await renderer.Dispatcher.InvokeAsync(() => result.WriteTo(writer, HtmlEncoder.Default));
var content = writer.ToString();

// Assert
Assert.Equal("<p>Generic value: 42</p>", content);
}

[Fact]
public async Task CanRender_ClosedGenericComponent_ServerMode()
{
// Arrange
var httpContext = GetHttpContext();
var protector = _dataprotectorProvider.CreateProtector(ServerComponentSerializationSettings.DataProtectionProviderPurpose)
.ToTimeLimitedDataProtector();

// Act
var parameters = ParameterView.FromDictionary(new Dictionary<string, object> { { "Value", "TestString" } });
var result = await renderer.PrerenderComponentAsync(httpContext, typeof(GenericComponent<string>), new InteractiveServerRenderMode(false), parameters);
var content = await renderer.Dispatcher.InvokeAsync(() => HtmlContentToString(result));
var match = Regex.Match(content, ComponentPattern);

// Assert
Assert.True(match.Success);
var marker = JsonSerializer.Deserialize<ComponentMarker>(match.Groups[1].Value, ServerComponentSerializationSettings.JsonSerializationOptions);
Assert.Equal(0, marker.Sequence);
Assert.Null(marker.PrerenderId);
Assert.NotNull(marker.Descriptor);
Assert.Equal("server", marker.Type);

var unprotectedServerComponent = protector.Unprotect(marker.Descriptor);
var serverComponent = JsonSerializer.Deserialize<ServerComponent>(unprotectedServerComponent, ServerComponentSerializationSettings.JsonSerializationOptions);
Assert.Equal(0, serverComponent.Sequence);
Assert.Equal(typeof(GenericComponent<string>).Assembly.GetName().Name, serverComponent.AssemblyName);
Assert.Equal(typeof(GenericComponent<string>).FullName, serverComponent.TypeName);
Assert.NotEqual(Guid.Empty, serverComponent.InvocationId);

var parameterDefinition = Assert.Single(serverComponent.ParameterDefinitions);
Assert.Equal("Value", parameterDefinition.Name);
Assert.Equal("System.String", parameterDefinition.TypeName);
Assert.Equal("System.Private.CoreLib", parameterDefinition.Assembly);

var value = Assert.Single(serverComponent.ParameterValues);
var rawValue = Assert.IsType<JsonElement>(value);
Assert.Equal("TestString", rawValue.GetString());
}

[Fact]
public async Task CanPrerender_ClosedGenericComponent_ServerMode()
{
// Arrange
var httpContext = GetHttpContext();
var protector = _dataprotectorProvider.CreateProtector(ServerComponentSerializationSettings.DataProtectionProviderPurpose)
.ToTimeLimitedDataProtector();

// Act
var parameters = ParameterView.FromDictionary(new Dictionary<string, object> { { "Value", 123 } });
var result = await renderer.PrerenderComponentAsync(httpContext, typeof(GenericComponent<int>), RenderMode.InteractiveServer, parameters);
var content = await renderer.Dispatcher.InvokeAsync(() => HtmlContentToString(result));
var match = Regex.Match(content, PrerenderedComponentPattern, RegexOptions.Multiline);

// Assert
Assert.True(match.Success);
var preamble = match.Groups["preamble"].Value;
var preambleMarker = JsonSerializer.Deserialize<ComponentMarker>(preamble, ServerComponentSerializationSettings.JsonSerializationOptions);
Assert.Equal(0, preambleMarker.Sequence);
Assert.NotNull(preambleMarker.PrerenderId);
Assert.NotNull(preambleMarker.Descriptor);
Assert.Equal("server", preambleMarker.Type);

var unprotectedServerComponent = protector.Unprotect(preambleMarker.Descriptor);
var serverComponent = JsonSerializer.Deserialize<ServerComponent>(unprotectedServerComponent, ServerComponentSerializationSettings.JsonSerializationOptions);
Assert.NotEqual(default, serverComponent);
Assert.Equal(0, serverComponent.Sequence);
Assert.Equal(typeof(GenericComponent<int>).Assembly.GetName().Name, serverComponent.AssemblyName);
Assert.Equal(typeof(GenericComponent<int>).FullName, serverComponent.TypeName);
Assert.NotEqual(Guid.Empty, serverComponent.InvocationId);

var prerenderedContent = match.Groups["content"].Value;
Assert.Equal("<p>Generic value: 123</p>", prerenderedContent);

var epilogue = match.Groups["epilogue"].Value;
var epilogueMarker = JsonSerializer.Deserialize<ComponentMarker>(epilogue, ServerComponentSerializationSettings.JsonSerializationOptions);
Assert.Equal(preambleMarker.PrerenderId, epilogueMarker.PrerenderId);
}

[Fact]
public async Task CanPrerender_ClosedGenericComponent_ClientMode()
{
// Arrange
var httpContext = GetHttpContext();
var writer = new StringWriter();

// Act
var parameters = ParameterView.FromDictionary(new Dictionary<string, object> { { "Value", 456 } });
var result = await renderer.PrerenderComponentAsync(httpContext, typeof(GenericComponent<int>), RenderMode.InteractiveWebAssembly, parameters);
await renderer.Dispatcher.InvokeAsync(() => result.WriteTo(writer, HtmlEncoder.Default));
var content = writer.ToString();
content = AssertAndStripWebAssemblyOptions(content);
var match = Regex.Match(content, PrerenderedComponentPattern, RegexOptions.Multiline);

// Assert
Assert.True(match.Success);
var preamble = match.Groups["preamble"].Value;
var preambleMarker = JsonSerializer.Deserialize<ComponentMarker>(preamble, ServerComponentSerializationSettings.JsonSerializationOptions);
Assert.NotNull(preambleMarker.PrerenderId);
Assert.Equal("webassembly", preambleMarker.Type);
Assert.Equal(typeof(GenericComponent<string>).Assembly.GetName().Name, preambleMarker.Assembly);
Assert.Equal(typeof(GenericComponent<string>).FullName, preambleMarker.TypeName);

var prerenderedContent = match.Groups["content"].Value;
Assert.Equal("<p>Generic value: 456</p>", prerenderedContent);

var epilogue = match.Groups["epilogue"].Value;
var epilogueMarker = JsonSerializer.Deserialize<ComponentMarker>(epilogue, ServerComponentSerializationSettings.JsonSerializationOptions);
Assert.Equal(preambleMarker.PrerenderId, epilogueMarker.PrerenderId);
}

[Fact]
public async Task ComponentWithInvalidRenderMode_Throws()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@typeparam TValue

<p>Generic value: @(Value?.ToString() ?? "(null)")</p>
@code {
[Parameter] public TValue Value { get; set; }
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Globalization;
using System.Text.Json;
using Microsoft.AspNetCore.Components.Endpoints;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.Logging.Abstractions;

namespace Microsoft.AspNetCore.Components.Server.Circuits;

public class ServerComponentDeserializerTest
namespace Microsoft.AspNetCore.Components.Server.Circuits
{
public class ServerComponentDeserializerTest
{
private readonly IDataProtectionProvider _ephemeralDataProtectionProvider;
private readonly ITimeLimitedDataProtector _protector;
private ServerComponentInvocationSequence _invocationSequence = new();
Expand Down Expand Up @@ -75,6 +76,74 @@ public void CanParseSingleMarkerWithNullParameters()
Assert.Null(parameters["Parameter"]);
}

[Fact]
public void CanParseSingleMarkerForClosedGenericComponent()
{
// Arrange
var markers = SerializeMarkers(CreateMarkers(typeof(GenericTestComponent<int>)));
var serverComponentDeserializer = CreateServerComponentDeserializer();

// Act & assert
Assert.True(serverComponentDeserializer.TryDeserializeComponentDescriptorCollection(markers, out var descriptors));
var deserializedDescriptor = Assert.Single(descriptors);
Assert.Equal(typeof(GenericTestComponent<int>).FullName, deserializedDescriptor.ComponentType.FullName);
Assert.Equal(0, deserializedDescriptor.Sequence);
}

[Fact]
public void CanParseSingleMarkerForClosedGenericComponentWithStringTypeParameter()
{
// Arrange
var markers = SerializeMarkers(CreateMarkers(typeof(GenericTestComponent<string>)));
var serverComponentDeserializer = CreateServerComponentDeserializer();

// Act & assert
Assert.True(serverComponentDeserializer.TryDeserializeComponentDescriptorCollection(markers, out var descriptors));
var deserializedDescriptor = Assert.Single(descriptors);
Assert.Equal(typeof(GenericTestComponent<string>).FullName, deserializedDescriptor.ComponentType.FullName);
Assert.Equal(0, deserializedDescriptor.Sequence);
}

[Fact]
public void CanParseSingleMarkerForClosedGenericComponentWithParameters()
{
// Arrange
var markers = SerializeMarkers(CreateMarkers(
(typeof(GenericTestComponent<int>), new Dictionary<string, object> { ["Value"] = 42 })));
var serverComponentDeserializer = CreateServerComponentDeserializer();

// Act & assert
Assert.True(serverComponentDeserializer.TryDeserializeComponentDescriptorCollection(markers, out var descriptors));
var deserializedDescriptor = Assert.Single(descriptors);
Assert.Equal(typeof(GenericTestComponent<int>).FullName, deserializedDescriptor.ComponentType.FullName);
Assert.Equal(0, deserializedDescriptor.Sequence);

var parameters = deserializedDescriptor.Parameters.ToDictionary();
Assert.Single(parameters);
Assert.Contains("Value", parameters.Keys);
Assert.Equal(42, Convert.ToInt64(parameters["Value"]!, CultureInfo.InvariantCulture));
}

[Fact]
public void CanParseMultipleMarkersForClosedGenericComponents()
{
// Arrange
var markers = SerializeMarkers(CreateMarkers(typeof(GenericTestComponent<int>), typeof(GenericTestComponent<string>)));
var serverComponentDeserializer = CreateServerComponentDeserializer();

// Act & assert
Assert.True(serverComponentDeserializer.TryDeserializeComponentDescriptorCollection(markers, out var descriptors));
Assert.Equal(2, descriptors.Count);

var firstDescriptor = descriptors[0];
Assert.Equal(typeof(GenericTestComponent<int>).FullName, firstDescriptor.ComponentType.FullName);
Assert.Equal(0, firstDescriptor.Sequence);

var secondDescriptor = descriptors[1];
Assert.Equal(typeof(GenericTestComponent<string>).FullName, secondDescriptor.ComponentType.FullName);
Assert.Equal(0, secondDescriptor.Sequence);
}

[Fact]
public void CanParseMultipleMarkers()
{
Expand Down Expand Up @@ -517,4 +586,13 @@ private class DynamicallyAddedComponent : IComponent
public void Attach(RenderHandle renderHandle) => throw new NotImplementedException();
public Task SetParametersAsync(ParameterView parameters) => throw new NotImplementedException();
}

private class GenericTestComponent<T> : IComponent
{
[Parameter] public T Value { get; set; }

public void Attach(RenderHandle renderHandle) => throw new NotImplementedException();
public Task SetParametersAsync(ParameterView parameters) => throw new NotImplementedException();
}
}
}