Como vincular a um PasswordBox no MVVM


251

Eu vim através de um problema com a ligação a um P asswordBox. Parece ser um risco à segurança, mas estou usando o padrão MVVM, por isso desejo contornar isso. Encontrei algum código interessante aqui (alguém usou isso ou algo parecido?)

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

Tecnicamente, parece ótimo, mas não tenho certeza de como recuperar a senha.

Basicamente, tenho propriedades no meu LoginViewModelfor Usernamee Password. Usernameestá bem e está funcionando como é um TextBox.

Usei o código acima, como indicado, e entrei neste

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

Quando eu tinha o PasswordBoxas TextBoxe, em Binding Path=Passwordseguida, a propriedade no meu LoginViewModelfoi atualizada.

Meu código é muito simples, basicamente eu tenho um Commandpara o meu Button. Quando pressiono, CanLoginé chamado e, se retornar verdadeiro, chama Login.
Você pode ver que eu checo minha propriedade Usernameaqui, o que funciona muito bem.

Ao Loginenviar para o meu serviço a Usernamee Password, Usernamecontém dados do meu Viewmas PasswordéNull|Empty

private DelegateCommand loginCommand;

public string Username { get; set; }
public string Password { get; set; }


public ICommand LoginCommand
{
    get
    {
        if (loginCommand == null)
        {
            loginCommand = new DelegateCommand(
                Login, CanLogin );
        }
        return loginCommand;
    }
}

private bool CanLogin()
{
    return !string.IsNullOrEmpty(Username);
}

private void Login()
{
    bool result = securityService.IsValidLogin(Username, Password);

    if (result) { }
    else { }
}

É isso que estou fazendo

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

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

Eu tenho o meu TextBox, isso não é problema, mas no meu ViewModelo Passwordestá vazio.

Estou fazendo algo errado ou faltando um passo?

Eu coloquei um ponto de interrupção e, com certeza, o código entra na classe auxiliar estática, mas ele nunca atualiza o meu Passwordno meu ViewModel.


3
Bem, acontece que o código não funcionou, mas eu tentei um código alternativo aqui e funciona perfeitamente. blog.functionalfun.net/2008/06/…
mark smith

5
A passagem de todo o controle da caixa de senhas não contraria a separação da visualização do modelo de visualização?

Respostas:


164

Desculpe, mas você está fazendo errado.

As pessoas devem ter as seguintes diretrizes de segurança tatuadas na parte interna das pálpebras:
nunca mantenha senhas de texto sem formatação na memória.

O motivo pelo qual o WPF / Silverlight PasswordBoxnão expõe um DP para a Passwordpropriedade está relacionado à segurança.
Se o WPF / Silverlight mantivesse um DP Password, seria necessário que a estrutura mantivesse a própria senha não criptografada na memória. O que é considerado um vetor de ataque de segurança bastante problemático. oPasswordBox usa memória criptografada (das sortes) e a única maneira de acessar a senha é através da propriedade CLR.

Sugiro que, ao acessar a PasswordBox.Passwordpropriedade CLR, você evite colocá-la em qualquer variável ou como valor para qualquer propriedade.
Manter sua senha em texto sem formatação na RAM da máquina cliente é um problema de segurança.
Então se livre dissopublic string Password { get; set; } você tem lá em cima.

Ao acessar PasswordBox.Password , basta tirá-lo e enviá-lo para o servidor o mais rápido possível. Não mantenha o valor da senha por perto e não a trate como faria com qualquer outro texto da máquina cliente. Não mantenha senhas de texto não criptografado na memória.

Eu sei que isso quebra o padrão MVVM, mas você nunca deve se ligar a PasswordBox.Password Attached DP, armazenar sua senha no ViewModel ou quaisquer outras travessuras semelhantes.

Se você está procurando uma solução com arquitetura demais, aqui está uma:
1. Crie a IHavePasswordinterface com um método que retorne o texto não criptografado da senha.
2. Tenha seu UserControlimplementar uma IHavePasswordinterface.
3. Registre a UserControlinstância no seu IoC como implementando a IHavePasswordinterface.
4. Quando uma solicitação de servidor que exige sua senha estiver ocorrendo, ligue para sua IoC para a IHavePasswordimplementação e somente obtenha a senha muito cobiçada.

Apenas a minha opinião sobre isso.

- Justin


19
Não foi possível usar o SecureString na VM para WPF para resolver esse problema? Não parece que exista algo para o Silverlight.
Bryant

35
Concordo com sua intenção e a mensagem que você está transmitindo, mas sua resposta implica que a sequência de senhas nunca estará na memória se você seguir esta abordagem. O valor da senha estará na memória a partir do momento em que o usuário a digitar. A eliminação da propriedade que contém sua senha é uma boa idéia e limitará as cópias de sua senha deixadas para o coletor de lixo colher ou que talvez possam ser encontradas por outro código gerenciado e não gerenciado em execução como parte do seu programa, mas serão não esconda tudo.
precisa saber é o seguinte

182
Na maioria dos casos, você não precisa desse nível de segurança. Qual o sentido de dificultar uma coisa quando existem muitas outras maneiras de roubar senhas? Pelo menos o WPF deveria ter permitido o uso do SecureString como o @Bryant disse.
Chakrit

335
Se os bandidos tiverem acesso à RAM da sua máquina, você terá problemas maiores do que eles roubando sua senha.
Cameron MacFarland

13
Por anos, tenho usado um controle de usuário personalizado que se comporta exatamente como o PasswordBox, mas retorna apenas o valor do texto como SecureString. Sim, isso impede que o Snoop exiba a senha em texto sem formatação. No entanto, o valor de texto sem formatação do SecureString ainda pode ser extraído com bastante facilidade e apenas detém os hackers iniciantes. Se o seu sistema corre o risco de empregar secretamente key loggers e sniffers como o Snoop, você deve reavaliar a segurança do sistema.
Mike Christian

199

Meus 2 centavos:

Desenvolvi uma vez uma caixa de diálogo de login típica (caixas de usuário e senha, mais o botão "Ok") usando WPF e MVVM. Eu resolvi o problema de ligação de senha simplesmente passando o controle PasswordBox como um parâmetro para o comando anexado ao botão "Ok". Então, na visão que eu tinha:

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

E no ViewModel, o Executemétodo do comando anexado era o seguinte:

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

Isso viola um pouco o padrão MVVM, já que agora o ViewModel sabe algo sobre como o View é implementado, mas nesse projeto em particular eu poderia pagar. Espero que seja útil para alguém também.


Olá Konamiman, quando o método Execute é chamado.No meu viewmodel, tenho uma classe User (login, pass) e um comando authenticate.How posso usar Execute nesse contexto?

3
muito útil, obrigado. fyi, alguém pode estar acostumado a ver algo como _loginCommand = new RelayCommand (param => Login (nome do usuário, (PasswordBox) param), param => CanLogIn);
Chuck Rostance

5
esta é uma solução ok, mas não para algo como um combo senha + confirmação de senha
Julien

Olá Konamiman, estou usando sua solução, mas ela não funciona no aplicativo Windows 8.1 Store. Eu fiz esta pergunta: stackoverflow.com/questions/26221594/…
VansFannel

2
Obrigado por isso! Isso resolveu um enorme problema que tive ao mover os dados do thread da interface do usuário para o thread do programa principal. Certifique-se de implementar a abordagem SecureString e ~ se livre da senha o mais rápido possível ~. Despejá-lo. Descarte-o. Limpe. Faça o que você precisa fazer. Além disso, certifique-se de implementar o IDisposable.
Steven C. Britton

184

Talvez esteja faltando alguma coisa, mas parece que a maioria dessas soluções complica demais as coisas e acaba com práticas seguras.

Este método não viola o padrão MVVM e mantém a segurança completa. Sim, tecnicamente é um código atrasado, mas nada mais é do que uma ligação de "caso especial". O ViewModel ainda não tem conhecimento da implementação do View, o que, em minha opinião, ocorre se você estiver tentando passar o PasswordBox para o ViewModel.

Code Behind! = Violação automática de MVVM. Tudo depende do que você faz com isso. Nesse caso, estamos apenas codificando manualmente uma ligação, portanto tudo isso é considerado parte da implementação da interface do usuário e, portanto, está ok.

No ViewModel, apenas uma propriedade simples. Fiz "somente gravação", pois não deveria ser necessário recuperá-lo de fora do ViewModel por qualquer motivo, mas não precisa ser. Observe que é um SecureString, não apenas uma string.

public SecureString SecurePassword { private get; set; }

No xaml, você configura um manipulador de eventos PasswordChanged.

<PasswordBox PasswordChanged="PasswordBox_PasswordChanged"/>

No código por trás:

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

Com esse método, sua senha permanece em um SecureString o tempo todo e, portanto, fornece segurança máxima. Se você realmente não se importa com segurança ou precisa da senha de texto não criptografado para um método downstream que a exige (observação: a maioria dos métodos .NET que exigem uma senha também oferece suporte a uma opção SecureString, portanto, talvez você não precise realmente de uma senha em texto não criptografado mesmo que você pense que sim), basta usar a propriedade Senha. Como isso:

(Propriedade ViewModel)

public string Password { private get; set; }

(Código por trás)

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

Se você quiser manter as coisas fortemente digitadas, poderá substituir a conversão (dinâmica) pela interface do seu ViewModel. Mas, na verdade, as ligações de dados "normais" também não são fortemente digitadas, portanto, não é tão importante assim.

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

O melhor de todos os mundos - sua senha é segura, seu ViewModel apenas possui uma propriedade como qualquer outra propriedade e seu View é independente, sem necessidade de referências externas.


1
Este parece bom para mim! Se você quisesse ser super rigoroso no lado da segurança, não tenho certeza se isso seria suficiente, mas para mim é um meio termo perfeito. obrigado!
jrich523

3
Obrigado pela praticidade em relação a dogmas rígidos sobre MVVM e paranóia. Funciona muito bem, obrigado.
Bruce Pierson

2
O exemplo do SecureString seria ótimo com esta extensão blogs.msdn.com/b/fpintos/archive/2009/06/12/…
Ayman

1
Bom mesmo. Eu gostaria que a Microsoft tivesse adicionado um DP de senha do tipo SecureString a esse controle.
Keith Hill #

1
Essa é a resposta perfeita, pois mantém a segurança e o MVVM.
LoRdPMN

20

Você pode 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>

E este comando executa o 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

Sem a necessidade de nomear um PasswordBox: CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=PasswordBox}}"(note: not RelativeSource Self ).
Wondra

Esta solução viola o padrão MVVM.
BionicCode 28/04

13

Isso funciona muito bem para mim.

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

3
E CommandParameter = "{Vinculando ElementName = MyPasswordBox, Path = SecurePassword"}?
Luken

2
LukeN, isso não funciona (pelo menos para mim). Provavelmente pelo mesmo motivo - SecurePassword não é propriedade de dependência.
vkrzv

Supondo que isso ICommandseja implementado no modelo de visualização, esta solução violaria o padrão MVVM.
BionicCode

9

Uma solução simples sem violar o padrão MVVM é introduzir um evento (ou delegar) no ViewModel que colha a senha.

No ViewModel :

public event EventHandler<HarvestPasswordEventArgs> HarvestPassword;

com estes EventArgs:

class HarvestPasswordEventArgs : EventArgs
{
    public string Password;
}

na visualização , assine o evento ao criar o ViewModel e preencha o valor da senha.

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

No ViewModel , quando você precisar da senha, poderá disparar o evento e coletar a senha a partir daí:

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

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

LoginHelpers.Login(Username, pwargs.Password);

A única coisa que está faltando é que, ao inscrever uma visualização em um evento de modelo de visualização, você deve usar a WeakEventManager<TEventSource, TEventArgs>para evitar vazamentos de memória. Muitas vezes, a exibição não terá a mesma duração do modelo de exibição. WeakEventManager<IViewModel, EventArgs>.AddHandler(iViewModelInstance, nameof(IViewModel.Event), eventHandlerMethod);
Todd A. Stedel

Prefiro esta solução, pois é simples, não viola o MVVM, possui um código mínimo por trás, permite o uso correto da senha (se você usar o 'SecurePassword'). Também é simples agora para implementar outros métodos HarvestPassword agora (como SmartCard ....)
Matt

8

Passei muito tempo olhando várias soluções. Não gostei da ideia dos decoradores, comportamentos atrapalham a interface de validação, código por trás ... realmente?

O melhor ainda é manter uma propriedade anexada personalizada e vincular à sua SecureStringpropriedade no seu modelo de visualização. Mantenha-o lá o máximo que puder. Sempre que precisar de acesso rápido à senha simples, converta-a temporariamente em uma sequência não segura usando o código abaixo:

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

Certifique-se de permitir que o GC colete seu elemento de interface do usuário; portanto, resista à necessidade de usar um manipulador de eventos estático para o PasswordChangedevento no PasswordBox. Também descobri uma anomalia em que o controle não estava atualizando a interface do usuário ao usar a SecurePasswordpropriedade para configurá-la, pelo que estou copiando a senha Password.

namespace Namespace.Controls
{
    using System.Security;
    using System.Windows;
    using System.Windows.Controls;
    using Namespace.Extensions;

    /// <summary>
    /// Creates a bindable attached property for the <see cref="PasswordBox.SecurePassword"/> property.
    /// </summary>
    public static class PasswordBoxHelper
    {
        // an attached behavior won't work due to view model validation not picking up the right control to adorn
        public static readonly DependencyProperty SecurePasswordBindingProperty = DependencyProperty.RegisterAttached(
            "SecurePassword",
            typeof(SecureString),
            typeof(PasswordBoxHelper),
            new FrameworkPropertyMetadata(new SecureString(),FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, AttachedPropertyValueChanged)
        );

        private static readonly DependencyProperty _passwordBindingMarshallerProperty = DependencyProperty.RegisterAttached(
            "PasswordBindingMarshaller",
            typeof(PasswordBindingMarshaller),
            typeof(PasswordBoxHelper),
            new PropertyMetadata()
        );

        public static void SetSecurePassword(PasswordBox element, SecureString secureString)
        {
            element.SetValue(SecurePasswordBindingProperty, secureString);
        }

        public static SecureString GetSecurePassword(PasswordBox element)
        {
            return element.GetValue(SecurePasswordBindingProperty) as SecureString;
        }

        private static void AttachedPropertyValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            // we'll need to hook up to one of the element's events
            // in order to allow the GC to collect the control, we'll wrap the event handler inside an object living in an attached property
            // don't be tempted to use the Unloaded event as that will be fired  even when the control is still alive and well (e.g. switching tabs in a tab control) 
            var passwordBox = (PasswordBox)d;
            var bindingMarshaller = passwordBox.GetValue(_passwordBindingMarshallerProperty) as PasswordBindingMarshaller;
            if (bindingMarshaller == null)
            {
                bindingMarshaller = new PasswordBindingMarshaller(passwordBox);
                passwordBox.SetValue(_passwordBindingMarshallerProperty, bindingMarshaller);
            }

            bindingMarshaller.UpdatePasswordBox(e.NewValue as SecureString);
        }

        /// <summary>
        /// Encapsulated event logic
        /// </summary>
        private class PasswordBindingMarshaller
        {
            private readonly PasswordBox _passwordBox;
            private bool _isMarshalling;

            public PasswordBindingMarshaller(PasswordBox passwordBox)
            {
                _passwordBox = passwordBox;
                _passwordBox.PasswordChanged += this.PasswordBoxPasswordChanged;
            }

            public void UpdatePasswordBox(SecureString newPassword)
            {
                if (_isMarshalling)
                {
                    return;
                }

                _isMarshalling = true;
                try
                {
                    // setting up the SecuredPassword won't trigger a visual update so we'll have to use the Password property
                    _passwordBox.Password = newPassword.ToUnsecuredString();

                    // you may try the statement below, however the benefits are minimal security wise (you still have to extract the unsecured password for copying)
                    //newPassword.CopyInto(_passwordBox.SecurePassword);
                }
                finally
                {
                    _isMarshalling = false;
                }
            }

            private void PasswordBoxPasswordChanged(object sender, RoutedEventArgs e)
            {
                // copy the password into the attached property
                if (_isMarshalling)
                {
                    return;
                }

                _isMarshalling = true;
                try
                {
                    SetSecurePassword(_passwordBox, _passwordBox.SecurePassword.Copy());
                }
                finally
                {
                    _isMarshalling = false;
                }
            }
        }
    }
}

E o uso de XAML:

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

Minha propriedade no modelo de exibição era assim:

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

O RequiredSecureStringé apenas um validador personalizado simples que possui a seguinte 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 você tem. Uma solução MVVM pura completa e testada.


7

Eu publiquei um GIST aqui que é uma caixa de senha vinculável.

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
Enquanto isso não é ruim, você perde a capacidade de simples conjunto atributos como estofamento e tabindex
Julien

1
Taylor, sublinhei a essência para que ela esteja disponível na resposta. (Caso contrário, parecia uma resposta apenas de link .. apenas tentando evitar que isso fosse excluído.) Sinta-se à vontade para mexer com o conteúdo embutido.
Lynn desintegração

@ Julien, mas você pode consertar isso com estilos. Eu resolvo esse problema de uma maneira semelhante, mas, usando um, ContentControlvocê pode simplesmente usar um PasswordBox como o conteúdo e o estilo que, em XAML, conforme você se encaixa. O objetivo do ContentControlé apenas se inscrever no PasswordChangedevento e expor uma propriedade vinculável bidirecional. Ao todo, são 65 linhas de código e praticamente o que essa classe de decoração faz. Veja aqui a minha lista dos seguintes gist.github.com/leidegre/c7343b8c720000fe3132
John Leidegren

6

Esta implementação é um pouco diferente. Você passa uma caixa de senha para o View através da ligação de uma propriedade no ViewModel, ele não usa nenhum parâmetro de comando. O ViewModel permanece ignorante da exibição. Eu tenho um projeto VB vs 2010 que pode ser baixado do SkyDrive. Exemplo de WPF MvvM PassWordBox.zip https://skydrive.live.com/redir.aspx?cid=e95997d33a9f8d73&resid=E95997D33A9F8D73!511

A maneira como estou usando o PasswordBox em um aplicativo Wpf MvvM é bastante simplista e funciona bem para mim. Isso não significa que eu acho que é o caminho correto ou o melhor caminho. É apenas uma implementação do Using PasswordBox e do MvvM Pattern.

Basicamente, você cria uma propriedade pública somente leitura à qual o Modo de Exibição pode se vincular como um PasswordBox (o controle real). Exemplo:

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

Eu uso um campo de apoio apenas para fazer a auto-inicialização da propriedade.

Em seguida, no Xaml, você vincula o conteúdo de um exemplo de ContentControl ou de contêiner de controle:

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

A partir daí, você tem controle total da caixa de senhas. Eu também uso um PasswordAccessor (apenas uma função de String) para retornar o valor da senha ao fazer login ou o que mais você desejar. No exemplo, tenho uma propriedade pública em um modelo de objeto de usuário genérico. Exemplo:

Public Property PasswordAccessor() As Func(Of String)

No objeto do usuário, a propriedade string da senha é somente leitura, sem nenhum armazenamento de backup, apenas retorna a senha da PasswordBox. Exemplo:

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

Em seguida, no ViewModel, verifique se o Accessor foi criado e definido como a propriedade PasswordBox.Password '. Exemplo:

Public Sub New()
    'Sets the Accessor for the Password Property
    SetPasswordAccessor(Function() ThePassWordBox.Password)
End Sub

Friend Sub SetPasswordAccessor(ByVal accessor As Func(Of String))
    If Not IsNothing(VMUser) Then VMUser.PasswordAccessor = accessor
End Sub

Quando eu preciso da palavra-passe Password, digamos, para login, apenas obtenho a propriedade Senha dos Objetos do Usuário que realmente chama a Função para pegar a senha e devolvê-la, então a senha real não é armazenada pelo Objeto do Usuário. Exemplo: estaria no 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

Isso deve resolver. O ViewModel não precisa de nenhum conhecimento dos controles da vista. A View Just vincula à propriedade no ViewModel, não é diferente da View Binding para uma Imagem ou Outro Recurso. Nesse caso, esse recurso (Propriedade) passa a ser um controle de usuário. Ele permite testar como o ViewModel cria e possui a Propriedade e a Propriedade é independente da Vista. Quanto à segurança, não sei o quão boa é essa implementação. Mas, usando uma Função, o Valor não é armazenado na própria Propriedade, apenas acessada pela Propriedade.


6

Para resolver o problema do OP sem interromper o MVVM, eu usaria um conversor de valor personalizado e um wrapper para o valor (a senha) que deve ser recuperado da caixa de senha.

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

No modelo de exibição:

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

Como o modelo de exibição usa IWrappedParameter<T>, ele não precisa ter nenhum conhecimento sobre PasswordBoxWrappernem PasswordBoxConverter. Dessa forma, você pode isolar o PasswordBoxobjeto do modelo de visualização e não quebrar o padrão MVVM.

Na visualização:

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

solução muito elegante imo. Eu baseei o meu nisso. a única diferença: passo o SecureString SecurePassword para a função de login em vez da String Password. para que não haja seqüências não criptografadas com a senha voando pela memória.
chame-me cenoura

Já faz um tempo, mas parece que não consigo fazer isso funcionar por causa do meu RelayCommand. você se importaria de adicionar o seu?
precisa saber é o seguinte

5

Embora eu concorde que é importante evitar o armazenamento da senha em qualquer lugar, ainda preciso instanciar o modelo de vista sem uma vista e executar meus testes.

A solução que funcionou para mim foi registrar a função PasswordBox.Password no modelo de exibição e fazer com que o modelo de exibição o invoque ao executar o código de login.

Isso faz significar uma linha de código no codebehind da vista.

Então, no meu Login.xaml eu tenho

<PasswordBox x:Name="PasswordBox"/>

e no Login.xaml.cs eu tenho

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

em LoginViewModel.cs, tenho o PasswordHandler definido

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

e quando o login precisa acontecer, o código chama o manipulador para obter a senha da visualização ...

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

Dessa forma, quando eu quiser testar o viewmodel, posso simplesmente definir o PasswordHandler como um método anônimo que permita que eu forneça a senha que desejar usar no teste.


4

Imaginei que misturaria minha solução, já que esse é um problema comum ... e ter muitas opções é sempre uma coisa boa.

Eu simplesmente envolvi um PasswordBoxem um UserControle implementei um DependencyPropertypara poder vincular. Eu estou fazendo tudo que posso para evitar armazenar qualquer texto claro na memória, então tudo é feito através de um SecureStringea PasswordBox.Passwordpropriedade. Durante o foreachloop, cada personagem é exposto, mas é muito breve. Honestamente, se você está preocupado com o comprometimento do seu aplicativo WPF com essa breve exposição, você tem problemas de segurança maiores que devem ser tratados.

A vantagem disso é que você não está violando nenhuma regra do MVVM, mesmo as "puristas", já que essa é uma regra UserControl, portanto é permitido ter código por trás. Ao usá-lo, você pode ter uma comunicação pura entre Viewe ViewModelsem que você VideModelesteja ciente de qualquer parte Viewou da fonte da senha. Apenas certifique-se de estar vinculado ao SecureStringseu 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 (Versão 1 - Não há suporte à ligação bidirecional.)

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 da versão 1:

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

BindablePasswordBox.xaml.cs (Versão 2 - Possui suporte à ligação bidirecional.)

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 da versão 2:

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

Eu tentei implementar isso, mas você obtém um loop infinito quando atualiza a senha na interface do usuário; porque if (Password != secure)sempre será falso, pois o SecureString não substitui iguais. Alguma ideia?
simonalexander2005


2

Eu usei esse método e passei a caixa de senha, embora isso viole o MVVM, era essencial para mim porque eu estava usando um controle de conteúdo com modelo de dados para o meu login no meu shell, que é um ambiente complexo do shell. Portanto, acessar o código por trás do shell teria sido uma porcaria.

Passando a caixa de senha, eu acho que é o mesmo que acessar o controle do código por trás, tanto quanto eu sei. Eu concordo com senhas, não guardo na memória, etc. Nesta implementação, não tenho propriedade para senha no modelo de exibição.

Comando de botão

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 é uma violação clara do padrão MVVM. O padrão não permite manipular controles no modelo de exibição.
BionicCode 28/04

2

Para mim, essas duas coisas parecem erradas:

  • Implementando propriedades de senha de texto não criptografado
  • Enviando o PasswordBoxcomo um parâmetro de comando para o ViewModel

Transferir a SecurePassword (instância SecureString), conforme descrito por Steve no CO, parece aceitável. Eu prefiro Behaviorscodificar por trás e também tinha o requisito adicional de poder redefinir a senha no viewmodel.

Xaml ( Passwordé a propriedade ViewModel):

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

Comportamento:

using System.Security;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;

namespace Evidence.OutlookIntegration.AddinLogic.Behaviors
{
    /// <summary>
    /// Intermediate class that handles password box binding (which is not possible directly).
    /// </summary>
    public class PasswordBoxBindingBehavior : Behavior<PasswordBox>
    {
        // BoundPassword
        public SecureString BoundPassword { get { return (SecureString)GetValue(BoundPasswordProperty); } set { SetValue(BoundPasswordProperty, value); } }
        public static readonly DependencyProperty BoundPasswordProperty = DependencyProperty.Register("BoundPassword", typeof(SecureString), typeof(PasswordBoxBindingBehavior), new FrameworkPropertyMetadata(OnBoundPasswordChanged));

        protected override void OnAttached()
        {
            this.AssociatedObject.PasswordChanged += AssociatedObjectOnPasswordChanged;
            base.OnAttached();
        }

        /// <summary>
        /// Link up the intermediate SecureString (BoundPassword) to the UI instance
        /// </summary>
        private void AssociatedObjectOnPasswordChanged(object s, RoutedEventArgs e)
        {
            this.BoundPassword = this.AssociatedObject.SecurePassword;
        }

        /// <summary>
        /// Reacts to password reset on viewmodel (ViewModel.Password = new SecureString())
        /// </summary>
        private static void OnBoundPasswordChanged(object s, DependencyPropertyChangedEventArgs e)
        {
            var box = ((PasswordBoxBindingBehavior)s).AssociatedObject;
            if (box != null)
            {
                if (((SecureString)e.NewValue).Length == 0)
                    box.Password = string.Empty;
            }
        }

    }
}

2

Para iniciantes completos como eu, aqui está uma amostra completa do que foi Konamimansugerido acima. Obrigado 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 é uma violação clara do padrão MVVM. O padrão não permite manipular controles no modelo de exibição.
BionicCode 28/04

1

Como você pode ver, estou vinculando a senha, mas talvez seja a ligação à classe estática.

É uma propriedade anexa . Esse tipo de propriedade pode ser aplicado a qualquer tipo de DependencyObject, não apenas ao tipo em que é declarado. Portanto, mesmo que seja declarado na PasswordHelperclasse estática, ele é aplicado ao PasswordBoxqual você o usa.

Para usar essa propriedade anexada, basta vinculá-la à Passwordpropriedade no seu ViewModel:

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

1

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

Funciona para mim!


Você me dá uma boa ideia. :)
Andre Mendonca

1

Como mencionado anteriormente, a VM não deve ter conhecimento da Visualização, mas passar a PasswordBox inteira parece a abordagem mais simples. Então, talvez, em vez de transmitir o parâmetro passado para o PasswordBox, use o Reflection para extrair a propriedade Password dele. Nesse caso, a VM espera algum tipo de contêiner de senha com a propriedade Password (estou usando RelayCommands do 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;
    }
}

Pode ser facilmente testado com classe anônima:

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

Comentários não são para discussão prolongada; esta conversa foi movida para o bate-papo .
Samuel Liew

1

No aplicativo universal do windows

você pode usar esse código com a propriedade "Senha" e vincular com o modelView

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


1

Para quem está ciente dos riscos que essa implementação impõe, para sincronizar a senha com o seu ViewModel, basta adicionar Mode = OneWayToSource .

XAML

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

Por que não apenas fazer OneWayToSource?
BK

@BK Editou minha resposta. Obrigado.
Kevin

1
Mode não deveria estar dentro dos aparelhos de ligação?
Mat

@Mat Yap. Obrigado.
Kevin

1

Aqui está a minha opinião:

  1. O uso de uma propriedade anexada para vincular a senha anula o objetivo de protegê-la. A propriedade Senha de uma caixa de senha não é vinculável por um motivo.

  2. Passar a caixa de senha como parâmetro de comando tornará o ViewModel ciente do controle. Isso não funcionará se você planeja tornar sua plataforma cruzada reutilizável do ViewModel. Não faça sua VM ciente de sua exibição ou de qualquer outro controle.

  3. Não acho que seja necessário introduzir uma nova propriedade, uma interface, assinar eventos alterados por senha ou qualquer outra coisa complicada para uma tarefa simples de fornecer a senha.

XAML

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

Código atrasado - o uso do código atrasado não viola necessariamente o MVVM. Contanto que você não coloque nenhuma lógica de negócios nela.

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

ViewModel

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

0

Você encontra uma solução para o PasswordBox no aplicativo de exemplo ViewModel do projeto WPF Application Framework (WAF) .

No entanto, Justin está certo. Não passe a senha como texto sem formatação entre View e ViewModel. Use o SecureString (consulte MSDN PasswordBox).


2
A maneira que é usada no Pop3SettingsView do WAF é engraçada. PasswordBox passwordBox = remetente (PasswordBox); if (ViewModel! = null) {ViewModel.Pop3Password = passwordBox.Password; } Pop3Password of ViewModel é a propriedade string. por isso, não é seguro, bem .. melhor usar a propriedade anexada
Michael sincronização

0

Eu usei uma verificação de autenticação seguida por uma sub chamada por uma classe mediadora para o View (que também implementa uma verificação de autenticação) para gravar a senha na classe de dados.

Não é uma solução perfeita; no entanto, corrigiu meu problema de não conseguir mover a senha.


0

Estou usando uma solução sucinta e amigável para MVVM que ainda não foi mencionada. Primeiro, nomeio o PasswordBox no XAML:

<PasswordBox x:Name="Password" />

Em seguida, adiciono uma única chamada de método ao construtor view:

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

E é isso. O modelo de vista receberá uma notificação quando estiver anexado a uma vista via DataContext e outra notificação quando for desanexado. O conteúdo desta notificação é configurável via lambdas, mas geralmente é apenas uma chamada de setter ou método no modelo de visualização, passando o controle problemático como parâmetro.

Ele pode ser facilmente adaptado ao MVVM com a interface de exibição de exibição em vez de controles filho.

O código acima se baseia na classe auxiliar publicada no meu blog.


0

Passei séculos tentando fazer isso funcionar. No final, desisti e usei o PasswordBoxEdit do DevExpress.

É a solução mais simples de sempre, pois permite encadernar sem fazer truques horríveis.

Solução no site DevExpress

Para que conste, não sou afiliado ao DevExpress de forma alguma.


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

É muito simples . Crie outra propriedade para a senha e vincule-a ao TextBox

Mas todas as operações de entrada são executadas com a propriedade da senha real

private string _Password;

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

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

            return szChar;
        }

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

public string Senha {get {return _Password; }

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


A razão pela qual a caixa da senha não é vinculável é porque não queremos armazenar a senha em uma sequência clara. String é imutável e não temos certeza de quanto tempo permanecerá na memória.
Lance

0

bem, minha resposta é mais simples, apenas no padrão MVVM

no modo de exibição de classe

public string password;

PasswordChangedCommand = new DelegateCommand<RoutedEventArgs>(PasswordChanged);

Private void PasswordChanged(RoutedEventArgs obj)

{

    var e = (WatermarkPasswordBox)obj.OriginalSource;

    //or depending or what are you using

    var e = (PasswordBox)obj.OriginalSource;

    password =e.Password;

}

a propriedade da senha do PasswordBox que win fornece ou WatermarkPasswordBox que o XCeedtoolkit fornece gera um RoutedEventArgs para que você possa vinculá-lo.

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

ou

<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

Envie um SecureStringpara o modelo de visualização usando um comportamento anexado eICommand

Não há nada errado com o code-behind ao implementar o MVVM. MVVM é um padrão arquitetural que visa separar a visualização do modelo / lógica de negócios. O MVVM descreve como atingir esse objetivo de maneira reproduzível (o padrão). Ele não se importa com detalhes da implementação, como estruturar ou implementar a exibição. Ele apenas desenha os limites e define qual é a visão, o modelo de visão e qual é o modelo em termos da terminologia desse padrão.

O MVVM não se importa com o idioma (XAML ou C #) ou compilador (partial classes). Ser independente do idioma é uma característica obrigatória de um padrão de design - deve ser neutro no idioma.

No entanto, o code-behind tem algumas desvantagens, como tornar a lógica da interface do usuário mais difícil de entender, quando é amplamente distribuída entre XAML e C #. Porém, a implementação mais importante da lógica da interface do usuário ou objetos como modelos, estilos, gatilhos, animações etc. em C # é muito complexa e feia / menos legível do que usar XAML. XAML é uma linguagem de marcação que usa tags e aninhamento para visualizar a hierarquia de objetos. Criar interface do usuário usando XAML é muito conveniente. Embora existam situações em que você pode optar por implementar a lógica da interface do usuário em C # (ou code-behind). Manipular o PasswordBoxé um exemplo.

Por esse motivo, manipular o PasswordBoxno code-behind manipulando o PasswordBox.PasswordChanged, não é violação do padrão MVVM.

Uma violação clara seria passar um controle (the PasswordBox) para o modelo de exibição. Muitas soluções de recomendar este exemplo, baía passando a instância do PasswordBoxquantoICommand.CommandParameter ao modelo de vista. Obviamente, uma recomendação muito ruim e desnecessária.

Se você não se importa em usar C #, mas apenas deseja manter seu arquivo code-behind limpo ou simplesmente deseja encapsular uma lógica de comportamento / interface do usuário, sempre pode usar as propriedades anexadas e implementar um comportamento anexado.

Ao contrário do infame auxiliar de propagação ampla que permite a vinculação à senha de texto sem formatação (risco muito ruim de antipadrão e segurança), esse comportamento usa um ICommandpara enviar a senha como SecureStringo modelo de exibição, sempre que isso PasswordBoxgera o 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);
  }
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.