Безопасность свойств зависимостей (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
См. также
.NET Desktop feedback