/
githubmirror
/
aspnetcore
Обзор
Документация
Войти
/
githubmirror
/
aspnetcore
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Components/Server/test/Circuits/ServerComponentDeserializerTest.cs
597 строк
26 KB
Korolev Dmitry
Verify Blazor union support; fix prerendering of unions with a null active case (#67296)
19 июн 2026, 17:51
Не верифицирован
19 июн 2026, 17:51
2bd9124
Код
Авторство
О чём код?
// 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 partial class ServerComponentDeserializerTest { private readonly IDataProtectionProvider _ephemeralDataProtectionProvider; private readonly ITimeLimitedDataProtector _protector; private ServerComponentInvocationSequence _invocationSequence = new(); public ServerComponentDeserializerTest() { _ephemeralDataProtectionProvider = new EphemeralDataProtectionProvider(); _protector = _ephemeralDataProtectionProvider .CreateProtector(ServerComponentSerializationSettings.DataProtectionProviderPurpose) .ToTimeLimitedDataProtector(); } [Fact] public void CanParseSingleMarker() { // Arrange var markers = SerializeMarkers(CreateMarkers(typeof(TestComponent))); var serverComponentDeserializer = CreateServerComponentDeserializer(); // Act & assert Assert.True(serverComponentDeserializer.TryDeserializeComponentDescriptorCollection(markers, out var descriptors)); var deserializedDescriptor = Assert.Single(descriptors); Assert.Equal(typeof(TestComponent).FullName, deserializedDescriptor.ComponentType.FullName); Assert.Equal(0, deserializedDescriptor.Sequence); } [Fact] public void CanParseSingleMarkerWithParameters() { // Arrange var markers = SerializeMarkers(CreateMarkers( (typeof(TestComponent), new Dictionary<string, object> { ["Parameter"] = "Value" }))); var serverComponentDeserializer = CreateServerComponentDeserializer(); // Act & assert Assert.True(serverComponentDeserializer.TryDeserializeComponentDescriptorCollection(markers, out var descriptors)); var deserializedDescriptor = Assert.Single(descriptors); Assert.Equal(typeof(TestComponent).FullName, deserializedDescriptor.ComponentType.FullName); Assert.Equal(0, deserializedDescriptor.Sequence); var parameters = deserializedDescriptor.Parameters.ToDictionary(); Assert.Single(parameters); Assert.Contains("Parameter", parameters.Keys); Assert.Equal("Value", parameters["Parameter"]); } [Fact] public void CanParseSingleMarkerWithNullParameters() { // Arrange var markers = SerializeMarkers(CreateMarkers( (typeof(TestComponent), new Dictionary<string, object> { ["Parameter"] = null }))); var serverComponentDeserializer = CreateServerComponentDeserializer(); // Act & assert Assert.True(serverComponentDeserializer.TryDeserializeComponentDescriptorCollection(markers, out var descriptors)); var deserializedDescriptor = Assert.Single(descriptors); Assert.Equal(typeof(TestComponent).FullName, deserializedDescriptor.ComponentType.FullName); Assert.Equal(0, deserializedDescriptor.Sequence); var parameters = deserializedDescriptor.Parameters.ToDictionary(); Assert.Single(parameters); Assert.Contains("Parameter", parameters.Keys); 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.ToInt32(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(1, secondDescriptor.Sequence); } [Fact] public void CanParseMultipleMarkers() { // Arrange var markers = SerializeMarkers(CreateMarkers(typeof(TestComponent), typeof(TestComponent))); 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(TestComponent).FullName, firstDescriptor.ComponentType.FullName); Assert.Equal(0, firstDescriptor.Sequence); var secondDescriptor = descriptors[1]; Assert.Equal(typeof(TestComponent).FullName, secondDescriptor.ComponentType.FullName); Assert.Equal(1, secondDescriptor.Sequence); } [Fact] public void CanParseMultipleMarkersWithParameters() { // Arrange var markers = SerializeMarkers(CreateMarkers( (typeof(TestComponent), new Dictionary<string, object> { ["First"] = "Value" }), (typeof(TestComponent), new Dictionary<string, object> { ["Second"] = null }))); 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(TestComponent).FullName, firstDescriptor.ComponentType.FullName); Assert.Equal(0, firstDescriptor.Sequence); var firstParameters = firstDescriptor.Parameters.ToDictionary(); Assert.Single(firstParameters); Assert.Contains("First", firstParameters.Keys); Assert.Equal("Value", firstParameters["First"]); var secondDescriptor = descriptors[1]; Assert.Equal(typeof(TestComponent).FullName, secondDescriptor.ComponentType.FullName); Assert.Equal(1, secondDescriptor.Sequence); var secondParameters = secondDescriptor.Parameters.ToDictionary(); Assert.Single(secondParameters); Assert.Contains("Second", secondParameters.Keys); Assert.Null(secondParameters["Second"]); } [Fact] public void CanParseMultipleMarkersWithAndWithoutParameters() { // Arrange var markers = SerializeMarkers(CreateMarkers( (typeof(TestComponent), new Dictionary<string, object> { ["First"] = "Value" }), (typeof(TestComponent), null))); 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(TestComponent).FullName, firstDescriptor.ComponentType.FullName); Assert.Equal(0, firstDescriptor.Sequence); var firstParameters = firstDescriptor.Parameters.ToDictionary(); Assert.Single(firstParameters); Assert.Contains("First", firstParameters.Keys); Assert.Equal("Value", firstParameters["First"]); var secondDescriptor = descriptors[1]; Assert.Equal(typeof(TestComponent).FullName, secondDescriptor.ComponentType.FullName); Assert.Equal(1, secondDescriptor.Sequence); Assert.Empty(secondDescriptor.Parameters.ToDictionary()); } [Fact] public void DoesNotParseOutOfOrderMarkers() { // Arrange var markers = SerializeMarkers(Enumerable.Reverse(CreateMarkers(typeof(TestComponent), typeof(TestComponent))).ToArray()); var serverComponentDeserializer = CreateServerComponentDeserializer(); // Act & assert Assert.False(serverComponentDeserializer.TryDeserializeComponentDescriptorCollection(markers, out var descriptors)); Assert.Empty(descriptors); } [Fact] public void DoesNotParseMarkersFromDifferentInvocationSequences() { // Arrange var firstChain = CreateMarkers(typeof(TestComponent)); var secondChain = CreateMarkers(new ServerComponentInvocationSequence(), typeof(TestComponent), typeof(TestComponent)).Skip(1); var markers = SerializeMarkers(firstChain.Concat(secondChain).ToArray()); var serverComponentDeserializer = CreateServerComponentDeserializer(); // Act & assert Assert.False(serverComponentDeserializer.TryDeserializeComponentDescriptorCollection(markers, out var descriptors)); Assert.Empty(descriptors); } [Fact] public void DoesNotParseMarkersWhoseSequenceDoesNotStartAtZero() { // Arrange var markers = SerializeMarkers(CreateMarkers(typeof(TestComponent), typeof(TestComponent)).Skip(1).ToArray()); var serverComponentDeserializer = CreateServerComponentDeserializer(); // Act & assert Assert.False(serverComponentDeserializer.TryDeserializeComponentDescriptorCollection(markers, out var descriptors)); Assert.Empty(descriptors); } [Fact] public void DoesNotParseMarkersWithGapsInTheSequence() { // Arrange var brokenChain = CreateMarkers(typeof(TestComponent), typeof(TestComponent), typeof(TestComponent)) .Where(m => m.Sequence != 1) .ToArray(); var markers = SerializeMarkers(brokenChain); var serverComponentDeserializer = CreateServerComponentDeserializer(); // Act & assert Assert.False(serverComponentDeserializer.TryDeserializeComponentDescriptorCollection(markers, out var descriptors)); Assert.Empty(descriptors); } [Fact] public void DoesNotParseMarkersWithMissingDescriptor() { // Arrange var missingDescriptorMarker = CreateMarkers(typeof(TestComponent)); missingDescriptorMarker[0].Descriptor = null; var markers = SerializeMarkers(missingDescriptorMarker); var serverComponentDeserializer = CreateServerComponentDeserializer(); // Act & assert Assert.False(serverComponentDeserializer.TryDeserializeComponentDescriptorCollection(markers, out var descriptors)); Assert.Empty(descriptors); } [Fact] public void DoesNotParseMarkersWithMissingType() { // Arrange var missingTypeMarker = CreateMarkers(typeof(TestComponent)); missingTypeMarker[0].Type = null; var markers = SerializeMarkers(missingTypeMarker); var serverComponentDeserializer = CreateServerComponentDeserializer(); // Act & assert Assert.False(serverComponentDeserializer.TryDeserializeComponentDescriptorCollection(markers, out var descriptors)); Assert.Empty(descriptors); } // Ensures we don't use untrusted data for validation. [Fact] public void AllowsMarkersWithMissingSequence() { // Arrange var missingSequenceMarker = CreateMarkers(typeof(TestComponent), typeof(TestComponent)); missingSequenceMarker[0].Sequence = null; missingSequenceMarker[1].Sequence = null; var markers = SerializeMarkers(missingSequenceMarker); var serverComponentDeserializer = CreateServerComponentDeserializer(); // Act & assert Assert.True(serverComponentDeserializer.TryDeserializeComponentDescriptorCollection(markers, out var descriptors)); Assert.Equal(2, descriptors.Count); } // Ensures that we don't try to load assemblies [Fact] public void DoesNotParseMarkersWithUnknownComponentTypeAssembly() { // Arrange var missingUnknownComponentTypeMarker = CreateMarkers(typeof(TestComponent)); missingUnknownComponentTypeMarker[0].Descriptor = _protector.Protect( SerializeComponent("UnknownAssembly", "System.String"), TimeSpan.FromSeconds(30)); var markers = SerializeMarkers(missingUnknownComponentTypeMarker); var serverComponentDeserializer = CreateServerComponentDeserializer(); // Act & assert Assert.False(serverComponentDeserializer.TryDeserializeComponentDescriptorCollection(markers, out var descriptors)); Assert.Empty(descriptors); } [Fact] public void DoesNotParseMarkersWithUnknownComponentTypeName() { // Arrange var missingUnknownComponentTypeMarker = CreateMarkers(typeof(TestComponent)); missingUnknownComponentTypeMarker[0].Descriptor = _protector.Protect( SerializeComponent(typeof(TestComponent).Assembly.GetName().Name, "Unknown.Type"), TimeSpan.FromSeconds(30)); var markers = SerializeMarkers(missingUnknownComponentTypeMarker); var serverComponentDeserializer = CreateServerComponentDeserializer(); // Act & assert Assert.False(serverComponentDeserializer.TryDeserializeComponentDescriptorCollection(markers, out var descriptors)); Assert.Empty(descriptors); } [Fact] public void DoesNotParseMarkersWithInvalidDescriptorPayloads() { // Arrange var invalidDescriptorMarker = CreateMarkers(typeof(TestComponent)); invalidDescriptorMarker[0].Descriptor = "nondataprotecteddata"; var markers = SerializeMarkers(invalidDescriptorMarker); var serverComponentDeserializer = CreateServerComponentDeserializer(); // Act & assert Assert.False(serverComponentDeserializer.TryDeserializeComponentDescriptorCollection(markers, out var descriptors)); Assert.Empty(descriptors); } [Fact] public void TryDeserializeWebRootComponentDescriptor_CanParseSingleMarker() { // Arrange var markers = CreateMarkers(typeof(TestComponent)); var serverComponentDeserializer = CreateServerComponentDeserializer(); // Act & assert Assert.True(serverComponentDeserializer.TryDeserializeWebRootComponentDescriptor(markers[0], out var descriptor)); Assert.Equal(typeof(TestComponent).FullName, descriptor.ComponentType.FullName); } [Fact] public void TryDeserializeWebRootComponentDescriptor_CanParseMultipleMarkersWithAndWithoutParameters() { // Arrange var markers = CreateMarkers( (typeof(TestComponent), new Dictionary<string, object> { ["First"] = "Value" }), (typeof(TestComponent), null)); var serverComponentDeserializer = CreateServerComponentDeserializer(); // Act & assert Assert.True(serverComponentDeserializer.TryDeserializeWebRootComponentDescriptor(markers[0], out var firstDescriptor)); Assert.True(serverComponentDeserializer.TryDeserializeWebRootComponentDescriptor(markers[1], out var secondDescriptor)); Assert.Equal(typeof(TestComponent).FullName, firstDescriptor.ComponentType.FullName); var firstParameters = firstDescriptor.Parameters.Parameters.ToDictionary(); Assert.Single(firstParameters); Assert.Contains("First", firstParameters.Keys); Assert.Equal("Value", firstParameters["First"]); Assert.Equal(typeof(TestComponent).FullName, secondDescriptor.ComponentType.FullName); Assert.Empty(secondDescriptor.Parameters.Parameters.ToDictionary()); } [Fact] public void TryDeserializeWebRootComponentDescriptor_AllowsParsingMarkersOutOfOrder() { // Arrange var markers = CreateMarkers(typeof(TestComponent), typeof(TestComponent)); var serverComponentDeserializer = CreateServerComponentDeserializer(); // Act & assert Assert.True(serverComponentDeserializer.TryDeserializeWebRootComponentDescriptor(markers[1], out _)); Assert.True(serverComponentDeserializer.TryDeserializeWebRootComponentDescriptor(markers[0], out _)); } [Fact] public void TryDeserializeWebRootComponentDescriptor_AllowsParsingMarkersFromMultipleInvocations() { // Arrange var firstInvocationMarkers = CreateMarkers(typeof(TestComponent)); StartNewInvocation(); var secondInvocationMarkers = CreateMarkers(typeof(TestComponent)); var serverComponentDeserializer = CreateServerComponentDeserializer(); // Act & assert Assert.True(serverComponentDeserializer.TryDeserializeWebRootComponentDescriptor(firstInvocationMarkers[0], out _)); Assert.True(serverComponentDeserializer.TryDeserializeWebRootComponentDescriptor(secondInvocationMarkers[0], out _)); } [Fact] public void TryDeserializeWebRootComponentDescriptor_DoesNotParseTheSameMarkerTwice() { // Arrange var markers = CreateMarkers(typeof(TestComponent)); var serverComponentDeserializer = CreateServerComponentDeserializer(); // Act & assert Assert.True(serverComponentDeserializer.TryDeserializeWebRootComponentDescriptor(markers[0], out _)); Assert.False(serverComponentDeserializer.TryDeserializeWebRootComponentDescriptor(markers[0], out _)); } [Fact] public void TryDeserializeWebRootComponentDescriptor_DoesNotParseMarkerFromOldInvocation() { // Arrange var firstInvocationMarkers = CreateMarkers(typeof(TestComponent), typeof(TestComponent)); StartNewInvocation(); var secondInvocationMarkers = CreateMarkers(typeof(TestComponent)); var serverComponentDeserializer = CreateServerComponentDeserializer(); // Act & assert Assert.True(serverComponentDeserializer.TryDeserializeWebRootComponentDescriptor(firstInvocationMarkers[0], out _)); Assert.True(serverComponentDeserializer.TryDeserializeWebRootComponentDescriptor(secondInvocationMarkers[0], out _)); Assert.False(serverComponentDeserializer.TryDeserializeWebRootComponentDescriptor(firstInvocationMarkers[0], out _)); } [Fact] public void UpdateRootComponents_TryDeserializeRootComponentOperationsReturnsFalse_WhenSsrComponentIdIsRepeated() { // Arrange var operation = new RootComponentOperation { Type = RootComponentOperationType.Update, SsrComponentId = 1, Marker = CreateMarker(typeof(DynamicallyAddedComponent), new() { ["Message"] = "Some other message", }), }; var other = new RootComponentOperation { Type = RootComponentOperationType.Remove, SsrComponentId = 1, Marker = CreateMarker(typeof(DynamicallyAddedComponent)), }; var batchJson = SerializeRootComponentOperationBatch(new() { Operations = [operation, other] }); var deserializer = CreateServerComponentDeserializer(); // Act var result = deserializer.TryDeserializeRootComponentOperations(batchJson, out var parsed); // Assert Assert.False(result); Assert.Null(parsed); } private string SerializeComponent(string assembly, string type) => JsonSerializer.Serialize( new ServerComponent(0, null, assembly, type, Array.Empty<ComponentParameter>(), Array.Empty<object>(), Guid.NewGuid()), ServerComponentSerializationSettings.JsonSerializationOptions); private string SerializeRootComponentOperationBatch(RootComponentOperationBatch batch) => JsonSerializer.Serialize(batch, ServerComponentSerializationSettings.JsonSerializationOptions); private ServerComponentDeserializer CreateServerComponentDeserializer() { return new ServerComponentDeserializer( _ephemeralDataProtectionProvider, NullLogger<ServerComponentDeserializer>.Instance, new RootTypeCache(), new ComponentParameterDeserializer(NullLogger<ComponentParameterDeserializer>.Instance, new ComponentParametersTypeCache())); } private string SerializeMarkers(ComponentMarker[] markers) => JsonSerializer.Serialize(markers, ServerComponentSerializationSettings.JsonSerializationOptions); private ComponentMarker CreateMarker(Type type, Dictionary<string, object> parameters = null) { var serializer = new ServerComponentSerializer(_ephemeralDataProtectionProvider); var key = new ComponentMarkerKey(type.FullName, null); var marker = ComponentMarker.Create(ComponentMarker.ServerMarkerType, false, key); serializer.SerializeInvocation( ref marker, _invocationSequence, type, parameters is null ? ParameterView.Empty : ParameterView.FromDictionary(parameters)); return marker; } private ComponentMarker[] CreateMarkers(params Type[] types) { var serializer = new ServerComponentSerializer(_ephemeralDataProtectionProvider); var markers = new ComponentMarker[types.Length]; for (var i = 0; i < types.Length; i++) { markers[i] = ComponentMarker.Create(ComponentMarker.ServerMarkerType, false, null); serializer.SerializeInvocation(ref markers[i], _invocationSequence, types[i], ParameterView.Empty); } return markers; } private ComponentMarker[] CreateMarkers(params (Type, Dictionary<string, object>)[] types) { var serializer = new ServerComponentSerializer(_ephemeralDataProtectionProvider); var markers = new ComponentMarker[types.Length]; for (var i = 0; i < types.Length; i++) { var (type, parameters) = types[i]; markers[i] = ComponentMarker.Create(ComponentMarker.ServerMarkerType, false, null); serializer.SerializeInvocation( ref markers[i], _invocationSequence, type, parameters == null ? ParameterView.Empty : ParameterView.FromDictionary(parameters)); } return markers; } private ComponentMarker[] CreateMarkers(ServerComponentInvocationSequence sequence, params Type[] types) { var serializer = new ServerComponentSerializer(_ephemeralDataProtectionProvider); var markers = new ComponentMarker[types.Length]; for (var i = 0; i < types.Length; i++) { markers[i] = ComponentMarker.Create(ComponentMarker.ServerMarkerType, false, null); serializer.SerializeInvocation(ref markers[i], sequence, types[i], ParameterView.Empty); } return markers; } private void StartNewInvocation() { _invocationSequence = new(); } private class TestComponent : IComponent { public void Attach(RenderHandle renderHandle) => throw new NotImplementedException(); public Task SetParametersAsync(ParameterView parameters) => throw new NotImplementedException(); } 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(); } }