c# - 将 SecurePassword 绑定(bind)到 ViewModel

标签 c# wpf mvvm

我尝试使用自定义BehaviorPasswordBoxSecurePassword 属性绑定(bind)到我的ViewModel。遗憾的是它不能正常工作。

基本上,我向 Behavior 添加了一个属性,其中包含我的 ViewModel 的目标属性。

有什么想法为什么它不起作用吗?

PS:我目前正在回家的路上,没有带笔记本电脑,我将在大约 15 分钟内用我的代码更新问题。但如果有人能发表想法或某事,那就太好了。

编辑

正如我所 promise 的,这里有一些代码:)

首先是行为:

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 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");
        }
    }
}

我希望任何人都可以提供帮助,atm 我使用代码隐藏版本,但我宁愿不这样做。

编辑2

实际上不起作用的是,TargetPassword 属性不会更新我的ViewModel 的属性

最佳答案

创建附加属性

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>

无论如何你可能都不想这样做,但如果你真的想继续的话。

The reason the WPF/Silverlight PasswordBox doesn't expose a DP for the Password property is security related. If WPF/Silverlight were to keep a DP for Password it would require the framework to keep the password itself unencrypted in memory. Which is considered quite a troublesome security attack vector. The PasswordBox uses encrypted memory (of sorts) and the only way to access the password is through the CLR property.

我建议在访问PasswordBox.Password CLR 属性时,不要将其放置在任何变量中或作为任何属性的值。 将密码以纯文本形式保存在客户端计算机 RAM 上是安全禁忌。

SecurePassword 无法通过绑定(bind)完成。

.NET documentation explains why the PasswordBox was not made bindable in the first place.

另一种解决方案是将PasswordBox放入您的ViewModel公共(public)类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 最佳实践,但是

  1. 最佳实践是“在大多数情况下行之有效的建议” 而不是严格的规则和
  2. 编写简单、易于阅读、可维护的代码并避免 不必要的复杂性也是那些“最佳实践”规则之一 (“附加属性(property)”可能会轻微违反 解决方法)。

关于c# - 将 SecurePassword 绑定(bind)到 ViewModel,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23020163/

相关文章:

c# - 列表框仅显示一项

c# - 用户控件之间的 WPF 通信

c# - 使用 Ionic Zip 库将文件夹添加到 Zip 根目录

C# 抽象方法强制更新方法

wpf - 降低 WPF 图像控件中的图像分辨率

java - RoboBinding:firePropertyChange() 上没有此类属性异常

c# - WPF ComboBox Mvvm 绑定(bind)

c# - 如何自动测试我的数据库驱动功能?

c# - 检查 SMS 消息是否在标准 GSM 字母表中

c# - WPF 平铺背景(以像素为单位)