我遇到了绑定到密码盒的问题。这似乎是一个安全风险,但我正在使用MVVM模式,所以我希望绕过这个。我在这里发现了一些有趣的代码(有人使用过这个或类似的代码吗?)

http://www.wpftutorial.net/PasswordBox.html

从技术上讲,它看起来很棒,但我不确定如何检索密码。

我基本上有属性在我的LoginViewModel用户名和密码。用户名是好的,正在工作,因为它是一个文本框。

我使用上面的代码,并输入这个

<PasswordBox ff:PasswordHelper.Attach="True"
    ff:PasswordHelper.Password="{Binding Path=Password}" Width="130"/>

当我有PasswordBox作为一个文本框和绑定路径=密码,然后在我的LoginViewModel属性更新。

我的代码非常简单,基本上我有一个命令为我的按钮。当我按下它CanLogin被调用,如果它返回真,它调用Login。 你可以看到,我检查了我的用户名属性,这很好。

在登录我发送到我的服务的用户名和密码,用户名包含数据从我的视图,但密码是空|空

private DelegateCommand loginCommand;

public string Username { get; set; }
public string Password { get; set; }


public ICommand LoginCommand
{
    get
    {
        if (loginCommand == null)
        {
            loginCommand = new DelegateCommand(
                Login, CanLogin );
        }
        return loginCommand;
    }
}

private bool CanLogin()
{
    return !string.IsNullOrEmpty(Username);
}

private void Login()
{
    bool result = securityService.IsValidLogin(Username, Password);

    if (result) { }
    else { }
}

这就是我正在做的

<TextBox Text="{Binding Path=Username, UpdateSourceTrigger=PropertyChanged}"
         MinWidth="180" />

<PasswordBox ff:PasswordHelper.Attach="True" 
             ff:PasswordHelper.Password="{Binding Path=Password}" Width="130"/>

我有我的文本框,这是没有问题的,但在我的ViewModel密码是空的。

是我做错了什么还是漏了一步?

我放了一个断点,果然代码进入静态助手类,但它从不更新我的ViewModel中的密码。


当前回答

虽然我同意避免将密码存储在任何地方很重要,但我仍然需要能够在没有视图的情况下实例化视图模型,并针对它执行测试。

对我来说,有效的解决方案是注册PasswordBox。Password函数,并让视图模型在执行登录代码时调用它。

这意味着视图隐藏代码中的一行代码。

在我的Login中。xaml我有

<PasswordBox x:Name="PasswordBox"/>

在Login.xaml.cs中

LoginViewModel.PasswordHandler = () => PasswordBox.Password;

然后在LoginViewModel.cs我有PasswordHandler定义

public Func<string> PasswordHandler { get; set; }

当需要登录时,代码调用处理程序从视图中获取密码…

bool loginResult = Login(Username, PasswordHandler());

这样,当我想要测试视图模型时,我可以简单地将PasswordHandler设置为一个匿名方法,让我在测试中交付我想要使用的任何密码。

其他回答

我使用这种方法并通过密码框,虽然这确实违反了MVVM,这对我来说是必不可少的,因为我正在使用一个内容控件与数据模板为我的登录在我的shell,这是一个复杂的shell环境。因此,访问shell背后的代码将是垃圾。

据我所知,我认为传递密码框与从后面的代码访问控制权是一样的。在这个实现中,我没有视图模型中的密码属性。

按钮命令

Command="{Binding Path=DataContext.LoginCommand, ElementName=MyShell}" CommandParameter="{Binding ElementName=PasswordBox}"

ViewModel

private void Login(object parameter)
{
    System.Windows.Controls.PasswordBox p = (System.Windows.Controls.PasswordBox)parameter;
    MessageBox.Show(p.Password);
}

这个实现略有不同。通过绑定ViewModel中的属性将PasswordBox传递给View。它不使用任何命令参数。ViewModel对视图保持无知。 我有一个VB VS 2010项目,可以从SkyDrive下载。WPF MVVM PassWordBox Example.zip

我在WPF MVVM应用程序中使用PasswordBox的方式非常简单,对我来说工作得很好。

基本上你创建了一个公共只读属性,视图可以绑定到PasswordBox(实际控件):

Private _thePassWordBox As PasswordBox
Public ReadOnly Property ThePassWordBox As PasswordBox
    Get
        If IsNothing(_thePassWordBox) Then _thePassWordBox = New PasswordBox
        Return _thePassWordBox
    End Get
End Property

我使用了一个支持字段来完成属性的自我初始化。

然后从Xaml中绑定ContentControl或Control Container的内容:

 <ContentControl Grid.Column="1" Grid.Row="1" Height="23" Width="120" Content="{Binding Path=ThePassWordBox}" HorizontalAlignment="Center" VerticalAlignment="Center" />

从那里你可以完全控制密码盒。我还使用PasswordAccessor(只是一个字符串的函数)返回密码值时,做登录或任何其他你想要的密码。在这个例子中,我在通用用户对象模型中有一个公共属性。 例子:

Public Property PasswordAccessor() As Func(Of String)

在用户对象中,密码字符串属性是只读的,没有任何备份存储。它只是从PasswordBox返回Password。 例子:

Public ReadOnly Property PassWord As String
    Get
        Return If((PasswordAccessor Is Nothing), String.Empty, PasswordAccessor.Invoke())
    End Get
End Property

然后在ViewModel中,我确保Accessor被创建并设置为PasswordBox。密码属性:

Public Sub New()
    'Sets the Accessor for the Password Property
    SetPasswordAccessor(Function() ThePassWordBox.Password)
End Sub

Friend Sub SetPasswordAccessor(ByVal accessor As Func(Of String))
    If Not IsNothing(VMUser) Then VMUser.PasswordAccessor = accessor
End Sub

当我需要密码字符串说登录,我只是得到用户对象密码属性,真正调用函数抓取密码并返回它,然后实际的密码不存储在用户对象。 例如:将在ViewModel中

Private Function LogIn() as Boolean
    'Make call to your Authentication methods and or functions. I usally place that code in the Model
    Return AuthenticationManager.Login(New UserIdentity(User.UserName, User.Password)
End Function

That should do it. The ViewModel doesn't need any knowledge of the View's Controls. The View just binds to the Property in the ViewModel, not any different than the View Binding to an image or other resource. In this case that resource(Property) just happens to be a usercontrol. It allows for testing as the ViewModel creates and owns the Property and the Property is independent of the View. As for security I don't know how good this implementation is. But by using a Function the value is not stored in the Property itself just accessed by the Property.

我在这里发布了一个GIST,它是一个可绑定的密码框。

using System.Windows;
using System.Windows.Controls;

namespace CustomControl
{
    public class BindablePasswordBox : Decorator
    {
        /// <summary>
        /// The password dependency property.
        /// </summary>
        public static readonly DependencyProperty PasswordProperty;

        private bool isPreventCallback;
        private RoutedEventHandler savedCallback;

        /// <summary>
        /// Static constructor to initialize the dependency properties.
        /// </summary>
        static BindablePasswordBox()
        {
            PasswordProperty = DependencyProperty.Register(
                "Password",
                typeof(string),
                typeof(BindablePasswordBox),
                new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(OnPasswordPropertyChanged))
            );
        }

        /// <summary>
        /// Saves the password changed callback and sets the child element to the password box.
        /// </summary>
        public BindablePasswordBox()
        {
            savedCallback = HandlePasswordChanged;

            PasswordBox passwordBox = new PasswordBox();
            passwordBox.PasswordChanged += savedCallback;
            Child = passwordBox;
        }

        /// <summary>
        /// The password dependency property.
        /// </summary>
        public string Password
        {
            get { return GetValue(PasswordProperty) as string; }
            set { SetValue(PasswordProperty, value); }
        }

        /// <summary>
        /// Handles changes to the password dependency property.
        /// </summary>
        /// <param name="d">the dependency object</param>
        /// <param name="eventArgs">the event args</param>
        private static void OnPasswordPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs eventArgs)
        {
            BindablePasswordBox bindablePasswordBox = (BindablePasswordBox) d;
            PasswordBox passwordBox = (PasswordBox) bindablePasswordBox.Child;

            if (bindablePasswordBox.isPreventCallback)
            {
                return;
            }

            passwordBox.PasswordChanged -= bindablePasswordBox.savedCallback;
            passwordBox.Password = (eventArgs.NewValue != null) ? eventArgs.NewValue.ToString() : "";
            passwordBox.PasswordChanged += bindablePasswordBox.savedCallback;
        }

        /// <summary>
        /// Handles the password changed event.
        /// </summary>
        /// <param name="sender">the sender</param>
        /// <param name="eventArgs">the event args</param>
        private void HandlePasswordChanged(object sender, RoutedEventArgs eventArgs)
        {
            PasswordBox passwordBox = (PasswordBox) sender;

            isPreventCallback = true;
            Password = passwordBox.Password;
            isPreventCallback = false;
        }
    }
}

这对我来说很好。

<Button Command="{Binding Connect}" 
        CommandParameter="{Binding ElementName=MyPasswordBox}"/>

如前所述,VM应该不知道视图,但传递整个PasswordBox看起来是最简单的方法。因此,可能不是将传递的参数强制转换为PasswordBox,而是使用反射从它提取Password属性。在这种情况下,虚拟机期望某种密码容器的属性密码(我使用RelayCommands从MVMM Light-Toolkit):

public RelayCommand<object> SignIn
{
    get
    {
        if (this.signIn == null)
        {
            this.signIn = new RelayCommand<object>((passwordContainer) => 
                {
                    var password = passwordContainer.GetType().GetProperty("Password").GetValue(passwordContainer) as string;
                    this.authenticationService.Authenticate(this.Login, password);
                });
        }

        return this.signIn;
    }
}

它可以很容易地用匿名类测试:

var passwordContainer = new
    {
        Password = "password"
    };