我遇到了绑定到密码盒的问题。这似乎是一个安全风险,但我正在使用MVVM模式,所以我希望绕过这个。我在这里发现了一些有趣的代码(有人使用过这个或类似的代码吗?)
http://www.wpftutorial.net/PasswordBox.html
从技术上讲,它看起来很棒,但我不确定如何检索密码。
我基本上有属性在我的LoginViewModel用户名和密码。用户名是好的,正在工作,因为它是一个文本框。
我使用上面的代码,并输入这个
<PasswordBox ff:PasswordHelper.Attach="True"
ff:PasswordHelper.Password="{Binding Path=Password}" Width="130"/>
当我有PasswordBox作为一个文本框和绑定路径=密码,然后在我的LoginViewModel属性更新。
我的代码非常简单,基本上我有一个命令为我的按钮。当我按下它CanLogin被调用,如果它返回真,它调用Login。
你可以看到,我检查了我的用户名属性,这很好。
在登录我发送到我的服务的用户名和密码,用户名包含数据从我的视图,但密码是空|空
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 { }
}
这就是我正在做的
<TextBox Text="{Binding Path=Username, UpdateSourceTrigger=PropertyChanged}"
MinWidth="180" />
<PasswordBox ff:PasswordHelper.Attach="True"
ff:PasswordHelper.Password="{Binding Path=Password}" Width="130"/>
我有我的文本框,这是没有问题的,但在我的ViewModel密码是空的。
是我做错了什么还是漏了一步?
我放了一个断点,果然代码进入静态助手类,但它从不更新我的ViewModel中的密码。
这个实现略有不同。通过绑定ViewModel中的属性将PasswordBox传递给View。它不使用任何命令参数。ViewModel对视图保持无知。
我有一个VB VS 2010项目,可以从SkyDrive下载。WPF MVVM PassWordBox Example.zip
我在WPF MVVM应用程序中使用PasswordBox的方式非常简单,对我来说工作得很好。
基本上你创建了一个公共只读属性,视图可以绑定到PasswordBox(实际控件):
Private _thePassWordBox As PasswordBox
Public ReadOnly Property ThePassWordBox As PasswordBox
Get
If IsNothing(_thePassWordBox) Then _thePassWordBox = New PasswordBox
Return _thePassWordBox
End Get
End Property
我使用了一个支持字段来完成属性的自我初始化。
然后从Xaml中绑定ContentControl或Control Container的内容:
<ContentControl Grid.Column="1" Grid.Row="1" Height="23" Width="120" Content="{Binding Path=ThePassWordBox}" HorizontalAlignment="Center" VerticalAlignment="Center" />
从那里你可以完全控制密码盒。我还使用PasswordAccessor(只是一个字符串的函数)返回密码值时,做登录或任何其他你想要的密码。在这个例子中,我在通用用户对象模型中有一个公共属性。
例子:
Public Property PasswordAccessor() As Func(Of String)
在用户对象中,密码字符串属性是只读的,没有任何备份存储。它只是从PasswordBox返回Password。
例子:
Public ReadOnly Property PassWord As String
Get
Return If((PasswordAccessor Is Nothing), String.Empty, PasswordAccessor.Invoke())
End Get
End Property
然后在ViewModel中,我确保Accessor被创建并设置为PasswordBox。密码属性:
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
当我需要密码字符串说登录,我只是得到用户对象密码属性,真正调用函数抓取密码并返回它,然后实际的密码不存储在用户对象。
例如:将在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
That should do it. The ViewModel doesn't need any knowledge of the View's Controls. The View just binds to the Property in the ViewModel, not any different than the View Binding to an image or other resource. In this case that resource(Property) just happens to be a usercontrol.
It allows for testing as the ViewModel creates and owns the Property and the Property is independent of the View.
As for security I don't know how good this implementation is. But by using a Function the value is not stored in the Property itself just accessed by the Property.
也许我遗漏了一些东西,但似乎大多数解决方案都把事情复杂化了,并且忽略了安全实践。
此方法不违反MVVM模式,并保持完全的安全性。是的,从技术上讲,它是后面的代码,但它只不过是一个“特殊情况”绑定。ViewModel仍然没有视图实现的知识,在我看来,如果你试图将PasswordBox传递到ViewModel,它就会知道。
Code Behind !=自动违反MVVM。这完全取决于你怎么处理它。在这种情况下,我们只是手动编码绑定,所以它都被认为是UI实现的一部分,因此是ok的。
在ViewModel中,只是一个简单的属性。我把它设置为“只写”,因为无论出于什么原因,都不需要从ViewModel外部检索它,但它并不一定要这样。注意,它是一个SecureString,而不仅仅是一个字符串。
public SecureString SecurePassword { private get; set; }
在xaml中,您设置了PasswordChanged事件处理程序。
<PasswordBox PasswordChanged="PasswordBox_PasswordChanged"/>
在后面的代码中:
private void PasswordBox_PasswordChanged(object sender, RoutedEventArgs e)
{
if (this.DataContext != null)
{ ((dynamic)this.DataContext).SecurePassword = ((PasswordBox)sender).SecurePassword; }
}
使用这种方法,您的密码始终保持在SecureString中,因此提供了最大的安全性。如果你真的不关心安全性,或者你需要为需要它的下游方法提供明文密码(注意:大多数需要密码的。net方法也支持SecureString选项,所以你可能真的不需要明文密码,即使你认为你需要),你可以只使用password属性。是这样的:
(ViewModel财产)
public string Password { private get; set; }
(代码后面)
private void PasswordBox_PasswordChanged(object sender, RoutedEventArgs e)
{
if (this.DataContext != null)
{ ((dynamic)this.DataContext).Password = ((PasswordBox)sender).Password; }
}
如果你想保持事物的强类型,你可以用ViewModel的接口代替(动态的)强制转换。但实际上,“正常的”数据绑定也不是强类型的,所以这不是什么大问题。
private void PasswordBox_PasswordChanged(object sender, RoutedEventArgs e)
{
if (this.DataContext != null)
{ ((IMyViewModel)this.DataContext).Password = ((PasswordBox)sender).Password; }
}
所以最好的是-你的密码是安全的,你的ViewModel就像任何其他属性一样有一个属性,你的View是自包含的,不需要外部引用。
使用附加行为和ICommand发送一个SecureString到视图模型
在实现MVVM时,代码隐藏并没有错。MVVM是一种架构模式,旨在将视图与模型/业务逻辑分离。MVVM描述了如何以可重复的方式(模式)实现这一目标。它不关心实现细节,比如如何构造或实现视图。它只是画出边界,并根据这个模式的术语定义什么是视图,视图模型和什么是模型。
MVVM不关心语言(XAML或c#)或编译器(部分类)。独立于语言是设计模式的强制性特征——它必须与语言无关。
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.
因此,在代码背后通过处理PasswordBox来处理PasswordBox。PasswordChanged没有违反MVVM模式。
一个明显的违规是将控件(PasswordBox)传递给视图模型。许多解决方案都推荐这样做,例如,将PasswordBox的实例作为ICommand传递。CommandParameter添加到视图模型。这显然是一个非常糟糕和不必要的建议。
如果你不关心使用c#,只是想保持你的代码文件干净,或者只是想封装一个行为/UI逻辑,你总是可以使用附加属性并实现附加行为。
与臭名昭著的支持绑定纯文本密码的广泛传播帮助器(非常糟糕的反模式和安全风险)相反,此行为使用ICommand将密码作为SecureString发送到视图模型,每当PasswordBox抛出PasswordBox时。PasswordChanged事件。
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);
}
}
为了在不破坏MVVM的情况下解决OP问题,我将使用自定义值转换器和必须从密码框中检索的值(密码)的包装器。
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.");
}
}
在视图模型中:
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...
}
因为视图模型使用IWrappedParameter<T>,所以它不需要任何关于PasswordBoxWrapper或PasswordBoxConverter的知识。这样您就可以将PasswordBox对象与视图模型隔离开来,并且不会破坏MVVM模式。
在视图中:
<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}}" />