Ignore overriden properties in component parameters (#13198)
* Ignore overriden properties in component parameters * Modify MemberAssignment to return shadowed properties, but not inherited properties * Modify ComponentProperties to throw if a Parameter property is non-public Fixes https://github.com/aspnet/AspNetCore/issues/13162
This commit is contained in:
parent
c0d5248fb4
commit
5bc79e8449
|
|
@ -238,9 +238,15 @@ namespace Microsoft.AspNetCore.Components.Reflection
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var propertyName = propertyInfo.Name;
|
||||||
|
if (parameterAttribute != null && (propertyInfo.SetMethod == null || !propertyInfo.SetMethod.IsPublic))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"The type '{targetType.FullName}' declares a parameter matching the name '{propertyName}' that is not public. Parameters must be public.");
|
||||||
|
}
|
||||||
|
|
||||||
var propertySetter = MemberAssignment.CreatePropertySetter(targetType, propertyInfo, cascading: cascadingParameterAttribute != null);
|
var propertySetter = MemberAssignment.CreatePropertySetter(targetType, propertyInfo, cascading: cascadingParameterAttribute != null);
|
||||||
|
|
||||||
var propertyName = propertyInfo.Name;
|
|
||||||
if (WritersByName.ContainsKey(propertyName))
|
if (WritersByName.ContainsKey(propertyName))
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException(
|
throw new InvalidOperationException(
|
||||||
|
|
|
||||||
|
|
@ -13,17 +13,34 @@ namespace Microsoft.AspNetCore.Components.Reflection
|
||||||
public static IEnumerable<PropertyInfo> GetPropertiesIncludingInherited(
|
public static IEnumerable<PropertyInfo> GetPropertiesIncludingInherited(
|
||||||
Type type, BindingFlags bindingFlags)
|
Type type, BindingFlags bindingFlags)
|
||||||
{
|
{
|
||||||
|
var dictionary = new Dictionary<string, List<PropertyInfo>>();
|
||||||
|
|
||||||
while (type != null)
|
while (type != null)
|
||||||
{
|
{
|
||||||
var properties = type.GetProperties(bindingFlags)
|
var properties = type.GetProperties(bindingFlags)
|
||||||
.Where(prop => prop.DeclaringType == type);
|
.Where(prop => prop.DeclaringType == type);
|
||||||
foreach (var property in properties)
|
foreach (var property in properties)
|
||||||
{
|
{
|
||||||
yield return property;
|
if (!dictionary.TryGetValue(property.Name, out var others))
|
||||||
|
{
|
||||||
|
others = new List<PropertyInfo>();
|
||||||
|
dictionary.Add(property.Name, others);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (others.Any(other => other.GetMethod?.GetBaseDefinition() == property.GetMethod?.GetBaseDefinition()))
|
||||||
|
{
|
||||||
|
// This is an inheritance case. We can safely ignore the value of property since
|
||||||
|
// we have seen a more derived value.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
others.Add(property);
|
||||||
}
|
}
|
||||||
|
|
||||||
type = type.BaseType;
|
type = type.BaseType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return dictionary.Values.SelectMany(p => p);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IPropertySetter CreatePropertySetter(Type targetType, PropertyInfo property, bool cascading)
|
public static IPropertySetter CreatePropertySetter(Type targetType, PropertyInfo property, bool cascading)
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.AspNetCore.Components.Rendering;
|
using Microsoft.AspNetCore.Components.Rendering;
|
||||||
using Microsoft.AspNetCore.Components.RenderTree;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components
|
namespace Microsoft.AspNetCore.Components
|
||||||
|
|
@ -71,6 +70,24 @@ namespace Microsoft.AspNetCore.Components
|
||||||
Assert.Equal(456, target.DerivedClassIntProp);
|
Assert.Equal(456, target.DerivedClassIntProp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IncomingParameterMatchesOverridenParameter_ThatDoesNotHasAttribute()
|
||||||
|
{
|
||||||
|
// Test for https://github.com/aspnet/AspNetCore/issues/13162
|
||||||
|
// Arrange
|
||||||
|
var parameters = new ParameterViewBuilder
|
||||||
|
{
|
||||||
|
{ nameof(DerivedType.VirtualProp), 123 },
|
||||||
|
}.Build();
|
||||||
|
var target = new DerivedType();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
parameters.SetParameterProperties(target);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(123, target.VirtualProp);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void NoIncomingParameterMatchesDeclaredParameter_LeavesValueUnchanged()
|
public void NoIncomingParameterMatchesDeclaredParameter_LeavesValueUnchanged()
|
||||||
{
|
{
|
||||||
|
|
@ -172,6 +189,43 @@ namespace Microsoft.AspNetCore.Components
|
||||||
ex.Message);
|
ex.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IncomingParameterMatchesPropertyNotPublic_Throws()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var target = new HasNonPublicPropertyWithParameterAttribute();
|
||||||
|
var parameters = new ParameterViewBuilder
|
||||||
|
{
|
||||||
|
{ nameof(HasNonPublicPropertyWithParameterAttribute.IntProp), 123 },
|
||||||
|
}.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var ex = Assert.Throws<InvalidOperationException>(
|
||||||
|
() => parameters.SetParameterProperties(target));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(default, target.IntProp);
|
||||||
|
Assert.Equal(
|
||||||
|
$"The type '{typeof(HasNonPublicPropertyWithParameterAttribute).FullName}' declares a parameter matching the name '{nameof(HasNonPublicPropertyWithParameterAttribute.IntProp)}' that is not public. Parameters must be public.",
|
||||||
|
ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IncomingCascadingParameterMatchesPropertyNotPublic_Works()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var target = new HasNonPublicCascadingParameter();
|
||||||
|
var builder = new ParameterViewBuilder();
|
||||||
|
builder.Add("Cascading", "Test", cascading: true);
|
||||||
|
var parameters = builder.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
parameters.SetParameterProperties(target);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("Test", target.GetCascadingValue());
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void IncomingNonCascadingValueMatchesCascadingParameter_Throws()
|
public void IncomingNonCascadingValueMatchesCascadingParameter_Throws()
|
||||||
{
|
{
|
||||||
|
|
@ -497,13 +551,9 @@ namespace Microsoft.AspNetCore.Components
|
||||||
|
|
||||||
class HasInstanceProperties
|
class HasInstanceProperties
|
||||||
{
|
{
|
||||||
// "internal" to show we're not requiring public accessors, but also
|
|
||||||
// to keep the assertions simple in the tests
|
|
||||||
|
|
||||||
[Parameter] public int IntProp { get; set; }
|
[Parameter] public int IntProp { get; set; }
|
||||||
[Parameter] public string StringProp { get; set; }
|
[Parameter] public string StringProp { get; set; }
|
||||||
|
|
||||||
// Also a truly private one to show there's nothing special about 'internal'
|
|
||||||
[Parameter] public object ObjectProp { get; set; }
|
[Parameter] public object ObjectProp { get; set; }
|
||||||
|
|
||||||
public static string ObjectPropName => nameof(ObjectProp);
|
public static string ObjectPropName => nameof(ObjectProp);
|
||||||
|
|
@ -521,6 +571,12 @@ namespace Microsoft.AspNetCore.Components
|
||||||
|
|
||||||
class HasPropertyWithoutParameterAttribute
|
class HasPropertyWithoutParameterAttribute
|
||||||
{
|
{
|
||||||
|
public int IntProp { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class HasNonPublicPropertyWithParameterAttribute
|
||||||
|
{
|
||||||
|
[Parameter]
|
||||||
internal int IntProp { get; set; }
|
internal int IntProp { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -539,6 +595,16 @@ namespace Microsoft.AspNetCore.Components
|
||||||
[Parameter] public int DerivedClassIntProp { get; set; }
|
[Parameter] public int DerivedClassIntProp { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class BaseType
|
||||||
|
{
|
||||||
|
[Parameter] public virtual int VirtualProp { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class DerivedType : BaseType
|
||||||
|
{
|
||||||
|
public override int VirtualProp { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
class HasParametersVaryingOnlyByCase
|
class HasParametersVaryingOnlyByCase
|
||||||
{
|
{
|
||||||
[Parameter] public object MyValue { get; set; }
|
[Parameter] public object MyValue { get; set; }
|
||||||
|
|
@ -575,6 +641,13 @@ namespace Microsoft.AspNetCore.Components
|
||||||
[Parameter(CaptureUnmatchedValues = true)] public KeyValuePair<string, object>[] CaptureUnmatchedValuesProp { get; set; }
|
[Parameter(CaptureUnmatchedValues = true)] public KeyValuePair<string, object>[] CaptureUnmatchedValuesProp { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class HasNonPublicCascadingParameter
|
||||||
|
{
|
||||||
|
[CascadingParameter] private string Cascading { get; set; }
|
||||||
|
|
||||||
|
public string GetCascadingValue() => Cascading;
|
||||||
|
}
|
||||||
|
|
||||||
class ParameterViewBuilder : IEnumerable
|
class ParameterViewBuilder : IEnumerable
|
||||||
{
|
{
|
||||||
private readonly List<(string Name, object Value, bool Cascading)> _keyValuePairs
|
private readonly List<(string Name, object Value, bool Cascading)> _keyValuePairs
|
||||||
|
|
|
||||||
|
|
@ -2231,7 +2231,7 @@ namespace Microsoft.AspNetCore.Components.Test
|
||||||
public object ObjectProperty { get; set; }
|
public object ObjectProperty { get; set; }
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string ReadonlyProperty { get; private set; }
|
public string ReadonlyProperty { get; set; }
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string PrivateProperty { get; set; }
|
public string PrivateProperty { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -3791,10 +3791,10 @@ namespace Microsoft.AspNetCore.Components.Test
|
||||||
private class FakeComponent : IComponent
|
private class FakeComponent : IComponent
|
||||||
{
|
{
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public int IntProperty { get; private set; }
|
public int IntProperty { get; set; }
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string StringProperty { get; private set; }
|
public string StringProperty { get; set; }
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public object ObjectProperty { get; set; }
|
public object ObjectProperty { get; set; }
|
||||||
|
|
@ -3927,7 +3927,7 @@ namespace Microsoft.AspNetCore.Components.Test
|
||||||
private class ReRendersParentComponent : AutoRenderComponent
|
private class ReRendersParentComponent : AutoRenderComponent
|
||||||
{
|
{
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public TestComponent Parent { get; private set; }
|
public TestComponent Parent { get; set; }
|
||||||
|
|
||||||
private bool _isFirstTime = true;
|
private bool _isFirstTime = true;
|
||||||
|
|
||||||
|
|
@ -4057,7 +4057,7 @@ namespace Microsoft.AspNetCore.Components.Test
|
||||||
public bool Disposed { get; private set; }
|
public bool Disposed { get; private set; }
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public Action DisposeAction { get; private set; }
|
public Action DisposeAction { get; set; }
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue