password c# wpf mvvm wpf-controls passwords

c# - textbox password wpf



Cómo enlazar a un PasswordBox en MVVM (30)

Como puede ver, estoy enlazando a Contraseña, pero tal vez esté enlazado a la clase estática ...

Es una propiedad adjunta . Este tipo de propiedad se puede aplicar a cualquier tipo de propiedad DependencyObject, no solo al tipo en el que se declara. Entonces, aunque se declara en la PasswordHelperclase estática, se aplica a la PasswordBoxen la que se usa.

Para usar esta propiedad adjunta, solo necesita vincularla a la Passwordpropiedad en su ViewModel:

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

Me he encontrado con un problema con el enlace a un PasswordBox. Parece que es un riesgo para la seguridad, pero estoy usando el patrón MVVM, así que deseo evitarlo. Encontré algún código interesante aquí (¿alguien ha usado esto o algo similar?)

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

Técnicamente se ve muy bien, pero no estoy seguro de cómo recuperar la contraseña.

Básicamente tengo propiedades en mi LoginViewModel para nombre de Username y Password . Username está bien y está funcionando ya que es un TextBox .

Utilicé el código anterior como se indica y entré en este

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

Cuando tuve el PasswordBox como TextBox y Binding Path=Password , la propiedad en mi LoginViewModel se actualizó.

Mi código es muy simple, básicamente tengo un Command para mi Button . Cuando lo CanLogin se llama a CanLogin y si devuelve verdadero, llama a Login .
Puede ver que verifico mi propiedad para el nombre de Username aquí, que funciona muy bien.

En el Login , envío a mi servicio un nombre de Username y Password , el nombre de Username contiene datos de mi View pero la Password es 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 { } }

Esto es lo que estoy haciendo

<TextBox Text="{Binding Path=Username, UpdateSourceTrigger=PropertyChanged}" MinWidth="180" /> <PasswordBox ff:PasswordHelper.Attach="True" ff:PasswordHelper.Password="{Binding Path=Password}" Width="130"/>

Tengo mi TextBox , esto no es un problema, pero en mi ViewModel la Password está vacía.

¿Estoy haciendo algo mal o me falta un paso?

Puse un punto de interrupción y seguro que el código ingresa a la clase de ayuda estática, pero nunca actualiza mi Password en mi ViewModel .


Esta implementación es ligeramente diferente. Usted pasa una casilla de contraseña a la Vista a través del enlace de una propiedad en ViewModel, no usa ningún parámetro de comando. El ViewModel se mantiene ignorante de la vista. Tengo un proyecto VB vs 2010 que se puede descargar desde SkyDrive. Wpf MvvM PassWordBox Example.zip https://skydrive.live.com/redir.aspx?cid=e95997d33a9f8d73&resid=E95997D33A9F8D73!511

La forma en que uso PasswordBox en una aplicación Wpf MvvM es bastante simplista y me funciona bien. Eso no quiere decir que creo que es la forma correcta o la mejor. Es solo una implementación del uso de PasswordBox y el patrón MvvM.

Básicamente, creas una propiedad pública de solo lectura a la que la Vista se puede enlazar como un PasswordBox (el control real). Ejemplo:

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

Utilizo un campo de respaldo solo para hacer la autoinicialización de la propiedad.

Luego, desde Xaml, se enlaza el Contenido de un ContentControl o un Ejemplo de Contenedor de Control:

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

Desde allí, tiene el control total del buzón de contraseñas. También utilizo un PasswordAccessor (solo una función de cadena) para devolver el valor de la contraseña cuando inicie sesión o para cualquier otra cosa que desee. En el ejemplo tengo una propiedad pública en un modelo de objeto de usuario genérico. Ejemplo:

Public Property PasswordAccessor() As Func(Of String)

En el Objeto de usuario, la propiedad de la cadena de contraseña es de solo lectura sin ningún almacenamiento de respaldo, solo devuelve la Contraseña del PasswordBox. Ejemplo:

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

Luego, en el ViewModel me aseguro de que el Accessor esté creado y configurado en la propiedad PasswordBox.Password ''Ejemplo:

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

Cuando necesito la cadena de contraseña, diga para iniciar sesión, solo obtengo la propiedad Contraseña de objetos de usuario que realmente invoca a la función para tomar la contraseña y devolverla, luego el objeto de usuario no guarda la contraseña real. Ejemplo: estaría en el 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

Deberias hacer eso. El ViewModel no necesita ningún conocimiento de los controles de la vista. La vista solo se enlaza a la propiedad en el modelo de vista, no es diferente a la vista de enlace a una imagen u otro recurso. En este caso, ese recurso (propiedad) simplemente pasa a ser un control de usuario. Permite realizar pruebas a medida que ViewModel crea y posee la Propiedad y la Propiedad es independiente de la Vista. En cuanto a la seguridad, no sé qué tan buena es esta implementación. Pero al usar una función, el valor no se almacena en la propiedad en sí a la que acaba de acceder la propiedad.


Esto funciona bien para mí.

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


Lo siento, pero lo estás haciendo mal.

Las personas deben tener tatuada la siguiente guía de seguridad en el interior de sus párpados:
Nunca guarde las contraseñas de texto plano en la memoria.

La razón por la que WPF / Silverlight PasswordBox no expone un DP para la propiedad Contraseña está relacionada con la seguridad.
Si WPF / Silverlight mantuviera un DP para la contraseña, se requeriría que el marco mantenga la contraseña sin cifrar en la memoria. Que se considera un vector de ataque de seguridad bastante problemático. El PasswordBox usa memoria encriptada (de algún tipo) y la única forma de acceder a la contraseña es a través de la propiedad CLR.

Yo sugeriría que al acceder a la propiedad de PasswordBox.Password CLR se abstendría de colocarlo en cualquier variable o como un valor para cualquier propiedad.
Mantener su contraseña en texto sin formato en la memoria RAM de la máquina cliente es una seguridad no-no.
Así que deshazte de esa "contraseña de cadena pública {get; set;}" que tienes ahí arriba.

Cuando acceda a PasswordBox.Password, simplemente sáquelo y envíelo al servidor lo antes posible. No mantenga el valor de la contraseña y no la trate como lo haría con cualquier otro texto de máquina cliente. No guarde las contraseñas de texto claro en la memoria.

Sé que esto rompe el patrón de MVVM, pero nunca debe vincularse a PasswordBox.Password Attached DP, almacene su contraseña en el ViewModel o en otros shenanigans similares.

Si está buscando una solución sobre-arquitectónica, aquí hay una:
1. Cree la interfaz IHavePassword con un método que devuelva el texto sin cifrar de la contraseña.
2. Haga que su UserControl implemente una interfaz IHavePassword.
3. Registre la instancia de UserControl con su IoC como implementando la interfaz IHavePassword.
4. Cuando se esté realizando una solicitud de servidor que requiera su contraseña, llame a su IoC para la implementación de IHavePassword y solo obtenga la contraseña más codiciada.

Sólo mi opinión sobre él.

Justino


Mis 2 centavos:

Una vez desarrollé un diálogo de inicio de sesión típico (cuadros de usuario y contraseña, más el botón "Aceptar") usando WPF y MVVM. Resolví el problema de enlace de contraseña simplemente pasando el control PasswordBox como un parámetro al comando adjunto al botón "Aceptar". Así que en la vista que tenía:

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

Y en el ViewModel, el método Execute del comando adjunto fue el siguiente:

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

Esto viola levemente el patrón de MVVM, ya que ahora el ViewModel sabe algo sobre cómo se implementa la Vista, pero en ese proyecto en particular podría pagarlo. Espero que sea útil para alguien también.


Para resolver el problema de OP sin romper la MVVM, usaría un convertidor de valor personalizado y una envoltura para el valor (la contraseña) que se debe recuperar del cuadro de contraseña.

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

En el modelo de vista:

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

Debido a que el modelo de vista utiliza IWrappedParameter<T> , no necesita tener ningún conocimiento sobre PasswordBoxWrapper ni PasswordBoxConverter . De esta manera, puede aislar el objeto PasswordBox del modelo de vista y no romper el patrón MVVM.

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


Pasé mucho tiempo buscando varias soluciones. No me gustó la idea de los decoradores, los comportamientos estropean la interfaz de usuario de validación, el código detrás ... ¿en serio?

La mejor aún es mantener una propiedad adjunta personalizada y vincularla a su propiedad SecureString en su modelo de vista. Mantenlo ahí todo el tiempo que puedas. Siempre que necesite un acceso rápido a la contraseña simple, conviértala temporalmente en una cadena no segura utilizando el siguiente código:

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

Asegúrate de permitir que el GC recopile tu elemento de UI, por lo que resistes la tentación de usar un controlador de eventos estático para el evento PasswordChanged en el PasswordBox . También descubrí una anomalía en la que el control no estaba actualizando la interfaz de usuario cuando usaba la propiedad SecurePassword para configurarlo, por lo que estoy copiando la contraseña en 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; } } } } }

Y el uso de XAML:

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

Mi propiedad en el modelo de vista se veía así:

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

RequiredSecureString es solo un simple validador personalizado que tiene la siguiente lógica:

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

Aqui lo tienes. Una solución MVVM pura completa y probada.


Pensé que iba a poner mi solución en la mezcla, ya que este es un problema tan común ... y tener muchas opciones siempre es bueno.

Simplemente envolví un PasswordBox en un UserControl e implementé una DependencyProperty para poder enlazar. Estoy haciendo todo lo posible para evitar almacenar cualquier texto claro en la memoria, por lo que todo se hace a través de SecureString y la propiedad PasswordBox.Password . Durante el bucle foreach , cada personaje se expone, pero es muy breve. Honestamente, si está preocupado de que su aplicación WPF se vea comprometida por esta breve exposición, tiene mayores problemas de seguridad que deben ser manejados.

La belleza de esto es que no estás rompiendo ninguna regla de MVVM, incluso las "puristas", ya que este es un UserControl , por lo que está permitido tener código de respaldo. Cuando lo esté utilizando, puede tener una comunicación pura entre View y ViewModel sin que VideModel tenga conocimiento de ninguna parte de View o la fuente de la contraseña. Solo asegúrese de estar vinculado a SecureString en su ViewModel .

BindablePasswordBox.xaml

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

BindablePasswordBox.xaml.cs (Versión 1 - No hay soporte de enlace bidireccional).

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

Uso de la versión 1:

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

BindablePasswordBox.xaml.cs (Versión 2 - Tiene soporte de enlace bidireccional).

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

Uso de la versión 2:

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


Publiqué un GIST here que es un cuadro de contraseña vinculable.

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


Puedes usar este 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>

Y este comando ejecuta el método:

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


Si bien estoy de acuerdo en que es importante evitar almacenar la contraseña en cualquier lugar, todavía necesito la capacidad de crear una instancia del modelo de vista sin una vista y ejecutar mis pruebas en su contra.

La solución que me funcionó fue registrar la función PasswordBox.Password con el modelo de vista y hacer que el modelo de vista lo invoque al ejecutar el código de inicio de sesión.

Esto significa una línea de código en el código de la vista.

Así, en mi Login.xaml tengo

<PasswordBox x:Name="PasswordBox"/>

y en Login.xaml.cs tengo

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

luego en LoginViewModel.cs tengo el PasswordHandler definido

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

y cuando es necesario iniciar sesión, el código invoca al controlador para obtener la contraseña de la vista ...

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

De esta manera, cuando quiero probar el modelo de vista, simplemente puedo configurar PasswordHandler en un método anónimo que me permita entregar cualquier contraseña que quiera usar en la prueba.


Tal vez me esté perdiendo algo, pero parece que la mayoría de estas soluciones complican las cosas y eliminan las prácticas seguras.

Este método no viola el patrón MVVM y mantiene una seguridad completa. Sí, técnicamente está detrás del código, pero no es más que un enlace de "casos especiales". El ViewModel aún no tiene conocimiento de la implementación de View, lo que en mi opinión sí lo tiene si está intentando pasar el PasswordBox al ViewModel.

Code Behind! = Violación automática de MVVM. Todo depende de lo que hagas con él. En este caso, simplemente estamos codificando manualmente un enlace, por lo que todo se considera parte de la implementación de la interfaz de usuario y, por lo tanto, está bien.

En el ViewModel, solo una propiedad simple. Lo hice "solo escritura", ya que no debería ser necesario recuperarlo desde fuera del ViewModel por ningún motivo, pero no tiene que ser así. Tenga en cuenta que es un SecureString, no solo una cadena.

public SecureString SecurePassword { private get; set; }

En el xaml, configura un controlador de eventos PasswordChanged.

<PasswordBox PasswordChanged="PasswordBox_PasswordChanged"/>

En el código de atrás:

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

Con este método, su contraseña permanece en un SecureString en todo momento y, por lo tanto, proporciona la máxima seguridad. Si realmente no le importa la seguridad o necesita la contraseña de texto sin cifrar para un método posterior que lo requiere (nota: la mayoría de los métodos .NET que requieren una contraseña también admiten una opción SecureString, por lo que es posible que no necesite una contraseña de texto simple) incluso si cree que lo hace), puede usar la propiedad Contraseña en su lugar. Me gusta esto:

(Propiedad ViewModel)

public string Password { private get; set; }

(Código detrás)

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

Si quisiera mantener las cosas fuertemente escritas, podría sustituir el modelo (dinámico) con la interfaz de su ViewModel. Pero en realidad, los enlaces de datos "normales" tampoco están fuertemente tipados, así que no es tan importante.

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

El mejor de todos los mundos: su contraseña es segura, su ViewModel solo tiene una propiedad como cualquier otra propiedad, y su vista es independiente sin necesidad de referencias externas.


Una solución simple sin violar el patrón de MVVM es introducir un evento (o delegado) en el ViewModel que recolecta la contraseña.

En el ViewModel :

public event EventHandler<HarvestPasswordEventArgs> HarvestPassword;

con estos EventArgs:

class HarvestPasswordEventArgs : EventArgs { public string Password; }

en la vista , suscríbase al evento al crear el modelo de vista y complete el valor de la contraseña.

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

En ViewModel , cuando necesite la contraseña, puede disparar el evento y recoger la contraseña desde allí:

if (HarvestPassword == null) //bah return; var pwargs = new HarvestPasswordEventArgs(); HarvestPassword(this, pwargs); LoginHelpers.Login(Username, pwargs.Password);


Utilicé este método y pasé el cuadro de contraseña, aunque esto viola el MVVM, era esencial para mí porque estaba usando un control de contenido con plantilla de datos para mi inicio de sesión en mi shell, que es un entorno complejo de shell. Así que acceder al código detrás de la shell hubiera sido una mierda.

Pasar el buzón de contraseña creo que es lo mismo que acceder al control desde el código que hay detrás, por lo que sé. Estoy de acuerdo con las contraseñas, no las guardo en la memoria, etc. En esta implementación no tengo propiedades para la contraseña en el modelo de vista.

Comando de botón

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


He hecho como

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>

DO#:

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

¡Esto funciona para mi!


Aquí está mi opinión sobre esto:

  1. El uso de una propiedad adjunta para enlazar la contraseña anula el propósito de asegurar la contraseña. La propiedad Contraseña de un cuadro de contraseña no es vinculable por una razón.

  2. Al pasar el cuadro de contraseña como parámetro de comando, ViewModel estará al tanto del control. Esto no funcionará si planea hacer que su ViewModel sea reutilizable entre plataformas. No haga que su VM esté al tanto de su Vista o de cualquier otro control.

  3. No creo que la introducción de una nueva propiedad, una interfaz, la suscripción a eventos de cambio de contraseña o cualquier otra cosa complicada sea necesaria para una tarea sencilla de proporcionar la contraseña.

XAML

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

Código detrás: usar código detrás no necesariamente viola MVVM. Mientras no le pongas ninguna lógica de negocios.

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

ViewModel

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


Bueno, mi respuesta es más simple solo en el patrón MVVM

en clase viewmodel

public string password; PasswordChangedCommand = new DelegateCommand<RoutedEventArgs>(PasswordChanged); Private void PasswordChanged(RoutedEventArgs obj) { var e = (WatermarkPasswordBox)obj.OriginalSource; //or depending or what are you using var e = (PasswordBox)obj.OriginalSource; password =e.Password; }

La propiedad de contraseña del PasswordBox que gana proporciona o WatermarkPasswordBox que proporciona XCeedtoolkit proporciona un RoutedEventArgs para que pueda enlazarlo.

ahora en xmal vista

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


En la aplicación universal de Windows.

Puede utilizar este código con la propiedad "Contraseña" y vincular con el modelView

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


Para mí, ambas cosas se sienten mal:

  • Implementando propiedades de contraseña de texto claro
  • Enviando el PasswordBoxcomo un parámetro de comando al ViewModel

Transferencia de SecurePassword (instancia de SecureString) como lo describe Steve en CO parece aceptable. Prefiero Behaviorscodificar detrás, y también tenía el requisito adicional de poder restablecer la contraseña desde el modelo de visualización.

Xaml ( Passwordes la propiedad ViewModel):

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

Comportamiento:

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


Si quieres que lo combine todo en un solo control y un comando

<PasswordBox Name="PasswordBoxPin" PasswordChar="*"> <PasswordBox.InputBindings> <KeyBinding Key="Return" Command="{Binding AuthentifyEmpCommand}" CommandParameter="{Binding ElementName=PasswordBoxPin}"/> </PasswordBox.InputBindings> </PasswordBox>

Y en tu Vm (como mostró Konamiman)

public void AuthentifyEmp(object obj) { var passwordBox = obj as PasswordBox; var password = passwordBox.Password; } private RelayCommand _authentifyEmpCommand; public RelayCommand AuthentifyEmpCommand => _authentifyEmpCommand ?? (_authentifyEmpCommand = new RelayCommand(AuthentifyEmp, null));


Utilicé una verificación de autenticación seguida de una sub llamada por una clase de mediador a la Vista (que también implementa una verificación de autenticación) para escribir la contraseña en la clase de datos.

No es una solución perfecta; sin embargo, solucionó mi problema de no poder mover la contraseña.


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

;) fácil!


Como se mencionó anteriormente, la VM no debe tener conocimiento de la Vista, pero pasar el PasswordBox completo parece ser el enfoque más simple. Entonces, en lugar de convertir el parámetro pasado a PasswordBox, use Reflection para extraer de él la propiedad Password. En este caso, VM espera algún tipo de contenedor de contraseñas con contraseña de propiedad (estoy usando RelayCommands de 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; } }

Se puede probar fácilmente con clase anónima:

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


Encontrará una solución para el cuadro de contraseña en la aplicación de ejemplo ViewModel del proyecto WPF Application Framework (WAF) .

Sin embargo, Justin tiene razón. No pase la contraseña como texto simple entre View y ViewModel. Utilice SecureString en su lugar (ver el cuadro de contraseña de MSDN).


Es muy sencillo . Crea otra propiedad para la contraseña y enlaza esto con TextBox

Pero todas las operaciones de entrada se realizan con propiedad de contraseña real

cadena privada _Password;

public string PasswordChar { get { string szChar = ""; foreach(char szCahr in _Password) { szChar = szChar + "*"; } return szChar; } set { _PasswordChar = value; NotifyPropertyChanged(); } }

cadena pública Contraseña {get {return _Password; }

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


Estoy usando una solución concisa para MVVM que no se ha mencionado todavía. Primero, nombro el PasswordBox en XAML:

<PasswordBox x:Name="Password" />

Luego agrego una sola llamada de método al constructor de vista:

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

Y eso es.El modelo de vista recibirá una notificación cuando se adjunte a una vista a través de DataContext y otra notificación cuando se desconecte. El contenido de esta notificación se puede configurar a través de las lambdas, pero generalmente es solo una llamada de establecimiento o método en el modelo de vista, pasando el control problemático como parámetro.

Puede ser compatible con MVVM muy fácilmente haciendo que la vista exponga la interfaz en lugar de los controles secundarios.

El código anterior se basa en la clase de ayuda publicada en mi blog.


Para cualquier persona que esté al tanto de los riesgos que impone esta implementación, para sincronizar la contraseña con su ViewModel, simplemente agregue Mode = OneWayToSource .

XAML

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


Para los novatos completos como yo, aquí hay una muestra completa de lo que se Konamimansugirió anteriormente. Gracias 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); } }


Pasé siglos tratando de hacer que esto funcionara. Al final, me di por vencido y solo usé el PasswordBoxEdit de DevExpress.

Es la solución más simple, ya que permite enlazar sin tirar de trucos horribles.

Solución en el sitio web de DevExpress

Para el registro, no estoy afiliado a DevExpress de ninguna manera.