So binden Sie an eine PasswordBox in MVVM


251

Ich bin auf ein Problem mit der Bindung an ein P gestoßen asswordBox. Es scheint ein Sicherheitsrisiko zu sein, aber ich verwende das MVVM-Muster, daher möchte ich dies umgehen. Ich habe hier einen interessanten Code gefunden (hat jemand diesen oder einen ähnlichen Code verwendet?)

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

Es sieht technisch gut aus, aber ich bin mir nicht sicher, wie ich das Passwort abrufen soll.

Ich habe grundsätzlich Eigenschaften in meinem LoginViewModelfür Usernameund Password. Usernameist in Ordnung und funktioniert so wie es ist TextBox.

Ich habe den obigen Code wie angegeben verwendet und diesen eingegeben

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

Als ich das PasswordBoxals TextBoxund Binding Path=Passworddann hatte, wurde die Eigenschaft in meinem LoginViewModelaktualisiert.

Mein Code ist sehr einfach, im Grunde habe ich einen Commandfür meinen Button. Wenn ich drücke, CanLoginwird es aufgerufen und wenn es true zurückgibt, ruft es auf Login.
Sie können sehen, dass ich meine Immobilie Usernamehier überprüfe , was großartig funktioniert.

In Loginsende ich an meinen Dienst ein Usernameund Password, Usernameenthalte Daten von meinem Viewaber PasswordistNull|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 { }
}

Das mache ich

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

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

Ich habe meine TextBox, ist das kein Problem, aber in meiner ViewModeldas Passwordist leer.

Mache ich etwas falsch oder verpasse ich einen Schritt?

Ich habe einen Haltepunkt gesetzt und sicher genug, dass der Code in die statische Hilfsklasse eingeht, aber er aktualisiert meinen Passwordin meinem nie ViewModel.


3
Nun, es stellt sich heraus, dass der Code nicht funktioniert hat, aber ich habe hier einen alternativen Code ausprobiert und er funktioniert perfekt. blog.functionalfun.net/2008/06/…
Mark Smith

5
Verstößt die Übergabe des gesamten Kennwortfeld-Steuerelements nicht gegen die Trennung der Ansicht vom Ansichtsmodell?

Antworten:


164

Entschuldigung, aber du machst es falsch.

Personen sollten die folgenden Sicherheitsrichtlinien auf die Innenseite ihrer Augenlider tätowieren lassen:
Bewahren Sie niemals Klartext-Passwörter im Speicher auf.

Der Grund, warum WPF / Silverlight PasswordBoxkeinen DP für die PasswordEigenschaft verfügbar macht, ist sicherheitsrelevant.
Wenn WPF / Silverlight einen DP dafür behalten Passwordwürde, müsste das Framework das Passwort selbst unverschlüsselt im Speicher behalten. Was als ziemlich lästiger Sicherheitsangriffsvektor angesehen wird. Der PasswordBoxverwendet verschlüsselten Speicher (sozusagen) und der einzige Weg, auf das Passwort zuzugreifen, ist über die CLR-Eigenschaft.

Ich würde vorschlagen, dass Sie beim Zugriff auf die PasswordBox.PasswordCLR-Eigenschaft darauf verzichten, sie in eine Variable oder als Wert für eine Eigenschaft zu platzieren.
Das Speichern Ihres Kennworts im Klartext im RAM des Client-Computers ist ein Sicherheits-Nein-Nein.
Also loswerden, dass public string Password { get; set; }du da oben bist.

Wenn Sie darauf zugreifen PasswordBox.Password, holen Sie es einfach heraus und senden Sie es so schnell wie möglich an den Server. Behalten Sie den Wert des Kennworts nicht bei und behandeln Sie es nicht wie jeden anderen Clientcomputertext. Bewahren Sie keine Klartextkennwörter im Speicher auf.

Ich weiß, dass dies das MVVM-Muster bricht, aber Sie sollten sich niemals an PasswordBox.PasswordAttached DP binden , Ihr Passwort im ViewModel oder anderen ähnlichen Spielereien speichern.

Wenn Sie nach einer überarchitekten Lösung suchen, gehen Sie wie folgt vor:
1. Erstellen Sie die IHavePasswordSchnittstelle mit einer Methode, die den Kennwort-Klartext zurückgibt.
2. Lassen Sie Ihr UserControlGerät eine IHavePasswordSchnittstelle implementieren .
3. Registrieren Sie die UserControlInstanz bei Ihrem IoC als Implementierung der IHavePasswordSchnittstelle.
4. Wenn eine Serveranforderung stattfindet, für die Ihr Kennwort erforderlich ist, rufen Sie Ihr IoC für die IHavePasswordImplementierung an und erhalten Sie erst dann das begehrte Kennwort.

Nur meine Meinung dazu.

- Justin


19
Könnten Sie den SecureString in der VM für WPF nicht verwenden, um dieses Problem zu lösen? Es scheint nicht, dass es etwas für Silverlight gibt.
Bryant

35
Ich stimme Ihrer Absicht und der von Ihnen übermittelten Nachricht zu, aber Ihre Antwort impliziert, dass sich die Kennwortzeichenfolge niemals im Speicher befindet, wenn Sie diesem Ansatz folgen. Der Wert des Kennworts wird ab dem Zeitpunkt gespeichert, an dem der Benutzer es eingibt. Das Entfernen der Eigenschaft, die Ihre Passphrase enthält, ist eine gute Idee und begrenzt die Kopien Ihres Kennworts, die für den Garbage Collector übrig bleiben, um zu ernten, oder die möglicherweise von anderem verwaltetem und nicht verwaltetem Code gefunden werden, der als Teil Ihres Programms ausgeführt wird, dies jedoch tun wird nicht ganz verstecken.
IanNorton

182
In den meisten Fällen benötigen Sie diese Sicherheitsstufe nicht. Was bringt es, diese eine Sache schwierig zu machen, wenn es so viele andere Möglichkeiten gibt, Passwörter zu stehlen? Zumindest WPF hätte die Verwendung von SecureString zulassen sollen, wie @Bryant sagte.
Chakrit

335
Wenn die bösen Jungs Zugriff auf den Arbeitsspeicher Ihres Computers haben, haben Sie größere Probleme als sie, die Ihr Passwort stehlen.
Cameron MacFarland

13
Seit Jahren verwende ich ein benutzerdefiniertes Benutzersteuerelement, das sich wie PasswordBox verhält, aber nur den Textwert als SecureString zurückgibt. Ja, dies verhindert, dass Snoop das Passwort im Klartext anzeigt. Der Klartextwert von SecureString kann jedoch immer noch recht einfach extrahiert werden und verhindert nur unerfahrene Hacks. Wenn auf Ihrem System das Risiko besteht, verdeckt Key Logger und Sniffer wie Snoop einzusetzen, sollten Sie die Sicherheit Ihres Systems neu bewerten.
Mike Christian

199

Meine 2 Cent:

Ich habe einmal einen typischen Anmeldedialog (Benutzer- und Passwortfelder sowie "Ok" -Schaltfläche) mit WPF und MVVM entwickelt. Ich habe das Problem mit der Passwortbindung gelöst, indem ich einfach das PasswordBox-Steuerelement selbst als Parameter an den Befehl übergeben habe, der an die Schaltfläche "OK" angehängt ist. Also in der Ansicht hatte ich:

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

Im ViewModel Executelautete die Methode des angehängten Befehls wie folgt:

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

Dies verstößt leicht gegen das MVVM-Muster, da das ViewModel jetzt etwas über die Implementierung der Ansicht weiß, aber in diesem speziellen Projekt könnte ich es mir leisten. Hoffe, es ist auch für jemanden nützlich.


Hallo Konamiman, wenn die Execute-Methode aufgerufen wird. In meinem Ansichtsmodell habe ich eine Klasse User (Login, Pass) und einen Befehl authenticate. Wie kann ich Execute in diesem Kontext verwenden?

3
sehr hilfreich, danke. Zu Ihrer Information, jemand könnte es gewohnt sein, so etwas wie _loginCommand = new RelayCommand (param => Login (Benutzername, (PasswordBox) param), param => CanLogIn) zu sehen.
Chuck Rostance

5
Dies ist eine gute Lösung, schlägt jedoch für so etwas wie eine Kombination aus Passwort und Passwortbestätigung fehl
Julien

Hallo Konamiman, ich verwende Ihre Lösung, aber sie funktioniert nicht unter Windows 8.1 Store App. Ich habe diese Frage gestellt: stackoverflow.com/questions/26221594/…
VansFannel

2
Danke dafür! Dies löste ein großes Problem, das ich beim Verschieben der Daten vom UI-Thread in den Hauptprogramm-Thread hatte. Stellen Sie sicher, dass Sie den SecureString-Ansatz implementieren, und entfernen Sie das Kennwort so schnell wie möglich. Wirf es weg. Entsorgen Sie es. Löschen Sie es. Tun Sie, was Sie tun müssen. Stellen Sie außerdem sicher, dass Sie IDisposable implementieren.
Steven C. Britton

184

Vielleicht fehlt mir etwas, aber es scheint, als ob die meisten dieser Lösungen die Dinge überkomplizieren und sichere Praktiken beseitigen.

Diese Methode verstößt nicht gegen das MVVM-Muster und gewährleistet vollständige Sicherheit. Ja, technisch gesehen ist es Code dahinter, aber es ist nichts weiter als eine "Sonderfall" -Bindung. Das ViewModel hat noch keine Kenntnisse über die View-Implementierung, was meiner Meinung nach der Fall ist, wenn Sie versuchen, die PasswordBox an das ViewModel zu übergeben.

Code Behind! = Automatische MVVM-Verletzung. Es hängt alles davon ab, was Sie damit machen. In diesem Fall codieren wir eine Bindung nur manuell, sodass sie als Teil der UI-Implementierung betrachtet wird und daher in Ordnung ist.

Im ViewModel nur eine einfache Eigenschaft. Ich habe es "nur schreiben" gemacht, da es aus keinem Grund erforderlich sein sollte, es von außerhalb des ViewModel abzurufen, aber es muss nicht sein. Beachten Sie, dass es sich um einen SecureString handelt, nicht nur um eine Zeichenfolge.

public SecureString SecurePassword { private get; set; }

In der xaml richten Sie einen PasswordChanged-Ereignishandler ein.

<PasswordBox PasswordChanged="PasswordBox_PasswordChanged"/>

Im Code dahinter:

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

Bei dieser Methode bleibt Ihr Kennwort jederzeit in einem SecureString und bietet daher maximale Sicherheit. Wenn Sie sich wirklich nicht um die Sicherheit kümmern oder das Klartextkennwort für eine nachgeschaltete Methode benötigen, die dies erfordert (Hinweis: Die meisten .NET-Methoden, für die ein Kennwort erforderlich ist, unterstützen auch eine SecureString-Option, sodass Sie möglicherweise kein Klartextkennwort benötigen Selbst wenn Sie glauben, dass Sie dies tun, können Sie stattdessen einfach die Password-Eigenschaft verwenden. So was:

(ViewModel-Eigenschaft)

public string Password { private get; set; }

(Code dahinter)

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

Wenn Sie die Dinge stark typisieren möchten, können Sie die (dynamische) Besetzung durch die Oberfläche Ihres ViewModel ersetzen. Aber wirklich, "normale" Datenbindungen sind auch nicht stark typisiert, also ist es keine so große Sache.

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

Das Beste von allen Welten: Ihr Passwort ist sicher, Ihr ViewModel hat nur eine Eigenschaft wie jede andere Eigenschaft und Ihre Ansicht ist eigenständig, ohne dass externe Referenzen erforderlich sind.


1
Dieser sieht gut aus für mich! Wenn Sie auf der Sicherheitsseite sehr streng sein wollten, bin ich mir nicht sicher, ob dies dazu führen würde, aber für mich ist es ein perfekter Mittelweg. Vielen Dank!
jrich523

3
Vielen Dank für die Praktikabilität über starre Dogmen über MVVM und Paranoia. Funktioniert super, danke.
Bruce Pierson

2
Das SecureString-Beispiel wäre großartig mit dieser Erweiterung blogs.msdn.com/b/fpintos/archive/2009/06/12/…
Ayman

1
Tatsächlich nett. Ich wünschte, MS hätte diesem Steuerelement gerade ein Kennwort DP vom Typ SecureString hinzugefügt.
Keith Hill

1
Dies ist die perfekte Antwort, da Sicherheit und MVVM erhalten bleiben.
LoRdPMN

20

Sie können diese XAML verwenden:

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

Und dieser Befehl führt die Methode aus:

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

3
Zu xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Ihrer Information

Ohne die PasswordBox benennen zu müssen: CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=PasswordBox}}"(Hinweis: nicht RelativeSource Self ).
Wondra

Diese Lösung verletzt das MVVM-Muster.
BionicCode

13

Das funktioniert gut für mich.

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

3
Was ist mit CommandParameter = "{Binding ElementName = MyPasswordBox, Path = SecurePassword"}?
LukeN

2
LukeN, das funktioniert nicht (zumindest für mich). Wahrscheinlich aus demselben Grund - SecurePassword ist keine Abhängigkeitseigenschaft.
vkrzv

Unter der Annahme, dass das ICommandim Ansichtsmodell implementiert ist, würde diese Lösung das MVVM-Muster verletzen.
BionicCode

9

Eine einfache Lösung ohne Verletzung des MVVM-Musters besteht darin, ein Ereignis (oder einen Delegaten) in das ViewModel einzuführen, das das Kennwort erntet.

Im ViewModel :

public event EventHandler<HarvestPasswordEventArgs> HarvestPassword;

mit diesen EventArgs:

class HarvestPasswordEventArgs : EventArgs
{
    public string Password;
}

Abonnieren Sie in der Ansicht das Ereignis beim Erstellen des ViewModel und geben Sie den Kennwortwert ein.

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

Wenn Sie im ViewModel das Kennwort benötigen, können Sie das Ereignis auslösen und das Kennwort von dort abrufen:

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

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

LoginHelpers.Login(Username, pwargs.Password);

Das einzige, was Sie vermissen, ist, dass Sie beim Abonnieren einer Ansicht für ein Ansichtsmodellereignis a verwenden sollten WeakEventManager<TEventSource, TEventArgs>, um Speicherverluste zu vermeiden. Oft hat die Ansicht nicht die gleiche Lebensdauer wie das Ansichtsmodell. WeakEventManager<IViewModel, EventArgs>.AddHandler(iViewModelInstance, nameof(IViewModel.Event), eventHandlerMethod);
Todd A. Stedel

Ich bevorzuge diese Lösung, da sie einfach ist, MVVM nicht verletzt, nur minimalen Code enthält und die richtige Verwendung der Passwortbox ermöglicht (wenn Sie stattdessen "SecurePassword" verwenden). Außerdem ist es jetzt einfach, andere HarvestPassword-Methoden (wie SmartCard ...) zu implementieren
Matt

8

Ich habe viel Zeit damit verbracht, nach verschiedenen Lösungen zu suchen. Ich mochte die Idee des Dekorateurs nicht, Verhaltensweisen bringen die Validierungs-Benutzeroberfläche durcheinander, Code dahinter ... wirklich?

Das Beste ist, sich an eine benutzerdefinierte angehängte Eigenschaft zu halten und sie SecureStringin Ihrem Ansichtsmodell an Ihre Eigenschaft zu binden . Bewahren Sie es so lange wie möglich dort auf. Wenn Sie schnellen Zugriff auf das einfache Kennwort benötigen, konvertieren Sie es vorübergehend mit dem folgenden Code in eine unsichere Zeichenfolge:

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

Stellen Sie sicher, dass Sie dem GC erlauben, Ihr UI-Element zu erfassen. Widerstehen Sie daher dem Drang, einen statischen Ereignishandler für das PasswordChangedEreignis auf dem zu verwenden PasswordBox. Ich habe auch eine Anomalie entdeckt, bei der das Steuerelement die Benutzeroberfläche nicht aktualisiert hat, als die SecurePasswordEigenschaft zum Einrichten verwendet wurde. Deshalb kopiere ich Passwordstattdessen das Kennwort in .

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

Und die XAML-Nutzung:

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

Meine Eigenschaft im Ansichtsmodell sah folgendermaßen aus:

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

Das RequiredSecureStringist nur ein einfacher benutzerdefinierter Validator, den die folgende Logik:

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

Hier hast du es. Eine vollständige und getestete reine MVVM-Lösung.


7

Ich habe hier eine GIST gepostet , die ein bindbares Passwortfeld ist.

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
Obwohl dies nicht schlecht ist, verlieren Sie die Fähigkeit, einfache Attribute wie Polsterung und Tabindex festzulegen
Julien

1
Taylor, ich habe das Wesentliche so eingefügt, dass es in der Antwort verfügbar ist. (Ansonsten sah es nach einer Nur-Link-Antwort aus. Ich versuche nur zu vermeiden, dass diese als solche gelöscht wird.) Sie können sich gerne mit dem inline-Inhalt herumschlagen.
Lynn Crumbling

@ Julien, aber Sie können das mit Stilen beheben. Ich löse dieses Problem auf ähnliche Weise, aber ich verwende eine. ContentControlSie können dann einfach eine PasswordBox als Inhalt und Stil verwenden, der in XAML nach Ihren Wünschen passt. Der Zweck von ContentControlist nur, das PasswordChangedEreignis zu abonnieren und eine in zwei Richtungen bindende Eigenschaft freizulegen. Alles in allem sind es 65 Codezeilen und so ziemlich das, was diese Dekorationsklasse macht. Siehe hier für meinen Kern der folgenden gist.github.com/leidegre/c7343b8c720000fe3132
John Leidegren

6

Diese Implementierung ist etwas anders. Sie übergeben ein Kennwortfeld an die Ansicht durch Bindung einer Eigenschaft in ViewModel. Es werden keine Befehlsparameter verwendet. Das ViewModel ignoriert die Ansicht. Ich habe ein VB vs 2010-Projekt, das von SkyDrive heruntergeladen werden kann. Wpf MvvM PassWordBox Example.zip https://skydrive.live.com/redir.aspx?cid=e95997d33a9f8d73&resid=E95997D33A9F8D73!511

Die Art und Weise, wie ich PasswordBox in einer Wpf-MvvM-Anwendung verwende, ist ziemlich simpel und funktioniert gut für mich. Das bedeutet nicht, dass ich denke, dass es der richtige oder der beste Weg ist. Es ist nur eine Implementierung von Using PasswordBox und dem MvvM-Muster.

Grundsätzlich erstellen Sie eine öffentliche schreibgeschützte Eigenschaft, an die die Ansicht als PasswordBox (das eigentliche Steuerelement) gebunden werden kann. Beispiel:

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

Ich benutze ein Hintergrundfeld, um die Eigeninitialisierung der Eigenschaft durchzuführen.

Dann binden Sie von Xaml aus den Inhalt eines ContentControls oder eines Steuercontainers. Beispiel:

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

Von dort aus haben Sie die volle Kontrolle über die Passwortbox. Ich verwende auch einen PasswordAccessor (nur eine Funktion der Zeichenfolge), um den Passwortwert beim Anmelden zurückzugeben oder für was auch immer Sie das Passwort möchten. Im Beispiel habe ich eine öffentliche Eigenschaft in einem generischen Benutzerobjektmodell. Beispiel:

Public Property PasswordAccessor() As Func(Of String)

Im Benutzerobjekt ist die Kennwortzeichenfolgeeigenschaft ohne Sicherungsspeicher schreibgeschützt. Sie gibt lediglich das Kennwort aus der Kennwortbox zurück. Beispiel:

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

Dann stelle ich im ViewModel sicher, dass der Accessor erstellt und auf die PasswordBox.Password-Eigenschaft 'Beispiel:

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

Wenn ich die Kennwortzeichenfolge für die Anmeldung sagen muss, erhalte ich nur die Eigenschaft User Objects Password, die die Funktion wirklich aufruft, um das Kennwort abzurufen und zurückzugeben. Dann wird das tatsächliche Kennwort nicht vom Benutzerobjekt gespeichert. Beispiel: wäre im 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

Das sollte es tun. Das ViewModel benötigt keine Kenntnisse über die Steuerelemente der Ansicht. Die Ansicht wird nur an die Eigenschaft im ViewModel gebunden, nicht anders als die Ansicht, die an ein Bild oder eine andere Ressource gebunden ist. In diesem Fall ist diese Ressource (Eigenschaft) zufällig eine Benutzersteuerung. Es ermöglicht Tests, während das ViewModel die Eigenschaft erstellt und besitzt und die Eigenschaft unabhängig von der Ansicht ist. Was die Sicherheit betrifft, weiß ich nicht, wie gut diese Implementierung ist. Bei Verwendung einer Funktion wird der Wert jedoch nicht in der Eigenschaft selbst gespeichert, auf die die Eigenschaft gerade zugreift.


6

Um das OP-Problem zu lösen, ohne die MVVM zu beschädigen, würde ich einen benutzerdefinierten Wertekonverter und einen Wrapper für den Wert (das Kennwort) verwenden, der aus dem Kennwortfeld abgerufen werden muss.

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

Im Ansichtsmodell:

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

Da das Ansichtsmodell verwendet wird IWrappedParameter<T>, muss es keine Kenntnisse über PasswordBoxWrapperoder haben PasswordBoxConverter. Auf diese Weise können Sie das PasswordBoxObjekt vom Ansichtsmodell isolieren und das MVVM-Muster nicht beschädigen.

In der Ansicht:

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

sehr elegante lösung imo. Ich habe meine darauf aufgebaut. der einzige Unterschied: Ich übergebe SecureString SecurePassword an die Anmeldefunktion anstelle von String Password. so dass es keine unverschlüsselten Zeichenfolgen gibt, bei denen das Passwort im Speicher herumfliegt.
Nennen Sie mich Karotte

Es ist eine Weile her, aber ich kann dies aufgrund meines RelayCommand nicht zum Laufen bringen. Würde es Ihnen etwas ausmachen, Ihre hinzuzufügen?
Ecnerwal

5

Obwohl ich der Meinung bin, dass es wichtig ist, das Speichern des Kennworts an keiner Stelle zu vermeiden, muss ich dennoch die Möglichkeit haben, das Ansichtsmodell ohne Ansicht zu instanziieren und meine Tests dagegen auszuführen.

Die Lösung, die für mich funktioniert hat, bestand darin, die Funktion PasswordBox.Password beim Ansichtsmodell zu registrieren und sie beim Ausführen des Anmeldecodes vom Ansichtsmodell aufrufen zu lassen.

Dies tut bedeutet eine Zeile Code in der Code - Behind - Ansicht.

Also, in meiner Login.xaml habe ich

<PasswordBox x:Name="PasswordBox"/>

und in Login.xaml.cs habe ich

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

dann habe ich in LoginViewModel.cs den PasswordHandler definiert

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

und wenn eine Anmeldung erforderlich ist, ruft der Code den Handler auf, um das Kennwort aus der Ansicht abzurufen ...

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

Auf diese Weise kann ich beim Testen des Ansichtsmodells PasswordHandler einfach auf eine anonyme Methode setzen, mit der ich das Kennwort angeben kann, das ich im Test verwenden möchte.


4

Ich dachte, ich würde meine Lösung in die Mischung werfen, da dies ein so häufiges Problem ist ... und es immer gut ist, viele Optionen zu haben.

Ich habe einfach ein PasswordBoxin ein eingewickelt UserControlund ein implementiert DependencyProperty, um binden zu können. Ich tue alles, um zu vermeiden, dass Klartext im Speicher gespeichert wird, sodass alles über a SecureStringund die PasswordBox.PasswordEigenschaft erfolgt. Während der foreachSchleife wird jedes Zeichen freigelegt, aber es ist sehr kurz. Ehrlich gesagt, wenn Sie sich Sorgen machen, dass Ihre WPF-Anwendung durch diese kurze Exposition gefährdet wird, haben Sie größere Sicherheitsprobleme, die behoben werden sollten.

Das Schöne daran ist, dass Sie keine MVVM-Regeln brechen, auch nicht die "puristischen", da dies eine ist UserControlund es daher erlaubt ist, Code-Behind zu haben. Wenn Sie es verwenden, können Sie eine reine Kommunikation zwischen Viewund ViewModelohne VideModelKenntnis eines Teils Viewoder der Quelle des Passworts haben. Stellen Sie einfach sicher, dass Sie an SecureStringIhre gebunden sind 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 (Version 1 - Keine Unterstützung für bidirektionale Bindungen.)

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

Verwendung von Version 1:

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

BindablePasswordBox.xaml.cs (Version 2 - Unterstützt die bidirektionale Bindung.)

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

Verwendung von Version 2:

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

Ich habe versucht, dies zu implementieren, aber Sie erhalten eine Endlosschleife, wenn Sie das Kennwort auf der Benutzeroberfläche aktualisieren. weil if (Password != secure)wird immer falsch sein, da SecureString nicht gleich überschreibt. Irgendwelche Gedanken?
Simonalexander2005


2

Ich habe diese Methode verwendet und das Kennwortfeld übergeben, obwohl dies gegen die MVVM verstößt. Dies war für mich von wesentlicher Bedeutung, da ich für mein Login in meiner Shell ein Inhaltssteuerelement mit Datenvorlage verwendet habe, was eine komplexe Shell-Umgebung darstellt. Der Zugriff auf den Code hinter der Shell wäre also Mist gewesen.

Das Übergeben der Passwortbox ist meines Erachtens gleichbedeutend mit dem Zugriff auf die Kontrolle über den Code, soweit ich weiß. Ich stimme Passwörtern zu, speichere sie nicht im Speicher usw. In dieser Implementierung habe ich keine Eigenschaft für das Passwort im Ansichtsmodell.

Tastenbefehl

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

Dies ist eine eindeutige Verletzung des MVVM-Musters. Das Muster erlaubt es nicht, Steuerelemente im Ansichtsmodell zu verarbeiten.
BionicCode

2

Für mich fühlen sich beide Dinge falsch an:

  • Implementieren von Klartext-Passworteigenschaften
  • Senden des PasswordBoxBefehlsparameters als an das ViewModel

Das Übertragen des SecurePassword (SecureString-Instanz), wie von Steve in CO beschrieben, scheint akzeptabel. ich bevorzugeBehaviors , hinterher zu codieren, und ich hatte auch die zusätzliche Anforderung, das Passwort vom Ansichtsmodell zurücksetzen zu können.

Xaml ( Passwordist die ViewModel-Eigenschaft):

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

Verhalten:

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

Für komplette Neulinge wie mich ist hier ein komplettes Arbeitsbeispiel der Konamimanoben vorgeschlagenen Vorschläge. Danke 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);
    }
}

Dies ist eine eindeutige Verletzung des MVVM-Musters. Das Muster erlaubt es nicht, Steuerelemente im Ansichtsmodell zu verarbeiten.
BionicCode

1

Wie Sie sehen können, binde ich an das Passwort, aber vielleicht binde ich es an die statische Klasse.

Es ist eine angehängte Eigenschaft . Diese Art von Eigenschaft kann auf jede Art von Eigenschaft angewendet werden DependencyObject, nicht nur auf die Art, in der sie deklariert ist. Obwohl es in der PasswordHelperstatischen Klasse deklariert ist , wird es auf die angewendetPasswordBox auf dem Sie es verwenden.

Um diese angehängte Eigenschaft zu verwenden, müssen Sie sie nur an die PasswordEigenschaft in Ihrem ViewModel binden :

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

1

Ich habe getan wie:

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

Für mich geht das!


Du gibst mir eine schöne Idee. :)
Andre Mendonca

1

Wie bereits erwähnt, sollte VM die Ansicht nicht kennen, aber das Übergeben der gesamten PasswordBox scheint der einfachste Ansatz zu sein. Anstatt übergebene Parameter an PasswordBox zu übertragen, verwenden Sie Reflection, um die Password-Eigenschaft daraus zu extrahieren. In diesem Fall erwartet VM eine Art Kennwortcontainer mit der Eigenschaft Kennwort (ich verwende RelayCommands von 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;
    }
}

Es kann leicht mit einer anonymen Klasse getestet werden:

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

Kommentare sind nicht für eine ausführliche Diskussion gedacht. Dieses Gespräch wurde in den Chat verschoben .
Samuel Liew

1

In Windows Universal App

Sie können diesen Code mit der Eigenschaft "Passwort" und der Bindung mit der modelView verwenden

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


1

Wenn Sie sich der Risiken bewusst sind, die diese Implementierung mit sich bringt, fügen Sie einfach Mode = OneWayToSource hinzu, damit das Kennwort mit Ihrem ViewModel synchronisiert wird .

XAML

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

Warum nicht einfach tun OneWayToSource?
BK

@BK Meine Antwort bearbeitet. Vielen Dank.
Kevin

1
Sollte sich Mode nicht in den Bindeklammern befinden?
Mat

@ Mat Yap. Vielen Dank.
Kevin

1

Hier ist meine Meinung dazu:

  1. Die Verwendung einer angehängten Eigenschaft zum Binden des Kennworts macht den Zweck der Sicherung des Kennworts zunichte. Die Kennworteigenschaft eines Kennwortfelds ist aus einem bestimmten Grund nicht bindbar.

  2. Wenn Sie das Kennwortfeld als Befehlsparameter übergeben, wird das ViewModel auf das Steuerelement aufmerksam. Dies funktioniert nicht, wenn Sie planen, Ihr ViewModel plattformübergreifend wiederverwendbar zu machen. Machen Sie Ihre VM nicht auf Ihre Ansicht oder andere Steuerelemente aufmerksam.

  3. Ich denke nicht, dass die Einführung einer neuen Eigenschaft, einer Schnittstelle, das Abonnieren von Ereignissen mit geändertem Passwort oder anderen komplizierten Dingen für eine einfache Aufgabe der Bereitstellung des Passworts erforderlich ist.

XAML

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

Code dahinter - Die Verwendung von Code dahinter verstößt nicht unbedingt gegen MVVM. Solange Sie keine Geschäftslogik einfügen.

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

ViewModel

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

0

Eine Lösung für die PasswordBox finden Sie in der ViewModel-Beispielanwendung des WPF Application Framework (WAF). -Projekts.

Justin hat jedoch recht. Übergeben Sie das Kennwort nicht als Nur-Text zwischen View und ViewModel. Verwenden Sie stattdessen SecureString (siehe MSDN PasswordBox).


2
Die Art und Weise, wie in Pop3SettingsView von WAF verwendet wird, ist lustig. PasswordBox passwordBox = (PasswordBox) Absender; if (ViewModel! = null) {ViewModel.Pop3Password = passwordBox.Password; } Pop3Password von ViewModel ist die Zeichenfolgeeigenschaft. Also, es ist auch nicht sicher. Besser, die angehängte Eigenschaft zu verwenden
Michael Sync

0

Ich habe eine Authentifizierungsprüfung verwendet, gefolgt von einem Sub, das von einer Mediator-Klasse in der Ansicht aufgerufen wurde (die auch eine Authentifizierungsprüfung implementiert), um das Kennwort in die Datenklasse zu schreiben.

Es ist keine perfekte Lösung; Es hat jedoch mein Problem behoben, dass das Passwort nicht verschoben werden konnte.


0

Ich verwende eine prägnante MVVM-freundliche Lösung, die noch nicht erwähnt wurde. Zuerst benenne ich die PasswordBox in XAML:

<PasswordBox x:Name="Password" />

Dann füge ich dem View-Konstruktor einen einzelnen Methodenaufruf hinzu:

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

Und das ist es. Das Ansichtsmodell erhält eine Benachrichtigung, wenn es über DataContext an eine Ansicht angehängt wird, und eine weitere Benachrichtigung, wenn es getrennt wird. Der Inhalt dieser Benachrichtigung kann über die Lambdas konfiguriert werden. In der Regel handelt es sich jedoch nur um einen Setter- oder Methodenaufruf für das Ansichtsmodell, bei dem die problematische Steuerung als Parameter übergeben wird.

Es kann sehr einfach MVVM-freundlich gemacht werden, indem die Ansicht die Benutzeroberfläche anstelle von untergeordneten Steuerelementen verfügbar macht.

Der obige Code stützt sich auf Hilfsklasse auf meinem Blog veröffentlicht.


0

Ich habe ewig versucht, dies zum Laufen zu bringen. Am Ende habe ich aufgegeben und nur das PasswordBoxEdit von DevExpress verwendet.

Es ist die einfachste Lösung aller Zeiten, da es das Binden ermöglicht, ohne schreckliche Tricks zu machen.

Lösung auf der DevExpress-Website

Ich bin in keiner Weise mit DevExpress verbunden.


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

;) einfach!


0

Es ist sehr einfach . Erstellen Sie eine weitere Eigenschaft für das Kennwort und binden Sie diese mit TextBox

Alle Eingabevorgänge werden jedoch mit der tatsächlichen Kennworteigenschaft ausgeführt

private Zeichenfolge _Password;

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

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

            return szChar;
        }

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

öffentlicher String Passwort {get {return _Password; }}

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


Der Grund, warum das Kennwortfeld nicht bindbar ist, liegt darin, dass wir das Kennwort nicht in einer eindeutigen Zeichenfolge speichern möchten. String ist unveränderlich und wir sind uns nicht sicher, wie lange es im Speicher bleiben wird.
Lance

0

Nun, meine Antwort ist einfacher, nur für das MVVM-Muster

im Klassenansichtsmodell

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;

}

Die Kennworteigenschaft der von win bereitgestellten PasswordBox oder der von XCeedtoolkit bereitgestellten WatermarkPasswordBox generiert ein RoutedEventArgs, damit Sie es binden können.

jetzt in xmal view

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

oder

<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

Sende ein SecureString an das Ansichtsmodell mit einem angehängten Verhalten undICommand

Bei der Implementierung von MVVM ist an Code-Behind nichts auszusetzen. MVVM ist ein Architekturmuster, das darauf abzielt, die Ansicht von der Modell- / Geschäftslogik zu trennen. MVVM beschreibt, wie dieses Ziel auf reproduzierbare Weise erreicht werden kann (das Muster). Es sind keine Implementierungsdetails wichtig, z. B. wie Sie die Ansicht strukturieren oder implementieren. Es zeichnet nur die Grenzen und definiert, was die Ansicht, das Ansichtsmodell und das Modell in Bezug auf die Terminologie dieses Musters ist.

MVVM kümmert sich nicht um die Sprache (XAML oder C #) oder den Compiler (partial Klassen). Sprachunabhängigkeit ist ein obligatorisches Merkmal eines Entwurfsmusters - es muss sprachneutral sein.

Code-Behind hat jedoch einige Nachteile, z. B. dass es schwieriger wird, Ihre UI-Logik zu verstehen, wenn sie wild zwischen XAML und C # verteilt ist. Die wichtigste Implementierung von UI-Logik oder Objekten wie Vorlagen, Stilen, Triggern, Animationen usw. in C # ist jedoch sehr komplex und hässlich / weniger lesbar als die Verwendung von XAML. XAML ist eine Auszeichnungssprache, die Tags und Verschachtelungen verwendet, um die Objekthierarchie zu visualisieren. Das Erstellen einer Benutzeroberfläche mit XAML ist sehr praktisch. Obwohl es Situationen gibt, in denen Sie die UI-Logik in C # (oder Code-Behind) implementieren können. Der Umgang mit PasswordBoxist ein Beispiel.

Aus diesen Gründen ist die Behandlung des PasswordBoxim Code-Behind durch die Behandlung des PasswordBox.PasswordChanged, keine Verletzung des MVVM-Musters.

Ein klarer Verstoß wäre, ein Steuerelement (das PasswordBox) an das Ansichtsmodell zu übergeben. Viele Lösungen empfehlen dies, z. B. das Übergeben der Instanz des PasswordBoxasICommand.CommandParameter an das Ansichtsmodell. Offensichtlich eine sehr schlechte und unnötige Empfehlung.

Wenn Sie sich nicht für die Verwendung von C # interessieren, sondern nur Ihre CodeBehind-Datei sauber halten oder einfach eine Verhaltens- / UI-Logik kapseln möchten, können Sie jederzeit angehängte Eigenschaften verwenden und ein angehängtes Verhalten implementieren.

Im Gegensatz zu dem berüchtigten, weit verbreiteten Helfer, der die Bindung an das Nur-Text-Passwort ermöglicht (wirklich schlechtes Anti-Pattern- und Sicherheitsrisiko), verwendet dieses Verhalten ein ICommand, um das Passwort SecureStringfür das Ansichtsmodell zu senden , wann immer das PasswordBoxausgelöst wirdPasswordBox.PasswordChanged Ereignis .

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);
  }
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.