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


251

Я сталкивался с проблемой связывания с P asswordBox. Кажется, это угроза безопасности, но я использую шаблон 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вызывается, и если он возвращает истину, он звонит 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.


3
Хорошо получается, что код не работал, но я попробовал альтернативный код здесь, и он работает отлично. blog.functionalfun.net/2008/06/…
Марк Смит

5
Разве передача всего элемента управления паролем не противоречит отделению представления от модели представления?

Ответы:


164

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

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

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

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

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

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

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

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

Джастин


19
Не могли бы вы использовать SecureString в виртуальной машине для WPF для решения этой проблемы? Не похоже, что есть что-то для Silverlight.
Брайант

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

182
В большинстве случаев вам не нужен этот уровень безопасности. Какой смысл усложнять одну вещь, когда есть так много других способов украсть пароли? Atleast WPF должен был разрешить использование SecureString, как сказал @Bryant.
чакрит

336
Если у плохих парней есть доступ к оперативной памяти вашей машины, у вас больше проблем, чем когда они крадут ваш пароль.
Кэмерон МакФарланд

13
В течение многих лет я использовал пользовательский элемент управления, который ведет себя так же, как PasswordBox, но возвращает только текстовое значение в виде SecureString. Да, это не позволяет Snoop отображать пароль в виде простого текста. Тем не менее, обычное текстовое значение SecureString все еще может быть извлечено довольно легко и только предотвращает взлом новичков. Если ваша система рискует скрытно использовать регистраторы и снифферы, такие как Snoop, вам следует пересмотреть систему безопасности вашей системы.
Майк Кристиан

199

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


Здравствуйте, Konamiman, когда вызывается метод Execute. В моей модели представления у меня есть класс User (login, pass) и команда authenticate. Как я могу использовать Execute в этом контексте?

3
очень полезно, спасибо Кстати, кто-то может привыкнуть видеть что-то вроде _loginCommand = new RelayCommand (param => Login (UserName, (PasswordBox) param), param => CanLogIn);
Чак Ростанс

5
это правильное решение, но не для чего-то вроде комбо пароля и подтверждения пароля
Julien

Здравствуйте, Konamiman, я использую ваше решение, но оно не работает в приложении Магазина Windows 8.1. Я задал этот вопрос: stackoverflow.com/questions/26221594/…
VansFannel

2
Спасибо за это! Это решило огромную проблему с перемещением данных из потока пользовательского интерфейса в основной поток программы. Обязательно реализуйте подход SecureString и ~ избавьтесь от пароля как можно скорее ~. Дамп это. Избавься от этого. Очисти это. Делай то, что тебе нужно. Также убедитесь, что вы внедрили IDisposable.
Стивен С. Бриттон

184

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

Этот метод не нарушает шаблон 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 самодостаточен без внешних ссылок.


1
Этот выглядит хорошо для меня! Если вы хотите быть очень строгим с точки зрения безопасности, я не уверен, что это может срезать это, но для меня это идеальная середина. Спасибо!
jrich523

3
Спасибо за практичность за жесткую догму о МВВМ и паранойе. Отлично работает, спасибо.
Брюс Пирсон

2
Пример SecureString было бы здорово с этим расширением blogs.msdn.com/b/fpintos/archive/2009/06/12/...
Айман

1
Действительно хорошо. Я бы хотел, чтобы MS просто добавила DP DP типа SecureString к этому элементу управления.
Кит Хилл

1
Это идеальный ответ, так как сохраняет безопасность и MVVM.
LoRdPMN

20

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

3
К вашему сведениюxmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
XAMLMAX

Без необходимости указывать имя PasswordBox: CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=PasswordBox}}"(примечание: нет RelativeSource Self ).
Wondra

Это решение нарушает схему MVVM.
BionicCode

13

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

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

3
А как насчет CommandParameter = "{Binding ElementName = MyPasswordBox, Path = SecurePassword"}?
ЛукиN

2
ЛюкN, это не работает (по крайней мере, для меня). Вероятно, по той же причине - SecurePassword не является свойством зависимости.
vkrzv

Предполагая, что ICommandэто реализовано в модели представления, это решение будет нарушать шаблон MVVM.
BionicCode

9

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

Единственное, чего вам не хватает, так это того, что при подписке представления на событие модели представления вы должны использовать, WeakEventManager<TEventSource, TEventArgs>чтобы избежать утечек памяти. Часто у представления не будет то же время жизни, что и у модели представления. WeakEventManager<IViewModel, EventArgs>.AddHandler(iViewModelInstance, nameof(IViewModel.Event), eventHandlerMethod);
Тодд А.

Я предпочитаю это решение, так как оно простое, не нарушает MVVM, имеет минимальный код позади, позволяет правильно использовать passwordbox (если вы используете вместо этого «SecurePassword»). Кроме того, теперь просто реализовать другие методы HarvestPassword (например, SmartCard ....)
Мэтт

8

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

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


7

Я отправил 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;
        }
    }
}

1
хотя это неплохо, вы теряете возможность устанавливать простые атрибуты, такие как padding и tabindex
Julien

1
Тейлор, я изложил суть так, чтобы это было доступно в ответе. (В противном случае это выглядело как ответ только для ссылки ... просто пытался избежать его удаления как такового.) Не стесняйтесь возиться со встроенным контентом.
Линн рушится

@Julien, но вы можете исправить это с помощью стилей. Я решаю эту проблему аналогичным образом, но я использую, а ContentControlзатем вы можете просто использовать PasswordBox в качестве содержимого и стиля, которые в XAML вам подходят. Цель состоит в том, ContentControlчтобы просто подписаться на PasswordChangedсобытие и предоставить двунаправленное связываемое свойство. В целом, это 65 строк кода и в значительной степени то, что делает этот класс decorate. См. Здесь мою суть следующего gist.github.com/leidegre/c7343b8c720000fe3132
Джон Лейдегрен

6

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

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

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


6

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

очень элегантное решение имо. Я основал свое на этом. Единственная разница: я передаю SecureString SecurePassword в функцию входа вместо String Password. так что нет никаких незашифрованных строк с паролем, летающими вокруг памяти.
назови меня морковкой

Это было какое-то время, но я не могу заставить это работать из-за моей RelayCommand. Вы не против добавить свой?
Ecnerwal

5

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

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

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

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

<PasswordBox x:Name="PasswordBox"/>

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

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

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

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

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

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

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


4

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

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

Прелесть этого в том, что вы не нарушаете никаких правил MVVM, даже «пуристических», поскольку это UserControlтак, поэтому разрешено иметь кодовый код. Когда вы используете его, вы можете иметь чистую связь междуViewViewModel вами и без VideModelзнания какой-либо части Viewили источника пароля. Просто убедитесь, что вы привязаны к SecureStringсвоемуViewModel .

BindablePasswordBox.xaml

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

BindablePasswordBox.xaml.cs (Версия 1 - двусторонняя привязка не поддерживается.)

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

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

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

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

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

Использование версии 1:

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

BindablePasswordBox.xaml.cs (версия 2 - имеет поддержку двусторонней привязки.)

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

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

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

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

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

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

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

Использование версии 2:

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

Я пытался реализовать это, но вы получаете бесконечный цикл при обновлении пароля в пользовательском интерфейсе; потому if (Password != secure)что всегда будет ложным, так как SecureString не переопределяет равно. Есть предположения?
simonalexander2005


2

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

Это явное нарушение шаблона MVVM. Шаблон не позволяет обрабатывать элементы управления в модели представления.
BionicCode

2

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

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

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

Xaml ( Passwordэто свойство ViewModel):

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

Поведение:

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

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

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

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

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

    }
}

2

Для полных новичков, как я, вот полный рабочий пример того, что Konamimanпредложено выше. Благодаря Konamiman.

XAML

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

ViewModel

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

    public YourViewModel()
    {
    }

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

Это явное нарушение шаблона MVVM. Шаблон не позволяет обрабатывать элементы управления в модели представления.
BionicCode

1

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

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

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

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

1

Я сделал как:

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

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


Вы даете мне хорошую идею. :)
Андре

1

Как упоминалось ранее, 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"
    };

Комментарии не для расширенного обсуждения; этот разговор был перенесен в чат .
Самуэль Лью

1

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

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

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


1

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

XAML

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

Почему бы просто не сделать OneWayToSource?
БК

@BK Отредактировал мой ответ. Спасибо.
Кевин

1
не должно ли Mode находиться внутри связующих скобок?
Мат

@ Мат Яп. Спасибо.
Кевин

1

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

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

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

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

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


2
Способ, который используется в Pop3SettingsView WAF, забавен. PasswordBox passwordBox = (PasswordBox) отправитель; if (ViewModel! = null) {ViewModel.Pop3Password = passwordBox.Password; } Pop3Password для ViewModel - это свойство строки. так что это небезопасно ... лучше использовать прикрепленное свойство
Michael Sync

0

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

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


0

Я использую сжатое 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

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

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

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

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


0

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

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

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

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

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

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

;) легко!


0

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

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

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

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

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

            return szChar;
        }

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

публичная строка Password {get {return _Password; }

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


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

0

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

Отправьте SecureStringмодель представления, используя прикрепленное поведение иICommand

В реализации кода нет ничего плохого при реализации MVVM. MVVM - это архитектурный шаблон, целью которого является отделение представления от модели / бизнес-логики. MVVM описывает, как достичь этой цели воспроизводимым способом (схема). Его не волнуют детали реализации, например, как вы структурируете или реализуете представление. Он просто рисует границы и определяет, что такое представление, модель представления и что такое модель с точки зрения терминологии этого шаблона.

MVVM не заботится о языке (XAML или C #) или компиляторе (partial классах). Независимость от языка является обязательной характеристикой шаблона проектирования - она ​​должна быть независимой от языка.

Однако у кода есть некоторые недостатки, такие как усложнение понимания вашей логики пользовательского интерфейса, когда она широко распространена между XAML и C #. Но наиболее важная реализация логики или объектов пользовательского интерфейса, таких как шаблоны, стили, триггеры, анимация и т. Д. В C #, очень сложна и уродлива / менее читаема, чем при использовании XAML. XAML - это язык разметки, который использует теги и вложенность для визуализации иерархии объектов. Создание пользовательского интерфейса с использованием XAML очень удобно. Хотя бывают ситуации, когда вы можете выбрать логику пользовательского интерфейса в C # (или коде). Обработка PasswordBox- один из примеров.

По этой причине обработка PasswordBoxкода в коде путем обработки PasswordBox.PasswordChanged, не является нарушением шаблона MVVM.

Явным нарушением будет передача управления ( PasswordBox) модели представления. Многие решения рекомендовать , например, лавровый передавая экземпляр класса , PasswordBoxкакICommand.CommandParameter в модель представления. Очевидно, очень плохая и ненужная рекомендация.

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

В отличие от печально известного широко распространенного помощника, который обеспечивает привязку к простому текстовому паролю (очень плохой анти-шаблон и угроза безопасности), это поведение использует ICommandпароль для отправки пароля SecureStringк модели представления всякий раз, когда PasswordBoxподнимаетсяPasswordBox.PasswordChanged событие.

MainWindow.xaml

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

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

ViewModel.cs

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

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

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

PasswordBox.cs

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

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

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

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

  #endregion

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

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

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

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

    ICommand sendCommand = GetCommand(attachedElement);
    sendCommand?.Execute(commandParameter);
  }
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.