Come associare un PasswordBox in MVVM


251

Mi sono imbattuto in un problema con il legame ad un P asswordBox. Sembra che sia un rischio per la sicurezza, ma sto usando il modello MVVM, quindi desidero bypassarlo. Ho trovato un codice interessante qui (qualcuno ha usato questo o qualcosa di simile?)

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

Tecnicamente sembra fantastico, ma non sono sicuro di come recuperare la password.

Fondamentalmente ho proprietà nel mio LoginViewModelper Usernamee Password. Usernameva bene e funziona come è a TextBox.

Ho usato il codice sopra come indicato e inserito questo

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

Quando ho avuto PasswordBoxcome a TextBoxe Binding Path=Passwordquindi la proprietà nella mia è LoginViewModelstata aggiornata.

Il mio codice è molto semplice, in pratica ne ho uno Commandper il mio Button. Quando premo CanLoginviene chiamato e se ritorna vero chiama Login.
Puoi vedere che controllo la mia proprietà Usernamequi che funziona alla grande.

In Logininvio al mio servizio a Usernamee Password, Usernamecontiene i dati dal mio Viewma lo 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 { }
}

Questo è quello che sto facendo

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

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

Io ho la mia TextBox, questo non è un problema, ma nella mia ViewModell' Passwordè vuoto.

Sto facendo qualcosa di sbagliato o mi manca un passo?

Ho inserito un punto di interruzione e sicuramente il codice entra nella classe helper statica ma non aggiorna mai il mio Passwordnel mio ViewModel.


3
Bene, risulta che il codice non ha funzionato, ma ho provato un codice alternativo qui e funziona perfettamente. blog.functionalfun.net/2008/06/…
mark smith,

5
Il passaggio dell'intero controllo passwordbox non impedisce di separare la vista dal viewmodel?

Risposte:


164

Scusa, ma stai sbagliando.

Le persone dovrebbero avere le seguenti linee guida di sicurezza tatuate all'interno delle palpebre:
Non conservare mai password di testo semplice in memoria.

Il motivo per cui WPF / Silverlight PasswordBoxnon espone un DP per la Passwordproprietà è legato alla sicurezza.
Se WPF / Silverlight dovesse mantenere un DP perché Passwordrichiederebbe al framework di mantenere la password stessa non crittografata in memoria. Che è considerato un vettore di attacco di sicurezza piuttosto problematico. L' PasswordBoxuso crittografata della memoria (di sorta), e l'unico modo per accedere alla password è attraverso la proprietà CLR.

Vorrei suggerire che quando si accede alla PasswordBox.Passwordproprietà CLR ti asterrai dal posizionarla in qualsiasi variabile o come valore per qualsiasi proprietà.
Mantenere la password in testo semplice sulla RAM della macchina client è un no-no di sicurezza.
Quindi liberati di quello public string Password { get; set; }che hai lassù.

Quando si accede PasswordBox.Password, basta estrarlo e spedirlo al server al più presto. Non mantenere il valore della password e non trattarlo come faresti con qualsiasi altro testo macchina client. Non conservare in chiaro password di testo.

So che questo interrompe il modello MVVM, ma non dovresti mai collegarti ad PasswordBox.PasswordAttached DP, memorizzare la tua password in ViewModel o altri shenanigans simili.

Se stai cercando una soluzione con architettura eccessiva, eccone una:
1. Crea l' IHavePasswordinterfaccia con un metodo che restituisca il testo in chiaro della password.
2. Avere la tua UserControlimplementazione di IHavePasswordun'interfaccia.
3. Registrare l' UserControlistanza con l'IoC come implementazione IHavePassworddell'interfaccia.
4. Quando si verifica una richiesta del server che richiede la password, chiamare l'IoC per l' IHavePasswordimplementazione e solo per ottenere la tanto ambita password.

Solo la mia opinione su di esso.

- Justin


19
Non è possibile utilizzare SecureString nella VM per WPF per risolvere questo problema? Non sembra che ci sia qualcosa per Silverlight.
Bryant,

35
Sono d'accordo con la tua intenzione e il messaggio che stai trasmettendo, ma la tua risposta implica che la stringa della password non è mai in memoria se segui questo approccio. Il valore della password sarà in memoria dal momento in cui l'utente la digita. Eliminare la proprietà che detiene la passphrase è una buona idea e limiterà le copie della password che vengono lasciate in giro per il garbage collector o che potrebbero essere trovate da altri codici gestiti e non gestiti in esecuzione come parte del programma, ma non nasconderlo del tutto.
IanNorton,

182
Nella maggior parte dei casi, non è necessario quel livello di sicurezza. Che senso ha rendere difficile quella cosa quando ci sono molti altri modi per rubare le password? Atleast WPF avrebbe dovuto consentire l'uso di SecureString come ha detto @Bryant.
Chakrit,

335
Se i cattivi hanno accesso alla RAM della tua macchina, hai problemi più grandi di loro che rubano la tua password.
Cameron MacFarland,

13
Per anni, ho usato un controllo utente personalizzato che si comporta proprio come PasswordBox, ma restituisce solo il valore di testo come SecureString. Sì, questo impedisce a Snoop di visualizzare la password in testo normale. Tuttavia, il valore del testo in chiaro di SecureString può ancora essere estratto abbastanza facilmente e scoraggia solo gli hack per principianti. Se il tuo sistema è a rischio di utilizzare segretamente key logger e sniffer come Snoop, dovresti rivalutare la sicurezza del tuo sistema.
Mike Christian,

199

I miei 2 centesimi:

Ho sviluppato una volta una tipica finestra di login (caselle utente e password, oltre al pulsante "Ok") usando WPF e MVVM. Ho risolto il problema di associazione password semplicemente passando il controllo PasswordBox stesso come parametro al comando associato al pulsante "Ok". Quindi nella vista avevo:

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

E in ViewModel, il Executemetodo del comando allegato era il seguente:

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

Ciò viola leggermente il modello MVVM poiché ora ViewModel sa qualcosa su come viene implementata la vista, ma in quel particolare progetto potrei permettermelo. Spero che sia utile anche per qualcuno.


Ciao Konamiman, quando viene chiamato il metodo Execute. Nel mio modello di vista ho una classe User (login, pass) e un comando di autenticazione. Come posso usare Execute in quel contesto?

3
molto utile, grazie. a proposito, qualcuno potrebbe essere abituato a vedere qualcosa come _loginCommand = new RelayCommand (param => Login (UserName, (PasswordBox) param), param => CanLogIn);
Chuck Rostance,

5
questa è una soluzione ok ma fallisce per qualcosa come una combinazione password + password conferma
Julien

Ciao Konamiman, sto usando la tua soluzione ma non funziona sull'app Store di Windows 8.1. Ho fatto questa domanda: stackoverflow.com/questions/26221594/...
VansFannel

2
Grazie per questo! Ciò ha risolto un enorme problema che ho avuto con lo spostamento dei dati dal thread dell'interfaccia utente al thread del programma principale. Assicurati di implementare l'approccio SecureString e ~ sbarazzati della password il prima possibile ~. Gettalo via. Smaltiscilo. Cancella. Fai quello che devi fare. Inoltre, assicurati di implementare IDisposable.
Steven C. Britton,

184

Forse mi manca qualcosa, ma sembra che la maggior parte di queste soluzioni complicano le cose e eliminano le pratiche sicure.

Questo metodo non viola il modello MVVM e mantiene la sicurezza completa. Sì, tecnicamente è il codice dietro, ma non è altro che un'associazione "caso speciale". ViewModel non ha ancora alcuna conoscenza dell'implementazione di View, cosa che a mio avviso accade se stai cercando di passare PasswordBox a ViewModel.

Code Behind! = Violazione automatica MVVM. Tutto dipende da cosa ci fai. In questo caso, stiamo semplicemente codificando manualmente un'associazione, quindi è tutto considerato parte dell'implementazione dell'interfaccia utente e quindi è ok.

Nel ViewModel, solo una proprietà semplice. L'ho fatto "solo scrittura" poiché non dovrebbe essere necessario recuperarlo dall'esterno del ViewModel per nessun motivo, ma non è necessario. Nota che è un SecureString, non solo una stringa.

public SecureString SecurePassword { private get; set; }

In xaml, hai impostato un gestore eventi PasswordChanged.

<PasswordBox PasswordChanged="PasswordBox_PasswordChanged"/>

Nel codice dietro:

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

Con questo metodo, la tua password rimane sempre in un SecureString e quindi offre la massima sicurezza. Se davvero non ti interessa la sicurezza o hai bisogno della password in chiaro per un metodo downstream che lo richiede (nota: la maggior parte dei metodi .NET che richiedono una password supportano anche un'opzione SecureString, quindi potresti non aver davvero bisogno di una password in chiaro anche se pensi di farlo), puoi semplicemente usare la proprietà Password. Come questo:

(Proprietà ViewModel)

public string Password { private get; set; }

(Codice dietro)

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

Se si desidera mantenere le cose fortemente tipizzate, è possibile sostituire il cast (dinamico) con l'interfaccia del ViewModel. Ma in realtà, neanche i "normali" binding di dati sono fortemente tipizzati, quindi non è un grosso problema.

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

Quindi, il migliore di tutti i mondi: la tua password è sicura, ViewModel ha solo una proprietà come qualsiasi altra proprietà e View è autonomo senza riferimenti esterni richiesti.


1
Questo mi sta bene! Se volessi essere super severo dal punto di vista della sicurezza, non sono sicuro che ciò lo taglierebbe, ma per me è una via di mezzo perfetta. Grazie!
jrich523,

3
Grazie per la praticità rispetto ai rigidi dogmi su MVVM e paranoia. Funziona benissimo, grazie.
Bruce Pierson,

2
L'esempio SecureString sarebbe fantastico con questa estensione blogs.msdn.com/b/fpintos/archive/2009/06/12/…
Ayman,

1
Davvero bello. Vorrei che MS avesse appena aggiunto una password DP di tipo SecureString a questo controllo.
Keith Hill,

1
Questa è la risposta perfetta, poiché mantiene la sicurezza e MVVM.
LoRdPMN,

20

Puoi usare questo 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>

E questo comando esegue il metodo:

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

3
Cordiali salutixmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
XAMlMAX,

Senza richiedere di assegnare un nome a PasswordBox: CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=PasswordBox}}"(nota: non RelativeSource Self ).
Wondra,

Questa soluzione viola il modello MVVM.
BionicCode

13

Questo funziona bene per me.

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

3
Che dire di CommandParameter = "{Binding ElementName = MyPasswordBox, Path = SecurePassword"}?
Luca,

2
LukeN, questo non funziona (almeno per me). Probabilmente per lo stesso motivo: SecurePassword non è proprietà di dipendenza.
vkrzv,

Supponendo che ICommandsia implementato nel modello di visualizzazione, questa soluzione violerebbe il modello MVVM.
BionicCode

9

Una soluzione semplice senza violare il modello MVVM è introdurre un evento (o delegare) nel ViewModel che raccoglie la password.

Nel ViewModel :

public event EventHandler<HarvestPasswordEventArgs> HarvestPassword;

con questi EventArgs:

class HarvestPasswordEventArgs : EventArgs
{
    public string Password;
}

nella vista , iscriviti all'evento durante la creazione del ViewModel e inserisci il valore della password.

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

Nel ViewModel , quando hai bisogno della password, puoi attivare l'evento e raccogliere la password da lì:

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

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

LoginHelpers.Login(Username, pwargs.Password);

L'unica cosa che manca è che quando si sottoscrive una vista a un evento del modello di vista, è necessario utilizzare a WeakEventManager<TEventSource, TEventArgs>per evitare perdite di memoria. Spesso la vista non avrà la stessa durata del modello di visualizzazione. WeakEventManager<IViewModel, EventArgs>.AddHandler(iViewModelInstance, nameof(IViewModel.Event), eventHandlerMethod);
Todd A. Stedel,

Preferisco questa soluzione, dal momento che è semplice, non viola MVVM, ha un codice minimo dietro, consente il corretto utilizzo di passwordbox (se si utilizza invece "Password sicura"). Inoltre ora è semplice implementare altri metodi HarvestPassword ora (come SmartCard ....)
Matt

8

Ho trascorso molto tempo a cercare varie soluzioni. Non mi piaceva l'idea dei decoratori, i comportamenti incasinano l'interfaccia utente di convalida, il codice dietro ... davvero?

La migliore ancora è attenersi a una proprietà collegata personalizzata e associarla alla propria SecureStringproprietà nel modello di visualizzazione. Tienilo lì il più a lungo possibile. Ogni volta che avrai bisogno di un rapido accesso alla semplice password, convertila temporaneamente in una stringa non sicura usando il codice qui sotto:

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

Assicurati di consentire al GC di raccogliere l'elemento dell'interfaccia utente, quindi resisti all'impulso di utilizzare un gestore di eventi statico per l' PasswordChangedevento sul PasswordBox. Ho anche scoperto un'anomalia in cui il controllo non stava aggiornando l'interfaccia utente quando si utilizzava la SecurePasswordproprietà per configurarla, motivo per cui sto copiando la password 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;
                }
            }
        }
    }
}

E l'uso di XAML:

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

La mia proprietà nel modello di visualizzazione era simile alla seguente:

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

Il RequiredSecureStringè solo un semplice validatore personalizzato che ha la seguente logica:

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

Ecco qui. Una soluzione MVVM pura completa e testata.


7

Ho pubblicato un GIST qui che è una casella di password associabile.

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
mentre questo non è male, perdi la possibilità di impostare attributi semplici come padding e tabindex
Julien,

1
Taylor, ho sottolineato l'essenza in modo che sia disponibile nella risposta. (Sembrava una risposta solo per il collegamento altrimenti .. sto solo cercando di evitare che questo venga eliminato come tale.) Sentiti libero di pasticciare con il contenuto incorporato.
Lynn che si sbriciola il

@Julien ma puoi sistemarlo con gli stili. Risolvo questo problema in un modo simile, ma io uso un ContentControl, quindi puoi semplicemente usare una PasswordBox come contenuto e stile che in XAML si adatta. Lo scopo del ContentControlè solo quello di iscriversi PasswordChangedall'evento ed esporre una proprietà vincolante bidirezionale. Tutto sommato, sono 65 righe di codice e praticamente ciò che fa questa classe di decorare. Vedi qui per il mio riassunto
John Leidegren,

6

Questa implementazione è leggermente diversa. Si passa una password alla vista attraverso l'associazione di una proprietà in ViewModel, non utilizza alcun parametro di comando. ViewModel rimane ignaro della vista. Ho un progetto VB vs 2010 che può essere scaricato da SkyDrive. Wpf MvvM PassWordBox Example.zip https://skydrive.live.com/redir.aspx?cid=e95997d33a9f8d73&resid=E95997D33A9F8D73!511

Il modo in cui sto usando PasswordBox in un'applicazione WPF MvvM è piuttosto semplicistico e funziona bene per me. Ciò non significa che penso che sia il modo giusto o il modo migliore. È solo un'implementazione di Utilizzo di PasswordBox e MvvM Pattern.

Fondamentalmente si crea una proprietà di sola lettura pubblica che la vista può associare come PasswordBox (il controllo effettivo) Esempio:

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

Uso un campo di supporto solo per eseguire l'inizializzazione automatica della proprietà.

Quindi da Xaml si collega il contenuto di un ContentControl o un esempio di contenitore di controllo:

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

Da lì hai il pieno controllo della passwordbox Anche io uso un PasswordAccessor (solo una funzione di stringa) per restituire il valore della password quando si effettua l'accesso o qualsiasi altra cosa per cui si desidera la password. Nell'esempio ho una proprietà pubblica in un modello di oggetto utente generico. Esempio:

Public Property PasswordAccessor() As Func(Of String)

Nell'oggetto utente la proprietà della stringa di password è di sola lettura senza alcun archivio di backup, ma restituisce semplicemente la password da PasswordBox. Esempio:

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

Quindi nel ViewModel mi assicuro che l'Accessor sia creato e impostato sulla proprietà PasswordBox.Password 'Esempio:

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

Quando ho bisogno della stringa Password per accedere, ottengo solo la proprietà Password oggetti utente che invoca davvero la funzione per afferrare la password e restituirla, quindi la password effettiva non viene memorizzata dall'oggetto utente. Esempio: sarebbe nel 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

Questo dovrebbe farlo. ViewModel non necessita di alcuna conoscenza dei controlli di View. View Just si lega alla proprietà nel ViewModel, non diverso dal View Binding a un'immagine o altra risorsa. In questo caso quella risorsa (Proprietà) sembra essere solo un controllo utente. Permette di eseguire test mentre ViewModel crea e possiede la proprietà e la proprietà è indipendente dalla vista. Per quanto riguarda la sicurezza, non so quanto sia valida questa implementazione. Ma usando una funzione il valore non viene memorizzato nella proprietà stessa appena acceduta dalla proprietà.


6

Per risolvere il problema OP senza interrompere MVVM, utilizzerei un convertitore di valori personalizzato e un wrapper per il valore (la password) che deve essere recuperato dalla casella della password.

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

Nel modello di visualizzazione:

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

Poiché il modello di visualizzazione utilizza IWrappedParameter<T>, non è necessario avere alcuna conoscenza di PasswordBoxWrapperPasswordBoxConverter. In questo modo è possibile isolare l' PasswordBoxoggetto dal modello di visualizzazione e non interrompere il modello MVVM.

Nella vista:

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

soluzione molto elegante imo. ho basato il mio su questo. l'unica differenza: passo SecureString SecurePassword alla funzione di accesso anziché a Password stringa. in modo che non ci siano stringhe non crittografate con passwort che volano intorno alla memoria.
Chiamami carota,

È passato un po 'di tempo ma non riesco a farlo funzionare a causa del mio RelayCommand. ti dispiacerebbe aggiungere il tuo?
Ecnerwal,

5

Sebbene sia d'accordo che è importante evitare di archiviare la password ovunque, ho ancora bisogno della possibilità di creare un'istanza del modello di vista senza vista ed eseguire i miei test contro di essa.

La soluzione che ha funzionato per me è stata quella di registrare la funzione PasswordBox.Password con il modello di visualizzazione e fare in modo che il modello di visualizzazione lo invocasse durante l'esecuzione del codice di accesso.

Questo fa significare una riga di codice nel codebehind della vista.

Quindi, nel mio Login.xaml ho

<PasswordBox x:Name="PasswordBox"/>

e in Login.xaml.cs che ho

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

quindi in LoginViewModel.cs ho definito PasswordHandler

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

e quando è necessario effettuare l'accesso, il codice richiama il gestore per ottenere la password dalla vista ...

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

In questo modo, quando voglio testare il modello di visualizzazione, posso semplicemente impostare PasswordHandler su un metodo anonimo che mi consente di fornire qualsiasi password che voglio usare nel test.


4

Ho pensato di buttare la mia soluzione nel mix, dal momento che questo è un problema così comune ... e avere un sacco di opzioni è sempre una buona cosa.

Ho semplicemente inserito a PasswordBoxin a UserControle implementato a DependencyPropertyper essere in grado di legare. Sto facendo tutto il possibile per evitare di archiviare qualsiasi testo in chiaro nella memoria, quindi tutto viene eseguito tramite a SecureStringe la PasswordBox.Passwordproprietà. Durante il foreachciclo, ogni personaggio viene esposto, ma è molto breve. Onestamente, se sei preoccupato che la tua applicazione WPF sia compromessa da questa breve esposizione, hai maggiori problemi di sicurezza che dovrebbero essere gestiti.

Il bello di questo è che non stai infrangendo alcuna regola MVVM, nemmeno quelle "puriste", dato che questo è un UserControl, quindi è permesso avere code-behind. Quando lo usi, puoi avere una comunicazione pura tra Viewe ViewModelsenza che tu ne VideModelsia consapevoleView o dell'origine della password. Assicurati solo di essere vincolanteSecureString nel tuo 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 (Versione 1 - Nessun supporto di associazione bidirezionale.)

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

Utilizzo della versione 1:

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

BindablePasswordBox.xaml.cs (Versione 2 - Ha il supporto di associazione bidirezionale.)

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

Utilizzo della versione 2:

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

Ho provato a implementarlo, ma ottieni un ciclo infinito quando aggiorni la password sull'interfaccia utente; perché if (Password != secure)sarà sempre falso in quanto SecureString non sostituisce uguale a. qualche idea?
simonalexander2005,


2

Ho usato questo metodo e ho passato la casella della password, anche se questo viola il MVVM, per me era essenziale perché stavo usando un controllo del contenuto con un modello di dati per il mio login all'interno della mia shell che è un ambiente complesso della shell. Quindi l'accesso al codice dietro la shell sarebbe stato una schifezza.

Passare la passwordbox penso che equivale ad accedere al controllo dal codice dietro per quanto ne so. Accetto le password, non conservarle in memoria, ecc. In questa implementazione non ho proprietà per la password nel modello di visualizzazione.

Comando pulsante

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

Questa è una chiara violazione del modello MVVM. Il modello non consente di gestire i controlli nel modello di visualizzazione.
BionicCode

2

Per me, entrambe queste cose sembrano sbagliate:

  • Implementazione delle proprietà della password in chiaro
  • Invio del PasswordBoxparametro come comando a ViewModel

Il trasferimento di SecurePassword (istanza SecureString) come descritto da Steve in CO sembra accettabile. Preferisco Behaviorsprogrammare dietro, e ho anche avuto il requisito aggiuntivo di poter reimpostare la password dal modello di visualizzazione.

Xaml ( Passwordè la proprietà ViewModel):

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

Comportamento:

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

Per i neofiti completi come me, ecco un esempio di lavoro completo di quanto Konamimansuggerito sopra. Grazie 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);
    }
}

Questa è una chiara violazione del modello MVVM. Il modello non consente di gestire i controlli nel modello di visualizzazione.
BionicCode

1

Come puoi vedere, sto vincolando la password, ma forse la lega alla classe statica.

È una proprietà collegata . Questo tipo di proprietà può essere applicato a qualsiasi tipo di DependencyObject, non solo al tipo in cui viene dichiarato. Quindi, anche se è dichiarato nella PasswordHelperclasse statica, viene applicato aPasswordBox su cui lo si utilizza.

Per utilizzare questa proprietà collegata, è sufficiente associarla alla Passwordproprietà nel ViewModel:

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

1

Ho fatto come:

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

Per me funziona!


Mi dai una bella idea. :)
Andre Mendonca,

1

Come accennato in precedenza, la VM non dovrebbe essere a conoscenza della vista, ma passare l'intera PasswordBox sembra l'approccio più semplice. Quindi forse invece di trasmettere il parametro passato a PasswordBox, utilizzare Reflection per estrarre la proprietà Password da esso. In questo caso VM si aspetta un tipo di contenitore password con proprietà Password (sto usando RelayCommands da 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;
    }
}

Può essere facilmente testato con classe anonima:

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

I commenti non sono per una discussione estesa; questa conversazione è stata spostata in chat .
Samuel Liew

1

Nell'app universale di Windows

puoi usare questo codice con la proprietà "Password" e associarlo a modelView

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


1

Per chiunque sia a conoscenza dei rischi che questa implementazione comporta, per sincronizzare la password con ViewModel è sufficiente aggiungere Mode = OneWayToSource .

XAML

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

Perché non farlo OneWayToSource?
BK,

@BK Modificata la mia risposta. Grazie.
Kevin,

1
Mode non dovrebbe trovarsi tra le parentesi graffe?
Mat

@Mat Yap. Grazie.
Kevin,

1

Ecco la mia opinione su di esso:

  1. L'uso di una proprietà collegata per associare la password vanifica lo scopo di proteggere la password. La proprietà Password di una casella password non è associabile per un motivo.

  2. Passare la casella della password come parametro di comando renderà ViewModel consapevole del controllo. Questo non funzionerà se si prevede di rendere riutilizzabile la multipiattaforma ViewModel. Non rendere la tua VM consapevole della tua Vista o di altri controlli.

  3. Non credo che l'introduzione di una nuova proprietà, un'interfaccia, l'abbonamento a eventi modificati con password o qualsiasi altra cosa complicata sia necessaria per un semplice compito di fornire la password.

XAML

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

Codice dietro: l'utilizzo del codice dietro non viola necessariamente MVVM. Finché non ci metti alcuna logica aziendale.

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

ViewModel

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

0

È possibile trovare una soluzione per PasswordBox nell'applicazione di esempio ViewModel di WPF Application Framework (WAF) .

Tuttavia, Justin ha ragione. Non passare la password come testo semplice tra View e ViewModel. Utilizzare invece SecureString (vedere MSDN PasswordBox).


2
Il modo in cui viene utilizzato Pop3SettingsView di WAF è divertente. PasswordBox passwordBox = mittente (PasswordBox); if (ViewModel! = null) {ViewModel.Pop3Password = passwordBox.Password; } Pop3Password di ViewModel è la proprietà della stringa. quindi, non è altrettanto sicuro .. meglio usare la proprietà allegata
Michael Sync,

0

Ho usato un controllo di autenticazione seguito da un sottotitolo chiamato da una classe mediatrice alla vista (che implementa anche un controllo di autenticazione) per scrivere la password nella classe di dati.

Non è una soluzione perfetta; tuttavia, ha risolto il mio problema di non riuscire a spostare la password.


0

Sto usando una soluzione concisa per MVVM che non è stata ancora menzionata. Per prima cosa, chiamo PasswordBox in XAML:

<PasswordBox x:Name="Password" />

Quindi aggiungo una singola chiamata al metodo nella vista costruttore:

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

E questo è tutto. Il modello di vista riceverà una notifica quando è collegato a una vista tramite DataContext e un'altra notifica quando è staccato. Il contenuto di questa notifica è configurabile tramite lambdas, ma di solito è solo un setter o una chiamata di metodo sul modello di vista, passando il controllo problematico come parametro.

Può essere reso facilmente MVVM-friendly avendo la vista esporre l'interfaccia invece dei controlli figlio.

Il codice sopra riportato si basa sulla classe di supporto pubblicata sul mio blog.


0

Ho passato anni cercando di farlo funzionare. Alla fine, ho rinunciato e ho appena usato PasswordBoxEdit di DevExpress.

È la soluzione più semplice di sempre, in quanto consente di legare senza fare trucchi orribili.

Soluzione sul sito Web DevExpress

Per la cronaca, non sono affiliato con DevExpress in alcun modo.


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

;) facile!


0

È molto semplice . Crea un'altra proprietà per la password e associalo a TextBox

Ma tutte le operazioni di input vengono eseguite con la proprietà della password effettiva

stringa privata _Password;

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

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

            return szChar;
        }

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

stringa stringa pubblica {get {return _Password; }

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


Il motivo per cui la casella della password non è associabile è perché non vogliamo memorizzare la password in una stringa chiara. La stringa è immutabile e non siamo sicuri per quanto tempo rimarrà in memoria.
Lancia,

0

bene il mio answerd è più semplice solo nel modello MVVM

nel modello di classe

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;

}

la proprietà password del PasswordBox che vincerà fornisce o WatermarkPasswordBox che XCeedtoolkit fornisce genera un RoutedEventArgs in modo da poterlo associare.

ora in vista 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>

o

<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

Invia SecureStringa al modello di visualizzazione utilizzando un comportamento allegato eICommand

Non c'è nulla di sbagliato nel code-behind durante l'implementazione di MVVM. MVVM è un modello architettonico che mira a separare la vista dal modello / dalla logica aziendale. MVVM descrive come raggiungere questo obiettivo in modo riproducibile (il modello). Non si preoccupa dei dettagli di implementazione, ad esempio come strutturare o implementare la vista. Disegna solo i confini e definisce quale sia la vista, il modello di vista e quale sia il modello in termini di terminologia di questo modello.

A MVVM non interessa il linguaggio (XAML o C #) o il compilatore (partial classi). Essere indipendenti dalla lingua è una caratteristica obbligatoria di un modello di progettazione - deve essere neutrale nella lingua.

Tuttavia, code-behind ha alcuni inconvenienti come rendere la tua logica dell'interfaccia utente più difficile da capire, quando è selvaggiamente distribuita tra XAML e C #. Ma la più importante implementazione della logica dell'interfaccia utente o oggetti come modelli, stili, trigger, animazioni ecc. In C # è molto complessa e brutta / meno leggibile rispetto all'utilizzo di XAML. XAML è un linguaggio di markup che utilizza tag e annidamento per visualizzare la gerarchia degli oggetti. La creazione dell'interfaccia utente tramite XAML è molto conveniente. Sebbene ci siano situazioni in cui stai bene scegliendo di implementare la logica dell'interfaccia utente in C # (o code-behind). La gestione di questo PasswordBoxè un esempio.

Per questo motivo, gestire il PasswordBoxcode-behind gestendo il PasswordBox.PasswordChanged, non costituisce una violazione del modello MVVM.

Una chiara violazione sarebbe quella di passare un controllo (il PasswordBox) al modello di vista. Molte soluzioni lo raccomandano, ad esempio, bay passando l'istanza di PasswordBoxasICommand.CommandParameter al modello di vista. Ovviamente una raccomandazione pessima e superflua.

Se non ti interessa usare C #, ma vuoi solo mantenere pulito il tuo file code-behind o semplicemente vuoi incapsulare un comportamento / logica dell'interfaccia utente, puoi sempre utilizzare le proprietà associate e implementare un comportamento associato.

Opposto al famigerato helper ad ampia diffusione che consente l'associazione alla password di testo semplice (anti-pattern e rischio per la sicurezza davvero pessimi), questo comportamento utilizza un ICommandinvio della password SecureStringper il modello di visualizzazione, ogni volta che PasswordBoxsolleva ilPasswordBox.PasswordChanged evento.

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);
  }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.