Improve SetParametersAsync docs (#12826)
* Improve SetParametersAsync docs Fixes: #12820 * Clarify semantics of clashing cascading parameters This change attempts to lock down the semantics of how cascading parameters behave inside ParameterView.SetParameterProperties. Previously a cascading value could be used to set a non-cascading parameter, and vice-versa. We were just missing tests in general for how cascading values are interpreted by the parameter set logic.
This commit is contained in:
parent
31cfa2e305
commit
2b0a1686c3
|
|
@ -171,10 +171,25 @@ namespace Microsoft.AspNetCore.Components
|
||||||
_renderHandle = renderHandle;
|
_renderHandle = renderHandle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Method invoked to apply initial or updated parameters to the component.
|
/// Sets parameters supplied by the component's parent in the render tree.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="parameters">The parameters to apply.</param>
|
/// <param name="parameters">The parameters.</param>
|
||||||
|
/// <returns>A <see cref="Task"/> that completes when the component has finished updating and rendering itself.</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// The <see cref="SetParametersAsync(ParameterView)"/> method should be passed the entire set of parameter values each
|
||||||
|
/// time <see cref="SetParametersAsync(ParameterView)"/> is called. It not required that the caller supply a parameter
|
||||||
|
/// value for all parameters that are logically understood by the component.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// The default implementation of <see cref="SetParametersAsync(ParameterView)"/> will set the value of each property
|
||||||
|
/// decorated with <see cref="ParameterAttribute" /> or <see cref="CascadingParameterAttribute" /> that has
|
||||||
|
/// a corresponding value in the <see cref="ParameterView" />. Parameters that do not have a corresponding value
|
||||||
|
/// will be unchanged.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
public virtual Task SetParametersAsync(ParameterView parameters)
|
public virtual Task SetParametersAsync(ParameterView parameters)
|
||||||
{
|
{
|
||||||
parameters.SetParameterProperties(this);
|
parameters.SetParameterProperties(this);
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ namespace Microsoft.AspNetCore.Components
|
||||||
(
|
(
|
||||||
propertyName: property.Name,
|
propertyName: property.Name,
|
||||||
propertyType: property.PropertyType,
|
propertyType: property.PropertyType,
|
||||||
setter: MemberAssignment.CreatePropertySetter(type, property)
|
setter: MemberAssignment.CreatePropertySetter(type, property, cascading: false)
|
||||||
)).ToArray();
|
)).ToArray();
|
||||||
|
|
||||||
return Initialize;
|
return Initialize;
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,11 @@ namespace Microsoft.AspNetCore.Components
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="parameters">The parameters.</param>
|
/// <param name="parameters">The parameters.</param>
|
||||||
/// <returns>A <see cref="Task"/> that completes when the component has finished updating and rendering itself.</returns>
|
/// <returns>A <see cref="Task"/> that completes when the component has finished updating and rendering itself.</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// The <see cref="SetParametersAsync(ParameterView)"/> method should be passed the entire set of parameter values each
|
||||||
|
/// time <see cref="SetParametersAsync(ParameterView)"/> is called. It not required that the caller supply a parameter
|
||||||
|
/// value for all parameters that are logically understood by the component.
|
||||||
|
/// </remarks>
|
||||||
Task SetParametersAsync(ParameterView parameters);
|
Task SetParametersAsync(ParameterView parameters);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,9 @@ namespace Microsoft.AspNetCore.Components.Reflection
|
||||||
{
|
{
|
||||||
private const BindingFlags _bindablePropertyFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.IgnoreCase;
|
private const BindingFlags _bindablePropertyFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.IgnoreCase;
|
||||||
|
|
||||||
|
// Right now it's not possible for a component to define a Parameter and a Cascading Parameter with
|
||||||
|
// the same name. We don't give you a way to express this in code (would create duplicate properties),
|
||||||
|
// and we don't have the ability to represent it in our data structures.
|
||||||
private readonly static ConcurrentDictionary<Type, WritersForType> _cachedWritersByType
|
private readonly static ConcurrentDictionary<Type, WritersForType> _cachedWritersByType
|
||||||
= new ConcurrentDictionary<Type, WritersForType>();
|
= new ConcurrentDictionary<Type, WritersForType>();
|
||||||
|
|
||||||
|
|
@ -44,6 +47,24 @@ namespace Microsoft.AspNetCore.Components.Reflection
|
||||||
ThrowForUnknownIncomingParameterName(targetType, parameterName);
|
ThrowForUnknownIncomingParameterName(targetType, parameterName);
|
||||||
throw null; // Unreachable
|
throw null; // Unreachable
|
||||||
}
|
}
|
||||||
|
else if (writer.Cascading && !parameter.Cascading)
|
||||||
|
{
|
||||||
|
// We don't allow you to set a cascading parameter with a non-cascading value. Put another way:
|
||||||
|
// cascading parameters are not part of the public API of a component, so it's not reasonable
|
||||||
|
// for someone to set it directly.
|
||||||
|
//
|
||||||
|
// If we find a strong reason for this to work in the future we can reverse our decision since
|
||||||
|
// this throws today.
|
||||||
|
ThrowForSettingCascadingParameterWithNonCascadingValue(targetType, parameterName);
|
||||||
|
throw null; // Unreachable
|
||||||
|
}
|
||||||
|
else if (!writer.Cascading && parameter.Cascading)
|
||||||
|
{
|
||||||
|
// We're giving a more specific error here because trying to set a non-cascading parameter
|
||||||
|
// with a cascading value is likely deliberate (but not supported), or is a bug in our code.
|
||||||
|
ThrowForSettingParameterWithCascadingValue(targetType, parameterName);
|
||||||
|
throw null; // Unreachable
|
||||||
|
}
|
||||||
|
|
||||||
SetProperty(target, writer, parameterName, parameter.Value);
|
SetProperty(target, writer, parameterName, parameter.Value);
|
||||||
}
|
}
|
||||||
|
|
@ -62,7 +83,24 @@ namespace Microsoft.AspNetCore.Components.Reflection
|
||||||
}
|
}
|
||||||
|
|
||||||
var isUnmatchedValue = !writers.WritersByName.TryGetValue(parameterName, out var writer);
|
var isUnmatchedValue = !writers.WritersByName.TryGetValue(parameterName, out var writer);
|
||||||
if (isUnmatchedValue)
|
|
||||||
|
if ((isUnmatchedValue && parameter.Cascading) || (writer != null && !writer.Cascading && parameter.Cascading))
|
||||||
|
{
|
||||||
|
// Don't allow an "extra" cascading value to be collected - or don't allow a non-cascading
|
||||||
|
// parameter to be set with a cascading value.
|
||||||
|
//
|
||||||
|
// This is likely a bug in our infrastructure or an attempt to deliberately do something unsupported.
|
||||||
|
ThrowForSettingParameterWithCascadingValue(targetType, parameterName);
|
||||||
|
throw null; // Unreachable
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (isUnmatchedValue ||
|
||||||
|
|
||||||
|
// Allow unmatched parameters to collide with the names of cascading parameters. This is
|
||||||
|
// valid because cascading parameter names are not part of the public API. There's no
|
||||||
|
// way for the user of a component to know what the names of cascading parameters
|
||||||
|
// are.
|
||||||
|
(writer.Cascading && !parameter.Cascading))
|
||||||
{
|
{
|
||||||
unmatched ??= new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
unmatched ??= new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
||||||
unmatched[parameterName] = parameter.Value;
|
unmatched[parameterName] = parameter.Value;
|
||||||
|
|
@ -138,6 +176,20 @@ namespace Microsoft.AspNetCore.Components.Reflection
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void ThrowForSettingCascadingParameterWithNonCascadingValue(Type targetType, string parameterName)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Object of type '{targetType.FullName}' has a property matching the name '{parameterName}', " +
|
||||||
|
$"but it does not have [{nameof(ParameterAttribute)}] applied.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ThrowForSettingParameterWithCascadingValue(Type targetType, string parameterName)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"The property '{parameterName}' on component type '{targetType.FullName}' cannot be set " +
|
||||||
|
$"using a cascading value.");
|
||||||
|
}
|
||||||
|
|
||||||
private static void ThrowForCaptureUnmatchedValuesConflict(Type targetType, string parameterName, Dictionary<string, object> unmatched)
|
private static void ThrowForCaptureUnmatchedValuesConflict(Type targetType, string parameterName, Dictionary<string, object> unmatched)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException(
|
throw new InvalidOperationException(
|
||||||
|
|
@ -179,13 +231,14 @@ namespace Microsoft.AspNetCore.Components.Reflection
|
||||||
foreach (var propertyInfo in GetCandidateBindableProperties(targetType))
|
foreach (var propertyInfo in GetCandidateBindableProperties(targetType))
|
||||||
{
|
{
|
||||||
var parameterAttribute = propertyInfo.GetCustomAttribute<ParameterAttribute>();
|
var parameterAttribute = propertyInfo.GetCustomAttribute<ParameterAttribute>();
|
||||||
var isParameter = parameterAttribute != null || propertyInfo.IsDefined(typeof(CascadingParameterAttribute));
|
var cascadingParameterAttribute = propertyInfo.GetCustomAttribute<CascadingParameterAttribute>();
|
||||||
|
var isParameter = parameterAttribute != null || cascadingParameterAttribute != null;
|
||||||
if (!isParameter)
|
if (!isParameter)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var propertySetter = MemberAssignment.CreatePropertySetter(targetType, propertyInfo);
|
var propertySetter = MemberAssignment.CreatePropertySetter(targetType, propertyInfo, cascading: cascadingParameterAttribute != null);
|
||||||
|
|
||||||
var propertyName = propertyInfo.Name;
|
var propertyName = propertyInfo.Name;
|
||||||
if (WritersByName.ContainsKey(propertyName))
|
if (WritersByName.ContainsKey(propertyName))
|
||||||
|
|
@ -213,7 +266,7 @@ namespace Microsoft.AspNetCore.Components.Reflection
|
||||||
ThrowForInvalidCaptureUnmatchedValuesParameterType(targetType, propertyInfo);
|
ThrowForInvalidCaptureUnmatchedValuesParameterType(targetType, propertyInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
CaptureUnmatchedValuesWriter = MemberAssignment.CreatePropertySetter(targetType, propertyInfo);
|
CaptureUnmatchedValuesWriter = MemberAssignment.CreatePropertySetter(targetType, propertyInfo, cascading: false);
|
||||||
CaptureUnmatchedValuesPropertyName = propertyInfo.Name;
|
CaptureUnmatchedValuesPropertyName = propertyInfo.Name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ namespace Microsoft.AspNetCore.Components.Reflection
|
||||||
{
|
{
|
||||||
internal interface IPropertySetter
|
internal interface IPropertySetter
|
||||||
{
|
{
|
||||||
|
bool Cascading { get; }
|
||||||
|
|
||||||
void SetValue(object target, object value);
|
void SetValue(object target, object value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ namespace Microsoft.AspNetCore.Components.Reflection
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IPropertySetter CreatePropertySetter(Type targetType, PropertyInfo property)
|
public static IPropertySetter CreatePropertySetter(Type targetType, PropertyInfo property, bool cascading)
|
||||||
{
|
{
|
||||||
if (property.SetMethod == null)
|
if (property.SetMethod == null)
|
||||||
{
|
{
|
||||||
|
|
@ -37,19 +37,23 @@ namespace Microsoft.AspNetCore.Components.Reflection
|
||||||
|
|
||||||
return (IPropertySetter)Activator.CreateInstance(
|
return (IPropertySetter)Activator.CreateInstance(
|
||||||
typeof(PropertySetter<,>).MakeGenericType(targetType, property.PropertyType),
|
typeof(PropertySetter<,>).MakeGenericType(targetType, property.PropertyType),
|
||||||
property.SetMethod);
|
property.SetMethod,
|
||||||
|
cascading);
|
||||||
}
|
}
|
||||||
|
|
||||||
class PropertySetter<TTarget, TValue> : IPropertySetter
|
class PropertySetter<TTarget, TValue> : IPropertySetter
|
||||||
{
|
{
|
||||||
private readonly Action<TTarget, TValue> _setterDelegate;
|
private readonly Action<TTarget, TValue> _setterDelegate;
|
||||||
|
|
||||||
public PropertySetter(MethodInfo setMethod)
|
public PropertySetter(MethodInfo setMethod, bool cascading)
|
||||||
{
|
{
|
||||||
_setterDelegate = (Action<TTarget, TValue>)Delegate.CreateDelegate(
|
_setterDelegate = (Action<TTarget, TValue>)Delegate.CreateDelegate(
|
||||||
typeof(Action<TTarget, TValue>), setMethod);
|
typeof(Action<TTarget, TValue>), setMethod);
|
||||||
|
Cascading = cascading;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool Cascading { get; }
|
||||||
|
|
||||||
public void SetValue(object target, object value)
|
public void SetValue(object target, object value)
|
||||||
{
|
{
|
||||||
if (value == null)
|
if (value == null)
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,8 @@ using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using Microsoft.AspNetCore.Components.Rendering;
|
||||||
using Microsoft.AspNetCore.Components.RenderTree;
|
using Microsoft.AspNetCore.Components.RenderTree;
|
||||||
using Microsoft.AspNetCore.Components.Test.Helpers;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components
|
namespace Microsoft.AspNetCore.Components
|
||||||
|
|
@ -96,23 +95,58 @@ namespace Microsoft.AspNetCore.Components
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void IncomingParameterMatchesNoDeclaredParameter_Throws()
|
public void IncomingCascadingValueMatchesCascadingParameter_SetsValue()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var target = new HasPropertyWithoutParameterAttribute();
|
var builder = new ParameterViewBuilder();
|
||||||
var parameters = new ParameterViewBuilder
|
builder.Add(nameof(HasCascadingParameter.Cascading), "hi", cascading: true);
|
||||||
{
|
var parameters = builder.Build();
|
||||||
{ "AnyOtherKey", 123 },
|
|
||||||
}.Build();
|
var target = new HasCascadingParameter();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var ex = Assert.Throws<InvalidOperationException>(
|
parameters.SetParameterProperties(target);
|
||||||
() => parameters.SetParameterProperties(target));
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("hi", target.Cascading);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void NoIncomingCascadingValueMatchesDeclaredCascadingParameter_LeavesValueUnchanged()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var builder = new ParameterViewBuilder();
|
||||||
|
var parameters = builder.Build();
|
||||||
|
|
||||||
|
var target = new HasCascadingParameter()
|
||||||
|
{
|
||||||
|
Cascading = "bye",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
parameters.SetParameterProperties(target);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("bye", target.Cascading);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IncomingCascadingValueMatchesNoDeclaredParameter_Throws()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var builder = new ParameterViewBuilder();
|
||||||
|
builder.Add("SomethingElse", "hi", cascading: true);
|
||||||
|
var parameters = builder.Build();
|
||||||
|
|
||||||
|
var target = new HasCascadingParameter();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var ex = Assert.Throws<InvalidOperationException>(() => parameters.SetParameterProperties(target));
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(
|
Assert.Equal(
|
||||||
$"Object of type '{typeof(HasPropertyWithoutParameterAttribute).FullName}' does not have a property " +
|
$"Object of type '{typeof(HasCascadingParameter).FullName}' does not have a property " +
|
||||||
$"matching the name 'AnyOtherKey'.",
|
$"matching the name 'SomethingElse'.",
|
||||||
ex.Message);
|
ex.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -138,6 +172,45 @@ namespace Microsoft.AspNetCore.Components
|
||||||
ex.Message);
|
ex.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IncomingNonCascadingValueMatchesCascadingParameter_Throws()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var target = new HasCascadingParameter();
|
||||||
|
var parameters = new ParameterViewBuilder
|
||||||
|
{
|
||||||
|
{ nameof(HasCascadingParameter.Cascading), 123 },
|
||||||
|
}.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var ex = Assert.Throws<InvalidOperationException>(() => parameters.SetParameterProperties(target));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(
|
||||||
|
$"Object of type '{typeof(HasCascadingParameter).FullName}' has a property matching the name '{nameof(HasCascadingParameter.Cascading)}', " +
|
||||||
|
$"but it does not have [{nameof(ParameterAttribute)}] applied.",
|
||||||
|
ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IncomingCascadingValueMatchesNonCascadingParameter_Throws()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var target = new HasInstanceProperties();
|
||||||
|
var builder = new ParameterViewBuilder();
|
||||||
|
builder.Add(nameof(HasInstanceProperties.IntProp), 16, cascading: true);
|
||||||
|
var parameters = builder.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var ex = Assert.Throws<InvalidOperationException>(() => parameters.SetParameterProperties(target));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(
|
||||||
|
$"The property '{nameof(HasInstanceProperties.IntProp)}' on component type '{typeof(HasInstanceProperties).FullName}' " +
|
||||||
|
$"cannot be set using a cascading value.",
|
||||||
|
ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void SettingCaptureUnmatchedValuesParameterExplicitlyWorks()
|
public void SettingCaptureUnmatchedValuesParameterExplicitlyWorks()
|
||||||
{
|
{
|
||||||
|
|
@ -274,6 +347,51 @@ namespace Microsoft.AspNetCore.Components
|
||||||
ex.Message);
|
ex.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IncomingNonCascadingValueMatchesCascadingParameter_WithCaptureUnmatchedValues_DoesNotThrow()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var target = new HasCaptureUnmatchedValuesPropertyAndCascadingParameter()
|
||||||
|
{
|
||||||
|
Cascading = "bye",
|
||||||
|
};
|
||||||
|
var parameters = new ParameterViewBuilder
|
||||||
|
{
|
||||||
|
{ nameof(HasCaptureUnmatchedValuesPropertyAndCascadingParameter.Cascading), "hi" },
|
||||||
|
}.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
parameters.SetParameterProperties(target);
|
||||||
|
|
||||||
|
Assert.Collection(
|
||||||
|
target.CaptureUnmatchedValues,
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal(nameof(HasCaptureUnmatchedValuesPropertyAndCascadingParameter.Cascading), kvp.Key);
|
||||||
|
Assert.Equal("hi", kvp.Value);
|
||||||
|
});
|
||||||
|
Assert.Equal("bye", target.Cascading);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IncomingCascadingValueMatchesNonCascadingParameter_WithCaptureUnmatchedValues_Throws()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var target = new HasCaptureUnmatchedValuesProperty();
|
||||||
|
var builder = new ParameterViewBuilder();
|
||||||
|
builder.Add(nameof(HasInstanceProperties.IntProp), 16, cascading: true);
|
||||||
|
var parameters = builder.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var ex = Assert.Throws<InvalidOperationException>(() => parameters.SetParameterProperties(target));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(
|
||||||
|
$"The property '{nameof(HasCaptureUnmatchedValuesProperty.IntProp)}' on component type '{typeof(HasCaptureUnmatchedValuesProperty).FullName}' " +
|
||||||
|
$"cannot be set using a cascading value.",
|
||||||
|
ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void IncomingParameterValueMismatchesDeclaredParameterType_Throws()
|
public void IncomingParameterValueMismatchesDeclaredParameterType_Throws()
|
||||||
{
|
{
|
||||||
|
|
@ -396,6 +514,11 @@ namespace Microsoft.AspNetCore.Components
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class HasCascadingParameter
|
||||||
|
{
|
||||||
|
[CascadingParameter] public string Cascading { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
class HasPropertyWithoutParameterAttribute
|
class HasPropertyWithoutParameterAttribute
|
||||||
{
|
{
|
||||||
internal int IntProp { get; set; }
|
internal int IntProp { get; set; }
|
||||||
|
|
@ -435,6 +558,12 @@ namespace Microsoft.AspNetCore.Components
|
||||||
[Parameter(CaptureUnmatchedValues = true)] public IReadOnlyDictionary<string, object> CaptureUnmatchedValues { get; set; }
|
[Parameter(CaptureUnmatchedValues = true)] public IReadOnlyDictionary<string, object> CaptureUnmatchedValues { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class HasCaptureUnmatchedValuesPropertyAndCascadingParameter
|
||||||
|
{
|
||||||
|
[CascadingParameter] public string Cascading { get; set; }
|
||||||
|
[Parameter(CaptureUnmatchedValues = true)] public IReadOnlyDictionary<string, object> CaptureUnmatchedValues { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
class HasDupliateCaptureUnmatchedValuesProperty
|
class HasDupliateCaptureUnmatchedValuesProperty
|
||||||
{
|
{
|
||||||
[Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object> CaptureUnmatchedValuesProp1 { get; set; }
|
[Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object> CaptureUnmatchedValuesProp1 { get; set; }
|
||||||
|
|
@ -448,11 +577,11 @@ namespace Microsoft.AspNetCore.Components
|
||||||
|
|
||||||
class ParameterViewBuilder : IEnumerable
|
class ParameterViewBuilder : IEnumerable
|
||||||
{
|
{
|
||||||
private readonly List<(string Name, object Value)> _keyValuePairs
|
private readonly List<(string Name, object Value, bool Cascading)> _keyValuePairs
|
||||||
= new List<(string, object)>();
|
= new List<(string, object, bool)>();
|
||||||
|
|
||||||
public void Add(string name, object value)
|
public void Add(string name, object value, bool cascading = false)
|
||||||
=> _keyValuePairs.Add((name, value));
|
=> _keyValuePairs.Add((name, value, cascading));
|
||||||
|
|
||||||
public IEnumerator GetEnumerator()
|
public IEnumerator GetEnumerator()
|
||||||
=> throw new NotImplementedException();
|
=> throw new NotImplementedException();
|
||||||
|
|
@ -460,13 +589,56 @@ namespace Microsoft.AspNetCore.Components
|
||||||
public ParameterView Build()
|
public ParameterView Build()
|
||||||
{
|
{
|
||||||
var builder = new RenderTreeBuilder();
|
var builder = new RenderTreeBuilder();
|
||||||
|
|
||||||
builder.OpenComponent<FakeComponent>(0);
|
builder.OpenComponent<FakeComponent>(0);
|
||||||
foreach (var kvp in _keyValuePairs)
|
foreach (var kvp in _keyValuePairs)
|
||||||
{
|
{
|
||||||
builder.AddAttribute(1, kvp.Name, kvp.Value);
|
if (!kvp.Cascading)
|
||||||
|
{
|
||||||
|
builder.AddAttribute(1, kvp.Name, kvp.Value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
builder.CloseComponent();
|
builder.CloseComponent();
|
||||||
return new ParameterView(builder.GetFrames().Array, ownerIndex: 0);
|
|
||||||
|
var view = new ParameterView(builder.GetFrames().Array, ownerIndex: 0);
|
||||||
|
|
||||||
|
var cascadingParameters = new List<CascadingParameterState>();
|
||||||
|
foreach (var kvp in _keyValuePairs)
|
||||||
|
{
|
||||||
|
if (kvp.Cascading)
|
||||||
|
{
|
||||||
|
cascadingParameters.Add(new CascadingParameterState(kvp.Name, new TestCascadingValueProvider(kvp.Value)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return view.WithCascadingParameters(cascadingParameters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestCascadingValueProvider : ICascadingValueComponent
|
||||||
|
{
|
||||||
|
public TestCascadingValueProvider(object value)
|
||||||
|
{
|
||||||
|
CurrentValue = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object CurrentValue { get; }
|
||||||
|
|
||||||
|
public bool CurrentValueIsFixed => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public bool CanSupplyValue(Type valueType, string valueName)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Subscribe(ComponentState subscriber)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Unsubscribe(ComponentState subscriber)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue