Cómo enlazar a un PasswordBox en MVVM


251

Me he encontrado con un problema con la unión a un P asswordBox. Parece que es un riesgo de seguridad, pero estoy usando el patrón MVVM, así que deseo evitarlo. Encontré un 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 LoginViewModelpara Usernamey Password. Usernameestá bien y funciona como es un TextBox.

Usé el código anterior como se indicó e ingresé esto

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

Cuando tuve el PasswordBoxcomo TextBoxy Binding Path=Passwordentonces la propiedad en mi LoginViewModelfue actualizado.

Mi código es muy simple, básicamente tengo un Commandpara mi Button. Cuando presiono se CanLoginllama y si devuelve verdadero, llama Login.
Puedes ver que reviso mi propiedad Usernameaquí, lo que funciona muy bien.

En Loginenvío a lo largo de mi servicio una Usernamey Password, Usernamecontiene datos de mi View, pero PasswordesNull|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 problema, pero en mi ViewModelel Passwordusuario está vacía.

¿Estoy haciendo algo mal o me falta un paso?

Puse un punto de interrupción y, efectivamente, el código ingresó a la clase auxiliar estática, pero nunca actualiza mi Passworden mi ViewModel.


3
Bueno, resulta que el código no funcionó, pero probé un código alternativo aquí y funciona perfectamente. blog.functionalfun.net/2008/06/…
mark smith

55
¿Pasar todo el control de contraseña no va en contra de separar la vista del modelo de vista?

Respuestas:


164

Lo siento, pero lo estás haciendo mal.

Las personas deben tener las siguientes pautas de seguridad tatuadas en el interior de sus párpados:
nunca guarde contraseñas de texto sin formato en la memoria.

La razón por la que WPF / Silverlight PasswordBoxno expone un DP para la Passwordpropiedad está relacionada con la seguridad.
Si WPF / Silverlight mantuviera un DP Password, sería necesario que el marco mantuviera la contraseña sin cifrar en la memoria. Lo que se considera un vector de ataque de seguridad bastante problemático. La PasswordBoxusos cifrado de memoria (de clases) y la única manera de acceder a la contraseña es a través de la propiedad de CLR.

Sugeriría que al acceder a la PasswordBox.Passwordpropiedad CLR se abstenga de colocarla en cualquier variable o como valor para cualquier propiedad.
Mantener su contraseña en texto sin formato en la RAM del equipo cliente es un no-no de seguridad.
Así que deshazte de eso public string Password { get; set; }que tienes allá arriba.

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

Sé que esto rompe el patrón MVVM, pero nunca debe vincularse a PasswordBox.PasswordDP adjunto, almacenar su contraseña en ViewModel o cualquier otra travesura similar.

Si está buscando una solución sobredimensionada, aquí tiene una:
1. Cree la IHavePasswordinterfaz con un método que devuelva el texto claro de la contraseña.
2. Haga que UserControlimplemente una IHavePasswordinterfaz.
3. Registre la UserControlinstancia con su IoC como implementando la IHavePasswordinterfaz.
4. Cuando se realiza una solicitud del servidor que requiere su contraseña, llame a su IoC para la IHavePasswordimplementación y solo para obtener la codiciada contraseña.

Solo mi opinión sobre eso.

- justin


19
¿No podría usar SecureString en la VM para WPF para resolver este problema? No parece que haya algo para Silverlight.
Bryant

35
Estoy de acuerdo con su intención y el mensaje que está transmitiendo, pero su respuesta implica que la cadena de contraseña nunca está en la memoria si sigue este enfoque. El valor de la contraseña estará en la memoria desde el momento en que el usuario la ingrese. Eliminar la propiedad que contiene su frase de contraseña es una buena idea y limitará las copias de su contraseña que quedan para que el recolector de basura pueda cosechar o que tal vez pueda encontrar otro código administrado y no administrado que se ejecute como parte de su programa, pero lo hará No ocultarlo por completo.
IanNorton

182
Para la mayoría de los casos, no necesita ese nivel de seguridad. ¿Cuál es el punto de dificultar eso cuando hay tantas otras formas de robar contraseñas? Al menos WPF debería haber permitido el uso de SecureString como dijo @Bryant.
chakrit

335
Si los malos tienen acceso a la RAM de su máquina, tiene problemas más grandes que el robo de su contraseña.
Cameron MacFarland

13
Durante años, he estado usando un control de usuario personalizado que se comporta como PasswordBox, pero solo devuelve el valor de texto como SecureString. Sí, esto evita que Snoop muestre la contraseña en texto sin formato. Sin embargo, el valor de texto sin formato de SecureString todavía se puede extraer con bastante facilidad y solo disuade a los piratas informáticos novatos. Si su sistema corre el riesgo de emplear secretamente registradores de claves y rastreadores como Snoop, debe volver a evaluar la seguridad de su sistema.
Mike Christian

199

Mis 2 centavos:

Una vez desarrollé un cuadro de 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 vinculación de contraseña simplemente pasando el control PasswordBox como parámetro al comando adjunto al botón "Aceptar". Entonces, 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 ViewModel, el Executemétodo 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 ligeramente el patrón MVVM ya que ahora ViewModel sabe algo acerca de cómo se implementa la Vista, pero en ese proyecto en particular podría pagarlo. Espero que sea útil para alguien también.


Hola Konamiman, cuando se llama al método Execute. En mi modelo de vista, tengo una clase User (login, pass) y un comando de autenticación. ¿Cómo puedo usar Execute en ese contexto?

3
Muy útil, gracias. para su información, alguien podría estar acostumbrado a ver algo como _loginCommand = new RelayCommand (param => Login (UserName, (PasswordBox) param), param => CanLogIn);
Chuck Rostance el

55
esta es una solución aceptable pero falla por algo como una combinación de contraseña + confirmación de contraseña
Julien

Hola Konamiman, estoy usando tu solución pero no funciona en la aplicación Windows 8.1 Store. He hecho esta pregunta: stackoverflow.com/questions/26221594/…
VansFannel

2
¡Gracias por esto! Esto resolvió un gran problema que tuve al mover los datos del hilo de la interfaz de usuario al hilo principal del programa. Asegúrese de implementar el enfoque SecureString y ~ elimine la contraseña lo antes posible ~. Arrojarlo. Deséchelo. Limpialo. Haz lo que necesitas hacer. Además, asegúrese de implementar IDisposable.
Steven C. Britton

184

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

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

Code Behind! = Infracción automática de MVVM. Todo depende de lo que hagas con él. En este caso, solo 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 ViewModel, solo una propiedad simple. Lo hice "escribir solo" ya que no debería ser necesario recuperarlo desde fuera del ViewModel por ninguna razón, 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 detrá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 SecureString en todo momento y, por lo tanto, proporciona la máxima seguridad. Si realmente no le importa la seguridad o si 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 sin cifrar) incluso si cree que lo hace), puede usar la propiedad Contraseña. 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 desea mantener las cosas fuertemente tipadas, puede sustituir el reparto (dinámico) con la interfaz de su ViewModel. Pero realmente, los enlaces de datos "normales" tampoco están fuertemente tipados, por lo que no es un gran problema.

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 autónoma sin necesidad de referencias externas.


1
¡Este me parece bien! Si quisieras ser muy estricto en el lado de la seguridad, no estoy seguro de que esto sea suficiente, pero para mí es un punto medio perfecto. ¡Gracias!
jrich523

3
Gracias por la practicidad sobre el dogma rígido sobre MVVM y paranoia. Funciona muy bien, gracias.
Bruce Pierson

2
El ejemplo de SecureString sería genial con esta extensión blogs.msdn.com/b/fpintos/archive/2009/06/12/…
Ayman

1
Ciertamente agradable. Desearía que MS hubiera agregado una contraseña DP de tipo SecureString a este control.
Keith Hill

1
Esta es la respuesta perfecta, ya que mantiene la seguridad y MVVM.
LoRdPMN

20

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

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

Sin requerir nombrar a PasswordBox: CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=PasswordBox}}"(nota: no RelativeSource Self ).
wondra

Esta solución viola el patrón MVVM.
BionicCode

13

Esto funciona bien para mí.

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

3
¿Qué pasa con CommandParameter = "{Binding ElementName = MyPasswordBox, Path = SecurePassword"}?
LukeN

2
LukeN, esto no funciona (al menos para mí). Probablemente por la misma razón: SecurePassword no es una propiedad de dependencia.
vkrzv

Suponiendo que ICommandse implementa en el modelo de vista, esta solución violaría el patrón MVVM.
BionicCode

9

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

En el modelo de vista :

public event EventHandler<HarvestPasswordEventArgs> HarvestPassword;

con estos EventArgs:

class HarvestPasswordEventArgs : EventArgs
{
    public string Password;
}

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

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

En ViewModel , cuando necesita la contraseña, puede activar el evento y cosechar la contraseña desde allí:

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

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

LoginHelpers.Login(Username, pwargs.Password);

Lo único que falta es que al suscribir una vista a un evento de modelo de vista, debe usar a WeakEventManager<TEventSource, TEventArgs>para evitar pérdidas de memoria. Muchas veces la vista no tendrá la misma duración que el modelo de vista. WeakEventManager<IViewModel, EventArgs>.AddHandler(iViewModelInstance, nameof(IViewModel.Event), eventHandlerMethod);
Todd A. Stedel

Prefiero esta solución, ya que es simple, no viola MVVM, tiene un código mínimo detrás, permite el uso correcto de passwordbox (si usa ´SecurePassword´ en su lugar). También ahora es sencillo implementar otros métodos HarvestPassword ahora (como SmartCard ...)
Matt

8

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

Lo mejor hasta ahora es apegarse a una propiedad adjunta personalizada y unirse a su SecureStringpropiedad en su modelo de vista. Mantenlo ahí todo el tiempo que puedas. Siempre que necesite acceso rápido a la contraseña simple, conviértala temporalmente en una cadena no segura utilizando el código a continuación:

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úrese de permitir que el GC recopile su elemento de IU, así que resista la necesidad de utilizar un controlador de eventos estático para el PasswordChangedevento en el PasswordBox. También descubrí una anomalía en la que el control no estaba actualizando la interfaz de usuario cuando usaba la SecurePasswordpropiedad para configurarla, por lo que estoy copiando la contraseña 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));
   }
}

El RequiredSecureStringes 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.


7

Publiqué un GIST aquí que es un cuadro de contraseña enlazable.

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
mientras que esto no es malo, se pierde la capacidad de conjunto simple como atributos de relleno y tabindex
Julien

1
Taylor, subrayé la esencia para que esté disponible en la respuesta. (Parecía una respuesta de solo enlace de lo contrario ... solo tratando de evitar que esto se elimine como tal). Siéntase libre de meterse con el contenido incorporado.
Lynn Desmoronando

@Julien pero puedes arreglar eso con estilos. Resuelvo este problema de una manera similar, pero utilizo un ContentControlque luego puede usar un PasswordBox como el contenido y el estilo que en XAML como le convenga. El propósito de la ContentControles simplemente suscribirse al PasswordChangedevento y exponer una propiedad enlazable en dos direcciones. Con todo, son 65 líneas de código y más o menos lo que hace esta clase de decorar. Vea aquí mi esencia de lo siguiente gist.github.com/leidegre/c7343b8c720000fe3132
John Leidegren

6

Esta implementación es ligeramente diferente. Pasa un cuadro de contraseña a la Vista a través del enlace de una propiedad en ViewModel, no utiliza ningún parámetro de comando. ViewModel permanece ignorante de la vista. Tengo un proyecto VB vs 2010 que se puede descargar de 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 funciona bien para mí. Eso no significa que piense que es la forma correcta o la mejor. Es solo una implementación del uso de PasswordBox y el patrón MvvM.

Básicamente, usted crea una propiedad pública de solo lectura a la que la Vista puede enlazar como 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, vincula 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 de la caja de contraseña. También uso un PasswordAccessor (solo una función de cadena) para devolver el valor de la contraseña al iniciar sesión o para cualquier otra cosa para la que desee la contraseña. 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 cadena de contraseña es de solo lectura sin ningún almacén 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 ViewModel, me aseguro de que el Accesor 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 que la cadena de Contraseña diga para iniciar sesión, solo obtengo la propiedad Contraseña de objetos de usuario que realmente invoca la Función para tomar la contraseña y devolverla, luego el Usuario Objeto no almacena la contraseña real. Ejemplo: estaría en 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. ViewModel no necesita ningún conocimiento de los controles de la vista. La vista solo se vincula a la propiedad en el modelo de vista, no es diferente al enlace de vista a una imagen u otro recurso. En este caso, ese recurso (Propiedad) resulta 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 a la que solo accede la Propiedad.


6

Para resolver el problema de OP sin romper el MVVM, usaría un convertidor de valor personalizado y un contenedor para el valor (la contraseña) que debe recuperarse 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 usa IWrappedParameter<T>, no necesita tener ningún conocimiento sobre PasswordBoxWrapperni PasswordBoxConverter. De esta manera, puede aislar el PasswordBoxobjeto 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}}" />

solución muy elegante imo. He basado el mío en esto. La única diferencia: paso SecureString SecurePassword a la función de inicio de sesión en lugar de String Password. para que no haya cadenas sin cifrar con passwort volando alrededor de la memoria.
Llámame zanahoria

Ha pasado un tiempo, pero parece que no puedo hacer que esto funcione debido a mi RelayCommand. ¿te importaría agregar el tuyo?
Ecnerwal

5

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 contra ella.

La solución que funcionó para mí 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 hace significar una línea de código en el código subyacente de la vista.

Entonces, en mi Login.xaml tengo

<PasswordBox x:Name="PasswordBox"/>

y en Login.xaml.cs tengo

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

luego en LoginViewModel.cs tengo definido el PasswordHandler

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

y cuando es necesario iniciar sesión, el código invoca el 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 a un método anónimo que me permita entregar cualquier contraseña que quiera usar en la prueba.


4

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

Simplemente envolví un PasswordBoxen UserControle implementé un DependencyPropertypara 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 ay SecureStringla PasswordBox.Passwordpropiedad. Durante el foreachciclo, cada personaje queda expuesto, pero es muy breve. Honestamente, si le preocupa que su aplicación WPF se vea comprometida por esta breve exposición, tiene problemas de seguridad más importantes que deben manejarse.

Lo bueno de esto es que no estás rompiendo ninguna regla MVVM, ni siquiera las "puristas", ya que esto es un UserControl, así que está permitido tener código subyacente. Cuando lo está utilizando, puede tener una comunicación pura entre Viewy ViewModelsin que tenga VideModelconocimiento de ninguna parte Viewo de la fuente de la contraseña. Solo asegúrate de estar atado a SecureStringtu 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}"/>

He intentado implementar esto, pero obtienes un bucle infinito cuando actualizas la contraseña en la interfaz de usuario; porque if (Password != secure)siempre será falso ya que SecureString no anula iguales. ¿Alguna idea?
simonalexander2005


2

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

Al pasar el cuadro de contraseña, creo que es lo mismo que acceder al control desde el código detrás de lo que sé. Acepto las contraseñas, no las guardo en la memoria, etc. En esta implementación no tengo propiedad 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);
}

Esta es una clara violación del patrón MVVM. El patrón no permite manejar controles en el modelo de vista.
BionicCode

2

Para mí, estas dos cosas se sienten mal:

  • Implementación de propiedades de contraseña de texto sin cifrar
  • Enviar PasswordBoxcomo un parámetro de comando a ViewModel

La transferencia de SecurePassword (instancia de SecureString) como lo describe Steve en CO parece aceptable. yo prefieroBehaviors codificar detrás, y también tuve el requisito adicional de poder restablecer la contraseña desde el modelo de vista.

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

    }
}

2

Para novatos completos como yo, aquí hay una muestra de trabajo 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);
    }
}

Esta es una clara violación del patrón MVVM. El patrón no permite manejar controles en el modelo de vista.
BionicCode

1

Como puede ver, estoy vinculado a la Contraseña, pero tal vez se vincule a la clase estática.

Es una propiedad adjunta . Este tipo de propiedad se puede aplicar a cualquier tipo de DependencyObject, no solo al tipo en el que se declara. Por lo tanto, aunque se declare en la PasswordHelperclase estática, se aplica a la PasswordBoxque 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}"/>

1

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>

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

¡Esto funciona para mi!


Me das una buena idea. :)
Andre Mendonca

1

Como se mencionó anteriormente, VM no debe ser consciente de la Vista, pero pasar todo PasswordBox parece el enfoque más simple. Entonces, en lugar de enviar el parámetro pasado a PasswordBox, use Reflection para extraer la propiedad Password de él. En este caso, VM espera algún tipo de Contenedor de contraseña con la 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 la clase anónima:

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

Los comentarios no son para discusión extendida; Esta conversación se ha movido al chat .
Samuel Liew

1

En la aplicación universal de windows

puede usar este código con la propiedad "Contraseña" y vincular con modelView

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


1

Para cualquiera que conozca los riesgos que impone esta implementación, para que la contraseña se sincronice con su ViewModel simplemente agregue Mode = OneWayToSource .

XAML

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

¿Por qué no simplemente hacer OneWayToSource?
BK

@BK editó mi respuesta. Gracias.
Kevin

1
¿No debería estar el Modo dentro de las llaves de unión?
Mat

@Mat Yap. Gracias.
Kevin

1

Aquí está mi opinión al respecto:

  1. El uso de una propiedad adjunta para vincular la contraseña anula el propósito de proteger la contraseña. La propiedad Contraseña de un cuadro de contraseña no se puede enlazar por un motivo.

  2. Pasar el cuadro de contraseña como parámetro de comando hará que ViewModel conozca el control. Esto no funcionará si planea hacer que su plataforma cruzada reutilizable ViewModel. No haga que su VM conozca su Vista o cualquier otro control.

  3. No creo que sea necesario introducir una nueva propiedad, una interfaz, suscribirse a eventos de cambio de contraseña o cualquier otra cosa complicada para una tarea simple de proporcionar la contraseña.

XAML

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

Código subyacente: el uso de código subyacente no necesariamente viola MVVM. Siempre y cuando no pongas ninguna lógica de negocios.

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

ViewModel

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

0

Puede encontrar una solución para PasswordBox en la aplicación de ejemplo ViewModel de WPF Application Framework (WAF) .

Sin embargo, Justin tiene razón. No pase la contraseña como texto sin formato entre View y ViewModel. Utilice SecureString en su lugar (consulte MSDN PasswordBox).


2
La forma en que se utiliza en Pop3SettingsView de WAF es divertida. PasswordBox passwordBox = (PasswordBox) remitente; if (ViewModel! = null) {ViewModel.Pop3Password = contraseñaBox.Password; } Pop3Password de ViewModel es la propiedad de cadena. así que tampoco es seguro ... mejor usar la propiedad adjunta
Michael Sync

0

Usé una verificación de autenticación seguida de un sub llamado por una clase mediadora 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.


0

Estoy usando una solución sucinta compatible con MVVM que aún no se ha mencionado. Primero, llamo PasswordBox en XAML:

<PasswordBox x:Name="Password" />

Luego agrego una llamada de método único al constructor de la 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 método o de establecimiento en el modelo de vista, pasando el control problemático como parámetro.

Se puede hacer MVVM amigable muy fácilmente al hacer que la vista exponga la interfaz en lugar de los controles secundarios.

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


0

Pasé años intentando que esto funcionara. Al final, me di por vencido y simplemente utilicé PasswordBoxEdit de DevExpress.

Es la solución más simple, ya que permite la unión sin hacer ningún truco horrible.

Solución en el sitio web DevExpress

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


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

;) fácil!


0

Es muy simple . Cree otra propiedad para contraseña y vincúlela con TextBox

Pero todas las operaciones de entrada se realizan con la 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();
        }
    }

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

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


La razón por la que el cuadro de contraseña no se puede enlazar es porque no queremos almacenar la contraseña en una cadena clara. La cadena es inmutable y no estamos seguros de cuánto tiempo permanecerá en la memoria.
Lance

0

Bueno, mi respuesta es más simple solo para 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 de PasswordBox que Win proporciona o WatermarkPasswordBox que XCeedtoolkit proporciona genera un RoutedEventArgs para que pueda vincularlo.

ahora en vista xmal

<Xceed:WatermarkPasswordBox Watermark="Input your Password" Grid.Column="1" Grid.ColumnSpan="3" Grid.Row="7" PasswordChar="*" >

        <i:Interaction.Triggers>

            <i:EventTrigger EventName="PasswordChanged">

                <prism:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path= DataContext.PasswordChangedCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path= Password}"/>

            </i:EventTrigger>

        </i:Interaction.Triggers>

    </Xceed:WatermarkPasswordBox>

o

<PasswordBox Grid.Column="1" Grid.ColumnSpan="3" Grid.Row="7" PasswordChar="*" >

        <i:Interaction.Triggers>

            <i:EventTrigger EventName="PasswordChanged">

                <prism:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path= DataContext.PasswordChangedCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path= Password}"/>

            </i:EventTrigger>

        </i:Interaction.Triggers>

    </PasswordBox>

0

Envíe un SecureStringal modelo de vista usando un comportamiento adjunto yICommand

No hay nada de malo con el código subyacente al implementar MVVM. MVVM es un patrón arquitectónico que tiene como objetivo separar la vista del modelo / lógica empresarial. MVVM describe cómo lograr este objetivo de forma reproducible (el patrón). No le importan los detalles de implementación, como cómo estructura o implementa la vista. Simplemente dibuja los límites y define qué es la vista, el modelo de vista y cuál es el modelo en términos de la terminología de este patrón.

MVVM no se preocupa por el lenguaje (XAML o C #) o el compilador (partial clases). Ser independiente del idioma es una característica obligatoria de un patrón de diseño; debe ser neutral en cuanto al idioma.

Sin embargo, el código subyacente tiene algunos inconvenientes, como hacer que la lógica de la interfaz de usuario sea más difícil de entender, cuando se distribuye ampliamente entre XAML y C #. Pero la implementación más importante de la lógica de la interfaz de usuario u objetos como plantillas, estilos, disparadores, animaciones, etc. en C # es muy compleja y fea / menos legible que usar XAML. XAML es un lenguaje de marcado que utiliza etiquetas y anidamiento para visualizar la jerarquía de objetos. Crear UI usando XAML es muy conveniente. Aunque hay situaciones en las que está bien elegir implementar la lógica de la interfaz de usuario en C # (o código subyacente). Manejando elPasswordBox es un ejemplo.

Por esta razón, manejando el PasswordBoxcódigo subyacente manejando elPasswordBox.PasswordChanged , no constituye una violación del patrón MVVM.

Una violación clara sería pasar un control (el PasswordBox) al modelo de vista. Muchas soluciones recomiendan este ejemplo, la bahía de pasar la instancia de la PasswordBoxque ICommand.CommandParameterel modelo de vista. Obviamente una recomendación muy mala e innecesaria.

Si no le importa usar C #, pero solo quiere mantener limpio su archivo de código subyacente o simplemente quiere encapsular una lógica de comportamiento / UI, siempre puede hacer uso de las propiedades adjuntas e implementar un comportamiento adjunto.

Opuesto al infame ayudante de difusión amplia que permite la vinculación a la contraseña de texto sin formato (muy mal antipatrón y riesgo de seguridad), este comportamiento utiliza un ICommandpara enviar la contraseña SecureStringal modelo de vista, siempre que se PasswordBoxproduce el PasswordBox.PasswordChangedevento.

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);
  }
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.