How to bind to a PasswordBox in MVVM

前端 未结 30 2389
执念已碎
执念已碎 2020-11-22 11:50

I have come across a problem with binding to a PasswordBox. It seems it\'s a security risk but I am using the MVVM pattern so I wish to bypass this. I found som

30条回答
  •  面向向阳花
    2020-11-22 12:31

    Send a SecureString to the view model using an Attached Behavior and ICommand

    There is nothing wrong with code-behind when implementing MVVM. MVVM is an architectural pattern that aims to separate the view from the model/business logic. MVVM describes how to achieve this goal in a reproducible way (the pattern). It doesn't care about implementation details, like how do you structure or implement the view. It just draws the boundaries and defines what is the view, the view model and what the model in terms of this pattern's terminology.

    MVVM doesn't care about the language (XAML or C#) or compiler (partial classes). Being language independent is a mandatory characteristic of a design pattern - it must be language neutral.

    However, code-behind has some draw backs like making your UI logic harder to understand, when it is wildly distributed between XAML and C#. But most important implementing UI logic or objects like templates, styles, triggers, animations etc in C# is very complex and ugly/less readable than using XAML. XAML is a markup language that uses tags and nesting to visualize object hierarchy. Creating UI using XAML is very convenient. Although there are situations where you are fine choosing to implement UI logic in C# (or code-behind). Handling the PasswordBox is one example.

    For this reasons handling the PasswordBox in the code-behind by handling the PasswordBox.PasswordChanged, is no violation of the MVVM pattern.

    A clear violation would be to pass a control (the PasswordBox) to the view model. Many solutions recommend this e.g., bay passing the instance of the PasswordBox as ICommand.CommandParameter to the view model. Obviously a very bad and unnecessary recommendation.

    If you don't care about using C#, but just want to keep your code-behind file clean or simply want to encapsulate a behavior/UI logic, you can always make use of attached properties and implement an attached behavior.

    Opposed of the infamous wide spread helper that enables binding to the plain text password (really bad anti-pattern and security risk), this behavior uses an ICommand to send the password as SecureString to the view model, whenever the PasswordBox raises the PasswordBox.PasswordChanged event.

    MainWindow.xaml

    
      
        
      
    
      
    
    

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

提交回复
热议问题