如何绑定到MVVM中的PasswordBox

时间:2009-09-27 16:34:26

标签: c# wpf mvvm wpf-controls passwords

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

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

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

LoginViewModelUsername的{​​{1}}基本上都有属性。 Password很好,因为它是Username

我使用了上述代码并输入了

TextBox

当我将<PasswordBox ff:PasswordHelper.Attach="True" ff:PasswordHelper.Password="{Binding Path=Password}" Width="130"/> 作为PasswordBoxTextBox时,Binding Path=Password中的媒体资源已更新。

我的代码很简单,基本上我的LoginViewModel有一个Command。当我按下它时Button被调用,如果它返回true,则调用CanLogin 你可以看到我在Login查看我的房产,这里有用。

Username我向我的服务发送了LoginUsernamePassword包含来自Username的数据,但View是{ {1}}

Password

这就是我正在做的事情

Null|Empty

我有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"/> TextBox为空。

我做错了什么或错过了一步?

我放了一个断点,确实代码进入了静态助手类,但它永远不会更新ViewModel中的Password

31 个答案:

答案 0 :(得分:179)

我的2美分:

我使用WPF和MVVM开发了一个典型的登录对话框(用户和密码框,加上“确定”按钮)。我通过简单地将PasswordBox控件本身作为参数传递给附加到“确定”按钮的命令来解决了密码绑定问题。所以我认为:

<PasswordBox Name="txtPassword" VerticalAlignment="Top" Width="120" />
<Button Content="Ok" Command="{Binding Path=OkCommand}"
   CommandParameter="{Binding ElementName=txtPassword}"/>

在ViewModel中,附加命令的Execute方法如下:

void Execute(object parameter)
{
    var passwordBox = parameter as PasswordBox;
    var password = passwordBox.Password;
    //Now go ahead and check the user name and password
}

这略微违反了MVVM模式,因为现在ViewModel知道View的实现方式,但在那个特定的项目中我能负担得起。希望它对某些人也有用。

答案 1 :(得分:158)

也许我错过了一些东西,但似乎大多数这些解决方案都过于复杂化并取消了安全实践。

此方法不违反MVVM模式并保持完整的安全性。是的,从技术上来说它是代码背后的,但它只不过是一个特殊情况&#34;捆绑。 ViewModel仍然不了解View实现,如果您尝试将PasswordBox传递给ViewModel,我认为这样做。

Code Behind!=自动MVVM违规。这一切都取决于你用它做什么。在这种情况下,我们只是手动编写绑定,因此它都被认为是UI实现的一部分,因此没问题。

在ViewModel中,只是一个简单的属性。我做了#34;只写#34;因为不应该出于任何原因需要从ViewModel之外检索它,但它并不是必须的。请注意,它是一个SecureString,而不仅仅是一个字符串。

public SecureString SecurePassword { private get; set; }

在xaml中,您设置了一个PasswordChanged事件处理程序。

<PasswordBox PasswordChanged="PasswordBox_PasswordChanged"/>

在背后的代码中:

private void PasswordBox_PasswordChanged(object sender, RoutedEventArgs e)
{
    if (this.DataContext != null)
    { ((dynamic)this.DataContext).SecurePassword = ((PasswordBox)sender).SecurePassword; }
}

使用此方法,您的密码始终保留在SecureString中,因此可提供最大的安全性。如果您真的不关心安全性,或者需要明文密码来获取需要它的下游方法(注意:大多数需要密码的.NET方法也支持SecureString选项,因此您可能不需要明确即使你认为你是文本密码,你也可以使用密码属性。像这样:

(ViewModel属性)

public string Password { private get; set; }

(代码背后)

private void PasswordBox_PasswordChanged(object sender, RoutedEventArgs e)
{
    if (this.DataContext != null)
    { ((dynamic)this.DataContext).Password = ((PasswordBox)sender).Password; }
}

如果你想保持强类型,你可以用ViewModel的界面替换(动态)强制转换。但实际上,&#34;正常&#34;数据绑定也不是强类型的,所以它没那么重要。

private void PasswordBox_PasswordChanged(object sender, RoutedEventArgs e)
{
    if (this.DataContext != null)
    { ((IMyViewModel)this.DataContext).Password = ((PasswordBox)sender).Password; }
}

所有世界中最好的 - 您的密码是安全的,您的ViewModel只有一个属性,就像任何其他属性一样,并且您的View是自包含的,不需要外部引用。

答案 2 :(得分:150)

抱歉,但你做错了。

人们的眼睑内侧应有以下安全指导纹身:
永远不要将纯文本密码保存在内存中。

WPF / Silverlight PasswordBox没有为Password属性公开DP的原因与安全性有关 如果WPF / Silverlight要保留DP for Password,则需要框架将密码本身保持在内存中未加密。这被认为是一个非常麻烦的安全攻击媒介。 PasswordBox使用加密内存(各种),访问密码的唯一方法是通过CLR属性。

我建议在访问PasswordBox.Password CLR属性时,不要将其放在任何变量或任何属性的值中。
在客户机RAM上以明文形式保存密码是一种安全禁忌 所以摆脱那个“公共字符串密码{get; set;}”你已经到了那里。

访问PasswordBox.Password时,只需将其取出并尽快发送到服务器。 不要保留密码的值,也不要像对待任何其他客户端机器文本那样对待它。不要在内存中保留明文密码。

我知道这打破了MVVM模式,但你不应该绑定到PasswordBox.Password附加DP,将你的密码存储在ViewModel或任何其他类似的恶作剧中。

如果你正在寻找一个过度架构的解决方案,那么这里有一个:
  1.使用一种返回密码明文的方法创建IHavePassword接口   2.让UserControl实现IHavePassword接口   3.使用IoC注册UserControl实例,以实现IHavePassword接口   4.当发生需要您密码的服务器请求时,请拨打IoC以获取IHavePassword实施,而不是获得令人垂涎的密码。

我接受它。

- 贾斯汀

答案 3 :(得分:18)

您可以使用此XAML:

<PasswordBox Name="PasswordBox">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="PasswordChanged">
            <i:InvokeCommandAction Command="{Binding PasswordChangedCommand}" CommandParameter="{Binding ElementName=PasswordBox}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</PasswordBox>

这个命令执行方法:

private void ExecutePasswordChangedCommand(PasswordBox obj)
{ 
   if (obj != null)
     Password = obj.Password;
}

答案 4 :(得分:13)

这对我来说很好。

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

答案 5 :(得分:10)

一个不违反MVVM模式的简单解决方案是在ViewModel中引入一个收集密码的事件(或委托)。

ViewModel

public event EventHandler<HarvestPasswordEventArgs> HarvestPassword;

使用这些EventArgs:

class HarvestPasswordEventArgs : EventArgs
{
    public string Password;
}

查看中,订阅有关创建ViewModel的事件并填写密码值。

_viewModel.HarvestPassword += (sender, args) => 
    args.Password = passwordBox1.Password;

ViewModel 中,当您需要密码时,您可以触发该事件并从中获取密码:

if (HarvestPassword == null)
  //bah 
  return;

var pwargs = new HarvestPasswordEventArgs();
HarvestPassword(this, pwargs);

LoginHelpers.Login(Username, pwargs.Password);

答案 6 :(得分:8)

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

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

答案 7 :(得分:7)

要在不破坏MVVM的情况下解决OP问题,我会使用自定义值转换器和必须从密码框中检索的值(密码)的包装器。

public interface IWrappedParameter<T>
{
    T Value { get; }
}

public class PasswordBoxWrapper : IWrappedParameter<string>
{
    private readonly PasswordBox _source;

    public string Value
    {
        get { return _source != null ? _source.Password : string.Empty; }
    }

    public PasswordBoxWrapper(PasswordBox source)
    {
        _source = source;
    }
}

public class PasswordBoxConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        // Implement type and value check here...
        return new PasswordBoxWrapper((PasswordBox)value);
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new InvalidOperationException("No conversion.");
    }
}

在视图模型中:

public string Username { get; set; }

public ICommand LoginCommand
{
    get
    {
        return new RelayCommand<IWrappedParameter<string>>(password => { Login(Username, password); });
    }
}

private void Login(string username, string password)
{
    // Perform login here...
}

由于视图模型使用IWrappedParameter<T>,因此无需了解PasswordBoxWrapperPasswordBoxConverter。这样,您可以将PasswordBox对象与视图模型隔离开来,而不会破坏MVVM模式。

在视图中:

<Window.Resources>
    <h:PasswordBoxConverter x:Key="PwdConverter" />
</Window.Resources>
...
<PasswordBox Name="PwdBox" />
<Button Content="Login" Command="{Binding LoginCommand}"
        CommandParameter="{Binding ElementName=PwdBox, Converter={StaticResource PwdConverter}}" />

答案 8 :(得分:6)

这种实现略有不同。您将密码框传递给View通过ViewModel中的属性绑定,它不使用任何命令参数。 ViewModel保持对视图的无知。 我有一个VB vs 2010项目,可以从SkyDrive下载。 Wpf MvvM PassWordBox Example.zip https://skydrive.live.com/redir.aspx?cid=e95997d33a9f8d73&resid=E95997D33A9F8D73!511

我在Wpf MvvM应用程序中使用PasswordBox的方式非常简单,对我来说效果很好。这并不意味着我认为这是正确的方式或最好的方式。它只是使用PasswordBox和MvvM模式的实现。

基本上您创建一个公共只读属性,View可以将其绑定为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的内容或控件容器示例:

 <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)

在用户对象中,密码字符串属性是readonly,没有任何后备存储,它只是从PasswordBox返回密码。 示例:

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

然后在ViewModel中,我确保创建了Accessor并将其设置为PasswordBox.Password属性' 例如:

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

应该这样做。 ViewModel不需要任何View的控件知识。视图仅绑定到ViewModel中的属性,与视图绑定到图像或其他资源没有任何区别。在这种情况下,资源(Property)恰好是用户控件。 它允许在ViewModel创建和拥有Property时进行测试,并且Property独立于View。 至于安全性,我不知道这个实现有多好。但是通过使用函数,Value不会存储在Property所访问的Property本身中。

答案 9 :(得分:5)

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

对我有用的解决方案是使用视图模型注册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());

这样,当我想测试viewmodel时,我可以简单地将PasswordHandler设置为匿名方法,该方法允许我提供我想在测试中使用的任何密码。

答案 10 :(得分:5)

我花了很多时间研究各种解决方案。我不喜欢装饰者的想法,行为弄乱了验证用户界面,代码背后......真的吗?

最好的方法是坚持自定义附加属性并绑定到视图模型中的> List.length [1,2,3];; val it : int = 1 > List.length [1,2,3,4];; val it : int = 1 属性。尽可能长时间保持在那里。每当您需要快速访问普通密码时,请使用以下代码暂时将其转换为不安全的字符串:

SecureString

确保您允许GC收集您的UI元素,因此不要强调在namespace Namespace.Extensions { using System; using System.Runtime.InteropServices; using System.Security; /// <summary> /// Provides unsafe temporary operations on secured strings. /// </summary> [SuppressUnmanagedCodeSecurity] public static class SecureStringExtensions { /// <summary> /// Converts a secured string to an unsecured string. /// </summary> public static string ToUnsecuredString(this SecureString secureString) { // copy&paste from the internal System.Net.UnsafeNclNativeMethods IntPtr bstrPtr = IntPtr.Zero; if (secureString != null) { if (secureString.Length != 0) { try { bstrPtr = Marshal.SecureStringToBSTR(secureString); return Marshal.PtrToStringBSTR(bstrPtr); } finally { if (bstrPtr != IntPtr.Zero) Marshal.ZeroFreeBSTR(bstrPtr); } } } return string.Empty; } /// <summary> /// Copies the existing instance of a secure string into the destination, clearing the destination beforehand. /// </summary> public static void CopyInto(this SecureString source, SecureString destination) { destination.Clear(); foreach (var chr in source.ToUnsecuredString()) { destination.AppendChar(chr); } } /// <summary> /// Converts an unsecured string to a secured string. /// </summary> public static SecureString ToSecuredString(this string plainString) { if (string.IsNullOrEmpty(plainString)) { return new SecureString(); } SecureString secure = new SecureString(); foreach (char c in plainString) { secure.AppendChar(c); } return secure; } } } 上使用PasswordChanged事件的静态事件处理程序。 我还发现了一个异常,当使用PasswordBox属性进行设置时,控件没有更新UI,这就是我将密码复制到SecurePassword的原因。

Password

XAML用法:

namespace Namespace.Controls
{
    using System.Security;
    using System.Windows;
    using System.Windows.Controls;
    using Namespace.Extensions;

    /// <summary>
    /// Creates a bindable attached property for the <see cref="PasswordBox.SecurePassword"/> property.
    /// </summary>
    public static class PasswordBoxHelper
    {
        // an attached behavior won't work due to view model validation not picking up the right control to adorn
        public static readonly DependencyProperty SecurePasswordBindingProperty = DependencyProperty.RegisterAttached(
            "SecurePassword",
            typeof(SecureString),
            typeof(PasswordBoxHelper),
            new FrameworkPropertyMetadata(new SecureString(),FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, AttachedPropertyValueChanged)
        );

        private static readonly DependencyProperty _passwordBindingMarshallerProperty = DependencyProperty.RegisterAttached(
            "PasswordBindingMarshaller",
            typeof(PasswordBindingMarshaller),
            typeof(PasswordBoxHelper),
            new PropertyMetadata()
        );

        public static void SetSecurePassword(PasswordBox element, SecureString secureString)
        {
            element.SetValue(SecurePasswordBindingProperty, secureString);
        }

        public static SecureString GetSecurePassword(PasswordBox element)
        {
            return element.GetValue(SecurePasswordBindingProperty) as SecureString;
        }

        private static void AttachedPropertyValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            // we'll need to hook up to one of the element's events
            // in order to allow the GC to collect the control, we'll wrap the event handler inside an object living in an attached property
            // don't be tempted to use the Unloaded event as that will be fired  even when the control is still alive and well (e.g. switching tabs in a tab control) 
            var passwordBox = (PasswordBox)d;
            var bindingMarshaller = passwordBox.GetValue(_passwordBindingMarshallerProperty) as PasswordBindingMarshaller;
            if (bindingMarshaller == null)
            {
                bindingMarshaller = new PasswordBindingMarshaller(passwordBox);
                passwordBox.SetValue(_passwordBindingMarshallerProperty, bindingMarshaller);
            }

            bindingMarshaller.UpdatePasswordBox(e.NewValue as SecureString);
        }

        /// <summary>
        /// Encapsulated event logic
        /// </summary>
        private class PasswordBindingMarshaller
        {
            private readonly PasswordBox _passwordBox;
            private bool _isMarshalling;

            public PasswordBindingMarshaller(PasswordBox passwordBox)
            {
                _passwordBox = passwordBox;
                _passwordBox.PasswordChanged += this.PasswordBoxPasswordChanged;
            }

            public void UpdatePasswordBox(SecureString newPassword)
            {
                if (_isMarshalling)
                {
                    return;
                }

                _isMarshalling = true;
                try
                {
                    // setting up the SecuredPassword won't trigger a visual update so we'll have to use the Password property
                    _passwordBox.Password = newPassword.ToUnsecuredString();

                    // you may try the statement below, however the benefits are minimal security wise (you still have to extract the unsecured password for copying)
                    //newPassword.CopyInto(_passwordBox.SecurePassword);
                }
                finally
                {
                    _isMarshalling = false;
                }
            }

            private void PasswordBoxPasswordChanged(object sender, RoutedEventArgs e)
            {
                // copy the password into the attached property
                if (_isMarshalling)
                {
                    return;
                }

                _isMarshalling = true;
                try
                {
                    SetSecurePassword(_passwordBox, _passwordBox.SecurePassword.Copy());
                }
                finally
                {
                    _isMarshalling = false;
                }
            }
        }
    }
}

视图模型中的我的属性如下所示:

<PasswordBox controls:PasswordBoxHelper.SecurePassword="{Binding LogonPassword, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}">

[RequiredSecureString] public SecureString LogonPassword { get { return _logonPassword; } set { _logonPassword = value; NotifyPropertyChanged(nameof(LogonPassword)); } } 只是一个简单的自定义验证器,具有以下逻辑:

RequiredSecureString

在这里,你拥有它。完整且经过测试的纯MVVM解决方案。

答案 11 :(得分:3)

我使用了这个方法并传递了密码框,虽然这确实违反了MVVM,但这对我来说是必不可少的,因为我在我的shell中使用了一个带有数据模板的内容控件,这是一个复杂的shell环境。因此,访问shell后面的代码就是垃圾。

据我所知,传递密码箱我认为与从后面的代码访问控制相同。我同意密码,不要留在内存中等。在这个实现中,我没有视图模型中的密码属性。

按钮命令

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

<强>视图模型

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

答案 12 :(得分:3)

我认为我会把我的解决方案放在混合中,因为这是一个常见的问题......而且有很多选择总是一件好事。

我只是将PasswordBox包裹在UserControl中并实施了DependencyProperty以便能够绑定。我正在尽我所能避免在内存中存储任何明文,所以一切都是通过SecureStringPasswordBox.Password属性完成的。在foreach循环期间,每个角色都会暴露出来,但它非常简短。老实说,如果你担心你的WPF应用程序会因为这次短暂的曝光而受到损害,那么你就会遇到更大的安全问题需要处理。

这样做的好处在于你没有违反任何MVVM规则,即使是“纯粹的”规则,因为这是一个UserControl,所以它允许有代码隐藏。当您使用它时,您可以在ViewViewModel之间进行纯粹的通信,而VideModel不知道View的任何部分或密码来源。只需确保您绑定到SecureString中的ViewModel

<强> BindablePasswordBox.xaml

<UserControl x:Class="BK.WPF.CustomControls.BindanblePasswordBox"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" d:DesignHeight="22" d:DesignWidth="150">
    <PasswordBox x:Name="PswdBox"/>
</UserControl>

BindablePasswordBox.xaml.cs(版本1 - 没有双向绑定支持。)

using System.ComponentModel;
using System.Security;
using System.Windows;
using System.Windows.Controls;

namespace BK.WPF.CustomControls
{
    public partial class BindanblePasswordBox : UserControl
    {
        public static readonly DependencyProperty PasswordProperty =
            DependencyProperty.Register("Password", typeof(SecureString), typeof(BindanblePasswordBox));

        public SecureString Password
        {
            get { return (SecureString)GetValue(PasswordProperty); }
            set { SetValue(PasswordProperty, value); }
        }

        public BindanblePasswordBox()
        {
            InitializeComponent();
            PswdBox.PasswordChanged += PswdBox_PasswordChanged;
        }

        private void PswdBox_PasswordChanged(object sender, RoutedEventArgs e)
        {
            var secure = new SecureString();
            foreach (var c in PswdBox.Password)
            {
                secure.AppendChar(c);
            }
            Password = secure;
        }
    }
}

版本1的使用:

<local:BindanblePasswordBox Width="150" HorizontalAlignment="Center"
                            VerticalAlignment="Center"
                            Password="{Binding Password, Mode=OneWayToSource}"/>

BindablePasswordBox.xaml.cs(版本2 - 具有双向绑定支持。)

public partial class BindablePasswordBox : UserControl
{
    public static readonly DependencyProperty PasswordProperty =
        DependencyProperty.Register("Password", typeof(SecureString), typeof(BindablePasswordBox),
        new PropertyMetadata(PasswordChanged));

    public SecureString Password
    {
        get { return (SecureString)GetValue(PasswordProperty); }
        set { SetValue(PasswordProperty, value); }
    }

    public BindablePasswordBox()
    {
        InitializeComponent();
        PswdBox.PasswordChanged += PswdBox_PasswordChanged;
    }

    private void PswdBox_PasswordChanged(object sender, RoutedEventArgs e)
    {
        var secure = new SecureString();
        foreach (var c in PswdBox.Password)
        {
            secure.AppendChar(c);
        }
        if (Password != secure)
        {
            Password = secure;
        }
    }

    private static void PasswordChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var pswdBox = d as BindablePasswordBox;
        if (pswdBox != null && e.NewValue != e.OldValue)
        {
            var newValue = e.NewValue as SecureString;
            if (newValue == null)
            {
                return;
            }

            var unmanagedString = IntPtr.Zero;
            string newString;
            try
            {
                unmanagedString = Marshal.SecureStringToGlobalAllocUnicode(newValue);
                newString = Marshal.PtrToStringUni(unmanagedString);
            }
            finally
            {
                Marshal.ZeroFreeGlobalAllocUnicode(unmanagedString);
            }

            var currentValue = pswdBox.PswdBox.Password;
            if (currentValue != newString)
            {
                pswdBox.PswdBox.Password = newString;
            }
        }
    }
}

版本2的使用:

<local:BindanblePasswordBox Width="150" HorizontalAlignment="Center"
                            VerticalAlignment="Center"
                            Password="{Binding Password, Mode=TwoWay}"/>

答案 13 :(得分:2)

对于像我这样的新手来说,这里是上述Konamiman的完整工作示例。谢谢Konamiman

XAML

    <PasswordBox x:Name="textBoxPassword"/>
    <Button x:Name="buttonLogin" Content="Login"
            Command="{Binding PasswordCommand}"
            CommandParameter="{Binding ElementName=textBoxPassword}"/> 

ViewModel

public class YourViewModel : ViewModelBase
{
    private ICommand _passwordCommand;
    public ICommand PasswordCommand
    {
        get {
            if (_passwordCommand == null) {
                _passwordCommand = new RelayCommand<object>(PasswordClick);
            }
            return _passwordCommand;
        }
    }

    public YourViewModel()
    {
    }

    private void PasswordClick(object p)
    {
        var password = p as PasswordBox;
        Console.WriteLine("Password is: {0}", password.Password);
    }
}

答案 14 :(得分:2)

  

正如您所看到我绑定到Password,但也许它将它绑定到静态类..

这是attached property。这种属性可以应用于任何类型的DependencyObject,而不仅仅是声明它的类型。因此,即使它在PasswordHelper静态类中声明,它也会应用于您使用它的PasswordBox

要使用此附加属性,只需将其绑定到ViewModel中的Password属性:

<PasswordBox w:PasswordHelper.Attach="True" 
         w:PasswordHelper.Password="{Binding Password}"/>

答案 15 :(得分:2)

你可以使用附加属性来查看它。PasswordBox with MVVM

答案 16 :(得分:1)

非常简单。为密码创建另一个属性,并使用TextBox

绑定它

但是所有输入操作都使用实际的密码属性执行

私人字符串_Password;

    public string PasswordChar
    {
        get
        {
            string szChar = "";

            foreach(char szCahr in _Password)
            {
                szChar = szChar + "*";
            }

            return szChar;
        }

        set
        {
            _PasswordChar = value; NotifyPropertyChanged();
        }
    }

公共字符串密码         {             得到             {                 return _Password;             }

        set
        {
            _Password = value; NotifyPropertyChanged();
            PasswordChar = _Password;
        }
    }

答案 17 :(得分:1)

在Windows通用应用

您可以将此代码与属性“Password”一起使用并与modelView

绑定

 <PasswordBox x:Uid="PasswordBox" Password="{Binding Waiter.Password, Mode=TwoWay}" Name="txtPassword" HorizontalAlignment="Stretch" Margin="50,200,50,0" VerticalAlignment="Top"/>

答案 18 :(得分:1)

使用“附加行为”和SecureStringICommand发送到视图模型

实现MVVM时,代码隐藏没有问题。 MVVM是一种架构模式,旨在将视图与模型/业务逻辑分开。 MVVM描述了如何以可重现的方式(模式)实现此目标。它不关心实现细节,例如如何构造或实现视图。它只是划定了边界,并根据此模式的术语定义了视图,视图模型以及模型。

MVVM不在乎语言(XAML或C#)或编译器(partial类)。语言独立是设计模式的强制性特征-必须与语言无关。

但是,当代码隐藏在XAML和C#之间时,它们有一些缺点,如使UI逻辑更难以理解。但是,与使用XAML相比,在C#中最重要的实现UI逻辑或对象(如模板,样式,触发器,动画等)非常复杂且丑陋/可读性较差。 XAML是一种标记语言,它使用标签和嵌套来可视化对象层次结构。使用XAML创建UI非常方便。尽管在某些情况下您可以选择使用C#(或隐藏代码)实现UI逻辑。处理PasswordBox是一个示例。

由于这个原因,通过处理PasswordBox来处理后面的代码中的PasswordBox.PasswordChanged并不违反MVVM模式。

一个明显的违反是将控件(PasswordBox)传递给视图模型。许多解决方案都建议这样做,例如,将PasswordBox的实例作为ICommand.CommandParameter传递给视图模型。显然,这是非常糟糕且不必要的建议。

如果您不关心使用C#,而只想保持代码隐藏文件的清洁或仅想封装行为/ UI逻辑,则始终可以使用附加属性并实现附加行为。

这种臭名昭著的广泛传播帮助程序可以绑定到纯文本密码(确实存在不良的反模式和安全风险),该行为使用ICommand来将密码作为SecureString发送到视图模型,只要PasswordBox引发PasswordBox.PasswordChanged事件。

MainWindow.xaml

<Window>
  <Window.DataContext>
    <ViewModel />
  </Window.DataContext>

  <PasswordBox PasswordBox.Command="{Binding VerifyPasswordCommand}" />
</Window>

ViewModel.cs

public class ViewModel : INotifyPropertyChanged
{
  public ICommand VerifyPasswordCommand => new RelayCommand(VerifyPassword);

  public void VerifyPassword(object commadParameter)
  {
    if (commandParameter is SecureString secureString)
    {
      IntPtr valuePtr = IntPtr.Zero;
      try
      {
        valuePtr = Marshal.SecureStringToGlobalAllocUnicode(value);
        string plainTextPassword = Marshal.PtrToStringUni(valuePtr);

        // Handle plain text password. 
        // It's recommended to convert the SecureString to plain text in the model, when really needed.
      } 
      finally 
      {
        Marshal.ZeroFreeGlobalAllocUnicode(valuePtr);
      }
    }
  }
}

PasswordBox.cs

// Attached behavior
class PasswordBox : DependencyObject
{
  #region Command attached property

  public static readonly DependencyProperty CommandProperty =
    DependencyProperty.RegisterAttached(
      "Command",
      typeof(ICommand),
      typeof(PasswordBox),
      new PropertyMetadata(default(ICommand), PasswordBox.OnSendPasswordCommandChanged));

  public static void SetCommand(DependencyObject attachingElement, ICommand value) =>
    attachingElement.SetValue(PasswordBox.CommandProperty, value);

  public static ICommand GetCommand(DependencyObject attachingElement) =>
    (ICommand) attachingElement.GetValue(PasswordBox.CommandProperty);

  #endregion

  private static void OnSendPasswordCommandChanged(
    DependencyObject attachingElement,
    DependencyPropertyChangedEventArgs e)
  {
    if (!(attachingElement is System.Windows.Controls.PasswordBox passwordBox))
    {
      throw new ArgumentException("Attaching element must be of type 'PasswordBox'");
    }

    if (e.OldValue != null)
    {
      return;
    }

    WeakEventManager<object, RoutedEventArgs>.AddHandler(
      passwordBox,
      nameof(System.Windows.Controls.PasswordBox.PasswordChanged),
      SendPassword_OnPasswordChanged);
  }

  private static void SendPassword_OnPasswordChanged(object sender, RoutedEventArgs e)
  {
    var attachedElement = sender as System.Windows.Controls.PasswordBox;
    SecureString commandParameter = attachedElement?.SecurePassword;
    if (commandParameter == null || commandParameter.Length < 1)
    {
      return;
    }

    ICommand sendCommand = GetCommand(attachedElement);
    sendCommand?.Execute(commandParameter);
  }
}

答案 19 :(得分:1)

对我来说,这两件事都错了:

  • 实施明文密码属性
  • PasswordBox作为命令参数发送到ViewModel

Steve in CO所述转移SecurePassword(SecureString实例)似乎是可以接受的。我更喜欢Behaviors后面的代码,我还有额外的要求,可以从viewmodel重置密码。

Xaml(Password是ViewModel属性):

<PasswordBox>
    <i:Interaction.Behaviors>
        <behaviors:PasswordBinding BoundPassword="{Binding Password, Mode=TwoWay}" />
    </i:Interaction.Behaviors>
</PasswordBox>

行为:

using System.Security;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;

namespace Evidence.OutlookIntegration.AddinLogic.Behaviors
{
    /// <summary>
    /// Intermediate class that handles password box binding (which is not possible directly).
    /// </summary>
    public class PasswordBoxBindingBehavior : Behavior<PasswordBox>
    {
        // BoundPassword
        public SecureString BoundPassword { get { return (SecureString)GetValue(BoundPasswordProperty); } set { SetValue(BoundPasswordProperty, value); } }
        public static readonly DependencyProperty BoundPasswordProperty = DependencyProperty.Register("BoundPassword", typeof(SecureString), typeof(PasswordBoxBindingBehavior), new FrameworkPropertyMetadata(OnBoundPasswordChanged));

        protected override void OnAttached()
        {
            this.AssociatedObject.PasswordChanged += AssociatedObjectOnPasswordChanged;
            base.OnAttached();
        }

        /// <summary>
        /// Link up the intermediate SecureString (BoundPassword) to the UI instance
        /// </summary>
        private void AssociatedObjectOnPasswordChanged(object s, RoutedEventArgs e)
        {
            this.BoundPassword = this.AssociatedObject.SecurePassword;
        }

        /// <summary>
        /// Reacts to password reset on viewmodel (ViewModel.Password = new SecureString())
        /// </summary>
        private static void OnBoundPasswordChanged(object s, DependencyPropertyChangedEventArgs e)
        {
            var box = ((PasswordBoxBindingBehavior)s).AssociatedObject;
            if (box != null)
            {
                if (((SecureString)e.NewValue).Length == 0)
                    box.Password = string.Empty;
            }
        }

    }
}

答案 20 :(得分:1)

对于了解此实施所带来的风险的任何人,要将密码同步到ViewModel,只需添加 Mode = OneWayToSource

<强> XAML

<PasswordBox
    ff:PasswordHelper.Attach="True"
    ff:PasswordHelper.Password="{Binding Path=Password, Mode=OneWayToSource}" />

答案 21 :(得分:1)

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

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

答案 22 :(得分:0)

我花了很多时间试图让这个工作。最后,我放弃了,只使用了DevExpress的PasswordBoxEdit。

这是迄今为止最简单的解决方案,因为它允许绑定而不会产生任何可怕的技巧。

Solution on DevExpress website

为了记录,我不以任何方式与DevExpress有关联。

答案 23 :(得分:0)

我使用的是简洁的MVVM友好型解决方案,尚未被提及。首先,我在XAML中命名PasswordBox:

<PasswordBox x:Name="Password" />

然后我在视图构造函数中添加一个方法调用:

public LoginWindow()
{
    InitializeComponent();
    ExposeControl<LoginViewModel>.Expose(this, view => view.Password,
        (model, box) => model.SetPasswordBox(box));
}

就是这样。视图模型在通过DataContext附加到视图时会收到通知,并在分离时通知另一个通知。此通知的内容可通过lambdas进行配置,但通常它只是视图模型上的setter或方法调用,将有问题的控件作为参数传递。

通过使视图公开接口而不是子控件,可以非常容易地使MVVM友好。

上述代码依赖于我博客上发布的helper class

答案 24 :(得分:0)

我使用了一个身份验证检查,后跟一个由mediator类调用的子到View(它还实现了一个身份验证检查),将密码写入数据类。

这不是一个完美的解决方案;但是,它解决了我无法移动密码的问题。

答案 25 :(得分:0)

<UserControl x:Class="Elections.Server.Handler.Views.LoginView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
             xmlns:cal="http://www.caliburnproject.org"
             mc:Ignorable="d" 
             Height="531" Width="1096">
    <ContentControl>
        <ContentControl.Background>
            <ImageBrush/>
        </ContentControl.Background>
        <Grid >
            <Border BorderBrush="#FFABADB3" BorderThickness="1" HorizontalAlignment="Left" Height="23" Margin="900,100,0,0" VerticalAlignment="Top" Width="160">
                <TextBox TextWrapping="Wrap"/>
            </Border>
            <Border BorderBrush="#FFABADB3" BorderThickness="1" HorizontalAlignment="Left" Height="23" Margin="900,150,0,0" VerticalAlignment="Top" Width="160">
                <PasswordBox x:Name="PasswordBox"/>
            </Border>
            <Button Content="Login" HorizontalAlignment="Left" Margin="985,200,0,0" VerticalAlignment="Top" Width="75">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="Click">
                        <cal:ActionMessage MethodName="Login">
                            <cal:Parameter Value="{Binding ElementName=PasswordBox}" />
                        </cal:ActionMessage>
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </Button>

        </Grid>
    </ContentControl>
</UserControl>

using System;
using System.Windows;
using System.Windows.Controls;
using Caliburn.Micro;

namespace Elections.Server.Handler.ViewModels
{
    public class LoginViewModel : PropertyChangedBase
    {
        MainViewModel _mainViewModel;
        public void SetMain(MainViewModel mainViewModel)
        {
            _mainViewModel = mainViewModel;
        }

        public void Login(Object password)
        {
            var pass = (PasswordBox) password;
            MessageBox.Show(pass.Password);

            //_mainViewModel.ScreenView = _mainViewModel.ControlPanelView;
            //_mainViewModel.TitleWindow = "Panel de Control";
            //HandlerBootstrapper.Title(_mainViewModel.TitleWindow);
        }
    }
}

;)容易!

答案 26 :(得分:0)

我的答案在MVVM模式中更简单

在类视图模型中

public string password;

PasswordChangedCommand = new DelegateCommand<RoutedEventArgs>(PasswordChanged);

Private void PasswordChanged(RoutedEventArgs obj)

{

    var e = (WatermarkPasswordBox)obj.OriginalSource;

    //or depending or what are you using

    var e = (PasswordBox)obj.OriginalSource;

    password =e.Password;

}

获胜的PasswordBox的密码属性或XCeedtoolkit提供的WatermarkPasswordBox生成RoutedEventArgs,以便您可以绑定它。

现在在xmal视图中

<Xceed:WatermarkPasswordBox Watermark="Input your Password" Grid.Column="1" Grid.ColumnSpan="3" Grid.Row="7" PasswordChar="*" >

        <i:Interaction.Triggers>

            <i:EventTrigger EventName="PasswordChanged">

                <prism:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path= DataContext.PasswordChangedCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path= Password}"/>

            </i:EventTrigger>

        </i:Interaction.Triggers>

    </Xceed:WatermarkPasswordBox>

<PasswordBox Grid.Column="1" Grid.ColumnSpan="3" Grid.Row="7" PasswordChar="*" >

        <i:Interaction.Triggers>

            <i:EventTrigger EventName="PasswordChanged">

                <prism:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path= DataContext.PasswordChangedCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path= Password}"/>

            </i:EventTrigger>

        </i:Interaction.Triggers>

    </PasswordBox>

答案 27 :(得分:0)

如果你想要它只在一个控件和一个命令中组合它

<PasswordBox Name="PasswordBoxPin" PasswordChar="*">
    <PasswordBox.InputBindings>
        <KeyBinding Key="Return" Command="{Binding AuthentifyEmpCommand}" CommandParameter="{Binding ElementName=PasswordBoxPin}"/>
    </PasswordBox.InputBindings>
</PasswordBox>

在您的Vm上(如Konamiman所示)

public void AuthentifyEmp(object obj)
{
    var passwordBox = obj as PasswordBox;
    var password = passwordBox.Password;
}
private RelayCommand _authentifyEmpCommand;
public RelayCommand AuthentifyEmpCommand => _authentifyEmpCommand ?? (_authentifyEmpCommand = new RelayCommand(AuthentifyEmp, null));

答案 28 :(得分:0)

这是我的看法:

  1. 使用附加属性绑定密码无法达到保护密码的目的。密码框的“密码”属性由于某种原因无法绑定。

  2. 将密码框作为命令参数传递将使ViewModel知道该控件。如果您计划使ViewModel可重用的跨平台,则此方法将无效。 不要让您的VM知道您的View或任何其他控件。

  3. 我不认为引入一个新的属性,一个接口,订阅密码更改事件或任何其他复杂的事情对于提供密码的简单任务是必要的。

XAML

<PasswordBox x:Name="pbPassword" />
<Button Content="Login" Command="{Binding LoginCommand}" x:Name="btnLogin"/>

后面的代码-使用后面的代码不一定违反MVVM。只要您不添加任何业务逻辑。

btnLogin.CommandParameter = new Func<string>(()=>pbPassword.Password); 

ViewModel

LoginCommand = new RelayCommand<Func<string>>(getpwd=> { service.Login(username, getpwd()); });

答案 29 :(得分:0)

我做过:

<强> XAML:

<PasswordBox x:Name="NewPassword" PasswordChanged="NewPassword_PasswordChanged"/>
<!--change tablenameViewSource: yours!-->
<Grid DataContext="{StaticResource tablenameViewSource}" Visibility="Hidden">
        <TextBox x:Name="Password" Text="{Binding password, Mode=TwoWay}"/>
</Grid>

<强> C#:

private void NewPassword_PasswordChanged(object sender, RoutedEventArgs e)
    {
        try
        {
           //change tablenameDataTable: yours! and tablenameViewSource: yours!
           tablenameDataTable.Rows[tablenameViewSource.View.CurrentPosition]["password"] = NewPassword.Password;
        }
        catch
        {
            this.Password.Text = this.NewPassword.Password;
        }
    }

对我有用!

答案 30 :(得分:0)

您可以在 WPF Application Framework (WAF) 项目的ViewModel示例应用程序中找到PasswordBox的解决方案。

然而,贾斯汀是对的。不要在View和ViewModel之间以纯文本形式传递密码。请改用SecureString(参见MSDN PasswordBox)。

相关问题