MVVMでPasswordBoxにバインドする方法


251

Pへのバインドに関する問題に遭遇しましたasswordBox。これはセキュリティリスクのようですが、MVVMパターンを使用しているため、これをバイパスしたいと思います。私はここでいくつかの興味深いコードを見つけました(これを使用した人はいますか?

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

技術的には素晴らしいように見えますが、パスワードを取得する方法がわかりません。

私は基本的に自分のLoginViewModelfor Usernameとにプロパティを持っていますPasswordUsername正常であり、そのまま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ここで作品。

ではLoginI私のサービスAに沿って送信UsernameしてPasswordUsername私からのデータが含まれています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


3
まあそれはコードが機能しなかったことがわかりましたが、私はここで別のコードを試しました、そしてそれは完全に機能します。blog.functionalfun.net/2008/06/...
マーク・スミス

5
passwordboxコントロール全体を渡すと、ビューをビューモデルから分離できなくなりますか?

回答:


164

申し訳ありませんが、あなたはそれを間違っています。

まぶたの内側に次のセキュリティガイドラインを入れておく必要があります。
必要です。プレーンテキストのパスワードをメモリに保存しないでください。

WPF / Silverlight PasswordBoxPasswordプロパティのDPを公開しない理由は、セキュリティに関連しています。
WPF / SilverlightがDPを保持するPassword場合、フレームワークはパスワード自体を暗号化せずにメモリに保持する必要があります。これは、かなり厄介なセキュリティ攻撃の方法と考えられています。PasswordBox用途は(種類の)メモリを暗号化し、パスワードにアクセスするための唯一の方法は、CLRプロパティを介してです。

PasswordBox.PasswordCLRプロパティにアクセスするときは、それを変数に配置したり、プロパティの値として配置したりしないことをお勧めします。
パスワードをクライアントマシンのRAMにプレーンテキストで保持することは、セキュリティの問題です。
だから、public string Password { get; set; }あなたがそこに立ち上がったことを取り除いてください。

にアクセスするときはPasswordBox.Password、取り出してできるだけ早くサーバーに発送してください。パスワードの値を保持したり、他のクライアントマシンのテキストのように扱ったりしないでください。クリアテキストのパスワードをメモリに保持しないでください。

これはMVVMパターンを壊すことは知っていますが、PasswordBox.Password接続されたDPにバインドしたり、パスワードをViewModelや他の類似の悪意のあるものに保存したりしないでください。

過度に設計されたソリューションを探している場合は、次のようになります
。1 IHavePassword.パスワードのクリアテキストを返す1つのメソッドでインターフェイスを作成します。
2. インターフェイスをUserControl実装しIHavePasswordます。
3. インターフェイスUserControlを実装するときに、インスタンスをIoCに登録しますIHavePassword
4.パスワードを必要とするサーバー要求が発生している場合は、IHavePassword実装のためにIoCを呼び出します。

ちょうど私の見解。

-ジャスティン


19
VM for WPFでSecureStringを使用してこの問題を解決できませんか?Silverlightに何かがあるようには見えません。
ブライアント、

35
私はあなたの意図とあなたが伝えているメッセージに同意しますが、あなたの答えは、あなたがこのアプローチに従った場合、パスワード文字列は決して記憶されないことを意味します。パスワードの値は、ユーザーが入力した瞬間から記憶されます。パスフレーズを保持するプロパティを削除することは良い考えであり、ガベージコレクターが取得するために放置されるパスワードのコピー、またはプログラムの一部として実行されている他のマネージドおよびアンマネージドコードによって検出される可能性があるパスワードのコピーを制限しますが、完全に非表示にしないでください。
IanNorton 2010

182
ほとんどの場合、そのレベルのセキュリティ必要ありません。パスワードを盗む方法が他にもたくさんあるとき、それを難し​​くする意味は何ですか?@Bryantが言ったように、少なくともWPFはSecureStringの使用を許可するべきでした。
chakrit

335
悪意のあるユーザーがマシンのRAMにアクセスできる場合、パスワードを盗むよりも大きな問題があります。
Cameron MacFarland 2012年

13
長年、PasswordBoxと同じように動作するカスタムユーザーコントロールを使用してきましたが、テキスト値をSecureStringとしてのみ返します。はい、これにより、Snoopがパスワードをプレーンテキストで表示できなくなります。ただし、SecureStringのプレーンテキスト値は非常に簡単に抽出でき、初心者のハッキングを阻止するだけです。システムがスヌープのようなキーロガーやスニファを密かに使用するリスクがある場合は、システムのセキュリティを再評価する必要があります。
Mike Christian

199

私の2セント:

WPFとMVVMを使用して、典型的なログインダイアログ(ユーザーボックスとパスワードボックス、および[OK]ボタン)をかつて開発しました。[OK]ボタンにアタッチされたコマンドにパラメーターとしてPasswordBoxコントロール自体を渡すだけで、パスワードバインドの問題を解決しました。だから私が持っていた見解では:

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

これはMVVMパターンに少し違反しています。これは、ViewModelがViewの実装方法について何かを知っているためですが、その特定のプロジェクトでは、余裕があります。それが誰かにも役立つことを願っています。


こんにちはコナミマン、実行メソッドが呼び出されたとき。私のビューモデルでは、クラスUser(login、pass)とコマンド認証があります。そのコンテキストで実行を使用するにはどうすればよいですか?

3
とても助かります、ありがとう。fyi、誰かが_loginCommand = new RelayCommand(param => Login(UserName、(PasswordBox)param)、param => CanLogIn);のようなものに慣れているかもしれません。
Chuck Rostance、2012

5
これは問題ありませんが、パスワード+パスワード確認のコンボのようなもので失敗します
Julien

こんにちは、私はあなたのソリューションを使用していますが、Windows 8.1ストアアプリでは動作しません。私はこの質問をしてきました:stackoverflow.com/questions/26221594/...
VansFannelを

2
これをありがとう!これにより、UIスレッドからメインプログラムスレッドにデータを移動するときに発生した大きな問題が解決しました。SecureStringアプローチを実装し、〜パスワードをできるだけ早く取り除いてください〜。それを捨てなさい。処分してください。それをクリアします。あなたがする必要があることをしてください。また、IDisposableを実装してください。
Steven C. Britton

184

多分私は何かが足りないかもしれませんが、これらのソリューションのほとんどは物事を複雑にしすぎて、安全な慣行を廃止するようです。

この方法は、MVVMパターンに違反せず、完全なセキュリティを維持します。はい、技術的にはコードビハインドですが、それは「特別な場合」のバインディングにすぎません。ViewModelはまだViewの実装を認識していません。私の考えでは、PasswordBoxを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には他のプロパティと同様のプロパティがあり、ビューは自己完結型であり、外部参照は必要ありません。


1
これは私によく見えます!セキュリティの面で非常に厳格になりたいと思っていたとしても、これでうまくいくとは思えませんが、私にとっては完璧な中間点です。ありがとう!
jrich523 14

3
MVVMとパラノイアに関する厳格な教義についての実用性に感謝します。ありがとうございます。
Bruce Pierson 2014

2
SecureStringの例は、この拡張機能blogs.msdn.com/b/fpintos/archive/2009/06/12/…で
Ayman

1
いいね。MSがこのコントロールにSecureStringタイプのパスワードDPを追加しただけでいいのですが。
キース・ヒル

1
セキュリティとMVVMを維持するため、これは完璧な答えです。
LoRdPMN 2016

20

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

3
FYIxmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
XAMlMAX 2015

PasswordBoxに名前を付ける必要はありません:(CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=PasswordBox}}"注:not RelativeSource Self)。
wondra

このソリューションは、MVVMパターンに違反しています。
BionicCode

13

これは私にとってはうまくいきます。

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

3
CommandParameter = "{Binding ElementName = MyPasswordBox、Path = SecurePassword"}はどうですか?
LukeN 2010

2
LukeN、これは機能しません(少なくとも私にとっては)。おそらく同じ理由で-SecurePasswordは依存関係プロパティではありません。
vkrzv

ICommandがビューモデルに実装されていると仮定すると、このソリューションはMVVMパターンに違反します。
BionicCode

9

MVVMパターンに違反しない簡単な解決策は、パスワードを取得するイベント(またはデリゲート)をViewModelに導入することです。

ViewModel

public event EventHandler<HarvestPasswordEventArgs> HarvestPassword;

これらのEventArgsを使用:

class HarvestPasswordEventArgs : EventArgs
{
    public string Password;
}

見ます、パスワード値にビューモデルと塗りつぶしの作成上のイベントをサブスクライブします。

_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);

欠けているのは、ビューをビューモデルイベントにサブスクライブするときWeakEventManager<TEventSource, TEventArgs>に、メモリリークを回避するためにを使用する必要があることです。多くの場合、ビューはビューモデルと同じ存続期間を持ちません。 WeakEventManager<IViewModel, EventArgs>.AddHandler(iViewModelInstance, nameof(IViewModel.Event), eventHandlerMethod);
トッドA.ステデル2017

私はこのソリューションを好みます。シンプルで、MVVMに違反せず、背後に最小限のコードがあり、パスワードボックスの正しい使用を許可します(「SecurePassword」を代わりに使用する場合)。(スマートカードのような....)現在、他のHarvestPasswordメソッドを実装するために、今もそれのシンプルな
マット・

8

私はさまざまな解決策を検討するのに長い時間を費やしました。私はデコレーターのアイデアが好きではなかった、ビヘイビアーが検証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ソリューション。


7

バインド可能なパスワードボックスであるGISTをここに投稿しました。

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
これは悪いことではありませんが、パディングやtabindexなどの単純な属性を設定できなくなります
Julien

1
テイラー、私はそれが答えで利用できるように要旨をインライン化した。(それ以外の場合はリンクのみの回答のように見えました。このように削除されないようにしてください。)インライン化されたコンテンツを自由に変更してください。
Lynn Crumbling 2015年

@Julienでもスタイルで修正できます。私はこの問題を同様の方法で解決しContentControlますが、XAMLでコンテンツとスタイルとしてPasswordBoxを使用するだけで、適切に使用できます。の目的は、イベントContentControlをサブスクライブして、PasswordChanged双方向のバインド可能なプロパティを公開することです。全体として、これは65行のコードであり、この装飾クラスが行うこととほぼ同じです。次のgist.github.com/leidegre/c7343b8c720000fe3132の
John Leidegren

6

この実装は少し異なります。パスワードボックスをViewModelのプロパティのバインドを介してビューに渡します。コマンドパラメータは使用しません。ViewModelはビューを無視します。SkyDriveからダウンロードできるVB対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またはコントロールコンテナーのコンテンツの例をバインドします。

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

そこからpasswordboxを完全に制御できます。PasswordAccessor(Just a Function of String)を使用して、ログインまたはその他のパスワードを実行するときに、パスワード値を返します。例では、汎用ユーザーオブジェクトモデルにパブリックプロパティがあります。例:

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

ログイン用のパスワード文字列が必要な場合は、関数を実際に呼び出してパスワードを取得して返すUser Objects Passwordプロパティを取得するだけで、実際のパスワードはユーザーオブジェクトに格納されません。例: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がプロパティを作成および所有し、プロパティがビューから独立しているため、テストが可能です。セキュリティに関しては、この実装がどれほど優れているかわかりません。ただし、関数を使用すると、プロパティによってアクセスされたばかりのプロパティ自体には値が格納されません。


6

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

非常にエレガントなソリューションimo。私はこれに基づいています。唯一の違い:文字列パスワードの代わりにSecureString SecurePasswordをログイン関数に渡します。そのため、暗号化されていない文字列がパスワートがメモリ内を飛んでいることはありません。
私にニンジンを呼んでください'19

しばらく経ちましたが、RelayCommandが原因でこれを機能させることができません。あなたはあなたを追加してくれませんか?
Ecnerwal 2016年

5

パスワードをどこにも保存しないことが重要であることに同意しますが、ビューなしでビューモデルをインスタンス化し、それに対してテストを実行する機能も必要です。

私にとってうまくいった解決策は、ビューモデルにPasswordBox.Password関数を登録し、ログインコードの実行時にビューモデルにそれを呼び出させることでした。

これ、ビューの分離コード内のコード行を意味します。

だから、私の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());

このようにして、viewmodelをテストしたい場合は、PasswordHandlerを匿名メソッドに設定するだけで、テストで使用したいパスワードを提供できます。


4

これは非常に一般的な問題であり、選択肢をたくさん用意することは常に良いことです。

私は単にa PasswordBoxをaでラップし、バインドできるようにをUserControl実装しましたDependencyProperty。クリアテキストをメモリに保存しないようにできる限りのことをしているので、SecureStringおよびPasswordBox.Passwordプロパティを通じてすべてが行われます。中にforeachループ、各文字が露出取得んが、それは非常に簡単です。正直なところ、この短い公開からWPFアプリケーションが危険にさらされるのではないかと心配している場合は、対処する必要のあるより大きなセキュリティ問題があります。

これの美点は、「純粋」なルールであってもMVVMルールを破らないことです。これはUserControlなので、コードビハインドを許可されています。パスワードを使用しているときは、パスワードの一部またはソースを意識することなく、それらの間Viewで純粋な通信を行うことができます。にバインドしていることを確認してください。ViewModelVideModelViewSecureStringViewModel

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

これを実装しようとしましたが、UIでパスワードを更新すると無限ループが発生します。if (Password != secure)SecureStringは等号をオーバーライドしないため、常にfalseになるためです。何かご意見は?
simonalexander2005


2

この方法を使用してパスワードボックスを渡しましたが、これはMVVMに違反していますが、複雑なシェル環境であるシェル内のログインにデータテンプレートを含むコンテンツコントロールを使用していたため、これは私にとって不可欠でした。したがって、シェルの背後にあるコードにアクセスするのはがらくたでしょう。

パスワードボックスを渡すことは、私の知る限り、コードビハインドからコントロールにアクセスすることと同じだと思います。パスワードに同意する、メモリに保持しないなどこの実装では、ビューモデルにパスワードのプロパティがありません。

ボタンコマンド

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

これは、MVVMパターンの明らかな違反です。このパターンでは、ビューモデルのコントロールを処理できません。
BionicCode

2

私には、これらの両方が間違っていると感じています:

  • クリアテキストのパスワードプロパティの実装
  • PasswordBoxコマンドパラメータとしてViewModelに送信する

COのスティーブによって説明されているようにSecurePassword(SecureStringインスタンス)を転送することは受け入れられるようです。私は好むBehaviorsはコードビハインド、viewmodelからパスワードをリセットできるという追加要件もありました。

Xaml(PasswordViewModelプロパティです):

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

    }
}

2

私のような完全な初心者のために、ここにKonamiman上記の提案の完全に機能するサンプルを示します。感謝し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);
    }
}

これは、MVVMパターンの明らかな違反です。このパターンでは、ビューモデルのコントロールを処理できません。
BionicCode

1

ご覧のとおり、私はパスワードにバインドしていますが、おそらくそれを静的クラスにバインドしています。

それは添付プロパティ。この種類のプロパティDependencyObjectは、宣言されているタイプだけでなく、あらゆる種類のに適用できます。したがって、PasswordHelper静的クラスで宣言されていても、それPasswordBoxを使用するに適用されます。

この添付プロパティを使用するには、それをPasswordViewModelのプロパティにバインドするだけです。

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

1

私は次のようにしました:

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

わたしにはできる!


あなたは私にいい考えをくれます。:)
Andre Mendonca

1

前述のように、VMはビューを認識しませんが、PasswordBox全体を渡すことは、最も単純なアプローチのように見えます。したがって、おそらく、渡されたパラメータをPasswordBoxにキャストする代わりに、リフレクションを使用して、そこから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"
    };

コメントは拡張ディスカッション用ではありません。この会話はチャットに移動しました
サミュエルLiew

1

Windowsユニバーサルアプリ

このコードをプロパティ「Password」で使用し、modelViewでバインドできます。

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


1

この実装によるリスクを認識している人は誰でも、パスワードをViewModelと同期させるには、Mode = OneWayToSourceを追加するだけです

XAML

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

なぜしないのですOneWayToSourceか?
BK

@BK私の回答を編集しました。ありがとう。
ケビン

1
モードはバインディングブレースの中にあるべきではありませんか?
マット

@マットヤップ。ありがとう。
ケビン

1

これが私の見解です。

  1. 添付プロパティを使用してパスワードをバインドすると、パスワードを保護する目的が損なわれます。パスワードボックスのPasswordプロパティは、何らかの理由でバインドできません。

  2. コマンドボックスとしてパスワードボックスを渡すと、ViewModelがコントロールを認識します。ViewModelを再利用可能なクロスプラットフォームにする場合、これは機能しません。VMにビューやその他のコントロールを認識させないでください。

  3. パスワードを提供するという単純なタスクでは、新しいプロパティ、インターフェース、パスワード変更イベントのサブスクライブなどの複雑なことを導入する必要はないと思います。

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

0

PasswordBoxのソリューションは、WPF Application Framework(WAF)の ViewModelサンプルアプリケーションにあります。プロジェクト。

しかし、ジャスティンは正しいです。ViewとViewModelの間でパスワードをプレーンテキストとして渡さないでください。代わりにSecureStringを使用してください(MSDN PasswordBoxを参照)。


2
WAFのPop3SettingsViewで使用される方法は面白いです。PasswordBox passwordBox =(PasswordBox)sender; if(ViewModel!= null){ViewModel.Pop3Password = passwordBox.Password; ViewModelのPop3Passwordは文字列プロパティです。そのため、同様に安全ではありません..添付プロパティを使用する方が良い
Michael Sync

0

認証チェックに続いて、メディエータークラスからビュー(認証チェックも実装)に呼び出されるサブルーチンを使用して、データクラスにパスワードを書き込みました。

これは完璧な解決策ではありません。ただし、パスワードを移動できないという私の問題は修正されました。


0

私はまだ言及されていない簡潔なMVVMフレンドリーなソリューションを使用しています。まず、XAMLでPasswordBoxに名前を付けます。

<PasswordBox x:Name="Password" />

次に、単一のメソッド呼び出しをビューコンストラクターに追加します。

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

以上です。ビューモデルは、DataContextを介してビューにアタッチされたときに通知を受け取り、デタッチされたときに別の通知を受け取ります。この通知の内容はラムダを介して構成できますが、通常は問題のあるコントロールをパラメーターとして渡して、ビューモデルのセッターまたはメソッドを呼び出すだけです。

子コントロールの代わりにビュー公開インターフェースを使用することで、MVVMに非常に簡単に対応できます。

上記のコードは、私のブログで公開されているヘルパークラスに依存しています。


0

私はこれを機能させるために何年も費やしました。最後に、私はあきらめて、DevExpressのPasswordBoxEditを使用しました。

恐ろしいトリックを使わずにバインドできるため、これまでで最もシンプルなソリューションです。

DevExpress Webサイトのソリューション

ちなみに、私はDevExpressとは何の関係もありません。


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

;)簡単です!


0

とてもシンプルです。パスワードの別のプロパティを作成し、これをTextBoxにバインドします

ただし、すべての入力操作は実際のパスワードプロパティで実行されます

プライベート文字列_Password;

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

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

            return szChar;
        }

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

パブリック文字列Password {get {return _Password; }

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


パスワードボックスをバインドできないのは、パスワードをクリアな文字列で格納したくないためです。文字列は不変であり、メモリに保持される期間はわかりません。
ランス

0

まあ、私の答えは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のpasswordプロパティは、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>

0

送る SecureString添付動作を使用してビューモデルにをICommand

MVVMを実装する場合、コードビハインドには何の問題もありません。MVVMは、ビューをモデル/ビジネスロジックから分離することを目的としたアーキテクチャパターンです。MVVMは、再現可能な方法(パターン)でこの目標を達成する方法を説明します。ビューの構造や実装方法など、実装の詳細は関係ありません。境界を描き、ビュー、ビューモデル、モデルのパターンをこのパターンの用語で定義します。

MVVMは、言語(XAMLまたはC#)やコンパイラ(partialクラス)を考慮しません。言語に依存しないことは、デザインパターンの必須の特性です。言語に中立である必要があります。

ただし、コードビハインドには、XAMLとC#の間で乱暴に分散されている場合、UIロジックを理解しにくくするなどの欠点があります。しかし、C#でテンプレート、スタイル、トリガー、アニメーションなどのUIロジックまたはオブジェクトを実装する最も重要なことは、XAMLを使用するよりも非常に複雑で、醜く/読みにくくなっています。XAMLは、タグとネストを使用してオブジェクト階層を視覚化するマークアップ言語です。XAMLを使用してUIを作成すると非常に便利です。ただし、C#(またはコードビハインド)でUIロジックを実装することを選択する場合もあります。の取り扱いPasswordBoxは一例です。

このため、を処理することPasswordBoxによってコードビハインドでを処理してPasswordBox.PasswordChangedも、MVVMパターンに違反しません。

明らかな違反は、コントロール(PasswordBox)をビューモデルに渡すことです。多くのソリューションではこれを推奨しています。たとえば、ベイがPasswordBoxas のインスタンスをICommand.CommandParameterビューモデルに渡します。明らかに、非常に悪い、不必要な推奨事項です。

C#の使用を気にせず、コードビハインドファイルをクリーンに保つ、または単に動作/ UIロジックをカプセル化したい場合は、常に添付プロパティを使用して添付動作を実装できます。

プレーンテキストパスワードへのバインドを可能にする悪名高いワイドスプレッドヘルパー(本当に悪いアンチパターンとセキュリティリスク)とは対照的に、この動作は、がイベントを発生させるたびに、を使用しICommandてパスワードをSecureStringビューモデルに送信します。PasswordBoxPasswordBox.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);
  }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.