相依性屬性的安全性 (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