Подтвердить что ты не робот

Как связать с паролем в 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 я отправляю свою услугу a Username и Password, Username содержит данные из моего 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 Password пусто.

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

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

4b9b3361

Ответ 1

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

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

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

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

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

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

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

Просто возьму это.

- Джастин

Ответ 2

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

Ответ 3

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

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

Code Behind!= Автоматическое нарушение 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 просто обладает свойством, как и любое другое свойство, и ваш вид сам по себе не требует внешних ссылок.

Ответ 4

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

Ответ 5

Это работает отлично для меня.

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

Ответ 6

Я разместил здесь 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;
        }
    }
}

Ответ 7

Простым решением, не нарушающим шаблон 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);

Ответ 8

Эта реализация немного отличается. Вы передаете пароль в представление через привязку свойства в ViewModel, он не использует никаких параметров команды. 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.

В основном вы создаете общедоступное свойство readonly, которое View может связывать как PasswordBox (фактический элемент управления) Пример:

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

Я использую поле поддержки только для самостоятельной инициализации свойства.

Затем из Xaml вы связываете содержимое ContentControl или контейнера управления. Пример:

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

Оттуда у вас есть полный контроль над паролем. Я также использую PasswordAccessor (Just a Function of String), чтобы вернуть значение пароля при входе в систему или в любом другом месте, в котором вы хотите использовать пароль. В Примере я имею общедоступное свойство в общей пользовательской объектной модели. Пример:

Public Property PasswordAccessor() As Func(Of String)

В пользовательском объекте свойство строки пароля является readonly без какого-либо резервного хранилища, оно просто возвращает пароль из PasswordBox. Пример:

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

Затем в ViewModel я убеждаюсь, что Accessor создан и установлен в свойство PasswordBox.Password ' Пример:

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

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

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

Ответ 9

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

Ответ 10

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

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

Это означает строку кода в коде кода.

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

<PasswordBox x:Name="PasswordBox"/>

и в Login.xaml.cs У меня

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

то в LoginViewModel.cs у меня определен PasswordHandler

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

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

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

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

Ответ 11

Я использовал этот метод и передал поле пароля, хотя это нарушает 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);
}

Ответ 12

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

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

Ответ 13

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

Ответ 14

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

XAML

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

Ответ 15

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

Я просто обернул PasswordBox в UserControl и реализовал a 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}"/>

Ответ 16

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

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

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

Ответ 17

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

Ответ 18

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

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

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

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

Ответ 19

Как уже упоминалось ранее, VM не должна знать о View, но передача всего 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"
    };

Ответ 20

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

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

Ответ 21

Я сделал:

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>

С#

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

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

Ответ 22

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

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

Ответ 23

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

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

Ответ 24

Для меня обе эти вещи не так:

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

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

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

    }
}

Ответ 25

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

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

Решение на веб-сайте DevExpress

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

Ответ 26

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

Ответ 27

хорошо, мой ответ более прост как раз в шаблоне 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, которое обеспечивает win или 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>