PasswordBoxへのバインドに関する問題に遭遇しました。セキュリティ上のリスクがあるようですが、MVVMパターンを使用しているため、これを回避したいと思います。私はここでいくつかの興味深いコードを見つけました(誰かがこれや似たようなものを使用しましたか?)
http://www.wpftutorial.net/PasswordBox.html
技術的には素晴らしく見えますが、パスワードを取得する方法がわかりません。
基本的に、LoginViewModel
とUsername
のPassword
にプロパティがあります。 Username
は問題なく、TextBox
として機能しています。
上記のコードを使用し、これを入力しました
<PasswordBox ff:PasswordHelper.Attach="True"
ff:PasswordHelper.Password="{Binding Path=Password}" Width="130"/>
PasswordBox
をTextBox
およびBinding Path=Password
として使用した場合、LoginViewModel
のプロパティが更新されました。
私のコードはとてもシンプルで、基本的にCommand
にButton
があります。押すとCanLogin
が呼び出され、trueが返されるとLogin
が呼び出されます。
ここで、Username
のプロパティを確認すると、うまく機能していることがわかります。
Login
で、サービスにUsername
およびPassword
を送信します。Username
にはView
からのデータが含まれますが、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 { }
}
これは私がやっていることです
<TextBox Text="{Binding Path=Username, UpdateSourceTrigger=PropertyChanged}"
MinWidth="180" />
<PasswordBox ff:PasswordHelper.Attach="True"
ff:PasswordHelper.Password="{Binding Path=Password}" Width="130"/>
TextBox
がありますが、これは問題ありませんが、ViewModel
ではPassword
は空です。
私は何か間違ったことをしているのですか、それとも足りませんか
ブレークポイントを設定し、コードが十分に静的ヘルパークラスに入ることを確認しますが、Password
のViewModel
を更新することはありません。
申し訳ありませんが、あなたは間違っています。
人々は、まぶたの内側に次のセキュリティガイドラインを入れておく必要があります。
プレーンテキストのパスワードをメモリに保存しないでください。
WPF/Silverlight PasswordBoxがPasswordプロパティのDPを公開しない理由は、セキュリティに関連しています。
。これは非常に面倒なセキュリティ攻撃ベクトルと見なされます。 PasswordBoxは(ある種の)暗号化されたメモリを使用し、パスワードにアクセスする唯一の方法はCLRプロパティを使用することです。
PasswordBox.Password CLRプロパティにアクセスするときは、変数やプロパティの値として配置しないことをお勧めします。
クライアントマシンでパスワードをプレーンテキストで保持するRAMは、セキュリティ上の問題はありません。
だから、そこにある「public string Password {get; set;}」を取り除きます。
PasswordBox.Passwordにアクセスするときは、それを取り出してサーバーにできるだけ早く出荷してください。パスワードの値を保持したり、他のクライアントマシンのテキストと同じように扱ったりしないでください。クリアテキストのパスワードをメモリに保存しないでください。
これによりMVVMパターンが壊れることはわかっていますが、PasswordBox.Password Attached DPにバインドしたり、ViewModelまたは他の類似のシェナンガンにパスワードを保存したりしないでください。
過剰に設計されたソリューションを探している場合は、次のとおりです。
1。パスワードのクリアテキストを返す1つのメソッドでIHavePasswordインターフェイスを作成します。
2。 UserControlにIHavePasswordインターフェイスを実装させます。
3。 IHavePasswordインターフェースの実装として、IoCにUserControlインスタンスを登録します。
4。パスワードを要求するサーバーリクエストが発生した場合、IHCを呼び出してIHavePasswordを実装し、切望されているパスワードを取得します。
ちょうど私の意見です。
-ジャスティン
私の2セント:
WPFとMVVMを使用して、典型的なログインダイアログ(ユーザーボックスとパスワードボックス、および[OK]ボタン)を開発しました。 PasswordBoxコントロール自体をパラメーターとして「Ok」ボタンに接続されたコマンドに渡すことで、パスワードバインドの問題を解決しました。だから私は見ました:
<PasswordBox Name="txtPassword" VerticalAlignment="Top" Width="120" />
<Button Content="Ok" Command="{Binding Path=OkCommand}"
CommandParameter="{Binding ElementName=txtPassword}"/>
また、ViewModelでは、添付コマンドのExecute
メソッドは次のとおりでした。
void Execute(object parameter)
{
var passwordBox = parameter as PasswordBox;
var password = passwordBox.Password;
//Now go ahead and check the user name and password
}
これは、ViewModelがViewの実装方法について何かを知っているため、MVVMパターンにわずかに違反していますが、その特定のプロジェクトではそれを買う余裕がありました。それが誰かにも役立つことを願っています。
たぶん私は何かを逃しているかもしれませんが、これらのソリューションのほとんどは物事を複雑にしすぎており、安全なプラクティスを廃止しているようです。
この方法はMVVMパターンに違反せず、完全なセキュリティを維持します。はい、技術的にはコードビハインドですが、「特殊なケース」バインディングにすぎません。 ViewModelにはまだView実装の知識がありません。これは、PasswordModelをViewModelに渡そうとしている場合には、それを認識していることです。
コードビハインド!=自動MVVM違反。それはあなたがそれをどうするかにかかっています。この場合、バインディングを手動でコーディングしているだけなので、UI実装の一部とみなされるため、問題ありません。
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は外部参照を必要としない自己完結型です。
この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>
そして、このコマンドはメソッドを実行します:
private void ExecutePasswordChangedCommand(PasswordBox obj)
{
if (obj != null)
Password = obj.Password;
}
これは私には問題ありません。
<Button Command="{Binding Connect}"
CommandParameter="{Binding ElementName=MyPasswordBox}"/>
MVVMパターンに違反しない簡単な解決策は、パスワードを取得するViewModelにイベント(またはデリゲート)を導入することです。
ViewModel:
public event EventHandler<HarvestPasswordEventArgs> HarvestPassword;
これらのEventArgsで:
class HarvestPasswordEventArgs : EventArgs
{
public string Password;
}
Viewで、ViewModel作成時のイベントにサブスクライブし、パスワード値を入力します。
_viewModel.HarvestPassword += (sender, args) =>
args.Password = passwordBox1.Password;
ViewModelでは、パスワードが必要なときに、イベントを起動してそこからパスワードを取得できます。
if (HarvestPassword == null)
//bah
return;
var pwargs = new HarvestPasswordEventArgs();
HarvestPassword(this, pwargs);
LoginHelpers.Login(Username, pwargs.Password);
Gist here を投稿しました。これはバインド可能なパスワードボックスです。
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;
}
}
}
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}}" />
この実装はわずかに異なります。パスワードボックスをViewModelのプロパティのバインディングを介してビューに渡します。コマンドボックスは使用しません。 ViewModelはビューを無視します。 SkyDriveからダウンロードできるVB vs 2010プロジェクトがあります。 Wpf MvvM PassWordBox Example.Zip https://skydrive.live.com/redir.aspx?cid=e95997d33a9f8d73&resid=E95997D33A9F8D73!511
Wpf MvvMアプリケーションでPasswordBoxを使用する方法は非常に単純化されており、うまく機能しています。だからといって、それが正しい方法だとか、最善の方法だと思うわけではありません。これは、PasswordBoxとMvvMパターンの使用の単なる実装です。
基本的に、ビューが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" />
そこからpasswordboxを完全に制御できます。また、PasswordAccessor(文字列の関数)を使用して、ログイン時またはパスワードが必要なその他のパスワード値を返します。例では、汎用ユーザーオブジェクトモデルにパブリックプロパティがあります。例:
Public Property PasswordAccessor() As Func(Of String)
ユーザーオブジェクトでは、パスワード文字列プロパティはバッキングストアなしで読み取り専用であり、PasswordBoxからパスワードを返します。例:
Public ReadOnly Property PassWord As String
Get
Return If((PasswordAccessor Is Nothing), String.Empty, PasswordAccessor.Invoke())
End Get
End Property
次に、ViewModelで、アクセサーが作成され、PasswordBox.Passwordプロパティに設定されていることを確認します '例:
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
それはそれを行う必要があります。 ViewModelは、ビューのコントロールに関する知識を必要としません。ビューはViewModelのプロパティにバインドするだけで、画像やその他のリソースへのビューバインドとは異なります。この場合、resource(Property)はたまたまユーザーコントロールです。 ViewModelがプロパティを作成および所有し、プロパティがビューから独立しているため、テストが可能です。セキュリティに関しては、この実装がどれほど優れているかわかりません。ただし、関数を使用すると、プロパティによってアクセスされたプロパティ自体には値が保存されません。
さまざまなソリューションを検討するのに多くの時間を費やしました。デコレータのアイデアが嫌いで、ビヘイビアが検証UIを台無しにして、コードビハインド...本当に?
最良の方法は、カスタムアタッチプロパティに固執し、ビューモデルのSecureString
プロパティにバインドすることです。できるだけ長くそこに保管してください。プレーンパスワードにすばやくアクセスする必要がある場合は、次のコードを使用して一時的に安全でない文字列に変換します。
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;
}
}
}
GCがUI要素を収集できるようにして、PasswordChanged
のPasswordBox
イベントに静的イベントハンドラーを使用する衝動に耐えるようにしてください。また、SecurePassword
プロパティを使用して設定するときにコントロールがUIを更新しないという異常を発見しました。これは、代わりにパスワードを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;
}
}
}
}
}
XAMLの使用法:
<PasswordBox controls:PasswordBoxHelper.SecurePassword="{Binding LogonPassword, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}">
ビューモデルのプロパティは次のようになりました。
[RequiredSecureString]
public SecureString LogonPassword
{
get
{
return _logonPassword;
}
set
{
_logonPassword = value;
NotifyPropertyChanged(nameof(LogonPassword));
}
}
RequiredSecureString
は、次のロジックを持つ単純なカスタムバリデーターです。
[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;
}
}
ここにあります。完全でテスト済みの純粋なMVVMソリューション。
パスワードをどこにも保存しないことが重要であることに同意しますが、ビューなしでビューモデルをインスタンス化し、それに対してテストを実行する機能が必要です。
私のために働いた解決策は、ビューボックスにPasswordBox.Password関数を登録し、ログインコードを実行するときにビューモデルにそれを呼び出させることでした。
このdoesは、ビューの分離コード内のコード行を意味します。
したがって、私のLogin.xamlには
<PasswordBox x:Name="PasswordBox"/>
login.xaml.csに私が持っています
LoginViewModel.PasswordHandler = () => PasswordBox.Password;
loginViewModel.csでPasswordHandlerを定義しています
public Func<string> PasswordHandler { get; set; }
ログインが必要になると、コードはビューからパスワードを取得するためにハンドラーを呼び出します...
bool loginResult = Login(Username, PasswordHandler());
このようにして、ビューモデルをテストする場合、PasswordHandlerを匿名メソッドに設定するだけで、テストで使用するパスワードを配信できます。
この方法を使用してパスワードボックスを渡しましたが、これはMVVMに違反しますが、複雑なシェル環境であるシェル内のログインにデータテンプレートを使用してコンテンツコントロールを使用していたため、MVVMに不可欠でした。したがって、シェルの背後にあるコードにアクセスするのはくだらないでしょう。
Passwordboxを渡すことは、私の知る限り、コードビハインドからコントロールにアクセスするのと同じだと思います。パスワードに同意し、メモリなどに保管しないでくださいこの実装では、ビューモデルにパスワードのプロパティがありません。
ボタンコマンド
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);
}
これは非常に一般的な問題であるため、私は自分のソリューションをミックスに投入するだろうと考えました...そして、多くのオプションを持つことは常に良いことです。
単にPasswordBox
をUserControl
でラップし、バインドできるようにDependencyProperty
を実装しました。クリアテキストをメモリに保存しないようにできる限りのことをしていますので、すべてはSecureString
およびPasswordBox.Password
プロパティを介して行われます。 foreach
ループ中、各文字は公開されますが、非常に簡単です。正直なところ、WPFアプリケーションがこの短い露出から危険にさらされるのではないかと心配している場合、対処すべき大きなセキュリティの問題があります。
これの美しさは、これがUserControl
であるため、MVVMの規則(「純粋な」規則さえ)に違反していないため、コードビハインドが許可されていることです。それを使用している場合、View
がViewModel
の一部またはパスワードのソースを認識せずに、VideModel
とView
の間で純粋な通信を行うことができます。 SecureString
の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(バージョン1-双方向バインディングサポートなし)
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;
}
}
}
バージョン1の使用法
<local:BindanblePasswordBox Width="150" HorizontalAlignment="Center"
VerticalAlignment="Center"
Password="{Binding Password, Mode=OneWayToSource}"/>
BindablePasswordBox.xaml.cs(バージョン2-双方向バインディングをサポートしています。)
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;
}
}
}
}
バージョン2の使用法
<local:BindanblePasswordBox Width="150" HorizontalAlignment="Center"
VerticalAlignment="Center"
Password="{Binding Password, Mode=TwoWay}"/>
添付プロパティでそれを行うことができます。参照してください。 MVVMを使用したPasswordBox
ご覧のとおり、私はPasswordにバインドしていますが、おそらく静的クラスにバインドしています。
添付プロパティ です。この種類のプロパティは、宣言されている型だけでなく、あらゆる種類のDependencyObject
に適用できます。したがって、PasswordHelper
静的クラスで宣言されていても、それを使用するPasswordBox
に適用されます。
この添付プロパティを使用するには、ViewModelのPassword
プロパティにバインドするだけです。
<PasswordBox w:PasswordHelper.Attach="True"
w:PasswordHelper.Password="{Binding Password}"/>
私のような完全な初心者のために、上記のKonamiman
が提案したものの完全な動作サンプルを以下に示します。 Konamiman
に感謝します。
<PasswordBox x:Name="textBoxPassword"/>
<Button x:Name="buttonLogin" Content="Login"
Command="{Binding PasswordCommand}"
CommandParameter="{Binding ElementName=textBoxPassword}"/>
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);
}
}
私には、これらの両方が間違っていると感じています:
PasswordBox
をコマンドパラメーターとしてViewModelに送信するSteve in CO で説明されているように、SecurePassword(SecureStringインスタンス)を転送することは受け入れられるようです。コードビハインドよりBehaviors
の方が好きです。また、ビューモデルからパスワードをリセットできるという追加の要件がありました。
Xaml(Password
はViewModelプロパティです):
<PasswordBox>
<i:Interaction.Behaviors>
<behaviors:PasswordBinding BoundPassword="{Binding Password, Mode=TwoWay}" />
</i:Interaction.Behaviors>
</PasswordBox>
動作:
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;
}
}
}
}
Windowsユニバーサルアプリで
プロパティ「Password」でこのコードを使用し、modelViewでバインドできます
<PasswordBox x:Uid="PasswordBox" Password="{Binding Waiter.Password, Mode=TwoWay}" Name="txtPassword" HorizontalAlignment="Stretch" Margin="50,200,50,0" VerticalAlignment="Top"/>
この実装が課すリスクを知っている人は、パスワードをViewModelに同期させるには、単にMode = OneWayToSourceを追加してください。
XAML
<PasswordBox
ff:PasswordHelper.Attach="True"
ff:PasswordHelper.Password="{Binding Path=Password, Mode=OneWayToSource}" />
その非常にシンプル。パスワードの別のプロパティを作成し、TextBoxでこれをバインドします
ただし、すべての入力操作は実際のパスワードプロパティで実行されます
プライベート文字列_Password;
public string PasswordChar
{
get
{
string szChar = "";
foreach(char szCahr in _Password)
{
szChar = szChar + "*";
}
return szChar;
}
set
{
_PasswordChar = value; NotifyPropertyChanged();
}
}
public string Password {get {return _Password; }
set
{
_Password = value; NotifyPropertyChanged();
PasswordChar = _Password;
}
}
私は次のようにしました:
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;
}
}
それは私のために働く!
前に述べたように、VMはビューを認識しないはずですが、PasswordBox全体を渡すことは最も単純なアプローチのように見えます。そのため、渡されたパラメーターをPasswordBoxにキャストする代わりに、Reflectionを使用してそこからPasswordプロパティを抽出します。この場合、VMは、プロパティPasswordを持つ何らかの種類のパスワードコンテナを想定しています(MVMM Light-ToolkitのRelayCommandsを使用しています):
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;
}
}
無名クラスで簡単にテストできます:
var passwordContainer = new
{
Password = "password"
};
必要に応じて、1つのコントロールと1つのコマンドですべてを組み合わせます
<PasswordBox Name="PasswordBoxPin" PasswordChar="*">
<PasswordBox.InputBindings>
<KeyBinding Key="Return" Command="{Binding AuthentifyEmpCommand}" CommandParameter="{Binding ElementName=PasswordBoxPin}"/>
</PasswordBox.InputBindings>
</PasswordBox>
そしてあなたのVm(コナミマンが示したように)
public void AuthentifyEmp(object obj)
{
var passwordBox = obj as PasswordBox;
var password = passwordBox.Password;
}
private RelayCommand _authentifyEmpCommand;
public RelayCommand AuthentifyEmpCommand => _authentifyEmpCommand ?? (_authentifyEmpCommand = new RelayCommand(AuthentifyEmp, null));
PasswordBoxのソリューションは、WPF Application Framework(WAF)プロジェクトのViewModelサンプルアプリケーションにあります。
しかし、ジャスティンは正しい。 ViewとViewModelの間でパスワードをプレーンテキストとして渡さないでください。代わりにSecureStringを使用してください(MSDN PasswordBoxを参照)。
まだ言及されていない簡潔なMVVMに優しいソリューションを使用しています。まず、XAMLでPasswordBoxに名前を付けます。
<PasswordBox x:Name="Password" />
次に、1つのメソッド呼び出しをビューコンストラクターに追加します。
public LoginWindow()
{
InitializeComponent();
ExposeControl<LoginViewModel>.Expose(this, view => view.Password,
(model, box) => model.SetPasswordBox(box));
}
以上です。ビューモデルは、DataContextを介してビューにアタッチされると通知を受け取り、デタッチされると別の通知を受け取ります。この通知の内容は、ラムダを介して構成できますが、通常はビューモデルでのセッターまたはメソッド呼び出しであり、問題のあるコントロールをパラメーターとして渡します。
ビューで子コントロールの代わりにインターフェイスを公開することで、MVVMに非常に簡単に対応できます。
上記のコードは、私のブログで公開されている ヘルパークラス に依存しています。
まあ私の答えはMVVMパターンのためだけにもっと簡単です
クラス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;
}
winが提供するPasswordBoxまたはXCeedtoolkitが提供するWatermarkPasswordBoxのパスワードプロパティは、ユーザーがバインドできるようにRoutedEventArgsを生成します。
今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>
または
<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>
私はこれを機能させるために何年も費やしました。最後に、私はあきらめて、DevExpressのPasswordBoxEditを使用しました。
これは、恐ろしいトリックを引き出すことなくバインディングを可能にするため、これまでで最もシンプルなソリューションです。
記録のために、私はDevExpressとは一切関係ありません。
認証チェックに続いて、ビューのメディエータークラスによって呼び出されるサブルーチン(認証チェックも実装)を使用して、データクラスにパスワードを書き込みました。
それは完璧な解決策ではありません。しかし、パスワードを移動できないという私の問題を解決しました。
これが私の見解です。
添付プロパティを使用してパスワードをバインドすると、パスワードを保護する目的が無効になります。パスワードボックスの[パスワード]プロパティは、理由によりバインドできません。
パスワードボックスをコマンドパラメーターとして渡すと、ViewModelがコントロールを認識します。 ViewModelを再利用可能なクロスプラットフォームにする予定がある場合、これは機能しません。 VMにビューやその他のコントロールを認識させないでください。
パスワードを提供する簡単なタスクには、新しいプロパティ、インターフェース、パスワード変更イベントのサブスクライブ、またはその他の複雑なものを導入する必要はないと思います。
XAML
<PasswordBox x:Name="pbPassword" />
<Button Content="Login" Command="{Binding LoginCommand}" x:Name="btnLogin"/>
コードビハインド-コードビハインドを使用しても、必ずしもMVVMに違反するわけではありません。ビジネスロジックを入れない限り。
btnLogin.CommandParameter = new Func<string>(()=>pbPassword.Password);
ViewModel
LoginCommand = new RelayCommand<Func<string>>(getpwd=> { service.Login(username, getpwd()); });
<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);
}
}
}
;)簡単!