Привязать SecurePassword к ViewModel
Я пытаюсь связать SecurePassword
свойство PasswordBox
к моему ViewModel
с обычаем Behavior
, К сожалению, это не работает должным образом.
В основном я добавил свойство к Behavior
который содержит целевое свойство моего ViewModel
,
Есть идеи, почему это не работает?
PS: в настоящее время я возвращаюсь домой без моего ноутбука, я собираюсь обновить вопрос своим кодом через 15 минут. Но было бы хорошо, если бы кто-то отправил идеи или что-то в этом роде.
РЕДАКТИРОВАТЬ
Как я и обещал, вот код:)
Behavior
первый:
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Interactivity;
using System.Security;
namespace Knerd.Behaviors {
public class PasswordChangedBehavior : Behavior<PasswordBox> {
protected override void OnAttached() {
AssociatedObject.PasswordChanged += AssociatedObject_PasswordChanged;
base.OnAttached();
}
private void AssociatedObject_PasswordChanged(object sender, RoutedEventArgs e) {
if (AssociatedObject.Password != null)
TargetPassword = AssociatedObject.SecurePassword;
}
protected override void OnDetaching() {
AssociatedObject.PasswordChanged -= AssociatedObject_PasswordChanged;
base.OnDetaching();
}
public SecureString TargetPassword {
get { return (SecureString)GetValue(TargetPasswordProperty); }
set { SetValue(TargetPasswordProperty, value); }
}
// Using a DependencyProperty as the backing store for TargetPassword. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TargetPasswordProperty = DependencyProperty.Register("TargetPassword", typeof(SecureString), typeof(PasswordChangedBehavior), new PropertyMetadata(default(SecureString)));
}
}
PasswordBox
:
<PasswordBox Grid.Column="1" Grid.Row="1" Margin="5" Width="300" MinWidth="200">
<i:Interaction.Behaviors>
<behaviors:PasswordChangedBehavior TargetPassword="{Binding Password}" />
</i:Interaction.Behaviors>
</PasswordBox>
И наконец, часть моего ViewModel
,
private SecureString password;
public SecureString Password {
get { return password; }
set {
if (password != value) {
password = value;
OnPropertyChanged("Password");
}
}
}
Я надеюсь, что кто-нибудь может помочь, я использую версию codebehind, но я бы не стал.
РЕДАКТИРОВАТЬ 2
Что на самом деле не работает, так это то, что TargetPassword
свойство не обновляет свойство моего ViewModel
2 ответа
Я думаю, что нашел странное решение. Пожалуйста, улучшайте, если есть что улучшить:)
Я просто изменил это так:
Behavior
:
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Interactivity;
using System.Security;
namespace Knerd.Behaviors {
public class PasswordChangedBehavior : Behavior<PasswordBox> {
protected override void OnAttached() {
AssociatedObject.PasswordChanged += AssociatedObject_PasswordChanged;
base.OnAttached();
}
private void AssociatedObject_PasswordChanged(object sender, RoutedEventArgs e) {
if (AssociatedObject.SecurePassword != null)
AssociatedObject.DataContext = AssociatedObject.SecurePassword.Copy();
}
protected override void OnDetaching() {
AssociatedObject.PasswordChanged -= AssociatedObject_PasswordChanged;
base.OnDetaching();
}
// Using a DependencyProperty as the backing store for TargetPassword. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TargetPasswordProperty = DependencyProperty.Register("TargetPassword", typeof(SecureString), typeof(PasswordChangedBehavior), new PropertyMetadata(default(SecureString)));
}
}
ViewModel
ничего не изменилось, но вот мой View
:
<PasswordBox Grid.Column="1" Grid.Row="1" Margin="5" Width="300" MinWidth="200" DataContext="{Binding Password, Mode=TwoWay}">
<i:Interaction.Behaviors>
<behaviors:PasswordChangedBehavior />
</i:Interaction.Behaviors>
</PasswordBox>
Это работает просто отлично, без раскрытия незашифрованного пароля.
Создать свойство вложения
public static class PasswordBoxAssistant
{
public static readonly DependencyProperty BoundPassword =
DependencyProperty.RegisterAttached("BoundPassword", typeof(string), typeof(PasswordBoxAssistant), new PropertyMetadata(string.Empty, OnBoundPasswordChanged));
public static readonly DependencyProperty BindPassword = DependencyProperty.RegisterAttached(
"BindPassword", typeof (bool), typeof (PasswordBoxAssistant), new PropertyMetadata(false, OnBindPasswordChanged));
private static readonly DependencyProperty UpdatingPassword =
DependencyProperty.RegisterAttached("UpdatingPassword", typeof(bool), typeof(PasswordBoxAssistant), new PropertyMetadata(false));
private static void OnBoundPasswordChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
PasswordBox box = d as PasswordBox;
// only handle this event when the property is attached to a PasswordBox
// and when the BindPassword attached property has been set to true
if (d == null || !GetBindPassword(d))
{
return;
}
// avoid recursive updating by ignoring the box's changed event
box.PasswordChanged -= HandlePasswordChanged;
string newPassword = (string)e.NewValue;
if (!GetUpdatingPassword(box))
{
box.Password = newPassword;
}
box.PasswordChanged += HandlePasswordChanged;
}
private static void OnBindPasswordChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
{
// when the BindPassword attached property is set on a PasswordBox,
// start listening to its PasswordChanged event
PasswordBox box = dp as PasswordBox;
if (box == null)
{
return;
}
bool wasBound = (bool)(e.OldValue);
bool needToBind = (bool)(e.NewValue);
if (wasBound)
{
box.PasswordChanged -= HandlePasswordChanged;
}
if (needToBind)
{
box.PasswordChanged += HandlePasswordChanged;
}
}
private static void HandlePasswordChanged(object sender, RoutedEventArgs e)
{
PasswordBox box = sender as PasswordBox;
// set a flag to indicate that we're updating the password
SetUpdatingPassword(box, true);
// push the new password into the BoundPassword property
SetBoundPassword(box, box.Password);
SetUpdatingPassword(box, false);
}
public static void SetBindPassword(DependencyObject dp, bool value)
{
dp.SetValue(BindPassword, value);
}
public static bool GetBindPassword(DependencyObject dp)
{
return (bool)dp.GetValue(BindPassword);
}
public static string GetBoundPassword(DependencyObject dp)
{
return (string)dp.GetValue(BoundPassword);
}
public static void SetBoundPassword(DependencyObject dp, string value)
{
dp.SetValue(BoundPassword, value);
}
private static bool GetUpdatingPassword(DependencyObject dp)
{
return (bool)dp.GetValue(UpdatingPassword);
}
private static void SetUpdatingPassword(DependencyObject dp, bool value)
{
dp.SetValue(UpdatingPassword, value);
}
}
И в твоем XAML
<Page xmlns:ff="clr-namespace:FunctionalFun.UI">
<!-- [Snip] -->
<PasswordBox x:Name="PasswordBox"
ff:PasswordBoxAssistant.BindPassword="true" ff:PasswordBoxAssistant.BoundPassword="{Binding Path=Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
</Page>
Вы, вероятно, не хотите делать это в любом случае, но если вы действительно хотите идти вперед.
Причина, по которой WPF/Silverlight PasswordBox не предоставляет DP для свойства Password, связана с безопасностью. Если бы WPF / Silverlight сохранял DP for Password, он потребовал бы от среды, чтобы сам пароль не был зашифрован в памяти. Что считается довольно неприятным вектором атаки безопасности. PasswordBox использует зашифрованную память (своего рода), и единственный способ получить доступ к паролю - через свойство CLR.
Я хотел бы предложить, чтобы при доступе к свойству CLR PasswordBox.Password вы воздерживались от размещения его в любой переменной или в качестве значения для любого свойства. Хранение пароля в виде обычного текста на оперативной памяти клиентского компьютера - это безопасность, нет-нет.
SecurePassword
не может быть сделано с привязками.
Документация.NET объясняет, почему PasswordBox не был привязан в первую очередь.
Альтернативное решение - поставить PasswordBox
в вашем ViewModel
открытый класс LoginViewModel
public class LoginViewModel
{
// other properties here
public PasswordBox Password
{
get { return m_passwordBox; }
}
// Executed when the Login button is clicked.
private void LoginExecute()
{
var password = Password.SecurePassword;
// do more stuff...
}
}
Да, вы нарушаете лучшие практики ViewModel здесь, но
- лучшие практики - это "рекомендации, которые в большинстве случаев работают хорошо", а не строгие правила и
- Написание простого, легко читаемого, поддерживаемого кода и избежание ненужной сложности также является одним из тех правил "наилучшей практики" (которые могут быть слегка нарушены обходным решением "присоединенного свойства").