Як прив’язати до 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 або будь-якому іншому подібному shenanigans.

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

Тільки моє взяти на це.

- Джастін


19
Не могли б ви використовувати SecureString у VM для WPF для вирішення цієї проблеми? Не здається, що є щось для Silverlight.
Брайант

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

182
У більшості випадків цей рівень безпеки вам не потрібен. Який сенс зробити це одне важким, коли існує стільки інших способів крадіжки паролів? Atleast WPF повинен був дозволити використання SecureString, як сказав @Bryant.
чакрит

335
Якщо погані хлопці мають доступ до оперативної пам’яті вашої машини, у вас виникають більші проблеми, ніж вони крадуть ваш пароль.
Камерон Макфарланд

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 знає щось про те, як реалізується View, але в цьому конкретному проекті я міг собі це дозволити. Сподіваюся, це корисно і для когось.


Привіт Конаміман, коли викликається метод Execute.У моєму переглядімодель у мене є клас користувача (логін, пароль) та команда, що підтверджує автентифікацію. Як я можу використовувати Execute в цьому контексті?

3
дуже корисно, спасибі fyi, хтось може звикнути бачити щось на зразок _loginCommand = новий RelayCommand (param => Login (UserName, (PasswordBox) param)), param => CanLogIn);
Чак Ростенюк

5
це нормальне рішення, але не вдається щось на зразок пароля + комбінація підтвердження пароля
Жульєн

Привіт, Конаміман, я використовую ваше рішення, але воно не працює в додатку Windows 8.1 Store. Я поставив це питання: 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, тому вам, можливо, не потрібен чіткий текстовий пароль навіть якщо ви вважаєте, що це робите), ви можете просто скористатися властивістю пароля. Подобається це:

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


1
Цей мені добре виглядає! Якщо ви хотіли бути дуже суворими з боку безпеки, я не впевнений, що це вирішить, але для мене це ідеальна середина. Дякую!
jrich523

3
Дякуємо за практичність щодо жорсткої догми про МВВМ та параної. Чудово працює, дякую.
Брюс Пірсон

2
Приклад SecureString був би чудовим із цим розширенням blogs.msdn.com/b/fpintos/archive/2009/06/12/…
Айман

1
Справді добре. Я хотів би, щоб MS щойно додала пароль до пароля типу 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
FYIxmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
XAMlMAX

Не вимагаючи назви імені пароля: 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
LukeN, це не працює (принаймні для мене). Можливо, з тієї ж причини - 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);

Єдине, чого вам не вистачає, це те, що підписуючи перегляд на подію моделі перегляду, ви повинні використовувати a, WeakEventManager<TEventSource, TEventArgs>щоб уникнути витоку пам'яті. Часто час перегляду не буде мати такий самий термін експлуатації, як модель перегляду. WeakEventManager<IViewModel, EventArgs>.AddHandler(iViewModelInstance, nameof(IViewModel.Event), eventHandlerMethod);
Todd A. Stedel

Я вважаю за краще це рішення, оскільки воно просте, не порушує MVVM, має мінімальний код позаду, дозволяє правильно використовувати скриньку паролів (якщо ви замість цього використовуєте "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;
        }
    }
}

Переконайтеся, що ви дозволяєте 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'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

Я опублікував тут ГІСТ, що є в'язким кодом паролів.

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, але ви можете це виправити за допомогою стилів. Я вирішую цю проблему аналогічним чином, але я використовую a, то ContentControlви можете просто використовувати PasswordBox як зміст і стиль, що в XAML, як вам підходить. Мета цього проекту ContentControl- просто підписатись на PasswordChangedподію та викрити двосторонній властивість, пов’язану зі зв'язками. Загалом, це 65 рядків коду і майже все, що робить цей клас прикраси. Ознайомтесь з моєю суттю наступних gist.github.com/leidegre/c7343b8c720000fe3132
John Leidegren

6

Ця реалізація дещо відрізняється. Ви передаєте скриньку паролів до перегляду через прив'язку властивості у 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.

В основному ви створюєте загальнодоступну властивість лише для читання, до якої 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 (просто функція рядка), щоб повернути значення пароля під час входу в систему або будь-що інше, для чого ви хочете отримати пароль. У прикладі я маю публічну властивість у загальній моделі користувача. Приклад:

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

Коли мені потрібен рядок Password сказати для входу, я просто отримую властивість Password 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. Перегляд просто пов'язує властивість у ViewModel, не відрізняючись від прив'язки перегляду до зображення чи іншого ресурсу. У цьому випадку цей ресурс (Властивість) просто є контролером користувача. Це дозволяє перевірити те, як ViewModel створює та володіє властивістю, а властивість не залежить від представлення даних. Щодо безпеки, я не знаю, наскільки хороша ця реалізація. Але за допомогою функції Значення не зберігається у власності, просто доступному Властивості.


6

Щоб вирішити проблему з ОП, не порушуючи 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}}" />

дуже елегантне рішення imo. я базував своє на цьому. Єдина відмінність: я передаю 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та реалізував a, DependencyPropertyщоб мати можливість зв’язувати. Я роблю все можливе, щоб уникнути збереження будь-якого чіткого тексту в пам'яті, тому все робиться через a 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}"/>

Я намагався реалізувати це, але ви отримуєте нескінченний цикл, коли ви оновлюєте пароль в інтерфейсі; тому що 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 за кодувати позаду, а також у мене була додаткова вимога щодо можливості скидання пароля з перегляду.

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 не повинен знати про Перегляд, але передача всього PasswordBox виглядає як найпростіший підхід. Тож, можливо, замість того, щоб передати переданий параметр у PasswordBox, використовуйте Reflection, щоб витягти з нього властивість пароля. У цьому випадку 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"
    };

Коментарі не для розширеного обговорення; ця розмова була переміщена до чату .
Самуель Liew

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

@BK Редагував мою відповідь. Дякую.
Кевін

1
чи не повинен режим знаходитись в обв'язувальних дужках?
Мат

@Mat Yap. Дякую.
Кевін

1

Ось мій погляд на це:

  1. Використання вкладеного властивості для прив’язки пароля перешкоджає безпеці пароля. Властивість пароля у вікні пароля не піддається друкуванню з причини.

  2. Якщо передати поле пароля як параметр команди, то ViewModel дізнається про управління. Це не спрацює, якщо ви плануєте зробити свою перехресну платформу ViewModel для багаторазового використання. Не повідомляйте своєму ВМ про свій Перегляд чи будь-які інші елементи управління.

  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

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

Це не ідеальне рішення; однак це вирішило мою проблему неможливості переміщення пароля.


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

відкритий рядок Пароль {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.

Явним порушенням було б передати контроль (the 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.