依赖属性安全性 (WPF .NET)
通过 Windows Presentation Foundation (WPF) 属性系统实现读写依赖属性的可访问性实际上使它们成为公共属性。 因此,无法对读写依赖项属性值进行安全保证。 WPF 属性系统为只读依赖属性提供更多的安全性,以便限制写入访问。
属性包装器的访问控制和安全保障
公共语言运行时 (CLR) 属性包装器通常包含在读写依赖属性实现中,以简化获取或设置属性值。 在包含情况下,CLR 属性包装器是一种便利方法,用于实现与基础依赖属性进行交互的 GetValue 和 SetValue 静态调用。 实质上,CLR 属性包装器将依赖属性公开为由依赖属性而不是专用字段支持的 CLR 属性。
应用安全机制并限制对 CLR 属性包装器的访问可能会阻止使用便利方法,但这些技术不会阻止直接调用 GetValue
或 SetValue
。 换句话说,始终可通过 WPF 属性系统访问读写依赖属性。 如果要实现读写依赖属性,请避免限制对 CLR 属性包装器的访问。 相反,请将 CLR 属性包装器声明为公共成员,以便调用方知道依赖属性的真实访问级别。
依赖属性的属性系统可见性
WPF 属性系统通过其 DependencyProperty 标识符提供对读写依赖属性的访问权限。 标识符在 GetValue 和 SetValue 调用中可用。 即使静态标识符字段是非公共的,属性系统的几个方面也会返回一个 DependencyProperty
,因为它存在于类或派生类的实例上。 例如,GetLocalValueEnumerator 方法返回具有本地设置值的依赖属性实例的标识符。 此外,还可以重写 OnPropertyChanged 虚拟方法,从而接收报告依赖属性已更改值的 DependencyProperty
标识符的事件数据。 若要让调用方了解读写依赖属性的真实访问级别,请将其标识符字段声明为公共成员。
注意
尽管将依赖属性标识符字段声明为 private
可减少读写依赖属性的可访问方式,但根据 CLR 语言定义,该属性不会是 私有。
验证安全
将 Demand 应用于 ValidateValueCallback,并期望在 Demand
失败时验证失败,这不是限制属性值更改的适当安全机制。 此外,如果这些调用方在应用程序域中运行,则通过 ValidateValueCallback
强制实施的新值失效可由恶意调用方抑制。
对只读依赖项属性的访问
若要限制访问,请通过调用 RegisterReadOnly 方法将属性注册为只读依赖属性。 RegisterReadOnly
方法返回一个 DependencyPropertyKey,你可以将其分配给非公共类字段。 对于只读依赖属性,WPF 属性系统将仅向引用 DependencyPropertyKey
的用户提供写入访问权限。 为了说明此行为,请执行以下测试代码:
- 实例化一个实现读写属性和只读属性依赖项的类。
- 为每个标识符分配
private
访问修饰符。 - 仅实现
get
访问器。 - 使用 GetLocalValueEnumerator 方法通过 WPF 属性系统访问基础依赖属性。
- 调用 GetValue 和 SetValue 来测试对每个依赖项属性值的访问。
/// <summary>
/// Test get/set access to dependency properties exposed through the WPF property system.
/// </summary>
public static void DependencyPropertyAccessTests()
{
// Instantiate a class that implements read-write and read-only dependency properties.
Aquarium _aquarium = new();
// Access each dependency property using the LocalValueEnumerator method.
LocalValueEnumerator localValueEnumerator = _aquarium.GetLocalValueEnumerator();
while (localValueEnumerator.MoveNext())
{
DependencyProperty dp = localValueEnumerator.Current.Property;
string dpType = dp.ReadOnly ? "read-only" : "read-write";
// Test read access.
Debug.WriteLine($"Attempting to get a {dpType} dependency property value...");
Debug.WriteLine($"Value ({dpType}): {(int)_aquarium.GetValue(dp)}");
// Test write access.
try
{
Debug.WriteLine($"Attempting to set a {dpType} dependency property value to 2...");
_aquarium.SetValue(dp, 2);
}
catch (InvalidOperationException e)
{
Debug.WriteLine(e.Message);
}
finally
{
Debug.WriteLine($"Value ({dpType}): {(int)_aquarium.GetValue(dp)}");
}
}
// Test output:
// Attempting to get a read-write dependency property value...
// Value (read-write): 1
// Attempting to set a read-write dependency property value to 2...
// Value (read-write): 2
// Attempting to get a read-only dependency property value...
// Value (read-only): 1
// Attempting to set a read-only dependency property value to 2...
// 'FishCountReadOnly' property was registered as read-only
// and cannot be modified without an authorization key.
// Value (read-only): 1
}
}
public class Aquarium : DependencyObject
{
public Aquarium()
{
// Assign locally-set values.
SetValue(FishCountProperty, 1);
SetValue(FishCountReadOnlyPropertyKey, 1);
}
// Failed attempt to restrict write-access by assigning the
// DependencyProperty identifier to a non-public field.
private static readonly DependencyProperty FishCountProperty =
DependencyProperty.Register(
name: "FishCount",
propertyType: typeof(int),
ownerType: typeof(Aquarium),
typeMetadata: new PropertyMetadata());
// Successful attempt to restrict write-access by assigning the
// DependencyPropertyKey to a non-public field.
private static readonly DependencyPropertyKey FishCountReadOnlyPropertyKey =
DependencyProperty.RegisterReadOnly(
name: "FishCountReadOnly",
propertyType: typeof(int),
ownerType: typeof(Aquarium),
typeMetadata: new PropertyMetadata());
// Declare public get accessors.
public int FishCount => (int)GetValue(FishCountProperty);
public int FishCountReadOnly => (int)GetValue(FishCountReadOnlyPropertyKey.DependencyProperty);
}
''' <summary>
''' ' Test get/set access to dependency properties exposed through the WPF property system.
''' </summary>
Public Shared Sub DependencyPropertyAccessTests()
' Instantiate a class that implements read-write and read-only dependency properties.
Dim _aquarium As New Aquarium()
' Access each dependency property using the LocalValueEnumerator method.
Dim localValueEnumerator As LocalValueEnumerator = _aquarium.GetLocalValueEnumerator()
While localValueEnumerator.MoveNext()
Dim dp As DependencyProperty = localValueEnumerator.Current.[Property]
Dim dpType As String = If(dp.[ReadOnly], "read-only", "read-write")
' Test read access.
Debug.WriteLine($"Attempting to get a {dpType} dependency property value...")
Debug.WriteLine($"Value ({dpType}): {CInt(_aquarium.GetValue(dp))}")
' Test write access.
Try
Debug.WriteLine($"Attempting to set a {dpType} dependency property value to 2...")
_aquarium.SetValue(dp, 2)
Catch e As InvalidOperationException
Debug.WriteLine(e.Message)
Finally
Debug.WriteLine($"Value ({dpType}): {CInt(_aquarium.GetValue(dp))}")
End Try
End While
' Test output
' Attempting to get a read-write dependency property value...
' Value (read-write): 1
' Attempting to set a read-write dependency property value to 2...
' Value (read-write): 2
' Attempting to get a read-only dependency property value...
' Value (read-only): 1
' Attempting to set a read-only dependency property value to 2...
' 'FishCountReadOnly' property was registered as read-only
' and cannot be modified without an authorization key.
' Value (read-only): 1
End Sub
End Class
Public Class Aquarium
Inherits DependencyObject
Public Sub New()
' Assign locally-set values.
SetValue(FishCountProperty, 1)
SetValue(FishCountReadOnlyPropertyKey, 1)
End Sub
' Failed attempt to restrict write-access by assigning the
' DependencyProperty identifier to a non-public field.
Private Shared ReadOnly FishCountProperty As DependencyProperty =
DependencyProperty.Register(
name:="FishCount",
propertyType:=GetType(Integer),
ownerType:=GetType(Aquarium),
typeMetadata:=New PropertyMetadata())
' Successful attempt to restrict write-access by assigning the
' DependencyPropertyKey to a non-public field.
Private Shared ReadOnly FishCountReadOnlyPropertyKey As DependencyPropertyKey =
DependencyProperty.RegisterReadOnly(
name:="FishCountReadOnly",
propertyType:=GetType(Integer),
ownerType:=GetType(Aquarium),
typeMetadata:=New PropertyMetadata())
' Declare public get accessors.
Public ReadOnly Property FishCount As Integer
Get
Return GetValue(FishCountProperty)
End Get
End Property
Public ReadOnly Property FishCountReadOnly As Integer
Get
Return GetValue(FishCountReadOnlyPropertyKey.DependencyProperty)
End Get
End Property
End Class