Support parameters on server-side rendered components
Fixes https://github.com/aspnet/AspNetCore/issues/14433
This commit is contained in:
parent
cc368c8e08
commit
6bc4d27bfa
|
|
@ -111,8 +111,8 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
var count = Descriptors.Count;
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var (componentType, sequence) = Descriptors[i];
|
||||
await Renderer.AddComponentAsync(componentType, sequence.ToString());
|
||||
var (componentType, parameters, sequence) = Descriptors[i];
|
||||
await Renderer.AddComponentAsync(componentType, parameters, sequence.ToString());
|
||||
}
|
||||
|
||||
Log.InitializationSucceeded(_logger);
|
||||
|
|
|
|||
|
|
@ -9,12 +9,11 @@ namespace Microsoft.AspNetCore.Components.Server
|
|||
{
|
||||
public Type ComponentType { get; set; }
|
||||
|
||||
public ParameterView Parameters { get; set; }
|
||||
|
||||
public int Sequence { get; set; }
|
||||
|
||||
public void Deconstruct(out Type componentType, out int sequence)
|
||||
{
|
||||
componentType = ComponentType;
|
||||
sequence = Sequence;
|
||||
}
|
||||
public void Deconstruct(out Type componentType, out ParameterView parameters, out int sequence) =>
|
||||
(componentType, sequence, parameters) = (ComponentType, Sequence, Parameters);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,188 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Server
|
||||
{
|
||||
internal class ComponentParameterDeserializer
|
||||
{
|
||||
private readonly ILogger<ComponentParameterDeserializer> _logger;
|
||||
private readonly ComponentParametersTypeCache _parametersCache;
|
||||
|
||||
public ComponentParameterDeserializer(
|
||||
ILogger<ComponentParameterDeserializer> logger,
|
||||
ComponentParametersTypeCache parametersCache)
|
||||
{
|
||||
_logger = logger;
|
||||
_parametersCache = parametersCache;
|
||||
}
|
||||
|
||||
public bool TryDeserializeParameters(IList<ComponentParameter> parametersDefinitions, IList<object> parameterValues, out ParameterView parameters)
|
||||
{
|
||||
parameters = default;
|
||||
var parametersDictionary = new Dictionary<string, object>();
|
||||
|
||||
if (parameterValues.Count != parametersDefinitions.Count)
|
||||
{
|
||||
// Mismatched number of definition/parameter values.
|
||||
Log.MismatchedParameterAndDefinitions(_logger, parametersDefinitions.Count, parameterValues.Count);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = 0; i < parametersDefinitions.Count; i++)
|
||||
{
|
||||
var definition = parametersDefinitions[i];
|
||||
if (definition.Name == null)
|
||||
{
|
||||
Log.MissingParameterDefinitionName(_logger);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (definition.TypeName == null && definition.Assembly == null)
|
||||
{
|
||||
parametersDictionary.Add(definition.Name, null);
|
||||
}
|
||||
else if (definition.TypeName == null || definition.Assembly == null)
|
||||
{
|
||||
Log.IncompleteParameterDefinition(_logger, definition.Name, definition.TypeName, definition.Assembly);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
var parameterType = _parametersCache.GetParameterType(definition.Assembly, definition.TypeName);
|
||||
if (parameterType == null)
|
||||
{
|
||||
Log.InvalidParameterType(_logger, definition.Name, definition.Assembly, definition.TypeName);
|
||||
return false;
|
||||
}
|
||||
try
|
||||
{
|
||||
// At this point we know the parameter is not null, as we don't serialize the type name or the assembly name
|
||||
// for null parameters.
|
||||
var value = (JsonElement)parameterValues[i];
|
||||
var parameterValue = JsonSerializer.Deserialize(
|
||||
value.GetRawText(),
|
||||
parameterType,
|
||||
ServerComponentSerializationSettings.JsonSerializationOptions);
|
||||
|
||||
parametersDictionary.Add(definition.Name, parameterValue);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.InvalidParameterValue(_logger, definition.Name, definition.TypeName, definition.Assembly, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parameters = ParameterView.FromDictionary(parametersDictionary);
|
||||
return true;
|
||||
}
|
||||
|
||||
private ComponentParameter[] GetParameterDefinitions(string parametersDefinitions)
|
||||
{
|
||||
try
|
||||
{
|
||||
return JsonSerializer.Deserialize<ComponentParameter[]>(parametersDefinitions, ServerComponentSerializationSettings.JsonSerializationOptions);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.FailedToParseParameterDefinitions(_logger, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private JsonDocument GetParameterValues(string parameterValues)
|
||||
{
|
||||
try
|
||||
{
|
||||
return JsonDocument.Parse(parameterValues);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.FailedToParseParameterValues(_logger, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static class Log
|
||||
{
|
||||
private static readonly Action<ILogger, Exception> _parameterValuesInvalidFormat =
|
||||
LoggerMessage.Define(
|
||||
LogLevel.Debug,
|
||||
new EventId(1, "ParameterValuesInvalidFormat"),
|
||||
"Parameter values must be an array.");
|
||||
|
||||
private static readonly Action<ILogger, string, string, string, Exception> _incompleteParameterDefinition =
|
||||
LoggerMessage.Define<string, string, string>(
|
||||
LogLevel.Debug,
|
||||
new EventId(2, "IncompleteParameterDefinition"),
|
||||
"The parameter definition for '{ParameterName}' is incomplete: Type='{TypeName}' Assembly='{Assembly}'.");
|
||||
|
||||
private static readonly Action<ILogger, string, string, string, Exception> _invalidParameterType =
|
||||
LoggerMessage.Define<string, string, string>(
|
||||
LogLevel.Debug,
|
||||
new EventId(3, "InvalidParameterType"),
|
||||
"The parameter '{ParameterName} with type '{TypeName}' in assembly '{Assembly}' could not be found.");
|
||||
|
||||
private static readonly Action<ILogger, string, string, string, Exception> _invalidParameterValue =
|
||||
LoggerMessage.Define<string, string, string>(
|
||||
LogLevel.Debug,
|
||||
new EventId(4, "InvalidParameterValue"),
|
||||
"Could not parse the parameter value for parameter '{Name}' of type '{TypeName}' and assembly '{Assembly}'.");
|
||||
|
||||
private static readonly Action<ILogger, Exception> _failedToParseParameterDefinitions =
|
||||
LoggerMessage.Define(
|
||||
LogLevel.Debug,
|
||||
new EventId(5, "FailedToParseParameterDefinitions"),
|
||||
"Failed to parse the parameter definitions.");
|
||||
|
||||
private static readonly Action<ILogger, Exception> _failedToParseParameterValues =
|
||||
LoggerMessage.Define(
|
||||
LogLevel.Debug,
|
||||
new EventId(6, "FailedToParseParameterValues"),
|
||||
"Failed to parse the parameter values.");
|
||||
|
||||
private static readonly Action<ILogger, int, int, Exception> _mismatchedParameterAndDefinitions =
|
||||
LoggerMessage.Define<int, int>(
|
||||
LogLevel.Debug,
|
||||
new EventId(7, "MismatchedParameterAndDefinitions"),
|
||||
"The number of parameter definitions '{DescriptorsLength}' does not match the number parameter values '{ValuesLength}'.");
|
||||
|
||||
private static readonly Action<ILogger, Exception> _missingParameterDefinitionName =
|
||||
LoggerMessage.Define(
|
||||
LogLevel.Debug,
|
||||
new EventId(8, "MissingParameterDefinitionName"),
|
||||
"The name is missing in a parameter definition.");
|
||||
|
||||
internal static void ParameterValuesInvalidFormat(ILogger<ComponentParameterDeserializer> logger) =>
|
||||
_parameterValuesInvalidFormat(logger, null);
|
||||
|
||||
internal static void IncompleteParameterDefinition(ILogger<ComponentParameterDeserializer> logger, string name, string typeName, string assembly) =>
|
||||
_incompleteParameterDefinition(logger, name, typeName, assembly, null);
|
||||
|
||||
internal static void InvalidParameterType(ILogger<ComponentParameterDeserializer> logger, string name, string assembly, string typeName) =>
|
||||
_invalidParameterType(logger, name, assembly, typeName, null);
|
||||
|
||||
internal static void InvalidParameterValue(ILogger<ComponentParameterDeserializer> logger, string name, string typeName, string assembly, Exception e) =>
|
||||
_invalidParameterValue(logger, name, typeName, assembly,e);
|
||||
|
||||
internal static void FailedToParseParameterDefinitions(ILogger<ComponentParameterDeserializer> logger, Exception e) =>
|
||||
_failedToParseParameterDefinitions(logger, e);
|
||||
|
||||
internal static void FailedToParseParameterValues(ILogger<ComponentParameterDeserializer> logger, Exception e) =>
|
||||
_failedToParseParameterValues(logger, e);
|
||||
|
||||
internal static void MismatchedParameterAndDefinitions(ILogger<ComponentParameterDeserializer> logger, int definitionsLength, int valuesLength) =>
|
||||
_mismatchedParameterAndDefinitions(logger, definitionsLength, valuesLength, null);
|
||||
|
||||
internal static void MissingParameterDefinitionName(ILogger<ComponentParameterDeserializer> logger) =>
|
||||
_missingParameterDefinitionName(logger, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components
|
||||
{
|
||||
internal class ComponentParametersTypeCache
|
||||
{
|
||||
private readonly ConcurrentDictionary<Key, Type> _typeToKeyLookUp = new ConcurrentDictionary<Key, Type>();
|
||||
|
||||
public Type GetParameterType(string assembly, string type)
|
||||
{
|
||||
var key = new Key(assembly, type);
|
||||
if (_typeToKeyLookUp.TryGetValue(key, out var resolvedType))
|
||||
{
|
||||
return resolvedType;
|
||||
}
|
||||
else
|
||||
{
|
||||
return _typeToKeyLookUp.GetOrAdd(key, ResolveType, AppDomain.CurrentDomain.GetAssemblies());
|
||||
}
|
||||
}
|
||||
|
||||
private static Type ResolveType(Key key, Assembly[] assemblies)
|
||||
{
|
||||
var assembly = assemblies
|
||||
.FirstOrDefault(a => string.Equals(a.GetName().Name, key.Assembly, StringComparison.Ordinal));
|
||||
|
||||
if (assembly == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return assembly.GetType(key.Type, throwOnError: false, ignoreCase: false);
|
||||
}
|
||||
|
||||
private struct Key : IEquatable<Key>
|
||||
{
|
||||
public Key(string assembly, string type) =>
|
||||
(Assembly, Type) = (assembly, type);
|
||||
|
||||
public string Assembly { get; set; }
|
||||
|
||||
public string Type { get; set; }
|
||||
|
||||
public override bool Equals(object obj) => Equals((Key)obj);
|
||||
|
||||
public bool Equals(Key other) => string.Equals(Assembly, other.Assembly, StringComparison.Ordinal) &&
|
||||
string.Equals(Type, other.Type, StringComparison.Ordinal);
|
||||
|
||||
public override int GetHashCode() => HashCode.Combine(Assembly, Type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -64,6 +64,24 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
return RenderRootComponentAsync(componentId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Associates the <see cref="IComponent"/> with the <see cref="RemoteRenderer"/>,
|
||||
/// causing it to be displayed in the specified DOM element.
|
||||
/// </summary>
|
||||
/// <param name="componentType">The type of the component.</param>
|
||||
/// <param name="parameters">The parameters for the component.</param>
|
||||
/// <param name="domElementSelector">A CSS selector that uniquely identifies a DOM element.</param>
|
||||
public Task AddComponentAsync(Type componentType, ParameterView parameters, string domElementSelector)
|
||||
{
|
||||
var component = InstantiateComponent(componentType);
|
||||
var componentId = AssignRootComponentId(component);
|
||||
|
||||
var attachComponentTask = _client.SendAsync("JS.AttachComponent", componentId, domElementSelector);
|
||||
CaptureAsyncExceptions(attachComponentTask);
|
||||
|
||||
return RenderRootComponentAsync(componentId, parameters);
|
||||
}
|
||||
|
||||
protected override void ProcessPendingRender()
|
||||
{
|
||||
if (_unacknowledgedRenderBatches.Count >= _options.MaxBufferedUnacknowledgedRenderBatches)
|
||||
|
|
|
|||
|
|
@ -25,8 +25,12 @@ namespace Microsoft.AspNetCore.Components.Server
|
|||
// 'sequence' indicates the order in which this component got rendered on the server.
|
||||
// 'assemblyName' the assembly name for the rendered component.
|
||||
// 'type' the full type name for the rendered component.
|
||||
// 'parameterDefinitions' a JSON serialized array that contains the definitions for the parameters including their names and types and assemblies.
|
||||
// 'parameterValues' a JSON serialized array containing the parameter values.
|
||||
// 'invocationId' a random string that matches all components rendered by as part of a single HTTP response.
|
||||
// For example: base64(dataprotection({ "sequence": 1, "assemblyName": "Microsoft.AspNetCore.Components", "type":"Microsoft.AspNetCore.Components.Routing.Router", "invocationId": "<<guid>>"}))
|
||||
// With parameters
|
||||
// For example: base64(dataprotection({ "sequence": 1, "assemblyName": "Microsoft.AspNetCore.Components", "type":"Microsoft.AspNetCore.Components.Routing.Router", "invocationId": "<<guid>>", parameterDefinitions: "[{ \"name\":\"Parameter\", \"typeName\":\"string\", \"assembly\":\"System.Private.CoreLib\"}], parameterValues: [<<string-value>>]}))
|
||||
|
||||
// Serialization:
|
||||
// For a given response, MVC renders one or more markers in sequence, including a descriptor for each rendered
|
||||
|
|
@ -55,11 +59,13 @@ namespace Microsoft.AspNetCore.Components.Server
|
|||
private readonly IDataProtector _dataProtector;
|
||||
private readonly ILogger<ServerComponentDeserializer> _logger;
|
||||
private readonly ServerComponentTypeCache _rootComponentTypeCache;
|
||||
private readonly ComponentParameterDeserializer _parametersDeserializer;
|
||||
|
||||
public ServerComponentDeserializer(
|
||||
IDataProtectionProvider dataProtectionProvider,
|
||||
ILogger<ServerComponentDeserializer> logger,
|
||||
ServerComponentTypeCache rootComponentTypeCache)
|
||||
ServerComponentTypeCache rootComponentTypeCache,
|
||||
ComponentParameterDeserializer parametersDeserializer)
|
||||
{
|
||||
// When we protect the data we use a time-limited data protector with the
|
||||
// limits established in 'ServerComponentSerializationSettings.DataExpiration'
|
||||
|
|
@ -74,6 +80,7 @@ namespace Microsoft.AspNetCore.Components.Server
|
|||
|
||||
_logger = logger;
|
||||
_rootComponentTypeCache = rootComponentTypeCache;
|
||||
_parametersDeserializer = parametersDeserializer;
|
||||
}
|
||||
|
||||
public bool TryDeserializeComponentDescriptorCollection(string serializedComponentRecords, out List<ComponentDescriptor> descriptors)
|
||||
|
|
@ -176,9 +183,16 @@ namespace Microsoft.AspNetCore.Components.Server
|
|||
return default;
|
||||
}
|
||||
|
||||
if (!_parametersDeserializer.TryDeserializeParameters(serverComponent.ParameterDefinitions, serverComponent.ParameterValues, out var parameters))
|
||||
{
|
||||
// TryDeserializeParameters does appropriate logging.
|
||||
return default;
|
||||
}
|
||||
|
||||
var componentDescriptor = new ComponentDescriptor
|
||||
{
|
||||
ComponentType = componentType,
|
||||
Parameters = parameters,
|
||||
Sequence = serverComponent.Sequence
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -58,6 +58,8 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
services.TryAddSingleton<CircuitFactory>();
|
||||
services.TryAddSingleton<ServerComponentDeserializer>();
|
||||
services.TryAddSingleton<ServerComponentTypeCache>();
|
||||
services.TryAddSingleton<ComponentParameterDeserializer>();
|
||||
services.TryAddSingleton<ComponentParametersTypeCache>();
|
||||
services.TryAddSingleton<CircuitIdFactory>();
|
||||
|
||||
services.TryAddScoped(s => s.GetRequiredService<ICircuitAccessor>().Circuit);
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@
|
|||
|
||||
<!-- Shared descriptor infrastructure with MVC -->
|
||||
<Compile Include="$(RepoRoot)src\Shared\Components\ServerComponent.cs" />
|
||||
<Compile Include="$(RepoRoot)src\Shared\Components\ComponentParameter.cs" />
|
||||
<Compile Include="$(RepoRoot)src\Shared\Components\ServerComponentSerializationSettings.cs" />
|
||||
<Compile Include="$(RepoRoot)src\Shared\Components\ServerComponentMarker.cs" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -40,6 +41,45 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
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 CanParseMultipleMarkers()
|
||||
{
|
||||
|
|
@ -60,6 +100,65 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
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()
|
||||
{
|
||||
|
|
@ -213,7 +312,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
|
||||
private string SerializeComponent(string assembly, string type) =>
|
||||
JsonSerializer.Serialize(
|
||||
new ServerComponent(0, assembly, type, Guid.NewGuid()),
|
||||
new ServerComponent(0, assembly, type, Array.Empty<ComponentParameter>(), Array.Empty<object>(), Guid.NewGuid()),
|
||||
ServerComponentSerializationSettings.JsonSerializationOptions);
|
||||
|
||||
private ServerComponentDeserializer CreateServerComponentDeserializer()
|
||||
|
|
@ -221,7 +320,8 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
return new ServerComponentDeserializer(
|
||||
_ephemeralDataProtectionProvider,
|
||||
NullLogger<ServerComponentDeserializer>.Instance,
|
||||
new ServerComponentTypeCache());
|
||||
new ServerComponentTypeCache(),
|
||||
new ComponentParameterDeserializer(NullLogger<ComponentParameterDeserializer>.Instance, new ComponentParametersTypeCache()));
|
||||
}
|
||||
|
||||
private string SerializeMarkers(ServerComponentMarker[] markers) =>
|
||||
|
|
@ -233,7 +333,24 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
var markers = new ServerComponentMarker[types.Length];
|
||||
for (var i = 0; i < types.Length; i++)
|
||||
{
|
||||
markers[i] = serializer.SerializeInvocation(_invocationSequence, types[i], false);
|
||||
markers[i] = serializer.SerializeInvocation(_invocationSequence, types[i], ParameterView.Empty, false);
|
||||
}
|
||||
|
||||
return markers;
|
||||
}
|
||||
|
||||
private ServerComponentMarker[] CreateMarkers(params (Type, Dictionary<string,object>)[] types)
|
||||
{
|
||||
var serializer = new ServerComponentSerializer(_ephemeralDataProtectionProvider);
|
||||
var markers = new ServerComponentMarker[types.Length];
|
||||
for (var i = 0; i < types.Length; i++)
|
||||
{
|
||||
var (type, parameters) = types[i];
|
||||
markers[i] = serializer.SerializeInvocation(
|
||||
_invocationSequence,
|
||||
type,
|
||||
parameters == null ? ParameterView.Empty : ParameterView.FromDictionary(parameters),
|
||||
false);
|
||||
}
|
||||
|
||||
return markers;
|
||||
|
|
@ -245,7 +362,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
var markers = new ServerComponentMarker[types.Length];
|
||||
for (var i = 0; i < types.Length; i++)
|
||||
{
|
||||
markers[i] = serializer.SerializeInvocation(sequence, types[i], false);
|
||||
markers[i] = serializer.SerializeInvocation(sequence, types[i], ParameterView.Empty, false);
|
||||
}
|
||||
|
||||
return markers;
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@
|
|||
<ItemGroup>
|
||||
<!-- Shared descriptor infrastructure with MVC -->
|
||||
<Compile Include="$(RepoRoot)src\Shared\Components\ServerComponent.cs" />
|
||||
<Compile Include="$(RepoRoot)src\Shared\Components\ComponentParameter.cs" />
|
||||
<Compile Include="$(RepoRoot)src\Shared\Components\ServerComponentSerializationSettings.cs" />
|
||||
<Compile Include="$(RepoRoot)src\Shared\Components\ServerComponentMarker.cs" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
|
|
@ -68,9 +68,10 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
|||
|
||||
var greets = Browser.FindElements(By.CssSelector(".greet-wrapper .greet")).Select(e => e.Text).ToArray();
|
||||
|
||||
Assert.Equal(4, greets.Length); // 1 statically rendered + 3 prerendered
|
||||
Assert.Equal(5, greets.Length); // 1 statically rendered + 3 prerendered + 1 server prerendered
|
||||
Assert.Single(greets, "Hello John");
|
||||
Assert.Equal(3, greets.Where(g => string.Equals("Hello", g)).Count()); // 3 prerendered
|
||||
Assert.Single(greets, "Hello Abraham");
|
||||
Assert.Equal(3, greets.Where(g => string.Equals("Hello", g)).Count()); // 3 server prerendered without parameters
|
||||
var content = Browser.FindElement(By.Id("test-container")).GetAttribute("innerHTML");
|
||||
var markers = ReadMarkers(content);
|
||||
var componentSequence = markers.Select(m => m.Item1.PrerenderId != null).ToArray();
|
||||
|
|
@ -84,6 +85,8 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
|||
true,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true
|
||||
};
|
||||
Assert.Equal(expectedComponentSequence, componentSequence);
|
||||
|
||||
|
|
@ -93,6 +96,8 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
|||
Browser.Exists(By.CssSelector("h3.interactive"));
|
||||
var updatedGreets = Browser.FindElements(By.CssSelector(".greet-wrapper .greet")).Select(e => e.Text).ToArray();
|
||||
Assert.Equal(7, updatedGreets.Where(g => string.Equals("Hello Alfred", g)).Count());
|
||||
Assert.Single(updatedGreets.Where(g => string.Equals("Hello Albert", g)));
|
||||
Assert.Single(updatedGreets.Where(g => string.Equals("Hello Abraham", g)));
|
||||
}
|
||||
|
||||
private (ServerComponentMarker, ServerComponentMarker)[] ReadMarkers(string content)
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
protected override void OnAfterRender(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
if (firstRender && Name == null)
|
||||
{
|
||||
Name = "Alfred";
|
||||
interactive = "interactive";
|
||||
|
|
|
|||
|
|
@ -27,6 +27,10 @@
|
|||
<p>Some content after</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="container">
|
||||
@(await Html.RenderComponentAsync<GreeterComponent>(RenderMode.Server, new { Name = "Albert" }))
|
||||
@(await Html.RenderComponentAsync<GreeterComponent>(RenderMode.ServerPrerendered, new { Name = "Abraham" }))
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@*
|
||||
|
|
|
|||
|
|
@ -94,11 +94,6 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
|
||||
private static async Task<IHtmlContent> PrerenderedServerComponentAsync(HttpContext context, ServerComponentInvocationSequence invocationId, Type type, ParameterView parametersCollection)
|
||||
{
|
||||
if (parametersCollection.GetEnumerator().MoveNext())
|
||||
{
|
||||
throw new InvalidOperationException("Prerendering server components with parameters is not supported.");
|
||||
}
|
||||
|
||||
var serviceProvider = context.RequestServices;
|
||||
var prerenderer = serviceProvider.GetRequiredService<StaticComponentRenderer>();
|
||||
var invocationSerializer = serviceProvider.GetRequiredService<ServerComponentSerializer>();
|
||||
|
|
@ -106,6 +101,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
var currentInvocation = invocationSerializer.SerializeInvocation(
|
||||
invocationId,
|
||||
type,
|
||||
parametersCollection,
|
||||
prerendered: true);
|
||||
|
||||
var result = await prerenderer.PrerenderComponentAsync(
|
||||
|
|
@ -121,14 +117,9 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
|
||||
private static IHtmlContent NonPrerenderedServerComponent(HttpContext context, ServerComponentInvocationSequence invocationId, Type type, ParameterView parametersCollection)
|
||||
{
|
||||
if (parametersCollection.GetEnumerator().MoveNext())
|
||||
{
|
||||
throw new InvalidOperationException("Server components with parameters are not supported.");
|
||||
}
|
||||
|
||||
var serviceProvider = context.RequestServices;
|
||||
var invocationSerializer = serviceProvider.GetRequiredService<ServerComponentSerializer>();
|
||||
var currentInvocation = invocationSerializer.SerializeInvocation(invocationId, type, prerendered: false);
|
||||
var currentInvocation = invocationSerializer.SerializeInvocation(invocationId, type, parametersCollection, prerendered: false);
|
||||
|
||||
return new ComponentHtmlContent(invocationSerializer.GetPreamble(currentInvocation));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@
|
|||
<Compile Include="$(RepoRoot)src\Shared\Components\ServerComponentSerializationSettings.cs" />
|
||||
<Compile Include="$(RepoRoot)src\Shared\Components\ServerComponentMarker.cs" />
|
||||
<Compile Include="$(RepoRoot)src\Shared\Components\ServerComponent.cs" />
|
||||
<Compile Include="$(RepoRoot)src\Shared\Components\ComponentParameter.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -19,22 +19,27 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
.CreateProtector(ServerComponentSerializationSettings.DataProtectionProviderPurpose)
|
||||
.ToTimeLimitedDataProtector();
|
||||
|
||||
public ServerComponentMarker SerializeInvocation(ServerComponentInvocationSequence invocationId, Type type, bool prerendered)
|
||||
public ServerComponentMarker SerializeInvocation(ServerComponentInvocationSequence invocationId, Type type, ParameterView parameters, bool prerendered)
|
||||
{
|
||||
var (sequence, serverComponent) = CreateSerializedServerComponent(invocationId, type);
|
||||
var (sequence, serverComponent) = CreateSerializedServerComponent(invocationId, type, parameters);
|
||||
return prerendered ? ServerComponentMarker.Prerendered(sequence, serverComponent) : ServerComponentMarker.NonPrerendered(sequence, serverComponent);
|
||||
}
|
||||
|
||||
private (int sequence, string payload) CreateSerializedServerComponent(
|
||||
ServerComponentInvocationSequence invocationId,
|
||||
Type rootComponent)
|
||||
Type rootComponent,
|
||||
ParameterView parameters)
|
||||
{
|
||||
var sequence = invocationId.Next();
|
||||
|
||||
var (definitions, values) = ComponentParameter.FromParameterView(parameters);
|
||||
|
||||
var serverComponent = new ServerComponent(
|
||||
sequence,
|
||||
rootComponent.Assembly.GetName().Name,
|
||||
rootComponent.FullName,
|
||||
definitions,
|
||||
values,
|
||||
invocationId.Value);
|
||||
|
||||
var serializedServerComponent = JsonSerializer.Serialize(serverComponent, ServerComponentSerializationSettings.JsonSerializationOptions);
|
||||
|
|
|
|||
|
|
@ -188,25 +188,208 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Test
|
|||
Assert.Equal("<p>Hello Steve!</p>", content);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(RenderMode.Server, "Server components with parameters are not supported.")]
|
||||
[InlineData(RenderMode.ServerPrerendered, "Prerendering server components with parameters is not supported.")]
|
||||
public async Task ComponentWithParametersObject_ThrowsInvalidOperationExceptionForServerRenderModes(
|
||||
RenderMode renderMode,
|
||||
string expectedMessage)
|
||||
[Fact]
|
||||
public async Task CanRender_ComponentWithParameters_ServerMode()
|
||||
{
|
||||
// Arrange
|
||||
var helper = CreateHelper();
|
||||
var writer = new StringWriter();
|
||||
var protector = _dataprotectorProvider.CreateProtector(ServerComponentSerializationSettings.DataProtectionProviderPurpose)
|
||||
.ToTimeLimitedDataProtector();
|
||||
|
||||
// Act & Assert
|
||||
var result = await Assert.ThrowsAsync<InvalidOperationException>(() => helper.RenderComponentAsync<GreetingComponent>(
|
||||
renderMode,
|
||||
// Act
|
||||
var result = await helper.RenderComponentAsync<GreetingComponent>(
|
||||
RenderMode.Server,
|
||||
new
|
||||
{
|
||||
Name = "Steve"
|
||||
}));
|
||||
Assert.Equal(expectedMessage, result.Message);
|
||||
Name = "Daniel"
|
||||
});
|
||||
result.WriteTo(writer, HtmlEncoder.Default);
|
||||
var content = writer.ToString();
|
||||
var match = Regex.Match(content, ServerComponentPattern);
|
||||
|
||||
// Assert
|
||||
Assert.True(match.Success);
|
||||
var marker = JsonSerializer.Deserialize<ServerComponentMarker>(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(GreetingComponent).Assembly.GetName().Name, serverComponent.AssemblyName);
|
||||
Assert.Equal(typeof(GreetingComponent).FullName, serverComponent.TypeName);
|
||||
Assert.NotEqual(Guid.Empty, serverComponent.InvocationId);
|
||||
|
||||
var parameterDefinition = Assert.Single(serverComponent.ParameterDefinitions);
|
||||
Assert.Equal("Name", 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("Daniel", rawValue.GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanRender_ComponentWithNullParameters_ServerMode()
|
||||
{
|
||||
// Arrange
|
||||
var helper = CreateHelper();
|
||||
var writer = new StringWriter();
|
||||
var protector = _dataprotectorProvider.CreateProtector(ServerComponentSerializationSettings.DataProtectionProviderPurpose)
|
||||
.ToTimeLimitedDataProtector();
|
||||
|
||||
// Act
|
||||
var result = await helper.RenderComponentAsync<GreetingComponent>(
|
||||
RenderMode.Server,
|
||||
new
|
||||
{
|
||||
Name = (string)null
|
||||
});
|
||||
result.WriteTo(writer, HtmlEncoder.Default);
|
||||
var content = writer.ToString();
|
||||
var match = Regex.Match(content, ServerComponentPattern);
|
||||
|
||||
// Assert
|
||||
Assert.True(match.Success);
|
||||
var marker = JsonSerializer.Deserialize<ServerComponentMarker>(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(GreetingComponent).Assembly.GetName().Name, serverComponent.AssemblyName);
|
||||
Assert.Equal(typeof(GreetingComponent).FullName, serverComponent.TypeName);
|
||||
Assert.NotEqual(Guid.Empty, serverComponent.InvocationId);
|
||||
|
||||
Assert.NotNull(serverComponent.ParameterDefinitions);
|
||||
var parameterDefinition = Assert.Single(serverComponent.ParameterDefinitions);
|
||||
Assert.Equal("Name", parameterDefinition.Name);
|
||||
Assert.Null(parameterDefinition.TypeName);
|
||||
Assert.Null(parameterDefinition.Assembly);
|
||||
|
||||
var value = Assert.Single(serverComponent.ParameterValues);;
|
||||
Assert.Null(value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanPrerender_ComponentWithParameters_ServerMode()
|
||||
{
|
||||
// Arrange
|
||||
var helper = CreateHelper();
|
||||
var writer = new StringWriter();
|
||||
var protector = _dataprotectorProvider.CreateProtector(ServerComponentSerializationSettings.DataProtectionProviderPurpose)
|
||||
.ToTimeLimitedDataProtector();
|
||||
|
||||
// Act
|
||||
var result = await helper.RenderComponentAsync<GreetingComponent>(
|
||||
RenderMode.ServerPrerendered,
|
||||
new
|
||||
{
|
||||
Name = "Daniel"
|
||||
});
|
||||
result.WriteTo(writer, HtmlEncoder.Default);
|
||||
var content = writer.ToString();
|
||||
var match = Regex.Match(content, PrerenderedServerComponentPattern, RegexOptions.Multiline);
|
||||
|
||||
// Assert
|
||||
Assert.True(match.Success);
|
||||
var preamble = match.Groups["preamble"].Value;
|
||||
var preambleMarker = JsonSerializer.Deserialize<ServerComponentMarker>(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(GreetingComponent).Assembly.GetName().Name, serverComponent.AssemblyName);
|
||||
Assert.Equal(typeof(GreetingComponent).FullName, serverComponent.TypeName);
|
||||
Assert.NotEqual(Guid.Empty, serverComponent.InvocationId);
|
||||
|
||||
var parameterDefinition = Assert.Single(serverComponent.ParameterDefinitions);
|
||||
Assert.Equal("Name", 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("Daniel", rawValue.GetString());
|
||||
|
||||
var prerenderedContent = match.Groups["content"].Value;
|
||||
Assert.Equal("<p>Hello Daniel!</p>", prerenderedContent);
|
||||
|
||||
var epilogue = match.Groups["epilogue"].Value;
|
||||
var epilogueMarker = JsonSerializer.Deserialize<ServerComponentMarker>(epilogue, ServerComponentSerializationSettings.JsonSerializationOptions);
|
||||
Assert.Equal(preambleMarker.PrerenderId, epilogueMarker.PrerenderId);
|
||||
Assert.Null(epilogueMarker.Sequence);
|
||||
Assert.Null(epilogueMarker.Descriptor);
|
||||
Assert.Null(epilogueMarker.Type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanPrerender_ComponentWithNullParameters_ServerMode()
|
||||
{
|
||||
// Arrange
|
||||
var helper = CreateHelper();
|
||||
var writer = new StringWriter();
|
||||
var protector = _dataprotectorProvider.CreateProtector(ServerComponentSerializationSettings.DataProtectionProviderPurpose)
|
||||
.ToTimeLimitedDataProtector();
|
||||
|
||||
// Act
|
||||
var result = await helper.RenderComponentAsync<GreetingComponent>(
|
||||
RenderMode.ServerPrerendered,
|
||||
new
|
||||
{
|
||||
Name = (string)null
|
||||
});
|
||||
result.WriteTo(writer, HtmlEncoder.Default);
|
||||
var content = writer.ToString();
|
||||
var match = Regex.Match(content, PrerenderedServerComponentPattern, RegexOptions.Multiline);
|
||||
|
||||
// Assert
|
||||
Assert.True(match.Success);
|
||||
var preamble = match.Groups["preamble"].Value;
|
||||
var preambleMarker = JsonSerializer.Deserialize<ServerComponentMarker>(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(GreetingComponent).Assembly.GetName().Name, serverComponent.AssemblyName);
|
||||
Assert.Equal(typeof(GreetingComponent).FullName, serverComponent.TypeName);
|
||||
Assert.NotEqual(Guid.Empty, serverComponent.InvocationId);
|
||||
|
||||
Assert.NotNull(serverComponent.ParameterDefinitions);
|
||||
var parameterDefinition = Assert.Single(serverComponent.ParameterDefinitions);
|
||||
Assert.Equal("Name", parameterDefinition.Name);
|
||||
Assert.Null(parameterDefinition.TypeName);
|
||||
Assert.Null(parameterDefinition.Assembly);
|
||||
|
||||
var value = Assert.Single(serverComponent.ParameterValues);
|
||||
Assert.Null(value);
|
||||
|
||||
var prerenderedContent = match.Groups["content"].Value;
|
||||
Assert.Equal("<p>Hello (null)!</p>", prerenderedContent);
|
||||
|
||||
var epilogue = match.Groups["epilogue"].Value;
|
||||
var epilogueMarker = JsonSerializer.Deserialize<ServerComponentMarker>(epilogue, ServerComponentSerializationSettings.JsonSerializationOptions);
|
||||
Assert.Equal(preambleMarker.PrerenderId, epilogueMarker.PrerenderId);
|
||||
Assert.Null(epilogueMarker.Sequence);
|
||||
Assert.Null(epilogueMarker.Descriptor);
|
||||
Assert.Null(epilogueMarker.Type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -547,7 +730,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Test
|
|||
var s = 0;
|
||||
base.BuildRenderTree(builder);
|
||||
builder.OpenElement(s++, "p");
|
||||
builder.AddContent(s++, $"Hello {Name}!");
|
||||
builder.AddContent(s++, $"Hello {Name ?? ("(null)")}!");
|
||||
builder.CloseElement();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components
|
||||
{
|
||||
internal struct ComponentParameter
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string TypeName { get; set; }
|
||||
public string Assembly { get; set; }
|
||||
|
||||
public static (IList<ComponentParameter> parameterDefinitions, IList<object> parameterValues) FromParameterView(ParameterView parameters)
|
||||
{
|
||||
var parameterDefinitions = new List<ComponentParameter>();
|
||||
var parameterValues = new List<object>();
|
||||
foreach (var kvp in parameters)
|
||||
{
|
||||
var valueType = kvp.Value?.GetType();
|
||||
parameterDefinitions.Add(new ComponentParameter
|
||||
{
|
||||
Name = kvp.Name,
|
||||
TypeName = valueType?.FullName,
|
||||
Assembly = valueType?.Assembly?.GetName()?.Name
|
||||
});
|
||||
|
||||
parameterValues.Add(kvp.Value);
|
||||
}
|
||||
|
||||
return (parameterDefinitions, parameterValues);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components
|
||||
{
|
||||
|
|
@ -14,8 +15,11 @@ namespace Microsoft.AspNetCore.Components
|
|||
int sequence,
|
||||
string assemblyName,
|
||||
string typeName,
|
||||
IList<ComponentParameter> parametersDefinitions,
|
||||
IList<object> parameterValues,
|
||||
Guid invocationId) =>
|
||||
(Sequence, AssemblyName, TypeName, InvocationId) = (sequence, assemblyName, typeName, invocationId);
|
||||
(Sequence, AssemblyName, TypeName, ParameterDefinitions, ParameterValues, InvocationId) =
|
||||
(sequence, assemblyName, typeName, parametersDefinitions, parameterValues, invocationId);
|
||||
|
||||
// The order in which this component was rendered
|
||||
public int Sequence { get; set; }
|
||||
|
|
@ -26,6 +30,12 @@ namespace Microsoft.AspNetCore.Components
|
|||
// The type name of the component.
|
||||
public string TypeName { get; set; }
|
||||
|
||||
// The definition for the parameters for the component.
|
||||
public IList<ComponentParameter> ParameterDefinitions { get; set; }
|
||||
|
||||
// The values for the parameters for the component.
|
||||
public IList<object> ParameterValues { get; set; }
|
||||
|
||||
// An id that uniquely identifies all components generated as part of a single HTTP response.
|
||||
public Guid InvocationId { get; set; }
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue