Cách liên kết với PasswordBox trong MVVM


251

Tôi đã đi qua một vấn đề với ràng buộc vào một P asswordBox. Có vẻ như đó là một rủi ro bảo mật nhưng tôi đang sử dụng mẫu MVVM vì vậy tôi muốn bỏ qua điều này. Tôi tìm thấy một số mã thú vị ở đây (có ai đã sử dụng cái này hoặc cái gì đó tương tự không?)

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

Về mặt kỹ thuật có vẻ tuyệt vời, nhưng tôi không chắc chắn làm thế nào để lấy lại mật khẩu.

Tôi về cơ bản có tài sản của tôi LoginViewModelcho UsernamePassword. Usernamelà tốt và đang làm việc như nó a TextBox.

Tôi đã sử dụng mã ở trên như đã nêu và nhập này

<PasswordBox ff:PasswordHelper.Attach="True"
    ff:PasswordHelper.Password="{Binding Path=Password}" Width="130"/>

Khi tôi có PasswordBoxmột TextBoxBinding Path=Passwordsau đó tài sản trong tôi LoginViewModelđã được cập nhật.

Mã của tôi rất đơn giản, về cơ bản tôi có một Commandcho tôi Button. Khi tôi nhấn nó CanLoginđược gọi và nếu nó trả về đúng thì nó gọi Login.
Bạn có thể thấy tôi kiểm tra tài sản của tôi Usernameở đây hoạt động tuyệt vời.

Trong Logintôi gửi cùng với dịch vụ của tôi a UsernamePassword, Usernamechứa dữ liệu từ của tôi Viewnhưng PasswordNull|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 { }
}

Đây là những gì tôi đang làm

<TextBox Text="{Binding Path=Username, UpdateSourceTrigger=PropertyChanged}"
         MinWidth="180" />

<PasswordBox ff:PasswordHelper.Attach="True" 
             ff:PasswordHelper.Password="{Binding Path=Password}" Width="130"/>

Tôi có của tôi TextBox, đây là không có vấn đề, nhưng trong tôi ViewModelsự Passwordtrống.

Tôi đang làm gì đó sai hay thiếu một bước?

Tôi đặt một điểm dừng và chắc chắn rằng mã vào lớp trình trợ giúp tĩnh nhưng nó không bao giờ cập nhật Passwordtrong tôi ViewModel.


3
Chà, hóa ra mã không hoạt động nhưng tôi đã thử một mã thay thế ở đây và nó hoạt động hoàn hảo. blog.feftalfun.net / 2008/06 / từ
đánh dấu smith

5
Không vượt qua trong toàn bộ điều khiển hộp mật khẩu có đi ngược lại việc tách chế độ xem khỏi chế độ xem không?

Câu trả lời:


164

Xin lỗi, nhưng bạn đang làm sai.

Mọi người nên có hướng dẫn bảo mật sau đây được xăm ở bên trong mí mắt của họ:
Không bao giờ giữ mật khẩu văn bản đơn giản trong bộ nhớ.

Lý do WPF / Silverlight PasswordBoxkhông tiết lộ DP cho Passwordtài sản là liên quan đến bảo mật.
Nếu WPF / Silverlight giữ DP cho Passwordnó, nó sẽ yêu cầu khung để giữ mật khẩu không bị mã hóa trong bộ nhớ. Mà được coi là một vector tấn công an ninh khá rắc rối. Bộ PasswordBoxnhớ sử dụng bộ nhớ được mã hóa (loại) và cách duy nhất để truy cập mật khẩu là thông qua thuộc tính CLR.

Tôi sẽ đề nghị rằng khi truy cập vào thuộc tính PasswordBox.PasswordCLR, bạn không được đặt nó vào bất kỳ biến nào hoặc làm giá trị cho bất kỳ thuộc tính nào.
Giữ mật khẩu của bạn ở dạng văn bản đơn giản trên RAM máy khách là điều không bảo mật.
Vì vậy, loại bỏ những gì public string Password { get; set; }bạn đã lên đó.

Khi truy cập PasswordBox.Password, chỉ cần lấy nó ra và gửi nó đến máy chủ càng sớm càng tốt. Đừng giữ giá trị của mật khẩu xung quanh và đừng coi nó như bất kỳ văn bản máy khách nào khác. Đừng giữ mật khẩu văn bản rõ ràng trong bộ nhớ.

Tôi biết điều này phá vỡ mẫu MVVM, nhưng bạn không bao giờ nên liên kết với PasswordBox.PasswordDP đính kèm, lưu trữ mật khẩu của bạn trong ViewModel hoặc bất kỳ shenanigans tương tự nào khác.

Nếu bạn đang tìm kiếm một giải pháp được kiến ​​trúc quá mức, thì đây là một:
1. Tạo IHavePasswordgiao diện với một phương thức trả về mật khẩu xóa văn bản.
2. Yêu cầu bạn UserControlthực hiện một IHavePasswordgiao diện.
3. Đăng ký UserControlcá thể với IoC của bạn khi thực hiện IHavePasswordgiao diện.
4. Khi một yêu cầu máy chủ yêu cầu mật khẩu của bạn đang diễn ra, hãy gọi cho IoC của bạn để IHavePasswordthực hiện và chỉ nhận được mật khẩu được nhiều người thèm muốn.

Chỉ cần tôi nhận nó.

- Justin


19
Bạn không thể sử dụng SecureString trong VM cho WPF để giải quyết vấn đề này? Dường như không có thứ gì cho Silverlight.
Bryant

35
Tôi đồng ý với ý định của bạn và thông điệp bạn đang truyền tải nhưng câu trả lời của bạn ngụ ý rằng chuỗi mật khẩu không bao giờ có trong bộ nhớ nếu bạn làm theo phương pháp này. Giá trị của mật khẩu sẽ nằm trong bộ nhớ từ thời điểm người dùng nhập nó. Loại bỏ tài sản giữ cụm mật khẩu của bạn là một ý tưởng hay và sẽ hạn chế các bản sao mật khẩu của bạn bị bỏ lại để người thu gom rác gặt hái hoặc có thể được tìm thấy bởi mã được quản lý và không được quản lý khác đang chạy như một phần của chương trình của bạn, nhưng sẽ không che giấu nó hoàn toàn.
IanNorton

182
Đối với hầu hết các trường hợp, bạn không cần mức bảo mật đó. Đâu là điểm khiến một điều khó khăn khi có quá nhiều cách khác để đánh cắp mật khẩu? Atleast WPF nên cho phép sử dụng SecureString như @Bryant đã nói.
chakrit

335
Nếu kẻ xấu có quyền truy cập vào RAM của máy bạn, bạn có vấn đề lớn hơn là chúng đánh cắp mật khẩu của bạn.
Cameron MacFarland

13
Trong nhiều năm, tôi đã sử dụng một điều khiển người dùng tùy chỉnh hoạt động giống như PasswordBox, nhưng chỉ trả về giá trị văn bản là SecureString. Có, điều này ngăn Snoop hiển thị mật khẩu bằng văn bản thuần túy. Tuy nhiên, giá trị văn bản đơn giản của SecureString vẫn có thể được trích xuất khá dễ dàng và chỉ ngăn chặn các bản hack mới. Nếu hệ thống của bạn có nguy cơ sử dụng các công cụ ghi nhật ký và đánh hơi chính như Snoop, bạn nên đánh giá lại về bảo mật hệ thống của mình.
Mike Christian

199

2 xu của tôi:

Tôi đã phát triển một lần hộp thoại đăng nhập thông thường (hộp người dùng và mật khẩu, cộng với nút "Ok") bằng WPF và MVVM. Tôi đã giải quyết vấn đề ràng buộc mật khẩu bằng cách chuyển chính điều khiển PasswordBox làm tham số cho lệnh được gắn vào nút "Ok". Vì vậy, theo quan điểm tôi đã có:

<PasswordBox Name="txtPassword" VerticalAlignment="Top" Width="120" />
<Button Content="Ok" Command="{Binding Path=OkCommand}"
   CommandParameter="{Binding ElementName=txtPassword}"/>

Và trong ViewModel, Executephương thức của lệnh đính kèm như sau:

void Execute(object parameter)
{
    var passwordBox = parameter as PasswordBox;
    var password = passwordBox.Password;
    //Now go ahead and check the user name and password
}

Điều này hơi vi phạm mẫu MVVM kể từ bây giờ ViewModel biết điều gì đó về cách thực hiện View, nhưng trong dự án cụ thể đó tôi có thể mua được. Hy vọng nó cũng hữu ích cho ai đó.


Xin chào Konamiman, khi phương thức Execute được gọi. Trong chế độ xem của tôi, tôi có một lớp Người dùng (đăng nhập, vượt qua) và một lệnh xác thực. Làm thế nào tôi có thể sử dụng Execute trong bối cảnh đó?

3
Rất hữu ích, cảm ơn. fyi, ai đó có thể được sử dụng để xem một cái gì đó như _loginCommand = new RelayCommand (param => Đăng nhập (Tên người dùng, (PasswordBox) param), param => CanLogIn);
Chuck Rostance

5
Đây là một giải pháp ok nhưng không thành công cho một cái gì đó như mật khẩu + kết hợp xác nhận mật khẩu
Julien

Xin chào Konamiman, tôi đang sử dụng giải pháp của bạn nhưng nó không hoạt động trên ứng dụng Windows 8.1 Store. Tôi đã hỏi câu hỏi này: stackoverflow.com/questions/26221594/ từ
VansFannel

2
Cảm ơn vì điều đó! Điều này đã giải quyết một vấn đề lớn mà tôi gặp phải khi chuyển dữ liệu từ luồng UI sang luồng chương trình chính. Đảm bảo triển khai phương pháp SecureString và ~ thoát mật khẩu càng sớm càng tốt ~. Đổ nó đi Vứt bỏ nó. Xóa nó đi. Hãy làm những gì bạn cần làm. Ngoài ra, hãy chắc chắn rằng bạn triển khai IDis Dùng một lần.
Steven C. Britton

184

Có thể tôi đang thiếu một cái gì đó, nhưng có vẻ như hầu hết các giải pháp này quá phức tạp mọi thứ và làm mất đi các thực tiễn an toàn.

Phương thức này không vi phạm mẫu MVVM và duy trì bảo mật hoàn toàn. Vâng, về mặt kỹ thuật nó là mã phía sau, nhưng nó không có gì khác hơn là một ràng buộc "trường hợp đặc biệt". ViewModel vẫn không có kiến ​​thức về triển khai View, điều này trong suy nghĩ của tôi nếu bạn đang cố gắng chuyển PasswordBox vào ViewModel.

Mã phía sau! = Vi phạm MVVM tự động. Tất cả phụ thuộc vào những gì bạn làm với nó. Trong trường hợp này, chúng tôi chỉ mã hóa thủ công một ràng buộc, vì vậy tất cả được coi là một phần của việc triển khai UI và do đó là ổn.

Trong ViewModel, chỉ là một thuộc tính đơn giản. Tôi đã làm cho nó "chỉ viết" vì không cần phải lấy nó từ bên ngoài ViewModel vì bất kỳ lý do gì, nhưng nó không phải như vậy. Lưu ý rằng đó là SecureString, không chỉ là một chuỗi.

public SecureString SecurePassword { private get; set; }

Trong xaml, bạn thiết lập trình xử lý sự kiện PasswordChanged.

<PasswordBox PasswordChanged="PasswordBox_PasswordChanged"/>

Trong mã phía sau:

private void PasswordBox_PasswordChanged(object sender, RoutedEventArgs e)
{
    if (this.DataContext != null)
    { ((dynamic)this.DataContext).SecurePassword = ((PasswordBox)sender).SecurePassword; }
}

Với phương pháp này, mật khẩu của bạn luôn ở trong SecureString và do đó cung cấp bảo mật tối đa. Nếu bạn thực sự không quan tâm đến bảo mật hoặc bạn cần mật khẩu văn bản rõ ràng cho phương thức hạ nguồn yêu cầu nó (lưu ý: hầu hết các phương thức .NET yêu cầu mật khẩu cũng hỗ trợ tùy chọn SecureString, vì vậy bạn có thể không thực sự cần mật khẩu văn bản rõ ràng ngay cả khi bạn nghĩ rằng bạn làm), bạn chỉ có thể sử dụng thuộc tính Mật khẩu thay thế. Như thế này:

(Thuộc tính ViewModel)

public string Password { private get; set; }

(Mã ẩn)

private void PasswordBox_PasswordChanged(object sender, RoutedEventArgs e)
{
    if (this.DataContext != null)
    { ((dynamic)this.DataContext).Password = ((PasswordBox)sender).Password; }
}

Nếu bạn muốn giữ mọi thứ được gõ mạnh, bạn có thể thay thế dàn diễn viên (động) bằng giao diện của ViewModel. Nhưng thực sự, các ràng buộc dữ liệu "bình thường" cũng không được gõ mạnh, vì vậy nó không phải là vấn đề lớn.

private void PasswordBox_PasswordChanged(object sender, RoutedEventArgs e)
{
    if (this.DataContext != null)
    { ((IMyViewModel)this.DataContext).Password = ((PasswordBox)sender).Password; }
}

Vì vậy, tốt nhất trong tất cả các thế giới - mật khẩu của bạn được bảo mật, ViewModel của bạn chỉ có một thuộc tính như bất kỳ thuộc tính nào khác và Chế độ xem của bạn không có tài liệu tham khảo bên ngoài.


1
Điều này có vẻ tốt với tôi! Nếu bạn muốn cực kỳ nghiêm ngặt về mặt an ninh thì tôi không chắc điều này sẽ cắt nó, nhưng với tôi đó là một nền tảng hoàn hảo. cảm ơn!
jrich523

3
Cảm ơn thực tiễn về giáo điều cứng nhắc về MVVM và hoang tưởng. Công trình tuyệt vời, cảm ơn.
Bruce Pierson

2
Ví dụ về SecureString sẽ rất tuyệt với blog
Ayman

1
Thực sự tốt đẹp. Tôi ước MS sẽ vừa thêm Mật khẩu DP loại SecureString vào điều khiển này.
Keith Hill

1
Đây là câu trả lời hoàn hảo, vì nó giữ bảo mật và MVVM.
LoRdPMN

20

Bạn có thể sử dụng XAML này:

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

Và lệnh này thực thi phương thức:

private void ExecutePasswordChangedCommand(PasswordBox obj)
{ 
   if (obj != null)
     Password = obj.Password;
}

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

Không yêu cầu Đặt tên cho PasswordBox: CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=PasswordBox}}"(lưu ý: không RelativeSource Self ).
wonderra

Giải pháp này vi phạm mẫu MVVM.
BionicCode

13

Điều này chỉ làm việc tốt cho tôi.

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

3
Điều gì về CommandParameter = "{Binding ElementName = MyPasswordBox, Path = SecurePassword"}?
LukeN

2
LukeN, điều này không hoạt động (ít nhất là đối với tôi). Có lẽ vì lý do tương tự - SecurePassword không phải là thuộc tính phụ thuộc.
vkrzv

Giả sử rằng ICommandđược triển khai trong mô hình khung nhìn, giải pháp này sẽ vi phạm mẫu MVVM.
BionicCode

9

Một giải pháp đơn giản mà không vi phạm mẫu MVVM là giới thiệu một sự kiện (hoặc đại biểu) trong ViewModel thu thập mật khẩu.

Trong ViewModel :

public event EventHandler<HarvestPasswordEventArgs> HarvestPassword;

với các EventArss này:

class HarvestPasswordEventArgs : EventArgs
{
    public string Password;
}

trong Chế độ xem , đăng ký sự kiện tạo ViewModel và điền giá trị mật khẩu.

_viewModel.HarvestPassword += (sender, args) => 
    args.Password = passwordBox1.Password;

Trong ViewModel , khi bạn cần mật khẩu, bạn có thể kích hoạt sự kiện và thu thập mật khẩu từ đó:

if (HarvestPassword == null)
  //bah 
  return;

var pwargs = new HarvestPasswordEventArgs();
HarvestPassword(this, pwargs);

LoginHelpers.Login(Username, pwargs.Password);

Một điều bạn đang thiếu là khi đăng ký một khung nhìn cho một sự kiện mô hình xem, bạn nên sử dụng một WeakEventManager<TEventSource, TEventArgs>để tránh rò rỉ bộ nhớ. Thông thường, chế độ xem sẽ không có thời gian tồn tại giống như chế độ xem. WeakEventManager<IViewModel, EventArgs>.AddHandler(iViewModelInstance, nameof(IViewModel.Event), eventHandlerMethod);
Todd A. Stedel

Tôi thích giải pháp này, vì nó đơn giản, không vi phạm MVVM, có mã tối thiểu phía sau, cho phép sử dụng đúng hộp mật khẩu (nếu bạn sử dụng thaySecurePassword mật). Ngoài ra, giờ đây thật đơn giản để thực hiện các phương thức HarvestPassword khác (như SmartCard ....)
Matt

8

Tôi đã dành rất nhiều thời gian để xem xét các giải pháp khác nhau. Tôi không thích ý tưởng trang trí, hành vi làm rối giao diện người dùng xác thực, mã phía sau ... thực sự?

Cách tốt nhất là gắn bó với một thuộc tính được đính kèm tùy chỉnh và liên kết với SecureStringthuộc tính của bạn trong mô hình xem của bạn. Giữ nó trong đó càng lâu càng tốt. Bất cứ khi nào bạn sẽ cần truy cập nhanh vào mật khẩu đơn giản, hãy tạm thời chuyển đổi nó thành một chuỗi không an toàn bằng cách sử dụng mã dưới đây:

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

Hãy chắc chắn rằng bạn cho phép GC thu thập phần tử UI của bạn, để chống lại sự thôi thúc sử dụng trình xử lý sự kiện tĩnh cho PasswordChangedsự kiện trên PasswordBox. Tôi cũng phát hiện ra sự bất thường khi điều khiển không cập nhật giao diện người dùng khi sử dụng thuộc SecurePasswordtính để thiết lập nó, lý do tại sao tôi sao chép mật khẩu vào Passwordthay thế.

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

Và việc sử dụng XAML:

<PasswordBox controls:PasswordBoxHelper.SecurePassword="{Binding LogonPassword, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}">

Tài sản của tôi trong mô hình xem như thế này:

[RequiredSecureString]
public SecureString LogonPassword
{
   get
   {
       return _logonPassword;
   }
   set
   {
       _logonPassword = value;
       NotifyPropertyChanged(nameof(LogonPassword));
   }
}

Đây RequiredSecureStringchỉ là một trình xác nhận tùy chỉnh đơn giản có logic sau:

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

Ở đây bạn có nó. Một giải pháp MVVM hoàn chỉnh và được thử nghiệm.


7

Tôi đã đăng một GIST ở đây là một hộp mật khẩu có thể ràng buộc.

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
trong khi điều này không tệ, bạn sẽ mất khả năng thiết lập các thuộc tính đơn giản như đệm và tabindex
Julien

1
Taylor, tôi đã đánh dấu ý chính để nó có sẵn trong câu trả lời. (Nó trông giống như một câu trả lời chỉ liên kết khác .. chỉ cố gắng tránh điều này bị xóa như vậy.) Hãy thoải mái để lộn xộn với nội dung được nội tuyến.
Lynn sụp đổ

@Julien nhưng bạn có thể sửa nó với các kiểu. Tôi giải quyết vấn đề này theo cách tương tự nhưng tôi sử dụng một ContentControlbạn sau đó có thể chỉ cần sử dụng PasswordBox làm nội dung và kiểu trong XAML khi bạn phù hợp. Mục đích của ContentControlchỉ là để đăng ký vào PasswordChangedsự kiện và phơi bày một tài sản ràng buộc hai chiều. Nói chung, đó là 65 dòng mã và khá nhiều thứ mà lớp trang trí này làm. Xem ở đây để biết ý chính của tôi về gist.github.com/leidegre/c7343b8c720000fe3132
John Leidegren

6

Việc thực hiện này hơi khác nhau. Bạn chuyển một hộp mật khẩu đến ràng buộc Xem thông qua của một thuộc tính trong ViewModel, nó không sử dụng bất kỳ thông số lệnh nào. ViewModel không biết gì về View. Tôi có Dự án VB vs 2010 có thể tải xuống từ SkyDrive. Wpf MvvM PassWordBox example.zip https://skydrive.live.com/redir.aspx?cid=e95997d33a9f8d73&resid=E95997D33A9F8D73!511

Cách tôi đang sử dụng PasswordBox trong Ứng dụng Wvf MvvM khá đơn giản và hoạt động tốt với tôi. Điều đó không có nghĩa là tôi nghĩ đó là cách chính xác hay cách tốt nhất. Nó chỉ là một triển khai của Sử dụng PasswordBox và Mẫu MvvM.

Về cơ bản Bạn tạo một thuộc tính chỉ đọc công khai mà Chế độ xem có thể liên kết với tư cách là Mật khẩu (Điều khiển thực tế) Ví dụ:

Private _thePassWordBox As PasswordBox
Public ReadOnly Property ThePassWordBox As PasswordBox
    Get
        If IsNothing(_thePassWordBox) Then _thePassWordBox = New PasswordBox
        Return _thePassWordBox
    End Get
End Property

Tôi sử dụng một trường sao lưu chỉ để tự khởi tạo tài sản.

Sau đó, từ Xaml, bạn liên kết Nội dung của ContentControl hoặc Ví dụ về Container Điều khiển:

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

Từ đó bạn có toàn quyền kiểm soát hộp mật khẩu, tôi cũng sử dụng PasswordAccessor (Chỉ là Hàm của Chuỗi) để trả về Giá trị Mật khẩu khi đăng nhập hoặc bất cứ thứ gì bạn muốn Mật khẩu. Trong ví dụ tôi có một thuộc tính công cộng trong Mô hình đối tượng người dùng chung. Thí dụ:

Public Property PasswordAccessor() As Func(Of String)

Trong Đối tượng người dùng, thuộc tính chuỗi mật khẩu chỉ đọc mà không có bất kỳ lưu trữ sao lưu nào, nó chỉ trả về Mật khẩu từ PasswordBox. Thí dụ:

Public ReadOnly Property PassWord As String
    Get
        Return If((PasswordAccessor Is Nothing), String.Empty, PasswordAccessor.Invoke())
    End Get
End Property

Sau đó, trong ViewModel, tôi đảm bảo rằng Accessor được tạo và được đặt thành thuộc tính PasswordBox.Password 'Ví dụ:

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

Khi tôi cần chuỗi Mật khẩu để đăng nhập, tôi chỉ cần lấy thuộc tính Mật khẩu đối tượng người dùng thực sự gọi Hàm để lấy mật khẩu và trả lại mật khẩu, sau đó mật khẩu thực tế không được lưu trữ bởi Đối tượng người dùng. Ví dụ: sẽ có trong 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

Nên làm vậy. ViewModel không cần bất kỳ kiến ​​thức nào về Điều khiển của View. Chế độ xem Chỉ liên kết với thuộc tính trong ViewModel, không khác gì Chế độ xem liên kết với hình ảnh hoặc tài nguyên khác. Trong trường hợp này, tài nguyên (Thuộc tính) chỉ là một điều khiển người dùng. Nó cho phép thử nghiệm khi ViewModel tạo và sở hữu Tài sản và Thuộc tính độc lập với Chế độ xem. Về phần Bảo mật, tôi không biết việc triển khai này tốt như thế nào. Nhưng bằng cách sử dụng Hàm, Giá trị không được lưu trữ trong Thuộc tính mà Tài sản vừa truy cập.


6

Để giải quyết vấn đề OP mà không phá vỡ MVVM, tôi sẽ sử dụng trình chuyển đổi giá trị tùy chỉnh và trình bao bọc cho giá trị (mật khẩu) phải được lấy từ hộp mật khẩu.

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

Trong mô hình xem:

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

Bởi vì mô hình khung nhìn sử dụng IWrappedParameter<T>, nó không cần phải có bất kỳ kiến ​​thức nào về PasswordBoxWrappercũng như PasswordBoxConverter. Bằng cách này bạn có thể cô lậpPasswordBox đối tượng khỏi mô hình khung nhìn và không phá vỡ mẫu MVVM.

Theo quan điểm:

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

giải pháp imo rất thanh lịch. tôi đã dựa vào cái này điểm khác biệt duy nhất: tôi chuyển SecureString SecurePassword cho chức năng đăng nhập thay vì Mật khẩu chuỗi. do đó, không có chuỗi không được mã hóa với mật khẩu bay xung quanh bộ nhớ.
gọi tôi là cà rốt

Đã được một thời gian nhưng tôi dường như không thể làm cho nó hoạt động được vì RelayCommand của tôi. bạn có phiền khi thêm bạn không?
Ecnerwal

5

Mặc dù tôi đồng ý rằng điều quan trọng là tránh lưu trữ mật khẩu ở bất cứ đâu, tôi vẫn cần khả năng khởi tạo mô hình chế độ xem mà không cần xem và thực hiện các thử nghiệm đối với mật khẩu đó.

Giải pháp hiệu quả với tôi là đăng ký hàm PasswordBox.Password với mô hình khung nhìn và có mô hình khung nhìn gọi nó khi thực thi mã đăng nhập.

Điều này không có nghĩa là một dòng mã trong cơ sở mã của khung nhìn.

Vì vậy, trong Đăng nhập.xaml của tôi, tôi có

<PasswordBox x:Name="PasswordBox"/>

và trong Đăng nhập.xaml.cs tôi có

LoginViewModel.PasswordHandler = () => PasswordBox.Password;

sau đó trong LoginViewModel.cs tôi đã xác định PasswordHandler

public Func<string> PasswordHandler { get; set; }

và khi đăng nhập cần xảy ra, mã sẽ gọi trình xử lý để lấy mật khẩu từ chế độ xem ...

bool loginResult = Login(Username, PasswordHandler());

Bằng cách này, khi tôi muốn kiểm tra viewmodel, tôi có thể chỉ cần đặt PasswordHandler thành một phương thức ẩn danh cho phép tôi gửi bất kỳ mật khẩu nào tôi muốn sử dụng trong thử nghiệm.


4

Tôi hình dung tôi sẽ đưa giải pháp của mình vào hỗn hợp, vì đây là một vấn đề phổ biến ... và có nhiều lựa chọn luôn là một điều tốt.

Tôi chỉ đơn giản là quấn một PasswordBoxtrong một UserControlvà thực hiện một DependencyPropertyđể có thể ràng buộc. Tôi đang làm mọi thứ có thể để tránh lưu trữ bất kỳ văn bản rõ ràng nào trong bộ nhớ, vì vậy mọi thứ đều được thực hiện thông qua a SecureStringvà thuộc PasswordBox.Passwordtính. Trong foreachvòng lặp, mỗi nhân vật sẽ bị lộ, nhưng nó rất ngắn gọn. Thành thật mà nói, nếu bạn lo lắng về việc ứng dụng WPF của mình bị xâm phạm từ lần tiếp xúc ngắn ngủi này, bạn đã gặp phải các vấn đề bảo mật lớn hơn cần được xử lý.

Cái hay của việc này là bạn không vi phạm bất kỳ quy tắc MVVM nào, ngay cả những quy tắc "thuần túy", vì đây là một UserControl, vì vậy nó được phép có mã phía sau. Khi bạn đang sử dụng nó, bạn có thể có giao tiếp thuần túy giữa ViewViewModelkhông cần VideModelbiết về bất kỳ phần nào Viewhoặc nguồn mật khẩu. Chỉ cần chắc chắn rằng bạn đang ràng buộc SecureStringtrong của bạn 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 (Phiên bản 1 - Không hỗ trợ ràng buộc hai chiều.)

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

Cách sử dụng Phiên bản 1:

<local:BindanblePasswordBox Width="150" HorizontalAlignment="Center"
                            VerticalAlignment="Center"
                            Password="{Binding Password, Mode=OneWayToSource}"/>

BindablePasswordBox.xaml.cs (Phiên bản 2 - Có hỗ trợ ràng buộc hai chiều.)

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

Cách sử dụng phiên bản 2:

<local:BindanblePasswordBox Width="150" HorizontalAlignment="Center"
                            VerticalAlignment="Center"
                            Password="{Binding Password, Mode=TwoWay}"/>

Tôi đã thử thực hiện điều này, nhưng bạn nhận được một vòng lặp vô hạn khi bạn cập nhật mật khẩu trên giao diện người dùng; bởi vì if (Password != secure)sẽ luôn sai vì SecureString không ghi đè bằng. Có suy nghĩ gì không?
simonalexander2005


2

Tôi đã sử dụng phương thức này và thông qua hộp mật khẩu, mặc dù điều này vi phạm MVVM nhưng nó rất cần thiết đối với tôi vì tôi đang sử dụng một điều khiển nội dung với mẫu dữ liệu để đăng nhập trong shell của mình, đây là một môi trường shell phức tạp. Vì vậy, việc truy cập mã phía sau của vỏ sẽ là tào lao.

Vượt qua hộp mật khẩu tôi sẽ nghĩ giống như truy cập kiểm soát từ mã phía sau theo như tôi biết. Tôi đồng ý mật khẩu, không lưu trong bộ nhớ, v.v. Trong triển khai này, tôi không có thuộc tính cho mật khẩu trong mô hình xem.

Nút lệnh

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

Đây là một sự vi phạm rõ ràng về mẫu MVVM. Mẫu không cho phép xử lý các điều khiển trong mô hình xem.
BionicCode

2

Đối với tôi, cả hai điều này đều cảm thấy sai:

  • Thực hiện các thuộc tính mật khẩu văn bản rõ ràng
  • Gửi PasswordBoxtham số dưới dạng tham số cho ViewModel

Chuyển từ SecurePassword (ví dụ SecureString) như được mô tả bởi Steve trong CO có vẻ chấp nhận được. Tôi thích Behaviorsmã phía sau và tôi cũng có yêu cầu bổ sung là có thể đặt lại mật khẩu từ chế độ xem.

Xaml ( Passwordlà thuộc tính ViewModel):

<PasswordBox>
    <i:Interaction.Behaviors>
        <behaviors:PasswordBinding BoundPassword="{Binding Password, Mode=TwoWay}" />
    </i:Interaction.Behaviors>
</PasswordBox>

Hành vi:

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

Đối với những người mới hoàn thành như tôi, đây là một mẫu hoàn chỉnh về những gì được Konamimanđề xuất ở trên. Cảm ơn 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);
    }
}

Đây là một sự vi phạm rõ ràng về mẫu MVVM. Mẫu không cho phép xử lý các điều khiển trong mô hình xem.
BionicCode

1

Như bạn có thể thấy tôi đang ràng buộc với Mật khẩu, nhưng có lẽ nó liên kết nó với lớp tĩnh ..

Nó là một tài sản gắn liền . Loại tài sản này có thể được áp dụng cho bất kỳ loại nào DependencyObject, không chỉ loại mà nó được khai báo. Vì vậy, mặc dù nó được khai báo trong PasswordHelperlớp tĩnh, nó được áp dụng cho cái PasswordBoxmà bạn sử dụng nó.

Để sử dụng thuộc tính được đính kèm này, bạn chỉ cần liên kết nó với thuộc Passwordtính trong ViewModel của bạn:

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

1

Tôi đã làm như sau:

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

Nó làm việc cho tôi!


Bạn cho tôi một ý tưởng tốt đẹp. :)
Andre Mendonca

1

Như đã đề cập trước VM nên không biết về View nhưng việc chuyển toàn bộ PasswordBox có vẻ như là cách tiếp cận đơn giản nhất. Vì vậy, có thể thay vì truyền tham số đã truyền vào PasswordBox, hãy sử dụng Reflection để trích xuất thuộc tính Mật khẩu từ nó. Trong trường hợp này, VM mong đợi một số loại Bộ chứa Mật khẩu có Mật khẩu thuộc tính (Tôi đang sử dụng RelayCommands từ MVMM Light-Toolkit):

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

Nó có thể dễ dàng được kiểm tra với lớp ẩn danh:

var passwordContainer = new
    {
        Password = "password"
    };

Bình luận không dành cho thảo luận mở rộng; cuộc trò chuyện này đã được chuyển sang trò chuyện .
Samuel Liew

1

Trong cửa sổ ứng dụng phổ quát

bạn có thể sử dụng mã này với thuộc tính "Mật khẩu" và liên kết với modelView

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


1

Đối với bất kỳ ai nhận thức được các rủi ro mà việc triển khai này đặt ra, để đồng bộ hóa mật khẩu với ViewModel của bạn chỉ cần thêm Mode = OneWayToSource .

XAML

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

Tại sao không làm OneWayToSource?
BK

@BK Chỉnh sửa câu trả lời của tôi. Cảm ơn.
Kevin

1
Chế độ không nên ở trong niềng răng ràng buộc?
Mat

@Mat Yap. Cảm ơn.
Kevin

1

Đây là của tôi về nó:

  1. Sử dụng một thuộc tính đính kèm để ràng buộc mật khẩu đánh bại mục đích bảo mật mật khẩu. Thuộc tính Mật khẩu của hộp mật khẩu không bị ràng buộc vì một lý do.

  2. Vượt qua hộp mật khẩu dưới dạng tham số lệnh sẽ làm cho ViewModel nhận biết điều khiển. Điều này sẽ không hoạt động nếu bạn có kế hoạch làm cho nền tảng chéo ViewModel có thể tái sử dụng của bạn. Đừng làm cho VM của bạn biết về Chế độ xem của bạn hoặc bất kỳ điều khiển nào khác.

  3. Tôi không nghĩ việc giới thiệu một tài sản mới, giao diện, đăng ký các sự kiện thay đổi mật khẩu hoặc bất kỳ điều phức tạp nào khác là cần thiết cho một nhiệm vụ đơn giản là cung cấp mật khẩu.

XAML

<PasswordBox x:Name="pbPassword" />
<Button Content="Login" Command="{Binding LoginCommand}" x:Name="btnLogin"/>

Mã phía sau - sử dụng mã phía sau không nhất thiết vi phạm MVVM. Miễn là bạn không đặt bất kỳ logic kinh doanh nào vào đó.

btnLogin.CommandParameter = new Func<string>(()=>pbPassword.Password); 

ViewModel

LoginCommand = new RelayCommand<Func<string>>(getpwd=> { service.Login(username, getpwd()); });

0

Bạn tìm một giải pháp cho PasswordBox trong ứng dụng mẫu ViewModel của dự án Khung ứng dụng WPF (WAF) .

Tuy nhiên, Justin đã đúng. Không chuyển mật khẩu dưới dạng văn bản đơn giản giữa View và ViewModel. Sử dụng SecureString thay thế (Xem MSDN PasswordBox).


2
Cách được sử dụng trong Pop3SinstallView của WAF thật buồn cười. Người gửi passwordBoxBoxBox = (PasswordBox); if (ViewModel! = null) {ViewModel.Pop3Password = passwordBox.Password; } Pop3Password của ViewModel là thuộc tính chuỗi. vì vậy, nó cũng không an toàn .. tốt hơn là sử dụng thuộc tính đính kèm
Michael Sync

0

Tôi đã sử dụng một kiểm tra xác thực theo sau bởi một phụ được gọi bởi một lớp trung gian cho View (cũng thực hiện kiểm tra xác thực) để ghi mật khẩu vào lớp dữ liệu.

Đó không phải là một giải pháp hoàn hảo; tuy nhiên, nó đã khắc phục vấn đề của tôi là không thể di chuyển mật khẩu.


0

Tôi đang sử dụng giải pháp thân thiện MVVM ngắn gọn chưa được đề cập. Đầu tiên, tôi đặt tên cho PasswordBox trong XAML:

<PasswordBox x:Name="Password" />

Sau đó, tôi thêm một cuộc gọi phương thức duy nhất vào view constructor:

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

Và đó là nó. Mô hình khung nhìn sẽ nhận được thông báo khi nó được gắn vào một khung nhìn thông qua DataContext và một thông báo khác khi nó được tách ra. Nội dung của thông báo này có thể được cấu hình thông qua lambdas, nhưng thông thường nó chỉ là một lệnh setter hoặc phương thức trên mô hình khung nhìn, chuyển điều khiển có vấn đề như một tham số.

Nó có thể được làm cho MVVM thân thiện rất dễ dàng bằng cách có giao diện hiển thị khung nhìn thay vì các điều khiển con.

Đoạn mã trên dựa vào lớp người trợ giúp được xuất bản trên blog của tôi.


0

Tôi đã dành nhiều năm cố gắng để làm việc này. Cuối cùng, tôi đã từ bỏ và chỉ sử dụng PasswordBoxEdit từ DevExpress.

Đó là giải pháp đơn giản nhất từ ​​trước đến nay, vì nó cho phép ràng buộc mà không cần kéo theo bất kỳ thủ đoạn khủng khiếp nào.

Giải pháp trên trang web DevExpress

Đối với hồ sơ, tôi không liên kết với DevExpress dưới bất kỳ hình thức nào.


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

;) dễ dàng!


0

Nó rất đơn giản . Tạo một thuộc tính khác cho mật khẩu và ràng buộc điều này với TextBox

Nhưng tất cả các hoạt động đầu vào thực hiện với thuộc tính mật khẩu thực tế

chuỗi riêng _Password;

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

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

            return szChar;
        }

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

Mật khẩu chuỗi công khai {get {return _Password; }

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


Lý do hộp mật khẩu không thể ràng buộc là vì chúng tôi không muốn lưu trữ mật khẩu trong một chuỗi rõ ràng. Chuỗi là bất biến và chúng tôi không chắc nó sẽ tồn tại trong bộ nhớ bao lâu.
Lance

0

Vâng, câu trả lời của tôi đơn giản hơn chỉ trong mẫu MVVM

trong lớp 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;

}

thuộc tính mật khẩu của PasswordBox mà win cung cấp hoặc WatermarkPasswordBox mà XCeedtoolkit cung cấp sẽ tạo ra RoutedEventArss để bạn có thể liên kết nó.

bây giờ trong chế độ xem 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>

hoặc là

<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

Gửi một SecureStringmô hình xem bằng Hành vi được đính kèm vàICommand

Không có gì sai với mã phía sau khi triển khai MVVM. MVVM là một mô hình kiến ​​trúc nhằm tách biệt khung nhìn khỏi logic mô hình / kinh doanh. MVVM mô tả cách đạt được mục tiêu này theo cách có thể tái tạo (mẫu). Nó không quan tâm đến các chi tiết triển khai, như cách bạn cấu trúc hoặc thực hiện chế độ xem. Nó chỉ vẽ các ranh giới và định nghĩa thế nào là khung nhìn, mô hình khung nhìn và mô hình nào theo thuật ngữ của mẫu này.

MVVM không quan tâm đến ngôn ngữ (XAML hoặc C #) hoặc trình biên dịch ( partiallớp). Độc lập ngôn ngữ là một đặc tính bắt buộc của một mẫu thiết kế - nó phải là ngôn ngữ trung lập.

Tuy nhiên, mã phía sau có một số nhược điểm như làm cho logic UI của bạn khó hiểu hơn, khi nó được phân phối mạnh mẽ giữa XAML và C #. Nhưng quan trọng nhất là triển khai logic UI hoặc các đối tượng như mẫu, kiểu, trình kích hoạt, hình động, v.v. trong C # rất phức tạp và xấu / ít đọc hơn so với sử dụng XAML. XAML là một ngôn ngữ đánh dấu sử dụng các thẻ và lồng nhau để trực quan hóa phân cấp đối tượng. Tạo UI bằng XAML rất thuận tiện. Mặc dù có những tình huống bạn vẫn ổn khi chọn triển khai logic UI trong C # (hoặc mã phía sau). Xử lýPasswordBox là một ví dụ.

Vì lý do này, việc xử lý PasswordBoxmã phía sau bằng cách xử lýPasswordBox.PasswordChanged , không vi phạm mẫu MVVM.

Một vi phạm rõ ràng sẽ là chuyển một điều khiển (the PasswordBox) cho mô hình khung nhìn. Nhiều giải pháp đề nghị ví dụ này, vịnh đi qua các thể hiện của PasswordBoxnhưICommand.CommandParameter với mô hình xem. Rõ ràng là một khuyến nghị rất xấu và không cần thiết.

Nếu bạn không quan tâm đến việc sử dụng C #, nhưng chỉ muốn giữ sạch tệp phía sau mã của mình hoặc chỉ muốn đóng gói logic hành vi / UI, bạn luôn có thể sử dụng các thuộc tính đính kèm và thực hiện hành vi đính kèm.

Đối lập với trình trợ giúp trải rộng phổ biến cho phép liên kết với mật khẩu văn bản đơn giản (nguy cơ chống mẫu và rủi ro bảo mật thực sự xấu), hành vi này sử dụng ICommandđể gửi mật khẩu SecureStringcho mô hình chế độ xem, bất cứ khi nào xảy PasswordBoxra PasswordBox.PasswordChangedsự kiện.

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);
  }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.