Monday, 23 June 2008

WPF PasswordBox and Data binding

Like Charles Petzold, I am something of a Xamlholic: I'll try for hours to find a way of expressing my UI in pure XAML, rather than pollute its purity with C# code, even if the code could go in a code-behind file. This addiction is largely driven by WPF's fantastic support for Data Binding which lets anything in the UI be data-bound to anything else. Well, almost anything in the UI. Just occasionally I find myself having to fall back on property setters and getters, poking data into controls then sucking it back again, the way Windows Forms made me earn my living. One control which nearly had me beat was PasswordBox.

PasswordBox is what you need whenever you want to mask the characters as a user types them: for some reason, WPF's standard TextBox doesn't support that scenario. Now PasswordBox has, as you'd expect, a Password property that allows you to get and set the password. However, it's a plain CLR property rather than a Dependency Property, so it doesn't support being the target of a Data binding.

Ben Westbrook of Microsoft explains in a forum post that it was not exposed as a Dependency Property for security reasons. The values of Dependency Properties are managed centrally by the Dependency Property sub-system so that it can handle all the data-binding and cool animation stuff (animating a password - now that would be interesting!); the consequence of this is that the data effectively becomes public property. With something as sensitive as a password it's reasonable that a control (like PasswordBox) would want to keep the data closer to its chest. So internally the PasswordBox stores the password in a good old field, and in fact holds it using a SecureString - a string that is automatically encrypted in memory and obliterated when no longer needed; if you Reflector PasswordBox you'll see that the Password property setter stuffs any value you set into a SecureString, and the getter reads it out for you; PasswordBox appends characters one by one to the SecureString as you type them, so the password is never stored as clear text until you retrieve it through the Password property.

As an aside, one thing I don't understand about the design is why PasswordBox doesn't provide a property to access the password as a SecureString, and so preserve the encryption? The act of retrieving the password through the Password property (as you'll have to do at some point in order to use the password) will turn it into clear text, and then you'll surely loose most of the benefit of ever having it encrypted? Perhaps someone from Microsoft can explain?

Update: As Ray pointed out in the comments, Microsoft have now (as from .Net 3.5 SP1) added a SecurePassword property to PasswordBox; it's still not a DependencyProperty though.

Anyhow, let's get to the point. Sometimes your main interest in using a PasswordBox is to have the password text masked as it's typed, and encryption isn't really an issue. In this case its a shame not to be able to use data binding.

Fortunately, WPF has the concept of Attached Properties that allow controls to be extended very easily. I've knocked up a class that extends PasswordBox with an Attached Property that lets you data bind the password. You use it as shown below (note particularly the additional xmlns element you need to add to the root element in your XAML):

<Page xmlns:ff="clr-namespace:FunctionalFun.UI">
  <!-- [Snip] -->
  <PasswordBox x:Name="PasswordBox"
      ff:PasswordBoxAssistant.BindPassword="true"  ff:PasswordBoxAssistant.BoundPassword="{Binding Path=Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">

</Page>

And here's the code. Very simple; setting the BindPassword property causes the code to start listening to the PasswordChanged event on the PasswordBox: whenever there is a change the code pushes the new value into the Attached Property BoundPassword which you can specify a binding for. It also takes care of the other direction, going from BoundPassword (whenever your the bound source property changes) into the PasswordBox. The only complication is making sure the whole thing doesn't get recursive and blow up: that's what the private attached property UpdatingPassword is for.

using System.Windows;
using System.Windows.Controls;

namespace FunctionalFun.UI
{
  public static class PasswordBoxAssistant
  {
      public static readonly DependencyProperty BoundPassword =
          DependencyProperty.RegisterAttached("BoundPassword", typeof(string), typeof(PasswordBoxAssistant), new FrameworkPropertyMetadata(string.Empty, OnBoundPasswordChanged));

      public static readonly DependencyProperty BindPassword = DependencyProperty.RegisterAttached(
          "BindPassword", typeof (bool), typeof (PasswordBoxAssistant), new PropertyMetadata(false, OnBindPasswordChanged));

      private static readonly DependencyProperty UpdatingPassword =
          DependencyProperty.RegisterAttached("UpdatingPassword", typeof(bool), typeof(PasswordBoxAssistant));

      private static void OnBoundPasswordChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
      {
          PasswordBox box = d as PasswordBox;

          // only handle this event when the property is attached to a PasswordBox
          // and when the BindPassword attached property has been set to true
          if (d == null || !GetBindPassword(d))
          {
              return;
          }

          // avoid recursive updating by ignoring the box's changed event
          box.PasswordChanged -= HandlePasswordChanged;

          string newPassword = (string)e.NewValue;

          if (!GetUpdatingPassword(box))
          {
              box.Password = newPassword;
          }

          box.PasswordChanged += HandlePasswordChanged;
      }

      private static void OnBindPasswordChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
      {
          // when the BindPassword attached property is set on a PasswordBox,
          // start listening to its PasswordChanged event

          PasswordBox box = dp as PasswordBox;

          if (box == null)
          {
              return;
          }

          bool wasBound = (bool)(e.OldValue);
          bool needToBind = (bool)(e.NewValue);

          if (wasBound)
          {
              box.PasswordChanged -= HandlePasswordChanged;
          }

          if (needToBind)
          {
              box.PasswordChanged += HandlePasswordChanged;
          }
      }

      private static void HandlePasswordChanged(object sender, RoutedEventArgs e)
      {
          PasswordBox box = sender as PasswordBox;

          // set a flag to indicate that we're updating the password
          SetUpdatingPassword(box, true);
          // push the new password into the BoundPassword property
          SetBoundPassword(box, box.Password);
          SetUpdatingPassword(box, false);
      }

      public static void SetBindPassword(DependencyObject dp, bool value)
      {
          dp.SetValue(BindPassword, value);
      }

      public static bool GetBindPassword(DependencyObject dp)
      {
          return (bool)dp.GetValue(BindPassword);
      }

      public static string GetBoundPassword(DependencyObject dp)
      {
          return (string)dp.GetValue(BoundPassword);
      }

      public static void SetBoundPassword(DependencyObject dp, string value)
      {
          dp.SetValue(BoundPassword, value);
      }

      private static bool GetUpdatingPassword(DependencyObject dp)
      {
          return (bool)dp.GetValue(UpdatingPassword);
      }

      private static void SetUpdatingPassword(DependencyObject dp, bool value)
      {
          dp.SetValue(UpdatingPassword, value);
      }
  }
}

30 comments:

Anonymous said...

Hi,
Line 23 : I Guess this must be something like:

if(d == null || !GetBindPassword(d))
{
return;
}

Anyway, thanks for this post.
Regards, Jeroen de Zeeuw

Sam said...

Jeroen,
Thanks for spotting this. Blogger must have swallowed my pipe symbols!

Should be fixed now.

Sam

Anonymous said...

Hello, i use your code but my binding to Password property for a User. The field password is empty.
Give me a simple example using PasswordBoxAssistant

my email is btvent_d_est@yahoo.fr

Anonymous said...

thanks man,
you saved my day,
and my nerves....

Eric R said...

I am also wondering if this helper was ever designed to get a saved password. Mine does not retrieve the string password it is bound to.

Sam said...

Eric,
I'm not quite sure what you mean by a saved password. Could you elaborate? As far as I know, PasswordBox doesn't implement saving of passwords.

Sam

Eric R said...

Sam,
My passwordbox is databound to a string property on an item within a collection using your helper. I would assume that when I reload that form, the passwordbox would Get that property and load it -- albeit masked. As of now it gives the impression that they have not entered a password.

The passwordbox, with this helper, sets that property just fine -- it's just the get that I'm struggling with. I get an error in my output. "System.Windows.Data Error: 16 : Cannot get 'Password' value (type 'String') from '' (type 'AccountSettingsItem')."

Any ideas? Thanks Sam.

Sam said...

Eric,
The error suggests that the WPF is having trouble accessing the getter of the Password property on your collection item. Is it public? might it be throwing an exception? Have you tried putting a breakpoint on it to check it is hit?

I've used this in a situation where we've saved the password, so my initial thought is that the problem doesn't lie with the PasswordBoxHelper.

Eric R said...

Good point.

Would you prefer to discuss this over email instead of cluttering the comments? My email is listed in my Blogger profile.

Sam said...

Just to wrap up the conversation with Eric: it appears that the property Eric was binding Password to was throwing an exception in its getter.

Anonymous said...

What about modifying the TextBox control to mask input. Would that be easier?

Sam said...

Anonymous,
In general, sub-classing a control in WPF is harder and not as flexible as the approaches similar to the one shown here which provide an attached behaviour, and allow that to be composed into other controls.

I believe I looked into masking TextBox input, and I don't think it was trivial.

Dragoljub said...

Thanks for creating this solution and I totally agree with you on your comments about their statement that "PasswordBox.Password as DP = security issue". I think if someone wanted to read out the password, they can probably do that even without the DP. I also want to use PasswordBox only for hiding the password from the actual users.

sundriedcoder said...

Hey Sam, Kevin from (formerly) Disney here.
This is going to come in very handy for my current project if I can convert this over to Silverlight :)

Hope all is well across the pond!

Anonymous said...

That's cool. Thank you buddy.

Ray said...

In .NET 3.5 the PasswordBox control has a SecurePassword property to get the password as a SecureString.

Sam said...

@Ray: thanks for pointing that out. I've updated the article to mention that property.

Anonymous said...

Very useful piece of code when doing ViewModels. Thanks for posting this.

id said...

What level of attribution do you require? Is a source code comment sufficient, or do you require something more?

Sam said...

A source code comment with a link back to the post is fine. Nice to know my code is in use!

Anonymous said...

In OnBoundPasswordChanged, I think you mean to check if box is null, not d.

Anonymous said...

Great code, just one question: is this possible in Silverlight?

Sam said...

Anonymous:
If you're using Silverlight 3 you don't need this code: the Password property is a dependency property, so you can already Databind to it.

Anonymous said...

Great, this came in handy

I'm working in WPF, and wanted to element-to-element bind the password. I know it's not as secure, but this is a reasonable way to do it.

Thanks!

Ibrahim said...

can anybody give it to me in vb.net. I am getting the error for first 2 declarations when i changed to vb.net

Ibrahim said...

ok i got that issue in vb.net we can prefix with AddressOf keyword before OnBound...

Anonymous said...

Hi everyone i try to convert this to vb.net and i've created this code ( and it compiles fine with no errors but i having troubles in my xaml code) if anyone can help i am using it in a user control:


Public Class PasswordBoxAssistant
Public ReadOnly BoundPassword As DependencyProperty = _
DependencyProperty.RegisterAttached("BoundPassword", GetType(String), GetType(PasswordBoxAssistant), New FrameworkPropertyMetadata(String.Empty, AddressOf OnBoundPasswordChanged))

Public ReadOnly BindPassword As DependencyProperty = _
DependencyProperty.RegisterAttached("BindPassword", GetType(Boolean), GetType(PasswordBoxAssistant), New PropertyMetadata(False, AddressOf OnBindPasswordChanged))

Public ReadOnly UpdatingPassword As DependencyProperty = _
DependencyProperty.RegisterAttached("UpdatingPassword", GetType(Boolean), GetType(PasswordBoxAssistant))

Private Sub OnBoundPasswordChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
Dim box As PasswordBox = TryCast(d, PasswordBox)

If box Is Nothing Or Not GetBindPassword(box) Then
Return
End If

RemoveHandler box.PasswordChanged, AddressOf HandlePasswordChanged

Dim newPassword As String = TryCast(e.NewValue, String)

If Not GetUpdatingPassword(box) Then
box.Password = newPassword
End If

AddHandler box.PasswordChanged, AddressOf HandlePasswordChanged
End Sub

Private Sub OnBindPasswordChanged(ByVal dp As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
Dim box As PasswordBox = TryCast(dp, PasswordBox)
If box Is Nothing Then
Return
End If

Dim wasBound As Boolean = e.OldValue
Dim needToBind As Boolean = e.NewValue

If wasBound Then
RemoveHandler box.PasswordChanged, AddressOf HandlePasswordChanged
End If

If needToBind Then
AddHandler box.PasswordChanged, AddressOf HandlePasswordChanged
End If
End Sub

Private Sub HandlePasswordChanged(ByVal sender As Object, ByVal e As RoutedEventArgs)
Dim box As PasswordBox = TryCast(sender, PasswordBox)
SetUpdatingPassword(box, True)
SetBoundPassword(box, box.Password)
SetUpdatingPassword(box, False)
End Sub

Public Sub SetBindPassword(ByVal dp As DependencyObject, ByVal valor As Boolean)
dp.SetValue(BindPassword, valor)
End Sub

Public Function GetBindPassword(ByVal dp As DependencyObject) As String
Return dp.GetValue(BindPassword)
End Function


Public Function GetBoundPassword(ByVal dp As DependencyObject) As String
Return dp.GetValue(BoundPassword)
End Function

Public Sub SetBoundPassword(ByVal dp As DependencyObject, ByVal valor As String)
dp.SetValue(BoundPassword, valor)
End Sub

Public Function GetUpdatingPassword(ByVal dp As DependencyObject) As Boolean
Return dp.GetValue(UpdatingPassword)
End Function

Public Sub SetUpdatingPassword(ByVal dp As DependencyObject, ByVal valor As Boolean)
dp.SetValue(UpdatingPassword, valor)
End Sub
End Class

Any help to cjtapiaa@hotmail.com

Anonymous said...

please anyone can explain how to use it in a user control wih wpf and vb net thanks

Mel said...

Thank you for this, it gave me just what I needed!

Magnus said...

Excellent post. You're in my code now. :-)

Post a Comment

If you have trouble posting a comment, try pressing the Preview button first - works for me!