วิธีการผูกกับ PasswordBox ใน MVVM


251

asswordBoxฉันได้เจอปัญหากับการผูกกับระดับ P ดูเหมือนว่าจะมีความเสี่ยงด้านความปลอดภัย แต่ฉันใช้รูปแบบ MVVM ดังนั้นฉันต้องการหลีกเลี่ยงสิ่งนี้ ฉันพบรหัสที่น่าสนใจที่นี่ (มีใครใช้สิ่งนี้หรือคล้ายกัน?)

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

ในทางเทคนิคมันดูดี แต่ฉันไม่แน่ใจว่าจะดึงรหัสผ่านได้อย่างไร

ฉันเป็นพื้นมีคุณสมบัติในของฉันLoginViewModelสำหรับและUsername เป็นเรื่องปกติและใช้งานได้ดีPasswordUsernameTextBox

ฉันใช้รหัสด้านบนตามที่ระบุและป้อนสิ่งนี้

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

เมื่อฉันมีการPasswordBoxเป็นTextBoxและBinding Path=Passwordจากนั้นคุณสมบัติในของฉันLoginViewModelถูกปรับปรุง

รหัสของฉันเป็นเรื่องง่ายมากโดยทั่วไปฉันมีสำหรับฉันCommand Buttonเมื่อผมกดมันจะเรียกว่าและถ้ามันกลับจริงที่เรียกว่าCanLogin คุณสามารถเห็นฉันตรวจสอบคุณสมบัติของฉันที่นี่ซึ่งใช้งานได้ดีLogin
Username

ในLoginฉันส่งไปพร้อมกับการบริการของฉันUsernameและPassword, Usernameมีข้อมูลจากฉันViewแต่Passwordเป็นNull|Empty

private DelegateCommand loginCommand;

public string Username { get; set; }
public string Password { get; set; }


public ICommand LoginCommand
{
    get
    {
        if (loginCommand == null)
        {
            loginCommand = new DelegateCommand(
                Login, CanLogin );
        }
        return loginCommand;
    }
}

private bool CanLogin()
{
    return !string.IsNullOrEmpty(Username);
}

private void Login()
{
    bool result = securityService.IsValidLogin(Username, Password);

    if (result) { }
    else { }
}

นี่คือสิ่งที่ฉันทำ

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

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

ฉันมีของฉันTextBoxนี้จะไม่มีปัญหา แต่ในของฉันเป็นที่ว่างเปล่าViewModelPassword

ฉันกำลังทำอะไรผิดหรือขาดขั้นตอนหรือไม่?

ฉันใส่เบรกพอยต์นั่นเองค่ะรหัสเข้าสู่ระดับผู้ช่วยคงที่ แต่มันไม่เคยปรับปรุงของฉันในของฉันPasswordViewModel


3
มันกลับกลายเป็นว่ารหัสไม่ทำงาน แต่ฉันลองใช้รหัสอื่นที่นี่และทำงานได้อย่างสมบูรณ์ blog.functionalfun.net/2008/06/…
ทำเครื่องหมายว่าสมิ ธ

5
การส่งต่อในการควบคุมกล่องรหัสผ่านทั้งหมดไม่ผ่านการแยกมุมมองจากมุมมองโมเดลหรือไม่?

คำตอบ:


165

ขออภัย แต่คุณทำผิด

ผู้คนควรมีแนวทางรักษาความปลอดภัยต่อไปนี้รอยสักที่ด้านในของเปลือกตา:
อย่าเก็บรหัสผ่านแบบข้อความธรรมดาไว้ในหน่วยความจำ

เหตุผลที่ WPF / Silverlight PasswordBoxไม่เปิดเผย DP สำหรับPasswordคุณสมบัติที่เกี่ยวข้องกับความปลอดภัย
หาก WPF / Silverlight ต้องเก็บ DP ไว้Passwordมันจะต้องใช้เฟรมเวิร์กในการเก็บรหัสผ่านไว้ในหน่วยความจำ ซึ่งถือว่าค่อนข้างปลอดภัยเวกเตอร์โจมตีความปลอดภัย การPasswordBoxใช้หน่วยความจำที่เข้ารหัส (แปลก ๆ ) และวิธีเดียวในการเข้าถึงรหัสผ่านคือผ่านคุณสมบัติ CLR

ฉันขอแนะนำว่าเมื่อเข้าถึงPasswordBox.Passwordคุณสมบัติ CLR คุณไม่ควรวางไว้ในตัวแปรใด ๆ หรือเป็นค่าสำหรับคุณสมบัติใด ๆ
การเก็บรหัสผ่านของคุณเป็นข้อความธรรมดาบน RAM ของเครื่องลูกข่ายคือความปลอดภัย
ดังนั้นกำจัดสิ่งที่public string Password { get; set; }คุณตื่นขึ้นมา

เมื่อเข้าสู่ PasswordBox.Passwordเพียงดึงออกแล้วส่งไปยังเซิร์ฟเวอร์โดยเร็ว อย่ารักษาคุณค่าของรหัสผ่านไว้รอบตัวและไม่ควรปฏิบัติเช่นเดียวกับข้อความของเครื่องไคลเอนต์อื่น ๆ อย่าเก็บรหัสผ่านข้อความที่ชัดเจนในหน่วยความจำ

ฉันรู้ว่านี่เป็นการทำลายรูปแบบ MVVM แต่คุณไม่ควรผูกมัด PasswordBox.Password Attached DP เก็บรหัสผ่านของคุณใน ViewModel หรือคนอื่นที่คล้ายกัน

หากคุณกำลังมองหาวิธีแก้ปัญหาแบบ over-architected ต่อไปนี้เป็นวิธีการหนึ่ง:
1. สร้างIHavePasswordอินเทอร์เฟซด้วยวิธีการหนึ่งที่ส่งกลับข้อความที่ชัดเจนของรหัสผ่าน
2. UserControlใช้IHavePasswordอินเทอร์เฟซของคุณ
3. ลงทะเบียนUserControlอินสแตนซ์กับ IoC ของคุณเป็นการใช้งานIHavePasswordอินเทอร์เฟซ
4. เมื่อมีการร้องขอเซิร์ฟเวอร์ที่ต้องการรหัสผ่านของคุณให้โทร IoC ของคุณเพื่อIHavePasswordดำเนินการและเพียงแค่รับรหัสผ่านที่เป็นโลภมากเท่านั้น

เพียงฉันใช้กับมัน

- จัสติน


19
คุณไม่สามารถใช้ SecureString ใน VM สำหรับ WPF เพื่อแก้ปัญหานี้ได้หรือไม่ ดูเหมือนว่าจะมีบางสิ่งสำหรับ Silverlight
ไบรอันท์

35
ฉันเห็นด้วยกับความตั้งใจของคุณและข้อความที่คุณกำลังสื่อ แต่คำตอบของคุณหมายความว่าสตริงรหัสผ่านไม่เคยอยู่ในความทรงจำถ้าคุณทำตามวิธีการนี้ มูลค่าของรหัสผ่านจะอยู่ในหน่วยความจำนับตั้งแต่วินาทีที่ผู้ใช้พิมพ์ การกำจัดคุณสมบัติที่ถือวลีรหัสผ่านของคุณเป็นความคิดที่ดีและจะ จำกัด สำเนาของรหัสผ่านของคุณที่เหลือไว้ให้ผู้รวบรวมขยะสามารถเก็บเกี่ยวได้หรืออาจพบได้โดยรหัสที่มีการจัดการและไม่มีการจัดการอื่น ๆ ไม่ซ่อนมันทั้งหมด
IanNorton

182
สำหรับกรณีส่วนใหญ่คุณไม่จำเป็นต้องมีระดับความปลอดภัยระดับนั้น อะไรคือสิ่งที่ทำให้สิ่งหนึ่งยากเมื่อมีวิธีอื่นอีกมากมายในการขโมยรหัสผ่าน? Atleast WPF ควรอนุญาตให้ใช้ SecureString อย่าง @Bryant กล่าว
chakrit

336
หากผู้ร้ายสามารถเข้าถึง RAM ของเครื่องของคุณได้คุณจะพบปัญหาที่ใหญ่กว่าการขโมยรหัสผ่านของคุณ
Cameron MacFarland

13
เป็นเวลาหลายปีที่ฉันใช้การควบคุมผู้ใช้ที่กำหนดเองซึ่งทำงานเหมือนกับ PasswordBox แต่ส่งกลับค่าข้อความเป็น SecureString เท่านั้น ใช่สิ่งนี้จะป้องกันไม่ให้ Snoop แสดงรหัสผ่านเป็นข้อความธรรมดา อย่างไรก็ตามค่าข้อความธรรมดาของ SecureString ยังคงสามารถแยกได้ง่ายและมีเพียงการแยกแฮ็กสามเณร หากระบบของคุณมีความเสี่ยงในการใช้ตัวบันทึกสำคัญและ sniffers อย่างซ่อนเร้นเช่น Snoop คุณควรประเมินความปลอดภัยของระบบอีกครั้ง
Mike Christian

199

2 เซนต์ของฉัน:

ฉันพัฒนากล่องโต้ตอบการเข้าสู่ระบบทั่วไป (กล่องผู้ใช้และรหัสผ่านรวมทั้งปุ่ม "ตกลง") โดยใช้ WPF และ MVVM ฉันแก้ไขปัญหาการผูกรหัสผ่านโดยเพียงแค่ผ่านการควบคุมรหัสผ่านของตัวเองเป็นพารามิเตอร์ไปยังคำสั่งที่แนบมากับปุ่ม "ตกลง" ดังนั้นในมุมมองของฉัน:

<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 แต่ในโครงการนั้นฉันสามารถจ่ายได้ หวังว่ามันจะมีประโยชน์สำหรับใครบางคนเช่นกัน


สวัสดี Konamiman เมื่อเรียกใช้เมธอด Execute ในมุมมองของฉันฉันมีคลาส User (ล็อกอิน, พาส) และคำสั่งรับรองความถูกต้องฉันจะใช้ Execute ในบริบทนั้นได้อย่างไร

3
มีประโยชน์มากขอบคุณ fyi, บางคนอาจเคยเห็นสิ่งที่ต้องการ _loginCommand = ใหม่ RelayCommand (param => เข้าสู่ระบบ (ชื่อผู้ใช้, (PasswordBox) พารามิเตอร์)), param => CanLogIn);
Chuck Rostance

5
นี่เป็นวิธีการแก้ปัญหาที่ตกลง แต่ล้มเหลวสำหรับบางอย่างเช่นรหัสผ่าน + คำสั่งผสมยืนยันรหัสผ่าน
Julien

สวัสดี Konamiman ฉันใช้โซลูชันของคุณ แต่ใช้ไม่ได้กับแอป Windows 8.1 Store ฉันได้ถามคำถามนี้: stackoverflow.com/questions/26221594/…
VansFannel

2
ขอบคุณสำหรับสิ่งนี้! สิ่งนี้แก้ไขปัญหาใหญ่ที่ฉันมีเมื่อย้ายข้อมูลจากเธรด UI ไปยังเธรดโปรแกรมหลัก ตรวจสอบให้แน่ใจว่าได้ใช้แนวทาง SecureString และ ~ กำจัดรหัสผ่านโดยเร็วที่สุด ~ ทิ้งมัน กำจัดมัน ล้างมัน ทำในสิ่งที่คุณต้องทำ นอกจากนี้ตรวจสอบให้แน่ใจว่าคุณใช้ IDisposable
Steven C. Britton

184

บางทีฉันอาจจะพลาดบางสิ่งบางอย่าง แต่ดูเหมือนว่าโซลูชันเหล่านี้ส่วนใหญ่จะเน้นย้ำสิ่งต่าง ๆ และกำจัดการปฏิบัติที่ปลอดภัย

วิธีนี้ไม่ได้ละเมิดรูปแบบ MVVM และรักษาความปลอดภัยอย่างสมบูรณ์ ใช่ในทางเทคนิคมันเป็นรหัสที่อยู่เบื้องหลัง แต่มันไม่มีอะไรมากไปกว่าการผูก "กรณีพิเศษ" ViewModel ยังไม่มีความรู้เกี่ยวกับการใช้งานมุมมองซึ่งในใจของฉันจะทำอย่างไรถ้าคุณพยายามที่จะส่งรหัสผ่านไปยัง 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 ดังนั้นคุณอาจไม่ต้องการรหัสผ่านแบบข้อความธรรมดา แม้ว่าคุณคิดว่าคุณทำ) คุณสามารถใช้คุณสมบัติรหัสผ่านแทน แบบนี้:

(คุณสมบัติ 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

3
ขอบคุณสำหรับการปฏิบัติจริงมากกว่าความเชื่อที่เข้มงวดเกี่ยวกับ MVVM และความหวาดระแวง ใช้งานได้ดีขอบคุณ
Bruce Pierson

2
ตัวอย่าง SecureString จะดีมากเมื่อใช้กับส่วนขยายนี้blogs.msdn.com/b/fpintos/archive/2009/06/12/…
Ayman

1
ดีมาก ฉันหวังว่า MS จะเพิ่งเพิ่มรหัสผ่าน DP ประเภท SecureString ให้กับการควบคุมนี้
Keith Hill

1
นี่คือคำตอบที่สมบูรณ์แบบเพราะมันรักษาความปลอดภัยและ MVVM
LoRdPMN

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

โดยไม่ต้องตั้งชื่อ PasswordBox: CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=PasswordBox}}"(หมายเหตุ: ไม่ RelativeSource Self )
wondra

โซลูชันนี้ละเมิดรูปแบบ MVVM
BionicCode

13

มันใช้งานได้ดีสำหรับฉัน

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

3
สิ่งที่เกี่ยวกับ CommandParameter = "{การผูก ElementName = MyPasswordBox, Path = SecurePassword"}?
ลุ

2
ลุคนี่ใช้งานไม่ได้ (อย่างน้อยสำหรับฉัน) อาจเป็นเพราะเหตุผลเดียวกัน - SecurePassword ไม่ใช่คุณสมบัติการพึ่งพา
vkrzv

สมมติว่าICommandมีการนำไปใช้ในโมเดลการดูโซลูชันนี้จะละเมิดรูปแบบ MVVM
BionicCode

9

ทางออกที่ง่ายโดยไม่ละเมิดรูปแบบ MVVM คือการแนะนำเหตุการณ์ (หรือตัวแทน) ใน ViewModel ที่เก็บเกี่ยวรหัสผ่าน

ในViewModel :

public event EventHandler<HarvestPasswordEventArgs> HarvestPassword;

ด้วย EventArgs เหล่านี้:

class HarvestPasswordEventArgs : EventArgs
{
    public string Password;
}

ในมุมมองสมัครสมาชิกเหตุการณ์ในการสร้าง ViewModel และกรอกค่ารหัสผ่าน

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

ในViewModelเมื่อคุณต้องการรหัสผ่านคุณสามารถเริ่มต้นเหตุการณ์และเก็บเกี่ยวรหัสผ่านได้จากที่นั่น:

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

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

LoginHelpers.Login(Username, pwargs.Password);

สิ่งหนึ่งที่คุณขาดหายไปก็คือเมื่อสมัครรับข้อมูลมุมมองไปยังเหตุการณ์มุมมองโมเดลคุณควรใช้ a WeakEventManager<TEventSource, TEventArgs>เพื่อหลีกเลี่ยงการรั่วไหลของหน่วยความจำ บ่อยครั้งที่มุมมองจะไม่มีอายุการใช้งานเท่ากับโมเดลมุมมอง WeakEventManager<IViewModel, EventArgs>.AddHandler(iViewModelInstance, nameof(IViewModel.Event), eventHandlerMethod);
Todd A. Stedel

ฉันชอบวิธีนี้เนื่องจากง่ายไม่ละเมิด MVVM มีรหัสน้อยที่สุดอนุญาตการใช้รหัสผ่านที่ถูกต้อง (ถ้าคุณใช้ "SecurePassword" แทน) นอกจากนี้ตอนนี้มันง่ายที่จะใช้วิธีการ HarvestPassword อื่น ๆ ตอนนี้ (เช่น SmartCard .... )
Matt

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

ให้แน่ใจว่าคุณอนุญาตให้ประชาคมโลกในการเก็บรวบรวมองค์ประกอบ UI ของคุณเพื่อต่อต้านการกระตุ้นของการใช้ตัวจัดการเหตุการณ์แบบคงที่สำหรับการจัดกิจกรรมในPasswordChanged PasswordBoxฉันยังค้นพบความผิดปกติที่ตัวควบคุมไม่ได้อัปเดต UI เมื่อใช้SecurePasswordคุณสมบัติสำหรับการตั้งค่าเหตุผลที่ฉันคัดลอกรหัสผ่านลงไป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));
   }
}

The 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

ฉันโพสต์รายการที่นี่ซึ่งเป็นกล่องรหัสผ่านที่ผูกมัดได้

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
ขณะนี้ไม่ได้เลวร้ายที่คุณสูญเสียความสามารถในการชุดที่เรียบง่ายคุณลักษณะเช่น padding และ TabIndex
Julien

1
เทย์เลอร์ฉันสรุปสาระสำคัญเพื่อให้มันมีอยู่ในคำตอบ (ดูเหมือนว่าจะเป็นคำตอบสำหรับลิงค์เท่านั้นมิฉะนั้นพยายามหลีกเลี่ยงสิ่งนี้ที่ถูกลบออกไป) อย่าลังเลที่จะยุ่งกับเนื้อหาที่อยู่ในรายการ
Lynn Crumbling

@ Julien แต่คุณสามารถแก้ไขได้ด้วยสไตล์ ฉันแก้ปัญหานี้ในลักษณะที่คล้ายกัน แต่ฉันใช้ContentControlคุณสามารถใช้ PasswordBox เป็นเนื้อหาและสไตล์ใน XAML ตามที่คุณต้องการ วัตถุประสงค์ของการContentControlเป็นเพียงเพื่อสมัครสมาชิกPasswordChangedเหตุการณ์และเปิดเผยคุณสมบัติ bindable สองทิศทาง สรุปแล้วมันคือ 65 บรรทัดของรหัสและสิ่งที่คลาสตกแต่งนี้ทำได้ ดูที่นี่สำหรับส่วนสำคัญของฉันของgist.github.com/leidegre/c7343b8c720000fe3132
John Leidegren

6

การใช้งานนี้แตกต่างกันเล็กน้อย คุณส่งกล่องรหัสผ่านไปยังมุมมองผ่านการเชื่อมโยงคุณสมบัติใน ViewModel แต่จะไม่ใช้พารามิเตอร์คำสั่งใด ๆ ViewModel อยู่ที่ไม่รู้ของมุมมอง ฉันมีโครงการ VB vs 2010 ที่สามารถดาวน์โหลดได้จาก SkyDrive Wpf MvvM PassWordBox Example.zip https://skydrive.live.com/redir.aspx?cid=e95997d33a9f8d73&resid=E95997D33A9F8D73

วิธีที่ฉันใช้ PasswordBox ในแอปพลิเคชัน Wpf MvvM นั้นค่อนข้างเรียบง่ายและทำงานได้ดีสำหรับฉัน ไม่ได้หมายความว่าฉันคิดว่ามันเป็นวิธีที่ถูกต้องหรือเป็นวิธีที่ดีที่สุด มันเป็นเพียงการนำไปใช้ของการใช้ 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" />

จากที่นั่นคุณสามารถควบคุมกล่องรหัสผ่านได้อย่างเต็มที่ฉันยังใช้ PasswordAccessor (เพียงฟังก์ชั่นของสตริง) เพื่อส่งคืนค่ารหัสผ่านเมื่อทำการเข้าสู่ระบบหรืออะไรก็ตามที่คุณต้องการรหัสผ่าน ในตัวอย่างฉันมีทรัพย์สินสาธารณะใน Generic User Object Model ตัวอย่าง:

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 ฉันตรวจสอบให้แน่ใจว่า Accessor ถูกสร้างขึ้นและตั้งค่าเป็นคุณสมบัติ PasswordBox.Password 'ตัวอย่าง:

Public Sub New()
    'Sets the Accessor for the Password Property
    SetPasswordAccessor(Function() ThePassWordBox.Password)
End Sub

Friend Sub SetPasswordAccessor(ByVal accessor As Func(Of String))
    If Not IsNothing(VMUser) Then VMUser.PasswordAccessor = accessor
End Sub

เมื่อฉันต้องการสตริงรหัสผ่านสำหรับเข้าสู่ระบบฉันเพิ่งได้รับคุณสมบัติรหัสผ่านวัตถุผู้ใช้ที่จริงเรียกใช้ฟังก์ชั่นเพื่อคว้ารหัสผ่านและส่งกลับแล้วรหัสผ่านที่แท้จริงจะไม่ถูกจัดเก็บโดยวัตถุผู้ใช้ ตัวอย่าง: จะอยู่ใน ViewModel

Private Function LogIn() as Boolean
    'Make call to your Authentication methods and or functions. I usally place that code in the Model
    Return AuthenticationManager.Login(New UserIdentity(User.UserName, User.Password)
End Function

ที่ควรทำ ViewModel ไม่ต้องการความรู้ใด ๆ เกี่ยวกับการควบคุมของมุมมอง กระบวนการ View Just ผูกกับคุณสมบัติใน ViewModel ไม่แตกต่างจาก View Binding กับรูปภาพหรือทรัพยากรอื่น ๆ ในกรณีนี้ทรัพยากร (Property) เพิ่งจะเป็น usercontrol จะช่วยให้การทดสอบเป็น ViewModel สร้างและเป็นเจ้าของทรัพย์สินและทรัพย์สินที่เป็นอิสระจากมุมมอง สำหรับเรื่องความปลอดภัยฉันไม่รู้ว่าการติดตั้งนี้มีประโยชน์เพียงใด แต่ด้วยการใช้ฟังก์ชั่นค่าจะไม่ถูกเก็บไว้ในคุณสมบัตินั้นเพียงแค่เข้าถึงโดยคุณสมบัติ


6

เพื่อแก้ปัญหา OP โดยไม่ทำลาย MVVM ฉันจะใช้ตัวแปลงค่าที่กำหนดเองและ wrapper สำหรับค่า (รหัสผ่าน) ที่ต้องดึงจากกล่องรหัสผ่าน

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 ไปยังฟังก์ชั่นเข้าสู่ระบบแทนรหัสผ่าน String เพื่อให้ไม่มีสตริงที่ไม่ได้เข้ารหัสด้วย passwort ที่บินไปรอบ ๆ หน่วยความจำ
โทรหาฉันแครอท

มันใช้เวลาสักครู่ แต่ดูเหมือนว่าฉันจะไม่สามารถใช้งานได้เพราะ RelayCommand ของฉัน คุณจะเพิ่มความคิดของคุณหรือไม่
Ecnerwal

5

ในขณะที่ฉันเห็นว่าเป็นเรื่องสำคัญที่จะต้องหลีกเลี่ยงการจัดเก็บรหัสผ่านที่ใดก็ได้ฉันยังคงต้องการความสามารถในการสร้างอินสแตนซ์ของมุมมองโดยไม่ต้องดูและดำเนินการทดสอบกับมัน

วิธีแก้ปัญหาที่ใช้งานได้สำหรับฉันคือการลงทะเบียนฟังก์ชัน PasswordBox.Password กับโมเดลมุมมองและให้โมเดลมุมมองเรียกใช้เมื่อเรียกใช้รหัสล็อกอิน

นี้ไม่เส้นค่าเฉลี่ยของรหัสใน codebehind มุมมองของ

ดังนั้นใน 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());

ด้วยวิธีนี้เมื่อฉันต้องการทดสอบรูปแบบวิวเวอร์ฉันสามารถตั้งค่ารหัสผ่านให้เป็นวิธีที่ไม่ระบุชื่อซึ่งช่วยให้ฉันสามารถส่งรหัสผ่านอะไรก็ได้ที่ฉันต้องการใช้ในการทดสอบ


4

ฉันคิดว่าฉันจะโยนทางออกของฉันในการผสมผสานตั้งแต่นี้เป็นปัญหาที่พบบ่อย ... และมีตัวเลือกมากมายอยู่เสมอเป็นสิ่งที่ดี

ฉันเพียงแค่ห่อPasswordBoxในUserControlและดำเนินการDependencyPropertyเพื่อให้สามารถผูก ฉันกำลังทำทุกอย่างเท่าที่ทำได้เพื่อหลีกเลี่ยงการเก็บข้อความที่ชัดเจนในความทรงจำดังนั้นทุกอย่างจึงทำผ่านทาง a SecureStringและPasswordBox.Passwordคุณสมบัติ ในระหว่างการforeachวนรอบตัวละครแต่ละตัวจะถูกเปิดเผย แต่มันสั้นมาก สุจริตถ้าคุณกังวลเกี่ยวกับแอปพลิเคชัน WPF ของคุณที่จะถูกโจมตีจากการเปิดเผยสั้น ๆ นี้คุณมีปัญหาด้านความปลอดภัยที่ใหญ่กว่าที่ควรได้รับการจัดการ

ความงามของสิ่งนี้คือคุณไม่ได้ฝ่าฝืนกฎ MVVM ใด ๆ แม้แต่กฎ "คนเจ้าระเบียบ" เนื่องจากนี่เป็น UserControlดังนั้นจึงอนุญาตให้มีการเขียนโค้ดไว้เบื้องหลัง เมื่อคุณใช้งานคุณสามารถสื่อสารอย่างแท้จริงระหว่างViewและViewModelไม่VideModelทราบว่าส่วนใดส่วนหนึ่งViewหรือแหล่งที่มาของรหัสผ่าน เพียงให้แน่ใจว่าคุณผูกพันกับSecureStringคุณViewModelในของคุณ

BindablePasswordBox.xaml

<UserControl x:Class="BK.WPF.CustomControls.BindanblePasswordBox"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" d:DesignHeight="22" d:DesignWidth="150">
    <PasswordBox x:Name="PswdBox"/>
</UserControl>

BindablePasswordBox.xaml.cs (เวอร์ชัน 1 - ไม่มีการสนับสนุนการเชื่อมสองทาง)

using System.ComponentModel;
using System.Security;
using System.Windows;
using System.Windows.Controls;

namespace BK.WPF.CustomControls
{
    public partial class BindanblePasswordBox : UserControl
    {
        public static readonly DependencyProperty PasswordProperty =
            DependencyProperty.Register("Password", typeof(SecureString), typeof(BindanblePasswordBox));

        public SecureString Password
        {
            get { return (SecureString)GetValue(PasswordProperty); }
            set { SetValue(PasswordProperty, value); }
        }

        public BindanblePasswordBox()
        {
            InitializeComponent();
            PswdBox.PasswordChanged += PswdBox_PasswordChanged;
        }

        private void PswdBox_PasswordChanged(object sender, RoutedEventArgs e)
        {
            var secure = new SecureString();
            foreach (var c in PswdBox.Password)
            {
                secure.AppendChar(c);
            }
            Password = secure;
        }
    }
}

การใช้งานเวอร์ชัน 1:

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

BindablePasswordBox.xaml.cs (เวอร์ชัน 2 - รองรับการเชื่อมต่อสองทาง)

public partial class BindablePasswordBox : UserControl
{
    public static readonly DependencyProperty PasswordProperty =
        DependencyProperty.Register("Password", typeof(SecureString), typeof(BindablePasswordBox),
        new PropertyMetadata(PasswordChanged));

    public SecureString Password
    {
        get { return (SecureString)GetValue(PasswordProperty); }
        set { SetValue(PasswordProperty, value); }
    }

    public BindablePasswordBox()
    {
        InitializeComponent();
        PswdBox.PasswordChanged += PswdBox_PasswordChanged;
    }

    private void PswdBox_PasswordChanged(object sender, RoutedEventArgs e)
    {
        var secure = new SecureString();
        foreach (var c in PswdBox.Password)
        {
            secure.AppendChar(c);
        }
        if (Password != secure)
        {
            Password = secure;
        }
    }

    private static void PasswordChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var pswdBox = d as BindablePasswordBox;
        if (pswdBox != null && e.NewValue != e.OldValue)
        {
            var newValue = e.NewValue as SecureString;
            if (newValue == null)
            {
                return;
            }

            var unmanagedString = IntPtr.Zero;
            string newString;
            try
            {
                unmanagedString = Marshal.SecureStringToGlobalAllocUnicode(newValue);
                newString = Marshal.PtrToStringUni(unmanagedString);
            }
            finally
            {
                Marshal.ZeroFreeGlobalAllocUnicode(unmanagedString);
            }

            var currentValue = pswdBox.PswdBox.Password;
            if (currentValue != newString)
            {
                pswdBox.PswdBox.Password = newString;
            }
        }
    }
}

การใช้งานเวอร์ชัน 2:

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

ฉันได้ลองใช้สิ่งนี้ แต่คุณได้รับการวนซ้ำไม่สิ้นสุดเมื่อคุณอัปเดตรหัสผ่านบน UI เพราะif (Password != secure)จะเป็นเท็จเสมอเนื่องจาก SecureString จะไม่แทนที่เท่ากับ ความคิดใด ๆ
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

การถ่ายโอน SecurePassword (อินสแตนซ์ SecureString) ตามที่อธิบายโดยSteve ใน COดูเหมือนว่ายอมรับได้ ฉันชอบBehaviorsโค้ดด้านหลังและฉันก็มีข้อกำหนดเพิ่มเติมว่าสามารถรีเซ็ตรหัสผ่านจาก viewmodel ได้

Xaml ( Passwordเป็นคุณสมบัติ ViewModel):

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

พฤติกรรม:

using System.Security;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;

namespace Evidence.OutlookIntegration.AddinLogic.Behaviors
{
    /// <summary>
    /// Intermediate class that handles password box binding (which is not possible directly).
    /// </summary>
    public class PasswordBoxBindingBehavior : Behavior<PasswordBox>
    {
        // BoundPassword
        public SecureString BoundPassword { get { return (SecureString)GetValue(BoundPasswordProperty); } set { SetValue(BoundPasswordProperty, value); } }
        public static readonly DependencyProperty BoundPasswordProperty = DependencyProperty.Register("BoundPassword", typeof(SecureString), typeof(PasswordBoxBindingBehavior), new FrameworkPropertyMetadata(OnBoundPasswordChanged));

        protected override void OnAttached()
        {
            this.AssociatedObject.PasswordChanged += AssociatedObjectOnPasswordChanged;
            base.OnAttached();
        }

        /// <summary>
        /// Link up the intermediate SecureString (BoundPassword) to the UI instance
        /// </summary>
        private void AssociatedObjectOnPasswordChanged(object s, RoutedEventArgs e)
        {
            this.BoundPassword = this.AssociatedObject.SecurePassword;
        }

        /// <summary>
        /// Reacts to password reset on viewmodel (ViewModel.Password = new SecureString())
        /// </summary>
        private static void OnBoundPasswordChanged(object s, DependencyPropertyChangedEventArgs e)
        {
            var box = ((PasswordBoxBindingBehavior)s).AssociatedObject;
            if (box != null)
            {
                if (((SecureString)e.NewValue).Length == 0)
                    box.Password = string.Empty;
            }
        }

    }
}

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ที่คุณใช้

ในการใช้คุณสมบัติที่แนบมานี้คุณเพียงแค่ผูกกับPasswordคุณสมบัติใน ViewModel ของคุณ:

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

ค#:

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 ใช้ Reflection เพื่อแยกคุณสมบัติรหัสผ่านออกมา ในกรณีนี้ VM คาดว่าคอนเทนเนอร์รหัสผ่านบางประเภทพร้อมรหัสผ่านคุณสมบัติ (ฉันกำลังใช้ RelayCommands จาก 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;
    }
}

สามารถทดสอบกับคลาสที่ไม่ระบุชื่อได้อย่างง่ายดาย:

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

ความคิดเห็นไม่ได้มีไว้สำหรับการอภิปรายเพิ่มเติม การสนทนานี้ได้รับการย้ายไปแชท
Samuel Liew

1

ในแอพสากลของ windows

คุณสามารถใช้รหัสนี้กับคุณสมบัติ "รหัสผ่าน" และเชื่อมโยงกับ 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 Mode

XAML

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

ทำไมไม่ทำอย่างนั้นOneWayToSource?
BK

@BK แก้ไขคำตอบของฉัน ขอบคุณ
เควิน

1
โหมดไม่ควรอยู่ในเครื่องหมายวงเล็บรวมหรือไม่
Mat

@ Mat Yap ขอบคุณ
Kevin

1

นี่คือสิ่งที่ฉันใช้:

  1. การใช้คุณสมบัติที่แนบมาเพื่อผูกรหัสผ่านเอาชนะวัตถุประสงค์ในการรักษารหัสผ่าน กระบวนการรหัสผ่านคุณสมบัติของรหัสผ่านกล่องไม่สามารถผูกด้วยเหตุผล

  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 ในแอปพลิเคชันตัวอย่าง ViewModel ของWPF Application Framework (WAF)โครงการ

อย่างไรก็ตามจัสตินพูดถูก อย่าส่งรหัสผ่านเป็นข้อความธรรมดาระหว่าง View และ ViewModel ใช้ SecureString แทน (ดู MSDN PasswordBox)


2
วิธีที่ใช้ใน Pop3SettingsView ของ WAF เป็นเรื่องตลก PasswordBox passwordBox = (PasswordBox) ผู้ส่ง; if (ViewModel! = null) {ViewModel.Pop3Password = passwordBox.Password; } Pop3Password ของ ViewModel เป็นคุณสมบัติสตริง ดังนั้นมันไม่ปลอดภัยเช่นกัน .. ดีกว่าที่จะใช้คุณสมบัติที่แนบมา
Michael Sync

0

ฉันใช้การตรวจสอบความถูกต้องตามด้วย sub ที่เรียกโดยคลาส mediator ไปยัง View (ซึ่งใช้การตรวจสอบการพิสูจน์ตัวตน) เพื่อเขียนรหัสผ่านไปยังคลาส data

มันไม่ใช่ทางออกที่สมบูรณ์แบบ อย่างไรก็ตามมันแก้ไขปัญหาของฉันที่ไม่สามารถย้ายรหัสผ่านได้


0

ฉันกำลังใช้วิธีแก้ปัญหาที่เป็นมิตรกับ MVVM ที่ยังไม่ได้กล่าวถึง ก่อนอื่นฉันชื่อ PasswordBox ใน XAML:

<PasswordBox x:Name="Password" />

จากนั้นฉันจะเพิ่มการเรียกเมธอดเดียวในตัวสร้างมุมมอง:

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

และนั่นคือมัน มุมมองรูปแบบจะได้รับการแจ้งเตือนเมื่อมีการแนบกับมุมมองผ่าน DataContext และการแจ้งเตือนอื่นเมื่อมีการแยกออก เนื้อหาของการแจ้งเตือนนี้สามารถกำหนดค่าได้ผ่าน lambdas แต่โดยปกติจะเป็นเพียงการตั้งค่าหรือการเรียกเมธอดในโมเดลการดูผ่านการควบคุมที่มีปัญหาเป็นพารามิเตอร์

มันสามารถทำให้เป็นมิตรกับ MVVM ได้อย่างง่ายดายโดยการมีมุมมองที่เปิดเผยอินเตอร์เฟสแทนการควบคุมลูก

รหัสข้างต้นขึ้นอยู่กับระดับผู้ช่วยที่เผยแพร่ในบล็อกของฉัน


0

ฉันใช้เวลาหลายปีพยายามที่จะทำงานนี้ ในที่สุดฉันก็ยอมแพ้และเพิ่งใช้ PasswordBoxEdit จาก DevExpress

มันเป็นวิธีการแก้ปัญหาที่ง่ายที่สุดเท่าที่เคยมีมาเพราะมันช่วยให้สามารถผูกมัดได้โดยไม่ต้องดึงลูกเล่นที่น่ากลัว

โซลูชันบนเว็บไซต์ DevExpress

สำหรับบันทึกฉันไม่ได้มีส่วนเกี่ยวข้องกับ 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

มันง่ายมาก สร้างคุณสมบัติอื่นสำหรับรหัสผ่านและผูกสิ่งนี้ด้วยกล่องข้อความ

แต่การดำเนินการอินพุตทั้งหมดจะดำเนินการด้วยคุณสมบัติรหัสผ่านจริง

สตริงส่วนตัว _Password;

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

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

            return szChar;
        }

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

รหัสผ่านสตริงสาธารณะ {รับ {return _Password; }

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


กล่องรหัสผ่านเหตุผลไม่สามารถผูกมัดได้เพราะเราไม่ต้องการเก็บรหัสผ่านไว้ในสตริงที่ชัดเจน สตริงไม่เปลี่ยนรูปและเราไม่แน่ใจว่ามันจะอยู่ในความทรงจำนานแค่ไหน
แลนซ์

0

คำตอบของฉันง่ายขึ้นสำหรับในรูปแบบ MVVM

ในโมเดลมุมมองคลาส

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;

}

คุณสมบัติรหัสผ่านของ PasswordBox ที่ชนะให้หรือ WatermarkPasswordBox ที่ XCeedtoolkit ให้สร้าง 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

ไม่มีอะไรผิดปกติกับ code-behind เมื่อใช้งาน MVVM MVVM เป็นรูปแบบสถาปัตยกรรมที่มีจุดมุ่งหมายเพื่อแยกมุมมองจากรูปแบบ / ตรรกะทางธุรกิจ MVVM อธิบายถึงวิธีการบรรลุเป้าหมายนี้ในรูปแบบที่ทำซ้ำได้ (รูปแบบ) ไม่สนใจรายละเอียดการใช้งานเช่นคุณจัดโครงสร้างหรือนำมุมมองไปใช้อย่างไร มันแค่วาดขอบเขตและกำหนดว่าอะไรคือมุมมอง, model view และอะไร model ในแง่ของคำศัพท์ของ pattern นี้

MVVM ไม่สนใจภาษา (XAML หรือ C #) หรือคอมไพเลอร์ (partialคลาส) การเป็นภาษาอิสระนั้นเป็นคุณสมบัติที่จำเป็นของรูปแบบการออกแบบซึ่งต้องเป็นภาษาที่เป็นกลาง

อย่างไรก็ตาม code-behind มีข้อ จำกัด บางประการเช่นทำให้ตรรกะ UI ของคุณยากต่อการเข้าใจเมื่อมีการกระจายระหว่าง XAML และ C # อย่างดุเดือด แต่สิ่งที่สำคัญที่สุดในการใช้ตรรกะหรือวัตถุ UI เช่นเทมเพลต, สไตล์, ทริกเกอร์, ภาพเคลื่อนไหวและอื่น ๆ ใน C # นั้นซับซ้อนและน่าเกลียด / อ่านน้อยกว่าการใช้ XAML XAML เป็นภาษามาร์กอัปที่ใช้แท็กและซ้อนเพื่อแสดงลำดับชั้นวัตถุ การสร้าง UI โดยใช้ XAML นั้นสะดวกมาก แม้ว่าจะมีสถานการณ์ที่คุณเลือกที่จะใช้ตรรกะของ UI ใน C # (หรือการใช้โค้ด) การจัดการPasswordBoxเป็นตัวอย่างหนึ่ง

ด้วยเหตุผลนี้ในการจัดการPasswordBoxโค้ดข้างหลังโดยการจัดการPasswordBox.PasswordChangedจะไม่เป็นการละเมิดรูปแบบ MVVM

การละเมิดที่ชัดเจนจะส่งตัวควบคุม ( PasswordBox) ไปยังโมเดลมุมมอง วิธีแก้ปัญหามากมายแนะนำสิ่งนี้เช่นช่องที่ผ่านอินสแตนซ์ของPasswordBoxasICommand.CommandParameterรูปแบบมุมมอง เห็นได้ชัดว่าเป็นคำแนะนำที่แย่มากและไม่จำเป็น

หากคุณไม่สนใจเกี่ยวกับการใช้ C # แต่เพียงต้องการทำให้ไฟล์โค้ดล้าหลังของคุณสะอาดหรือเพียงแค่ต้องการห่อหุ้มพฤติกรรม / ตรรกะของ UI คุณสามารถใช้คุณสมบัติที่แนบมาและใช้พฤติกรรมที่แนบมาได้ตลอดเวลา

ตรงข้ามกับตัวช่วยการแพร่กระจายที่น่าอับอายที่เปิดใช้งานการเชื่อมโยงกับรหัสผ่านข้อความธรรมดา (การป้องกันรูปแบบที่ไม่ดีและความเสี่ยงด้านความปลอดภัย) พฤติกรรมนี้ใช้การICommandส่งรหัสผ่านSecureStringในรูปแบบมุมมองเมื่อใดก็ตามPasswordBoxที่PasswordBox.PasswordChangedเหตุการณ์เกิดขึ้น

MainWindow.xaml

<Window>
  <Window.DataContext>
    <ViewModel />
  </Window.DataContext>

  <PasswordBox PasswordBox.Command="{Binding VerifyPasswordCommand}" />
</Window>

ViewModel.cs

public class ViewModel : INotifyPropertyChanged
{
  public ICommand VerifyPasswordCommand => new RelayCommand(VerifyPassword);

  public void VerifyPassword(object commadParameter)
  {
    if (commandParameter is SecureString secureString)
    {
      IntPtr valuePtr = IntPtr.Zero;
      try
      {
        valuePtr = Marshal.SecureStringToGlobalAllocUnicode(value);
        string plainTextPassword = Marshal.PtrToStringUni(valuePtr);

        // Handle plain text password. 
        // It's recommended to convert the SecureString to plain text in the model, when really needed.
      } 
      finally 
      {
        Marshal.ZeroFreeGlobalAllocUnicode(valuePtr);
      }
    }
  }
}

PasswordBox.cs

// Attached behavior
class PasswordBox : DependencyObject
{
  #region Command attached property

  public static readonly DependencyProperty CommandProperty =
    DependencyProperty.RegisterAttached(
      "Command",
      typeof(ICommand),
      typeof(PasswordBox),
      new PropertyMetadata(default(ICommand), PasswordBox.OnSendPasswordCommandChanged));

  public static void SetCommand(DependencyObject attachingElement, ICommand value) =>
    attachingElement.SetValue(PasswordBox.CommandProperty, value);

  public static ICommand GetCommand(DependencyObject attachingElement) =>
    (ICommand) attachingElement.GetValue(PasswordBox.CommandProperty);

  #endregion

  private static void OnSendPasswordCommandChanged(
    DependencyObject attachingElement,
    DependencyPropertyChangedEventArgs e)
  {
    if (!(attachingElement is System.Windows.Controls.PasswordBox passwordBox))
    {
      throw new ArgumentException("Attaching element must be of type 'PasswordBox'");
    }

    if (e.OldValue != null)
    {
      return;
    }

    WeakEventManager<object, RoutedEventArgs>.AddHandler(
      passwordBox,
      nameof(System.Windows.Controls.PasswordBox.PasswordChanged),
      SendPassword_OnPasswordChanged);
  }

  private static void SendPassword_OnPasswordChanged(object sender, RoutedEventArgs e)
  {
    var attachedElement = sender as System.Windows.Controls.PasswordBox;
    SecureString commandParameter = attachedElement?.SecurePassword;
    if (commandParameter == null || commandParameter.Length < 1)
    {
      return;
    }

    ICommand sendCommand = GetCommand(attachedElement);
    sendCommand?.Execute(commandParameter);
  }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.