依赖属性安全性 (WPF .NET)

通过 Windows Presentation Foundation (WPF) 属性系统实现读写依赖属性的可访问性实际上使它们成为公共属性。 因此,无法对读写依赖项属性值进行安全保证。 WPF 属性系统为只读依赖属性提供更多的安全性,以便限制写入访问。

属性包装器的访问控制和安全保障

公共语言运行时 (CLR) 属性包装器通常包含在读写依赖属性实现中,以简化获取或设置属性值。 在包含情况下,CLR 属性包装器是一种便利方法,用于实现与基础依赖属性进行交互的 GetValueSetValue 静态调用。 实质上,CLR 属性包装器将依赖属性公开为由依赖属性而不是专用字段支持的 CLR 属性。

应用安全机制并限制对 CLR 属性包装器的访问可能会阻止使用便利方法,但这些技术不会阻止直接调用 GetValueSetValue。 换句话说,始终可通过 WPF 属性系统访问读写依赖属性。 如果要实现读写依赖属性,请避免限制对 CLR 属性包装器的访问。 相反,请将 CLR 属性包装器声明为公共成员,以便调用方知道依赖属性的真实访问级别。

依赖属性的属性系统可见性

WPF 属性系统通过其 DependencyProperty 标识符提供对读写依赖属性的访问权限。 标识符在 GetValueSetValue 调用中可用。 即使静态标识符字段是非公共的,属性系统的几个方面也会返回一个 DependencyProperty,因为它存在于类或派生类的实例上。 例如,GetLocalValueEnumerator 方法返回具有本地设置值的依赖属性实例的标识符。 此外,还可以重写 OnPropertyChanged 虚拟方法,从而接收报告依赖属性已更改值的 DependencyProperty 标识符的事件数据。 若要让调用方了解读写依赖属性的真实访问级别,请将其标识符字段声明为公共成员。

注意

尽管将依赖属性标识符字段声明为 private 可减少读写依赖属性的可访问方式,但根据 CLR 语言定义,该属性不会是 私有

验证安全

Demand 应用于 ValidateValueCallback,并期望在 Demand 失败时验证失败,这不是限制属性值更改的适当安全机制。 此外,如果这些调用方在应用程序域中运行,则通过 ValidateValueCallback 强制实施的新值失效可由恶意调用方抑制。

对只读依赖项属性的访问

若要限制访问,请通过调用 RegisterReadOnly 方法将属性注册为只读依赖属性。 RegisterReadOnly 方法返回一个 DependencyPropertyKey,你可以将其分配给非公共类字段。 对于只读依赖属性,WPF 属性系统将仅向引用 DependencyPropertyKey的用户提供写入访问权限。 为了说明此行为,请执行以下测试代码:

  • 实例化一个实现读写属性和只读属性依赖项的类。
  • 为每个标识符分配 private 访问修饰符。
  • 仅实现 get 访问器。
  • 使用 GetLocalValueEnumerator 方法通过 WPF 属性系统访问基础依赖属性。
  • 调用 GetValueSetValue 来测试对每个依赖项属性值的访问。
    /// <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

另请参阅