it-swarm.com.ru

Как привязать к 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, свойство в моей LoginViewModel было обновлено.

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

В Login я отправляю к своему сервису Username и Password, Username содержит данные из моего View, но 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, это не проблема, но в моем ViewModelPassword пуст.

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

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

224
mark smith

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

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

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

Я бы предложил, чтобы при доступе к свойству CLR PasswordBox.Password вы воздерживались от размещения его в любой переменной или в качестве значения для любого свойства.
Хранение вашего пароля в виде обычного текста на клиентском компьютере RAM - это безопасность, нет-нет.
Так что избавьтесь от той "публичной строки Password {get; set;}", которую вы там получили. 

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

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

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

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

Джастин 

148
JustinAngel

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

176
Konamiman

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

Этот метод не нарушает шаблон MVVM и обеспечивает полную безопасность. Да, технически это код, но это не что иное, как «особый случай». ViewModel все еще не знает о реализации View, что, на мой взгляд, происходит, если вы пытаетесь передать 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, поэтому вам может не понадобиться открытый текстовый пароль даже если вы так думаете), вы можете просто использовать вместо этого свойство Password. Как это:

(Свойство 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 просто есть свойство, как у любого другого свойства, и ваш View самодостаточен без каких-либо внешних ссылок.

151
Steve In CO

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

Это прекрасно работает для меня.

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

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

В ViewModel :

public event EventHandler<HarvestPasswordEventArgs> HarvestPassword;

с этими EventArgs:

class HarvestPasswordEventArgs : EventArgs
{
    public string Password;
}

в View , подпишитесь на событие при создании 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);
10
Jan Willem B

Я опубликовал 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;
        }
    }
}
8
Taylor Leese

Эта реализация немного отличается. Вы передаете поле пароля для привязки View через свойство в ViewModel, оно не использует никаких параметров команды. Модель представления остается неосведомленной о представлении. У меня есть проект VB vs 2010, который можно загрузить со SkyDrive. Wpf MvvM PassWordBox Example.Zip https://skydrive.live.com/redir.aspx?cid=e95997d33a9f8d73&resid=E95997D33A9F8D73!511

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

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

В пользовательском объекте свойство строки пароля доступно только для чтения без какого-либо резервного хранилища, оно просто возвращает пароль из 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. View просто привязывается к свойству в ViewModel, ничем не отличающимся от View, привязанного к изображению или другому ресурсу. В этом случае этот ресурс (свойство) просто является пользовательским контролем. Он позволяет проводить тестирование, поскольку ViewModel создает и владеет свойством, а свойство не зависит от View . Что касается безопасности, я не знаю, насколько хороша эта реализация. Но при использовании функции значение не сохраняется в самом свойстве, просто доступ к нему осуществляется.

6
William Rawson

Чтобы решить проблему 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}}" />
6
Aoi Karasu

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

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

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

Итак, в моем Login.xaml у меня есть

<PasswordBox x:Name="PasswordBox"/>

а в Login.xaml.cs у меня есть

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

тогда в LoginViewModel.cs я определил PasswordHandler

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

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

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

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

5
mike mckechnie

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

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

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

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 - это простой пользовательский валидатор со следующей логикой:

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

4
MoonStom

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

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

Кнопка Команда

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

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

Я просто обернул 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}"/>
3
B.K.

вы можете сделать это с прикрепленным свойством, посмотрите это .. PasswordBox с MVVM

2
Rangel

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

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

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

Передача SecurePassword (экземпляр SecureString), как описано Стивом в CO , кажется приемлемой. Я предпочитаю Behaviors для кода, и у меня также было дополнительное требование, чтобы иметь возможность сбросить пароль из модели представления.

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

    }
}
1
Mike Fuchs

В Windows универсальное приложение

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

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

1
Baz08

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

XAML

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

Это очень просто. Создайте другое свойство для пароля и свяжите это с TextBox

Но все операции ввода выполняются с фактическим свойством пароля

приватная строка _Password;

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

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

            return szChar;
        }

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

публичная строка Пароль { получить { вернуть _Password; }

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

1
Niji

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

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

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

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

Как упоминалось ранее, VM не должен знать о представлении, но передача всего PasswordBox выглядит как самый простой подход. Поэтому, возможно, вместо приведения переданного параметра к PasswordBox используйте Reflection для извлечения из него свойства Password. В этом случае VM ожидает некоторый контейнер паролей со свойством Password (я использую RelayCommands из MVMM Light-Toolkit):

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

        return this.signIn;
    }
}

Это легко проверить с помощью анонимного класса:

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

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

<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));
0
Cristian G

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

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

0
jbe

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

<PasswordBox x:Name="Password" />

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

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

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

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

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

0
Robert Važan

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

}

свойство пароля выигравшего PasswordBox или WatermarkPasswordBox, предоставляемого XCeedtoolkit, генерирует 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>
0
carlos rodriguez

Я сделал как:

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

Меня устраивает!

0
José Roberto Cuello Alcaraz

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

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

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

Для справки, я никак не связан с DevExpress.

0
Contango

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

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

0
Miles

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

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

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

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

;) легко! 

0
Hector Lobo