Впервые прочитав знаменитую статью Джоша Смита WPF Apps With The Model-View-ViewModel Design Pattern я был очень вдохновлен философией MVVM и решил все будущие проекты писать исключительно с ее использованием. По мере «внедрения» MVVM я столкнулся с определенными трудностями и недостатками (на мой субъективный взгляд). В одном из проектов мне был необходим UserControl, который бы позволял авторизироваться на веб-сервисе. При попытке забиндить свойство ViewModel к элементу управления PasswordBox я получил такую ошибку:
A 'Binding' cannot be set on the 'Password' property of type 'PasswordBox'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject
Причина, по которой свойство Password не сделано DependencyProperty объясняется соображениями безопасности. Если бы можно было забиндить свойство к PasswordBox, то оно бы хранилось в памяти в открытом виде, а это не очень хорошая идея с точки зрения Microsoft. Поэтому пароль в PasswordBox хранится в зашифрованном виде и дешифруется при вызове свойства Password.
Я нашел несколько решений этой проблемы. Есть способы, которые сохраняют принципы MVVM, но создают описанную выше проблему. Если требования не предусматривают такого рода безопасности, то можно пойти против системы при помощи Attached Properties все-таки забиндить свойство ViewModel. Я же опишу другой способ, который, возможно, слегка нарушает принципы MVVM, но сохраняет принцип безопасности хранения пароля.
Создаем простой котрол:
<Label Content="Login:" HorizontalAlignment="Right"/>
<TextBox Grid.Column="1" Margin="3" Text="{Binding UserName, Mode=TwoWay}"/>
<Label Grid.Row="1" Content="Password:" HorizontalAlignment="Right"/>
<PasswordBox Grid.Row="1" Grid.Column="1" Margin="3" Name="pwdBox"/>
<Button Grid.Row="2" Grid.ColumnSpan="2" Width="70" Height="25"
HorizontalAlignment="Right" Content="Login"
Command="{Binding LoginCommand}"/>
Для получения пароля нам понадобится интерфейс:
public interface IPasswordSupplier
{
string GetPassword();
}
Наследуем контрол от этого интерфейса и возвращаем в функции значение пароля:
public partial class LoginControl : UserControl, IPasswordSupplier
{
public LoginControl()
{
InitializeComponent();
}
public string GetPassword()
{
return pwdBox.Password;
}
}
Неприятная особенность метода заключается в том, что LoginControl придется создавать в коде, т.к. необходимо получить IPasswordSupplier. C помощью IoC контейнера регистрируем экземпляр котрола в App.xaml.cs:
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
IUnityContainer container = new UnityContainer();
LoginControl loginControl = new LoginControl();
container.RegisterInstance<IPasswordSupplier>(loginControl);
LoginViewModel loginViewModel = new LoginViewModel(container);
loginControl.DataContext = loginViewModel;
MainWindow mainWindow = new MainWindow(loginControl);
MainWindowViewModel windowViewModel = new MainWindowViewModel(loginViewModel);
mainWindow.DataContext = windowViewModel;
mainWindow.Show();
}
Теперь во ViewModel контрола мы можем получить доступ к Password свойству PasswordBox:
public string Password
{
get
{
IPasswordSupplier passwordSupplier = container.Resolve<IPasswordSupplier>();
return passwordSupplier.GetPassword();
}
}
Таким образом, свойство Password нашего ViewModel не хранит значение пароля а получает его при каждом обращении из PasswordBox, который в свою очередь дешифрует его для нас. Возможно, это нарушает MVVM, т.к. теперь наш ViewModel косвенно связан с View посредством данного интерфейса, но все же мне этот способ кажется наиболее подходящим для решения проблемы.
Автор: mrdamian