Cara mengikat ke PasswordBox di MVVM


251

Saya telah menemukan masalah dengan mengikat ke P asswordBox. Tampaknya ini adalah risiko keamanan tetapi saya menggunakan pola MVVM jadi saya ingin memotong ini. Saya menemukan beberapa kode menarik di sini (adakah yang pernah menggunakan ini atau yang serupa?)

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

Secara teknis tampak hebat, tetapi saya tidak yakin bagaimana cara mengambil kata sandi.

Saya pada dasarnya memiliki properti LoginViewModeluntuk Usernamedan Password. Usernamebaik-baik saja dan berfungsi sebagaimana mestinyaTextBox .

Saya menggunakan kode di atas seperti yang dinyatakan dan memasukkan ini

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

Ketika saya punya PasswordBoxsebagai TextBoxdan Binding Path=Passwordkemudian properti di sayaLoginViewModel diperbarui.

Kode saya sangat sederhana, pada dasarnya saya punya Commanduntuk saya Button. Ketika saya menekan itu CanLogindipanggil dan jika itu mengembalikan benar itu panggilan Login.
Anda dapat melihat saya memeriksa properti saya di Usernamesini yang berfungsi dengan baik.

Dalam Loginsaya mengirim bersama untuk layanan saya Usernamedan Password, Usernameberisi data dari saya Viewtapi PasswordadalahNull|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 { }
}

Inilah yang sedang saya lakukan

<TextBox Text="{Binding Path=Username, UpdateSourceTrigger=PropertyChanged}"
         MinWidth="180" />

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

Aku punya saya TextBox, ini tidak masalah, tetapi dalam saya ViewModelyang Passwordkosong.

Apakah saya melakukan sesuatu yang salah atau melewatkan satu langkah?

Saya meletakkan breakpoint dan cukup yakin kode masuk ke kelas pembantu statis tetapi tidak pernah memperbarui saya Passworddi saya ViewModel.


3
Ternyata kode itu tidak berfungsi tetapi saya mencoba kode alternatif di sini dan berfungsi dengan baik. blog.functionalfun.net/2008/06/...
tandai smith

5
Tidakkah melewatkan seluruh kontrol kotak kata sandi bertentangan dengan memisahkan tampilan dari model tampilan?

Jawaban:


164

Maaf, tapi Anda salah melakukannya.

Orang-orang harus memiliki tato pedoman keamanan berikut di bagian kelopak mata mereka:
Jangan pernah menyimpan kata sandi teks biasa dalam memori.

Alasan WPF / Silverlight PasswordBoxtidak mengekspos DP untuk Passwordproperti terkait keamanan.
Jika WPF / Silverlight ingin menyimpan DP untuk Passworditu akan membutuhkan kerangka kerja untuk menjaga kata sandi itu sendiri tidak terenkripsi dalam memori. Yang dianggap vektor serangan keamanan cukup merepotkan. ItuPasswordBox penggunaan dienkripsi memori (dari jenis) dan satu-satunya cara untuk mengakses password adalah melalui properti CLR.

Saya menyarankan bahwa ketika mengakses PasswordBox.Passwordproperti CLR Anda harus menahan diri untuk tidak menempatkannya dalam variabel apa pun atau sebagai nilai untuk properti apa pun.
Menyimpan kata sandi Anda dalam teks biasa di mesin klien RAM adalah keamanan tidak-tidak.
Jadi singkirkan bahwa public string Password { get; set; }Anda sudah sampai di sana.

Saat mengakses PasswordBox.Password, cukup keluarkan dan kirimkan ke server ASAP. Jangan menyimpan nilai kata sandi dan jangan memperlakukannya seperti yang Anda lakukan pada teks mesin klien lainnya. Jangan simpan kata sandi teks dalam memori.

Saya tahu ini melanggar pola MVVM, tetapi Anda tidak boleh mengikat PasswordBox.PasswordDP Terlampir, menyimpan kata sandi Anda di ViewModel atau shenanigans serupa lainnya.

Jika Anda mencari solusi over-architected, berikut ini:
1. Buat IHavePasswordantarmuka dengan satu metode yang mengembalikan teks yang dihapus kata sandi.
2. Mintalah Anda UserControlmengimplementasikan IHavePasswordantarmuka.
3. Daftarkan UserControlinstance dengan IoC Anda sebagai mengimplementasikan IHavePasswordantarmuka.
4. Ketika permintaan server yang memerlukan kata sandi Anda sedang berlangsung, hubungi IoC Anda untuk IHavePasswordimplementasi dan hanya mendapatkan kata sandi yang banyak diidam-idamkan.

Saya ambil saja.

- Justin


19
Tidak bisakah Anda menggunakan SecureString di VM untuk WPF untuk menyelesaikan masalah ini? Sepertinya tidak ada sesuatu untuk Silverlight.
Bryant

35
Saya setuju dengan maksud Anda dan pesan yang Anda sampaikan tetapi jawaban Anda menyiratkan bahwa kata sandi tidak pernah ada dalam memori jika Anda mengikuti pendekatan ini. Nilai kata sandi akan berada dalam memori sejak pengguna mengetiknya. Menghapus properti yang memegang kata sandi Anda adalah ide yang bagus dan akan membatasi salinan kata sandi Anda yang tersisa untuk dipungut oleh pengumpul sampah atau yang mungkin dapat ditemukan oleh kode dikelola dan tidak dikelola lainnya yang berjalan sebagai bagian dari program Anda, tetapi akan tidak menyembunyikannya sama sekali.
IanNorton

182
Untuk sebagian besar kasus, Anda tidak memerlukan tingkat keamanan seperti itu. Apa gunanya menyulitkan satu hal ketika ada begitu banyak cara lain untuk mencuri kata sandi? Atleast WPF seharusnya mengizinkan penggunaan SecureString seperti kata @Bryant.
chakrit

335
Jika penjahat memiliki akses ke RAM mesin Anda, Anda memiliki masalah lebih besar daripada mencuri kata sandi Anda.
Cameron MacFarland

13
Selama bertahun-tahun, saya telah menggunakan kontrol pengguna khusus yang berperilaku seperti PasswordBox, tetapi hanya mengembalikan nilai teks sebagai SecureString. Ya, ini mencegah Snoop menampilkan kata sandi dalam teks biasa. Namun, nilai teks biasa dari SecureString masih dapat diekstraksi dengan cukup mudah dan hanya menghalangi peretasan pemula. Jika sistem Anda berisiko menggunakan penebang kunci dan sniffer rahasia seperti Snoop, Anda harus mengevaluasi ulang pada keamanan sistem Anda.
Mike Christian

199

2 sen saya:

Saya mengembangkan dialog login yang khas (kotak pengguna dan kata sandi, ditambah tombol "Oke") menggunakan WPF dan MVVM. Saya memecahkan masalah pengikatan kata sandi dengan hanya meneruskan kontrol PasswordBox itu sendiri sebagai parameter pada perintah yang dilampirkan pada tombol "Ok". Jadi dalam pandangan saya:

<PasswordBox Name="txtPassword" VerticalAlignment="Top" Width="120" />
<Button Content="Ok" Command="{Binding Path=OkCommand}"
   CommandParameter="{Binding ElementName=txtPassword}"/>

Dan di ViewModel, Executemetode dari perintah terlampir adalah sebagai berikut:

void Execute(object parameter)
{
    var passwordBox = parameter as PasswordBox;
    var password = passwordBox.Password;
    //Now go ahead and check the user name and password
}

Ini sedikit melanggar pola MVVM karena sekarang ViewModel tahu sesuatu tentang bagaimana Lihat diimplementasikan, tetapi dalam proyek tertentu saya bisa membelinya. Semoga bermanfaat bagi seseorang juga.


Halo Konamiman, ketika metode Execute dipanggil. Di viewmodel saya, saya memiliki Pengguna kelas (login, pass) dan perintah otentikasi. Bagaimana saya bisa menggunakan Execute dalam konteks itu?

3
sangat membantu, terima kasih. fyi, seseorang mungkin terbiasa melihat sesuatu seperti _loginCommand = new RelayCommand (param => Login (UserName, (PasswordBox) param), param => CanLogIn);
Chuck Rostance

5
ini adalah solusi yang baik tetapi gagal untuk sesuatu seperti kombo + konfirmasi kata sandi
Julien

Halo Konamiman, saya menggunakan solusi Anda tetapi tidak berfungsi pada aplikasi Windows 8.1 Store. Saya telah menanyakan pertanyaan ini: stackoverflow.com/questions/26221594/…
VansFannel

2
Terima kasih untuk ini! Ini memecahkan masalah besar yang saya miliki dengan memindahkan data dari utas UI ke utas program utama. Pastikan untuk menerapkan pendekatan SecureString, dan ~ singkirkan kata sandi sesegera mungkin ~. Buang itu. Buang itu. Bersihkan itu. Lakukan apa yang perlu Anda lakukan. Juga, pastikan Anda menerapkan IDisposable.
Steven C. Britton

184

Mungkin saya kehilangan sesuatu, tetapi sepertinya sebagian besar solusi ini terlalu rumit dan menghapus praktik yang aman.

Metode ini tidak melanggar pola MVVM dan mempertahankan keamanan lengkap. Ya, secara teknis itu adalah kode di belakang, tetapi tidak lebih dari "kasus khusus" yang mengikat. The ViewModel masih tidak memiliki pengetahuan tentang implementasi Lihat, yang menurut saya itu terjadi jika Anda mencoba untuk mengirimkan PasswordBox ke ViewModel.

Code Behind! = Pelanggaran MVVM otomatis. Itu semua tergantung pada apa yang Anda lakukan dengannya. Dalam hal ini, kami hanya secara manual mengkodekan suatu penjilidan, sehingga semuanya dianggap sebagai bagian dari implementasi UI dan oleh karena itu tidak masalah.

Di ViewModel, hanya properti sederhana. Saya membuatnya "menulis saja" karena seharusnya tidak perlu mengambilnya dari luar ViewModel dengan alasan apa pun, tetapi tidak harus begitu. Perhatikan bahwa ini adalah SecureString, bukan hanya string.

public SecureString SecurePassword { private get; set; }

Di xaml, Anda mengatur pengendali event PasswordChanged.

<PasswordBox PasswordChanged="PasswordBox_PasswordChanged"/>

Dalam kode di belakang:

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

Dengan metode ini, kata sandi Anda tetap dalam SecureString setiap saat dan karenanya memberikan keamanan maksimum. Jika Anda benar-benar tidak peduli dengan keamanan atau Anda memerlukan kata sandi teks yang jelas untuk metode hilir yang memerlukannya (catatan: sebagian besar. Metode NET yang memerlukan kata sandi juga mendukung opsi SecureString, sehingga Anda mungkin tidak benar-benar membutuhkan kata sandi teks yang jelas bahkan jika Anda merasa demikian), Anda dapat menggunakan properti Kata Sandi saja. Seperti ini:

(Properti ViewModel)

public string Password { private get; set; }

(Kode di belakang)

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

Jika Anda ingin agar semuanya tetap diketik dengan benar, Anda bisa mengganti pemain (dinamis) dengan antarmuka ViewModel Anda. Tapi sungguh, binding data "normal" juga tidak terlalu diketik, jadi itu bukan masalah besar.

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

Jadi yang terbaik dari semua dunia - kata sandi Anda aman, ViewModel Anda hanya memiliki properti seperti properti lainnya, dan Tampilan Anda mandiri tanpa memerlukan referensi eksternal.


1
Yang ini terlihat bagus untuk saya! Jika Anda ingin menjadi sangat ketat di sisi keamanan saya tidak yakin ini akan memotongnya, tetapi bagi saya itu jalan tengah yang sempurna. Terima kasih!
jrich523

3
Terima kasih atas kepraktisannya tentang dogma kaku tentang MVVM dan paranoia. Bagus sekali, terima kasih.
Bruce Pierson

2
Contoh SecureString akan sangat bagus dengan ekstensi ini blogs.msdn.com/b/fpintos/archive/2009/06/12/…
Ayman

1
Bagus dong. Saya berharap MS baru saja menambahkan kata sandi DP tipe SecureString ke kontrol ini.
Keith Hill

1
Ini adalah jawaban yang sempurna, karena menjaga keamanan dan MVVM.
LoRdPMN

20

Anda dapat menggunakan XAML ini:

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

Dan perintah ini menjalankan metode:

private void ExecutePasswordChangedCommand(PasswordBox obj)
{ 
   if (obj != null)
     Password = obj.Password;
}

3
FYIxmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
XAMlMAX

Tanpa harus memberi Nama pada Kotak Kata Sandi: CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=PasswordBox}}"(catatan: tidak RelativeSource Self ).
Mukjizat

Solusi ini melanggar pola MVVM.
BionicCode

13

Ini bekerja dengan baik untuk saya.

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

3
Bagaimana dengan CommandParameter = "{Binding ElementName = MyPasswordBox, Path = SecurePassword"}?
LukeN

2
LukeN, ini tidak berhasil (setidaknya untuk saya). Mungkin karena alasan yang sama - SecurePassword bukan properti ketergantungan.
vkrzv

Dengan asumsi bahwa ICommanddiimplementasikan dalam model tampilan, solusi ini akan melanggar pola MVVM.
BionicCode

9

Solusi sederhana tanpa melanggar pola MVVM adalah dengan memperkenalkan acara (atau mendelegasikan) di ViewModel yang memanen kata sandi.

Dalam ViewModel :

public event EventHandler<HarvestPasswordEventArgs> HarvestPassword;

dengan EventArgs ini:

class HarvestPasswordEventArgs : EventArgs
{
    public string Password;
}

di View , berlangganan acara untuk membuat ViewModel dan isi nilai kata sandi.

_viewModel.HarvestPassword += (sender, args) => 
    args.Password = passwordBox1.Password;

Di ViewModel , saat Anda membutuhkan kata sandi, Anda dapat menjalankan acara dan mengambil kata sandi dari sana:

if (HarvestPassword == null)
  //bah 
  return;

var pwargs = new HarvestPasswordEventArgs();
HarvestPassword(this, pwargs);

LoginHelpers.Login(Username, pwargs.Password);

Satu hal yang Anda lewatkan adalah ketika berlangganan tampilan ke acara model tampilan, Anda harus menggunakan a WeakEventManager<TEventSource, TEventArgs>untuk menghindari kebocoran memori. Seringkali tampilan tidak memiliki masa pakai yang sama dengan model tampilan. WeakEventManager<IViewModel, EventArgs>.AddHandler(iViewModelInstance, nameof(IViewModel.Event), eventHandlerMethod);
Todd A. Stedel

Saya lebih suka solusi ini, karena sederhana, tidak melanggar MVVM, memiliki kode minimal di belakang, memungkinkan penggunaan kata sandi yang benar (jika Anda menggunakan ´SecurePassword´ sebagai gantinya). Juga sederhana sekarang untuk mengimplementasikan metode HarvestPassword lainnya sekarang (seperti SmartCard ....)
Matt

8

Saya menghabiskan banyak waktu mencari berbagai solusi. Saya tidak suka ide dekorator, perilaku mengacaukan validasi UI, kode di belakang ... sungguh?

Yang terbaik adalah tetap berpegang pada properti terlampir kustom dan mengikat ke SecureStringproperti Anda dalam model tampilan Anda. Simpan di sana selama mungkin. Kapan pun Anda membutuhkan akses cepat ke kata sandi biasa, untuk sementara ubahlah ke string yang tidak aman menggunakan kode di bawah ini:

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

Pastikan Anda mengizinkan GC untuk mengumpulkan elemen UI Anda, jadi tahan keinginan menggunakan pengendali acara statis untuk PasswordChangedacara di PasswordBox. Saya juga menemukan anomali di mana kontrol tidak memperbarui UI ketika menggunakan SecurePasswordproperti untuk menyiapkannya, alasan mengapa saya menyalin kata sandi ke dalam Passwordgantinya.

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

Dan penggunaan XAML:

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

Properti saya dalam model tampilan tampak seperti ini:

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

Ini RequiredSecureStringhanya validator khusus sederhana yang memiliki logika berikut:

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

Ini dia. Solusi MVVM murni lengkap dan teruji.


7

Saya memasang GIST di sini yang merupakan kotak kata sandi yang dapat diikat.

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
sementara ini tidak buruk, Anda kehilangan kemampuan untuk menetapkan atribut sederhana seperti padding dan tabindex
Julien

1
Taylor, saya sebaris intinya sehingga tersedia dalam jawabannya. (Itu tampak seperti jawaban tautan saja, sebaliknya .. hanya mencoba untuk menghindari ini dihapus seperti itu.) Jangan ragu untuk mengacaukan dengan konten yang digariskan.
Lynn Crumbling

@ Julien tetapi Anda bisa memperbaikinya dengan gaya. Saya memecahkan masalah ini dengan cara yang sama tetapi saya menggunakan ContentControlAnda kemudian hanya dapat menggunakan PasswordBox sebagai konten dan gaya yang di XAML sesuai keinginan Anda. Tujuan dari ContentControlhanya untuk berlangganan PasswordChangedacara dan mengekspos properti bindable dua arah. Secara keseluruhan, ini adalah 65 baris kode dan cukup banyak apa yang kelas dekorasi ini lakukan. Lihat di sini untuk intisari saya dari gist.github.com/leidegre/c7343b8c720000fe3132
John Leidegren

6

Implementasi ini sedikit berbeda. Anda meneruskan kotak kata sandi ke View melalui pengikatan properti di ViewModel, itu tidak menggunakan perintah params. The ViewModel Tetap Mengabaikan Tampilan. Saya memiliki Proyek VB vs 2010 yang dapat diunduh dari SkyDrive. Wpf MvvM PassWordBox Example.zip https://skydrive.live.com/redir.aspx?cid=e95997d33a9f8d73&resid=E95997D33A9F8D73!511

Cara Saya Menggunakan Kata Sandi dalam Aplikasi Wpf MvvM cukup sederhana dan berfungsi dengan baik untuk Saya. Itu tidak berarti bahwa saya pikir itu adalah cara yang benar atau cara terbaik. Ini hanyalah implementasi dari Menggunakan Kata Sandi dan Pola MvvM.

Pada dasarnya, Anda membuat properti hanya baca publik yang dapat diikat oleh View sebagai Contoh Kotak Kata Sandi (Kontrol aktual):

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

Saya menggunakan bidang dukungan hanya untuk melakukan Inisialisasi properti sendiri.

Kemudian Dari Xaml Anda mengikat Konten ContentControl atau Contoh Kontainer Kontrol:

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

Dari sana Anda memiliki kontrol penuh terhadap kotak kata sandi. Saya juga menggunakan PasswordAccessor (Just a Function of String) untuk mengembalikan Nilai Kata Sandi saat melakukan login atau apa pun yang Anda inginkan untuk kata sandi. Dalam Contoh saya memiliki properti publik dalam Model Objek Pengguna Generik. Contoh:

Public Property PasswordAccessor() As Func(Of String)

Di Objek Pengguna, properti string kata sandi hanya dapat dibaca tanpa dukungan toko apa pun, hanya mengembalikan Kata Sandi dari Kotak Kata Sandi. Contoh:

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

Kemudian di ViewModel saya memastikan Accessor dibuat dan diatur ke properti PasswordBox.Password 'Contoh:

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

Ketika saya membutuhkan kata sandi string untuk login saya hanya mendapatkan properti Kata Sandi Objek Pengguna yang benar-benar memanggil Fungsi untuk mengambil kata sandi dan mengembalikannya, maka kata sandi yang sebenarnya tidak disimpan oleh Objek Pengguna. Contoh: akan berada di 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

Itu harus melakukannya. ViewModel tidak memerlukan pengetahuan tentang Kontrol Tampilan. View Just mengikat properti di ViewModel, tidak berbeda dengan View Binding ke Gambar atau Sumber Daya Lain. Dalam hal ini sumber daya (Properti) kebetulan menjadi kontrol pengguna. Ini memungkinkan untuk pengujian karena ViewModel menciptakan dan memiliki Properti dan Properti tidak tergantung pada View. Adapun Keamanan, saya tidak tahu seberapa baik implementasi ini. Tetapi dengan menggunakan Fungsi, Nilai tidak disimpan di Properti itu sendiri hanya diakses oleh Properti.


6

Untuk mengatasi masalah OP tanpa memecahkan MVVM, saya akan menggunakan konverter nilai kustom dan pembungkus untuk nilai (kata sandi) yang harus diambil dari kotak kata sandi.

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

Dalam model tampilan:

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

Karena model tampilan menggunakan IWrappedParameter<T>, tidak perlu memiliki pengetahuan tentang PasswordBoxWrapperatau tidak PasswordBoxConverter. Dengan cara ini Anda dapat mengisolasi PasswordBoxobjek dari model tampilan dan tidak merusak pola MVVM.

Dalam tampilan:

<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}}" />

solusi imo yang sangat elegan. Saya telah mendasarkan milik saya pada ini. satu-satunya perbedaan: saya meneruskan SecureString SecurePassword ke fungsi login alih-alih String Password. sehingga tidak ada string yang tidak terenkripsi dengan passwort yang terbang di sekitar memori.
panggil aku wortel

Sudah lama tetapi saya tidak bisa mendapatkan ini untuk bekerja karena RelayCommand saya. maukah kamu menambahkan milikmu?
Ecnerwal

5

Meskipun saya setuju bahwa penting untuk menghindari menyimpan kata sandi di mana saja, saya masih memerlukan kemampuan untuk membuat Instantiate model tampilan tanpa tampilan dan menjalankan pengujian saya terhadapnya.

Solusi yang berhasil bagi saya adalah mendaftarkan fungsi PasswordBox.Password dengan model tampilan, dan meminta model tampilan memintanya ketika menjalankan kode login.

Ini tidak berarti baris kode di codebehind pandangan ini.

Jadi, di Login.xaml saya miliki

<PasswordBox x:Name="PasswordBox"/>

dan di Login.xaml.cs saya miliki

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

kemudian di LoginViewModel.cs saya telah mendefinisikan PasswordHandler

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

dan ketika masuk perlu terjadi kode memanggil pawang untuk mendapatkan kata sandi dari tampilan ...

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

Dengan cara ini, ketika saya ingin menguji model tampilan, saya cukup mengatur PasswordHandler ke metode anonim yang memungkinkan saya memberikan kata sandi apa pun yang ingin saya gunakan dalam pengujian.


4

Saya pikir saya akan melempar solusi saya, karena ini adalah masalah umum ... dan memiliki banyak pilihan selalu merupakan hal yang baik.

Aku hanya dibungkus PasswordBoxdalam UserControldan menerapkan DependencyPropertyuntuk dapat mengikat. Saya melakukan semua yang saya bisa untuk menghindari penyimpanan teks yang jelas dalam memori, jadi semuanya dilakukan melalui a SecureStringdan PasswordBox.Passwordproperti. Selama foreachpengulangan, setiap karakter terpapar, tetapi sangat singkat. Jujur, jika Anda khawatir aplikasi WPF Anda akan dikompromikan dari paparan singkat ini, Anda punya masalah keamanan yang lebih besar yang harus ditangani.

Keindahan dari ini adalah bahwa Anda tidak melanggar aturan MVVM, bahkan yang "purist", karena ini adalah UserControl, jadi diizinkan untuk memiliki kode-belakang. Saat Anda menggunakannya, Anda dapat memiliki komunikasi murni antara Viewdan ViewModeltanpa Anda VideModelsadari bagian Viewatau sumber kata sandi. Pastikan Anda mengikat SecureStringdi 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 (Versi 1 - Tidak ada dukungan mengikat dua arah.)

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

Penggunaan Versi 1:

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

BindablePasswordBox.xaml.cs (Versi 2 - Memiliki dukungan pengikatan dua arah.)

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

Penggunaan Versi 2:

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

Saya sudah mencoba mengimplementasikan ini, tetapi Anda mendapatkan loop tak terbatas ketika Anda memperbarui kata sandi pada UI; karena if (Password != secure)akan selalu salah karena SecureString tidak menimpa sama dengan. Adakah pikiran?
simonalexander2005


2

Saya menggunakan metode ini dan melewati kotak kata sandi, meskipun ini melanggar MVVM itu penting bagi saya karena saya menggunakan kontrol konten dengan templat data untuk login saya di dalam shell saya yang merupakan lingkungan shell yang kompleks. Jadi mengakses kode di balik shell pasti omong kosong.

Melewati kotak kata sandi yang menurut saya sama dengan mengakses kontrol dari kode di belakang sejauh yang saya tahu. Saya setuju kata sandi, jangan simpan di memori dll Dalam implementasi ini saya tidak memiliki properti untuk kata sandi dalam model tampilan.

Tombol perintah

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

Ini jelas merupakan pelanggaran pola MVVM. Pola tidak memungkinkan untuk menangani kontrol dalam model tampilan.
BionicCode

2

Bagi saya, kedua hal ini terasa salah:

  • Menerapkan properti kata sandi teks yang jelas
  • Mengirim PasswordBoxparameter perintah ke ViewModel

Mentransfer SecurePassword (instance SecureString) seperti yang dijelaskan oleh Steve dalam CO tampaknya dapat diterima. saya lebih memilihBehaviors kode di belakang, dan saya juga memiliki persyaratan tambahan untuk dapat mengatur ulang kata sandi dari viewmodel.

Xaml ( Passwordadalah properti ViewModel):

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

Tingkah laku:

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

Untuk pemula yang lengkap seperti saya, berikut adalah contoh kerja lengkap dari apa yang Konamimandisarankan di atas. Terima kasih 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);
    }
}

Ini jelas merupakan pelanggaran pola MVVM. Pola tidak memungkinkan untuk menangani kontrol dalam model tampilan.
BionicCode

1

Seperti yang Anda lihat saya mengikat kata sandi, tapi mungkin itu mengikatnya ke kelas statis ..

Ini adalah properti terlampir . Jenis properti ini dapat diterapkan untuk jenis apa pun DependencyObject, bukan hanya tipe yang dinyatakan. Jadi meskipun dideklarasikan di PasswordHelperkelas statis, itu diterapkan PasswordBoxpada yang Anda gunakan.

Untuk menggunakan properti terlampir ini, Anda hanya perlu mengikatnya ke Passwordproperti di ViewModel Anda:

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

1

Saya telah melakukan seperti:

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

Ini bekerja untuk saya!


Anda memberi saya ide yang bagus. :)
Andre Mendonca

1

Seperti yang disebutkan sebelumnya, VM seharusnya tidak mengetahui tentang View tetapi melewatkan seluruh PasswordBox sepertinya pendekatan yang paling sederhana. Jadi mungkin alih-alih casting parameter yang diteruskan ke PasswordBox menggunakan Reflection untuk mengekstrak properti Password darinya. Dalam hal ini VM mengharapkan semacam Kontainer Kata Sandi dengan kata sandi properti (Saya menggunakan RelayCommands dari 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;
    }
}

Itu dapat dengan mudah diuji dengan kelas anonim:

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

Komentar bukan untuk diskusi panjang; percakapan ini telah dipindahkan ke obrolan .
Samuel Liew

1

Di windows universal app

Anda dapat menggunakan kode ini dengan properti "Kata Sandi" dan mengikat dengan modelView

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


1

Bagi siapa pun yang mengetahui risiko penerapan ini, untuk memiliki sinkronisasi kata sandi ke ViewModel Anda cukup tambahkan Mode = OneWayToSource .

XAML

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

Kenapa tidak lakukan saja OneWayToSource?
BK

@ BK Mengedit jawaban saya. Terima kasih.
Kevin

1
bukankah seharusnya Mode berada di dalam kurung kurawal?
Mat

@Mat Yap. Terima kasih.
Kevin

1

Inilah pendapat saya:

  1. Menggunakan properti terlampir untuk mengikat kata sandi mengalahkan tujuan mengamankan kata sandi. Properti Kata Sandi dari kotak kata sandi tidak dapat diikat karena suatu alasan.

  2. Melewati kotak kata sandi sebagai parameter perintah akan membuat ViewModel mengetahui kontrolnya. Ini tidak akan berfungsi jika Anda berencana untuk membuat lintas platform ViewModel Anda dapat digunakan kembali. Jangan buat VM Anda tahu tentang View Anda atau kontrol lainnya.

  3. Saya tidak berpikir memperkenalkan properti baru, antarmuka, berlangganan acara yang diubah kata sandi atau hal rumit lainnya diperlukan untuk tugas sederhana memberikan kata sandi.

XAML

<PasswordBox x:Name="pbPassword" />
<Button Content="Login" Command="{Binding LoginCommand}" x:Name="btnLogin"/>

Kode di belakang - menggunakan kode di belakang tidak selalu melanggar MVVM. Selama Anda tidak memasukkan logika bisnis di dalamnya.

btnLogin.CommandParameter = new Func<string>(()=>pbPassword.Password); 

ViewModel

LoginCommand = new RelayCommand<Func<string>>(getpwd=> { service.Login(username, getpwd()); });

0

Anda menemukan solusi untuk PasswordBox di aplikasi sampel ViewModel dari proyek WPF Application Framework (WAF) .

Namun, Justin benar. Jangan berikan kata sandi sebagai teks biasa antara View dan ViewModel. Gunakan SecureString sebagai gantinya (Lihat MSDN PasswordBox).


2
Cara yang digunakan dalam Pop3SettingsView of WAF lucu. PasswordBox passwordBox = (PasswordBox) pengirim; if (ViewModel! = null) {ViewModel.Pop3Password = passwordBox.Password; } Pop3Password of ViewModel adalah properti string. jadi, itu tidak aman juga .. lebih baik menggunakan properti terlampir
Michael Sync

0

Saya menggunakan pemeriksaan otentikasi diikuti oleh sub yang dipanggil oleh kelas mediator ke View (yang juga mengimplementasikan pemeriksaan otentikasi) untuk menulis kata sandi ke kelas data.

Itu bukan solusi yang sempurna; Namun, itu memperbaiki masalah saya karena tidak bisa memindahkan kata sandi.


0

Saya menggunakan solusi ramah-MVVM ringkas yang belum disebutkan. Pertama, saya beri nama PasswordBox di XAML:

<PasswordBox x:Name="Password" />

Lalu saya menambahkan pemanggilan metode tunggal ke dalam konstruktor tampilan:

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

Dan itu saja. Model tampilan akan menerima notifikasi ketika dilampirkan ke tampilan melalui DataContext dan notifikasi lain ketika dilepaskan. Isi pemberitahuan ini dapat dikonfigurasi melalui lambda, tetapi biasanya itu hanya setter atau metode panggilan pada model tampilan, melewati kontrol yang bermasalah sebagai parameter.

Itu dapat dibuat ramah-MVVM dengan sangat mudah dengan memiliki tampilan mengekspos antarmuka bukan kontrol anak.

Kode di atas bergantung pada kelas pembantu yang dipublikasikan di blog saya.


0

Saya menghabiskan waktu lama untuk mencoba ini bekerja. Pada akhirnya, saya menyerah dan hanya menggunakan PasswordBoxEdit dari DevExpress.

Ini adalah solusi paling sederhana yang pernah ada, karena memungkinkan pengikatan tanpa menarik trik yang mengerikan.

Solusi di situs web DevExpress

Sebagai catatan, saya tidak berafiliasi dengan DevExpress dengan cara apa pun.


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

;) mudah!


0

Sangat sederhana. Buat properti lain untuk kata sandi dan ikat ini dengan TextBox

Tetapi semua operasi input bekerja dengan properti kata sandi yang sebenarnya

private string _Password;

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

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

            return szChar;
        }

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

public string Password {get {return _Password; }

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


Alasan kotak kata sandi tidak dapat diikat adalah karena kami tidak ingin menyimpan kata sandi dalam string yang jelas. String tidak dapat diubah dan kami tidak yakin berapa lama akan tersimpan di memori.
Lance

0

baik jawabanku lebih sederhana hanya untuk pola MVVM

di viewmodel kelas

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;

}

properti kata sandi dari PasswordBox yang menang menyediakan atau WatermarkPasswordBox yang disediakan oleh XCeedtoolkit menghasilkan RoutedEventArgs sehingga Anda dapat mengikatnya.

sekarang dalam tampilan 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>

atau

<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

Kirim SecureStringke model tampilan menggunakan Perilaku Terlampir danICommand

Tidak ada yang salah dengan kode-belakang ketika menerapkan MVVM. MVVM adalah pola arsitektur yang bertujuan untuk memisahkan pandangan dari model / logika bisnis. MVVM menjelaskan cara mencapai tujuan ini dengan cara yang dapat direproduksi (polanya). Itu tidak peduli tentang detail implementasi, seperti bagaimana Anda menyusun atau mengimplementasikan tampilan. Ini hanya menggambar batas-batas dan mendefinisikan apa pandangan, model tampilan dan apa model dalam hal terminologi pola ini.

MVVM tidak peduli dengan bahasa (XAML atau C #) atau kompiler (partial kelas). Kemandirian bahasa adalah karakteristik wajib dari pola desain - bahasa harus netral.

Namun, kode-belakang memiliki beberapa kelemahan seperti membuat logika UI Anda lebih sulit untuk dipahami, ketika didistribusikan secara liar antara XAML dan C #. Tetapi yang paling penting mengimplementasikan logika atau objek UI seperti templat, gaya, pemicu, animasi dll dalam C # sangat kompleks dan jelek / kurang dapat dibaca daripada menggunakan XAML. XAML adalah bahasa markup yang menggunakan tag dan bersarang untuk memvisualisasikan hierarki objek. Membuat UI menggunakan XAML sangat mudah. Meskipun ada situasi di mana Anda baik-baik saja memilih untuk mengimplementasikan logika UI di C # (atau kode-belakang). Menangani PasswordBoxadalah salah satu contohnya.

Untuk alasan ini, menangani PasswordBoxkode di belakang dengan menangani PasswordBox.PasswordChanged, bukan merupakan pelanggaran pola MVVM.

Pelanggaran yang jelas adalah untuk melewatkan kontrol (the PasswordBox) ke model tampilan. Banyak solusi merekomendasikan hal ini, misalnya, melewati instance dari PasswordBoxasICommand.CommandParameter untuk model tampilan. Jelas rekomendasi yang sangat buruk dan tidak perlu.

Jika Anda tidak peduli tentang menggunakan C #, tetapi hanya ingin menjaga file di belakang kode Anda tetap bersih atau hanya ingin merangkum logika perilaku / UI, Anda selalu dapat menggunakan properti terlampir dan menerapkan perilaku terlampir.

Diseberang dari helper penyebaran luas terkenal yang memungkinkan pengikatan kata sandi teks biasa (anti-pola yang sangat buruk dan risiko keamanan), perilaku ini menggunakan a ICommanduntuk mengirim kata sandi SecureStringpada model tampilan, kapan pun PasswordBoxmemunculkan PasswordBox.PasswordChangedperistiwa.

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);
  }
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.