Как привязать к PasswordBox в MVVM

я столкнулся с проблемой привязки к PasswordBox. Кажется, это риск для безопасности, но я использую шаблон MVVM, поэтому я хочу обойти это. Я нашла здесь интересный код (кто-нибудь использовал это или что-то подобное?)

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

это технически выглядит отлично, но я не уверен, как получить пароль.

у меня в основном есть свойства в моем LoginViewModel на Username и Password. Username отлично и работает, как это TextBox.

я использовал код выше, как указано, и ввел это

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

когда у меня был PasswordBox как TextBox и Binding Path=Password тогда свойство в моем .

мой код очень прост, в основном у меня есть Command для меня Button. Когда я нажимаю его CanLogin вызывается, и если он возвращает true, он вызывает Login.
Вы можете видеть, что я проверяю свою собственность на Username здесь работает отличный.

на Login посылаю на службу a Username и Password, Username содержит данные из my View но Password is 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 the Password пусто.

я делаю что-то неправильно или пропустил шаг?

я поставил точку останова и код введите статический вспомогательный класс, но это никогда не обновляет мой Password в своем ViewModel.

30 ответов


Извините, но вы делаете это неправильно.

люди должны иметь следующую директиву безопасности татуировку на внутренней стороне их век:
никогда не храните текстовые пароли в памяти.

причина, по которой WPF/Silverlight PasswordBox не предоставляет DP для свойства Password, связана с безопасностью.
Если бы WPF / Silverlight должны были сохранить DP для пароля, это потребовало бы, чтобы платформа хранила сам пароль незашифрованным в памяти. Какой считается довольно хлопотным вектором атаки безопасности. PasswordBox использует зашифрованную память (видов), и единственный способ получить доступ к паролю-через свойство CLR.

Я бы предложил это при доступе к PasswordBox.Свойство Password CLR вы воздержитесь от размещения его в любой переменной или в качестве значения для любого свойства.
Сохранение пароля в виде обычного текста в ОЗУ клиентской машины-это безопасность no-no.
Поэтому избавьтесь от этого " публичного строкового пароля { get; set; } "ты попал туда.

при доступе к PasswordBox.Пароль, просто вытащите его и отправьте на сервер как можно скорее. Не сохраняйте значение пароля и не относитесь к нему так, как к любому другому тексту клиентской машины. Не храним пароли открытым текстом в памяти.

Я знаю, что это нарушает шаблон MVVM, но вы никогда не должны привязываться к PasswordBox.Пароль прилагается DP, хранить пароль в ViewModel или любые другие подобные махинации.

Если вы глядя на чрезмерно архитектурное решение, вот один:
1. Создайте интерфейс IHavePassword с помощью одного метода, который возвращает текст пароля.
2. Попросите ваш UserControl реализовать интерфейс IHavePassword.
3. Зарегистрируйте экземпляр UserControl в IoC как реализующий интерфейс IHavePassword.
4. Когда происходит запрос сервера, требующий вашего пароля, вызовите свой IoC для реализации IHavePassword и только тогда получите желанный пароль:.

просто мой взгляд на это.

-- Джастин


мои 2 цента:

Я разработал один раз типичный диалог входа в систему (поля пользователя и пароля, а также кнопку "ОК") с помощью WPF и MVVM. Я решил проблему привязки пароля, просто передав сам элемент управления PasswordBox в качестве параметра команде, прикрепленной к кнопке "Ok". Так что в представлении у меня было:

<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 знает что-то о том, как реализовано представление, но в этом конкретном проекте я мог бы себе это позволить. Надеюсь, это полезно для кого-то.


может быть, я что-то упускаю, но похоже, что большинство этих решений чрезмерно усложняют вещи и устраняют безопасные практики.

этот метод не нарушает шаблон MVVM и поддерживает полную безопасность. Да, технически это код позади, но это не что иное, как привязка "особого случая". ViewModel по-прежнему не знает о реализации представления, что, на мой взгляд, он делает, если вы пытаетесь передать PasswordBox в ViewModel.

Код Позади != Автоматическое нарушение MVVM. Все зависит от того, что с ним делать. В этом случае мы просто вручную кодируем привязку, поэтому ее все считают частью реализации пользовательского интерфейса и поэтому в порядке.

в ViewModel, просто простое свойство. Я сделал это "только писать", так как не должно быть необходимости извлекать его из-за пределов 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. Но на самом деле" нормальные " привязки данных также не сильно типизированы, поэтому это не так уж важно.

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

так лучший из всех миров-ваш пароль безопасен, ваша ViewModel просто имеет свойство, как и любое другое свойство, и Ваше представление автономно без внешних ссылок.


вы можете использовать этот 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;
}

это работает просто отлично для меня.

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

простым решением без нарушения шаблона 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);

я опубликовал 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;
        }
    }
}

эта реализация немного отличается. Вы передаете passwordbox в представление через привязку свойства в ViewModel, он не использует никаких параметров команды. ViewModel остается в неведении о представлении. У меня есть проект VB vs 2010, который можно загрузить с SkyDrive. Пример WPF С Использованием MVVM Приложения PasswordBox.zip https://skydrive.live.com/redir.aspx?cid=e95997d33a9f8d73&resid=E95997D33A9F8D73!511

способ, которым я использую PasswordBox в приложении WPF MvvM, довольно упрощенный и хорошо работает для меня. Это не означает, что я думаю, что это правильный или лучший способ. Это просто реализация использования PasswordBox и шаблона MvvM.

в основном вы создаете общедоступное свойство readonly, к которому представление может привязываться как 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" />

оттуда у вас есть полный контроль над passwordbox я также использую 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 я удостоверяюсь, что аксессор создан и установлен в 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

это должно сделать это. ViewModel не нуждается в знании элементов управления представления. Представление просто привязывается к свойству в ViewModel, не отличающемуся от привязки представления к изображению или другому ресурсу. В этом случае этот ресурс(свойство) просто является usercontrol. Он позволяет тестировать, поскольку ViewModel создает и владеет свойством, а свойство не зависит от представления. Что касается безопасности, я не знаю, насколько хороша эта реализация. Но при использовании функции значение не сохраняется в свойстве сам только что доступ к собственности.


чтобы решить проблему OP, не нарушая MVVM, я бы использовал пользовательский конвертер значений и оболочку для значения (пароля), которое должно быть извлечено из поля пароля.

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>, он не должен иметь никаких знаний о PasswordBoxWrapper, ни PasswordBoxConverter. Таким образом, вы можете изолировать 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}}" />

хотя я согласен, что важно избегать хранения пароля в любом месте, мне все еще нужна возможность создать экземпляр модели представления без представления и выполнить мои тесты против него.

решение, которое сработало для меня, было зарегистрировать PasswordBox.Функция пароля с моделью представления и модель представления вызывают ее при выполнении кода входа в систему.

этой тут означает строку кода в codebehind представления.

Итак, в моем логине.в XAML я есть

<PasswordBox x:Name="PasswordBox"/>

и в логин.код XAML.cs у меня

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

затем в LoginViewModel.cs у меня есть определяемый PasswordHandler

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

и когда необходимо войти в систему, код вызывает обработчик для получения пароля из представления...

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

таким образом, когда я хочу протестировать viewmodel, я могу просто установить PasswordHandler на анонимный метод, который позволяет мне доставлять любой пароль, который я хочу использовать в тесте.


Я потратил много времени, рассматривая различные решения. Мне не понравилась идея декораторов, поведение испортило интерфейс проверки, код позади... неужели?

лучший из них-придерживаться пользовательского прикрепленного свойства и привязываться к вашему SecureString свойства в модели представления. Держи его там как можно дольше. Всякий раз, когда вам понадобится быстрый доступ к простому паролю, временно преобразуйте его в незащищенную строку, используя код ниже:

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

сделать конечно, вы позволяете GC собирать ваш элемент UI, поэтому сопротивляйтесь желанию использовать статический обработчик событий для PasswordChanged событие PasswordBox. Я также обнаружил аномалию, когда элемент управления не обновлял пользовательский интерфейс при использовании SecurePassword свойство для его настройки, причина, по которой я копирую пароль в .

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

и использование XAML:

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

мое свойство в модели представления выглядела так:

[RequiredSecureString]
public SecureString LogonPassword
{
   get
   {
       return _logonPassword;
   }
   set
   {
       _logonPassword = value;
       NotifyPropertyChanged(nameof(LogonPassword));
   }
}

на RequiredSecureString is просто простой пользовательский валидатор, который имеет следующую логику:

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true)]    
public class RequiredSecureStringAttribute:ValidationAttribute
{
    public RequiredSecureStringAttribute()
        :base("Field is required")
    {            
    }

    public override bool IsValid(object value)
    {
        return (value as SecureString)?.Length > 0;
    }
}

вот оно. Полное и испытанное чистое решение MVVM.


я использовал этот метод и передал поле пароля, хотя это нарушает MVVM, это было важно для меня, потому что я использовал элемент управления содержимым с шаблоном данных для моего входа в мою оболочку, которая является сложной средой оболочки. Таким образом, доступ к коду за оболочкой был бы дерьмом.

передача passwordbox я бы подумал, что это то же самое, что доступ к управлению из кода, насколько я знаю. Я согласен с паролями, не храню в памяти и т. д. В этой реализации я не иметь свойство для пароля в view model.

Кнопка 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);
}

я решил, что брошу свое решение в микс, так как это такая распространенная проблема... и иметь много вариантов всегда хорошо.

я просто обернул PasswordBox на UserControl и осуществляла DependencyProperty чтобы иметь возможность связывать. Я делаю все возможное, чтобы избежать хранения любого четкого текста в памяти, поэтому все делается через SecureString и PasswordBox.Password собственность. Во время foreach цикл, каждый символ подвергается воздействию, но он очень короткий. Честно, если ты беспокоясь о том, что ваше приложение WPF будет скомпрометировано от этой краткой экспозиции, у вас есть большие проблемы безопасности, которые должны быть обработаны.

красота этого в том, что вы не нарушаете никаких правил MVVM, даже "пуристских", так как это UserControl, поэтому разрешено иметь код-за. Когда вы используете его, вы можете иметь чистую связь между View и ViewModel без 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}"/>

вы можете сделать это с помощью вложенного свойства, увидеть его.. PasswordBox с MVVM


как вы можете видеть, я привязываюсь к паролю, но, возможно, его привязка к статическому классу..

это добавленные собственность. Это свойство может быть применено к любому виду DependencyObject, а не просто тип, в котором он объявлен. Так что, даже если это объявлено в PasswordHelper статический класс, он применяется к PasswordBox на что вы его используете.

чтобы использовать это прикрепленное свойство, вам просто нужно привязать его к Password недвижимость в вашей ViewModel :

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

Как упоминалось ранее, VM не должен знать о представлении, но передача всего PasswordBox выглядит как самый простой подход. Поэтому, возможно, вместо приведения переданного параметра в PasswordBox используйте отражение для извлечения свойства пароля из него. В этом случае VM ожидает какой-то контейнер паролей с паролем свойства(я ussing 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"
    };

для меня обе эти вещи кажутся неправильными:

  • реализация свойств пароля с открытым текстом
  • отправить PasswordBox в качестве параметра команды для ViewModel

передача SecurePassword (экземпляр SecureString), как описано Стив в CO вполне приемлемо. Я предпочитаю 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;
            }
        }

    }
}

в универсальном приложении windows

вы можете использовать этот код со свойством "пароль" и привязкой к modelView

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

его очень просто . Создайте другое свойство для пароля и свяжите его с 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;
        }
    }


для тех, кто знает о рисках, которые налагает эта реализация, чтобы синхронизировать пароль с ViewModel, просто добавьте Mode=OneWayToSource.

XAML

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

вы найдете решение для PasswordBox в примере приложения ViewModel WPF Application Framework (WAF).

тем не менее, Джастин прав. Не передавайте пароль как обычный текст между View и ViewModel. Вместо этого используйте SecureString (см. MSDN PasswordBox).


Я сделал так:

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

это работает для меня!


Я использовал проверку подлинности, за которой следует sub, вызываемый классом-посредником в представление (которое также реализует проверку подлинности) для записи пароля в класс данных.

Это не идеальное решение; однако, это исправило мою проблему невозможности переместить пароль.


Я использую краткое MVVM-дружественное решение, которое еще не упоминалось. Во-первых, я называю PasswordBox в XAML:

<PasswordBox x:Name="Password" />

затем я добавляю один вызов метода в конструкторе вид:

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

и это все. Модель представления получит уведомление, когда она присоединена к представлению через DataContext, и другое уведомление, когда она отсоединена. Содержимое этого уведомления настраивается через lambdas, но обычно это просто вызов сеттера или метода на просмотр модели, передача проблемного элемента управления в качестве параметра.

Это может быть сделано MVVM-friendly очень легко, имея вид выставить интерфейс вместо дочерних элементов управления.

вышеуказанный код полагается на вспомогательный класс опубликовано в моем блоге.


Я потратил века, пытаясь заставить это работать. В конце концов, я сдался и просто использовал PasswordBoxEdit от DevExpress.

Это самое простое решение, поскольку оно позволяет связывать, не вытягивая никаких ужасных трюков.

решение на сайте DevExpress

для записи, я никоим образом не связан с DevExpress.


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

;) легко!


Ну, мой answerd более прост просто для шаблона MVVM

в классе viewmodel

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;

}

свойство password PasswordBox, которое предоставляет win, или WatermarkPasswordBox, которое предоставляет XCeedtoolkit, генерирует RoutedEventArgs, чтобы вы могли его привязать.

Теперь в xmal view

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

Если вы хотите, чтобы он объединил все это только в одном элементе управления и одной команде

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

и на вашей виртуальной машине (как показал 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));

вот мой взгляд на это:

  1. использование вложенного свойства для привязки пароля побеждает цель защиты пароля. Свойство Password поля password не может быть привязано по какой-либо причине.

  2. передача поля пароля в качестве параметра команды сделает ViewModel осведомленным об элементе управления. Это не сработает, если вы планируете сделать свою ViewModel многоразовой кросс-платформой. не сообщайте VM о своем представлении или любом другом контроли.

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

для полных новичков, таких как я, вот полный рабочий образец того, что 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);
    }
}