C# 如何绑定到 MVVM 中的 PasswordBox

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1483892/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 17:58:04  来源:igfitidea点击:

How to bind to a PasswordBox in MVVM

c#wpfmvvmwpf-controlspasswords

提问by mark smith

I have come across a problem with binding to a PasswordBox. It seems it's a security risk but I am using the MVVM pattern so I wish to bypass this. I found some interesting code here (has anyone used this or something similar?)

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

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

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

It technically looks great, but I am unsure of how to retrieve the password.

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

I basically have properties in my LoginViewModelfor Usernameand Password. Usernameis fine and is working as it's a TextBox.

我的LoginViewModelforUsername和 中基本上都有属性PasswordUsername很好,并且正在工作,因为它是TextBox.

I used the code above as stated and entered this

我按照上面的说明使用了代码并输入了这个

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

When I had the PasswordBoxas a TextBoxand Binding Path=Passwordthen the property in my LoginViewModelwas updated.

当我有PasswordBox一个TextBoxBinding Path=Password然后我的财产LoginViewModel进行了更新。

My code is very simple, basically I have a Commandfor my Button. When I press it CanLoginis called and if it returns true it calls Login.
You can see I check my property for Usernamehere which works great.

我的代码很简单,基本上Command我的Button. 当我按下它时,它CanLogin被调用,如果它返回 true,它会调用Login.
你可以看到我Username在这里检查我的财产,这很好用。

In LoginI send along to my service a Usernameand Password, Usernamecontains data from my Viewbut Passwordis Null|Empty

Login我发送给我的服务 a Usernameand PasswordUsername包含来自我的数据View但是PasswordNull|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 { }
}


This is what I am doing

这就是我正在做的

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

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

I have my TextBox, this is no problem, but in my ViewModelthe Passwordis empty.

我有我的TextBox,这没问题,但在我ViewModelPassword是空的。

Am I doing something wrong or missing a step?

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

I put a breakpoint and sure enough the code enter the static helper class but it never updates my Passwordin my ViewModel.

我放了一个断点,果然代码进入了静态帮助器类,但它从不更新我PasswordViewModel.

采纳答案by JustinAngel

Sorry, but you're doing it wrong.

对不起,但你做错了。

People should have the following security guideline tattooed on the inside of their eyelids:
Never keep plain text passwords in memory.

人们应该在眼睑内侧纹上以下安全准则:
永远不要将纯文本密码保存在记忆中。

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

WPF/SilverlightPasswordBox不公开Password属性的 DP的原因与安全相关。
如果 WPF/Silverlight 为Password它保留一个 DP,它将要求框架在内存中保持密码本身未加密。这被认为是一个相当麻烦的安全攻击媒介。该PasswordBox(的种类)使用加密存储和访问密码的唯一方法是通过CLR属性。

I would suggest that when accessing the PasswordBox.PasswordCLR property you'd refrain from placing it in any variable or as a value for any property.
Keeping your password in plain text on the client machine RAM is a security no-no.
So get rid of that public string Password { get; set; }you've got up there.

我建议在访问PasswordBox.PasswordCLR 属性时不要将它放在任何变量中或作为任何属性的值。
将您的密码以纯文本形式保存在客户端计算机 RAM 上是一个安全禁忌。
所以摆脱public string Password { get; set; }你已经到了那里。

When accessing PasswordBox.Password, just get it out and ship it to the server ASAP. Don't keep the value of the password around and don't treat it as you would any other client machine text. Don't keep clear text passwords in memory.

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

I know this breaks the MVVM pattern, but you shouldn't ever bind to PasswordBox.PasswordAttached DP, store your password in the ViewModel or any other similar shenanigans.

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

If you're looking for an over-architected solution, here's one:
1. Create the IHavePasswordinterface with one method that returns the password clear text.
2. Have your UserControlimplement a IHavePasswordinterface.
3. Register the UserControlinstance with your IoC as implementing the IHavePasswordinterface.
4. When a server request requiring your password is taking place, call your IoC for the IHavePasswordimplementation and only than get the much coveted password.

如果您正在寻找一种过度架构的解决方案,这里有一个:
1.IHavePassword使用一种返回密码明文的方法创建接口。
2. 让你UserControl实现一个IHavePassword接口。
3. 向UserControl您的 IoC注册实例以实现IHavePassword接口。
4. 当服务器请求需要您的密码时,调用您的 IoC 进行IHavePassword实施,然后才获得梦寐以求的密码。

Just my take on it.

只是我的看法。

-- Justin

——贾斯汀

回答by Thomas Levesque

As you can see i am binding to Password, but maybe its bind it to the static class..

正如你所看到的,我绑定到密码,但也许它绑定到静态类..

It is an attached property. This kind of property can be applied to any kind of DependencyObject, not just the type in which it is declared. So even though it is declared in the PasswordHelperstatic class, it is applied to the PasswordBoxon which you use it.

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

To use this attached property, you just need to bind it to the Passwordproperty in your ViewModel :

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

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

回答by Rangel

you can do it with attached property, see it.. PasswordBox with MVVM

你可以用附加的属性来做,看看它..带 MVVM 的 PasswordBox

回答by jbe

You find a solution for the PasswordBox in the ViewModel sample application of the WPF Application Framework (WAF)project.

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

However, Justin is right. Don't pass the password as plain text between View and ViewModel. Use SecureString instead (See MSDN PasswordBox).

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

回答by Vladislav Borovikov

This works just fine for me.

这对我来说很好用。

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

回答by Taylor Leese

I posted a GIST herethat is a bindable password box.

我在这里发布了一个 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;
        }
    }
}

回答by Konamiman

My 2 cents:

我的 2 美分:

I developed once a typical login dialog (user and password boxes, plus "Ok" button) using WPF and MVVM. I solved the password binding issue by simply passing the PasswordBox control itself as a parameter to the command attached to the "Ok" button. So in the view I had:

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

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

And in the ViewModel, the Executemethod of the attached command was as follows:

而在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
}

This slightly violates the MVVM pattern since now the ViewModel knows something about how the View is implemented, but in that particular project I could afford it. Hope it is useful for someone as well.

这稍微违反了 MVVM 模式,因为现在 ViewModel 知道一些关于 View 是如何实现的,但在那个特定的项目中我可以负担得起。希望它对某人也有用。

回答by Legz

I used this method and passed the password box, although this does violate the MVVM it was essential for me because I was using a content control with data template for my login within my shell which is a complex shell enviroment. So accessing the code behind of the shell would have been crap.

我使用了这种方法并传递了密码框,尽管这确实违反了 MVVM,但这对我来说是必不可少的,因为我在 shell 中使用了带有数据模板的内容控件进行登录,这是一个复杂的 shell 环境。因此,访问 shell 背后的代码将是废话。

Passing the passwordbox I would think is same as accessing control from code behind as far as I know. I agree passwords, dont keep in memory etc In this implementation I don't have property for password in view model.

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

Button Command

按钮命令

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

回答by William Rawson

This implementation is slightly different. You pass a passwordbox to the View thru binding of a property in ViewModel, it doesn't use any command params. The ViewModel Stays Ignorant of the View. I have a VB vs 2010 Project that can be downloaded from SkyDrive. Wpf MvvM PassWordBox Example.zip https://skydrive.live.com/redir.aspx?cid=e95997d33a9f8d73&resid=E95997D33A9F8D73!511

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

The way that I am Using PasswordBox in a Wpf MvvM Application is pretty simplistic and works well for Me. That does not mean that I think it is the correct way or the best way. It is just an implementation of Using PasswordBox and the MvvM Pattern.

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

Basicly You create a public readonly property that the View can bind to as a PasswordBox (The actual control) Example:

基本上,您创建一个公共只读属性,视图可以将其绑定为 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

I use a backing field just to do the self Initialization of the property.

我使用后备字段只是为了进行属性的自初始化。

Then From Xaml you bind the Content of a ContentControl or a Control Container Example:

然后从 Xaml 绑定 ContentControl 或控件容器示例的内容:

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

From there you have full control of the passwordbox I also use a PasswordAccessor (Just a Function of String) to return the Password Value when doing login or whatever else you want the Password for. In the Example I have a public property in a Generic User Object Model. Example:

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

Public Property PasswordAccessor() As Func(Of String)

In the User Object the password string property is readonly without any backing store it just returns the Password from the PasswordBox. Example:

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

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

Then in the ViewModel I make sure that the Accessor is created and set to the PasswordBox.Password property' Example:

然后在 ViewModel 中,我确保创建了访问器并将其设置为 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

When I need the Password string say for login I just get the User Objects Password property that really invokes the Function to grab the password and return it, then the actual password is not stored by the User Object. Example: would be in the ViewModel

当我需要密码字符串表示登录时,我只获取真正调用函数以获取密码并返回它的用户对象密码属性,然后用户对象不存储实际密码。示例:将在 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 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.

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

回答by Jan Willem B

A simple solution without violating the MVVM pattern is to introduce an event (or delegate) in the ViewModel that harvests the password.

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

In the ViewModel:

视图模型中

public event EventHandler<HarvestPasswordEventArgs> HarvestPassword;

public event EventHandler<HarvestPasswordEventArgs> HarvestPassword;

with these EventArgs:

使用这些 EventArgs:

class HarvestPasswordEventArgs : EventArgs
{
    public string Password;
}

in the View, subscribe to the event on creating the ViewModel and fill in the password value.

View 中,订阅创建 ViewModel 的事件并填写密码值。

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

In the ViewModel, when you need the password, you can fire the event and harvest the password from there:

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

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

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

LoginHelpers.Login(Username, pwargs.Password);