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

由于可通过 Windows Presentation Foundation (WPF) 属性系统访问读写依赖项属性,因此该属性可有效地成为公共属性。 因此,无法对读写依赖项属性值进行安全保证。 WPF 属性系统可为只读依赖项属性提供更多的安全性,以便限制写入访问。

重要

面向 .NET 7 和 .NET 6 的桌面指南文档正在撰写中。

属性包装器的访问和安全性

公共语言运行时 (CLR) 属性包装器通常包含在读写依赖项属性实现中,以简化属性值的获取或设置。 如果包含,CLR 属性包装器是一种便捷方法,可实现 GetValueSetValue 静态调用以与基础依赖项属性交互。 从本质上讲,CLR 属性包装器将依赖项属性公开为由依赖项属性而不是私有字段支持的 CLR 属性。

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

依赖项属性的属性系统公开

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

注意

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

验证安全性

ValidateValueCallback 应用 Demand 和预期 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

另请参阅