c# – Авторизация в СУБД MySQL в связке с [WPF MVVM Entity Framework] – Stack Overflow на русском

Introduction

This article aims to demonstrate how to implement UI element access control in WPF using the ICommand interface and Markup Extension.

Authdelegatecommand

AuthDelegateCommand is the concrete implementation of ICommand interface. The methods defined in ICommand have the notion of authorization concept and it perfectly matches the authorization requirement interpreted by the author here.

More importantly, using ICommand in WPF abide to MVVM design pattern in which the logic of access control and business logic execution is implemented in the view model. Furthermore, we can use Attached Properties as a mechanism to access control the UI elements in response to the event triggered. I will reveal more about this technique later in this article.

The Command property of MenuItem, ButtonBase and their derived controls are ICommand consumer. It is expected to call CanExecute to determine whether to enable or disable itself and its associated command invocation code.

It also can determine when to invoke that method by subscribing to the CanExecuteChanged event. The AuthDelegateCommand shown below overloads the constructor and inject the access control function ‘AuthProvider.Instance.CheckAccess(…)’ to the CanExecute method and we only need to provide the business logic code through the executeMethod when creating an instance of AuthDelegateCommand.

publicclass AuthDelegateCommand : DelegateCommandBase
{
    public AuthDelegateCommand(Action executeMethod)
        : base((op) => executeMethod(), (op) => AuthProvider.Instance.CheckAccess(op))
    {
        if (executeMethod == null)
            thrownew ArgumentNullException("executeMethod");
    }

    publicvoid Execute()
    {
        base.Execute(null);
    }
}

The code shown below shows how to use AuthDelegateCommand in view and its associated view model.

private ICommand _createCommand;
public ICommand CreateCommand
{
    get 
    { 
        return _createCommand ?? (_createCommand = new AuthDelegateCommand(
            
            () => MessageBox.Show("You can execute the Create command.", 
				"Authorization"))
    ); 
}

Authorization providers

Application can use their own custom authorization providers together with the authorization primitives developed in this article. But they must adhere to the following design guidelines:

Похожее:  MPSRS RU ЛИЧНЫЙ КАБИНЕТ ВХОД В ЛИЧНЫЙ

Authtoenabledextension

[MarkupExtensionReturnType(typeof(bool))]
publicclass AuthToEnabledExtension : MarkupExtension
{
    publicstring Operation { get; set; }

    public AuthToEnabledExtension()
    {
        Operation = String.Empty;
    }

    public AuthToEnabledExtension(string operation)
    {
        Operation = operation;
    }

    publicoverrideobject ProvideValue(IServiceProvider serviceProvider)
    {
        if (String.IsNullOrEmpty(Operation))
            returnfalse;

        return AuthProvider.Instance.CheckAccess(Operation);
    }
}

Authtovisibilityextension

[MarkupExtensionReturnType(typeof(Visibility))]
publicclass AuthToVisibilityExtension : MarkupExtension
{
    publicstring Operation { get; set; }

    public AuthToVisibilityExtension()
    {
        Operation = String.Empty;
    }

    public AuthToVisibilityExtension(string operation)
    {
        Operation = operation;
    }

    publicoverrideobject ProvideValue(IServiceProvider serviceProvider)
    {
        if (String.IsNullOrEmpty(Operation))
            return Visibility.Collapsed;

        if (AuthProvider.Instance.CheckAccess(Operation))
            return Visibility.Visible;
        return Visibility.Collapsed;
    }
}

As you can see, it is very simple to create an authorization primitive. The implementation performs the authorization logic in the ProvideValue method by calling the AuthProvider.Instance.CheckAccess(Operation). You can see its usage in the code shown below:

History

  • 4th November, 2022: Initial version

Points of interest

With Authorization

Авторизация в рамках mvvm

Почему бы не использовать IoC контейнер в таком случае? Ведь вы на этапе разработки точно знаете, какой именно тип окна надо показать первым, так почему бы это не указать явно? Какая разница, передавать тип строчкой или готовым типом? С точки зрения проектирования, я не вижу разницы.

Вы верно подметили, VM не должна управлять окнами, UI должен решать какие данные в каком окне и с какими контролами надо отображать, но точка входа все равно указывается явно в точке сборки приложения – Composition Root, там и стартует главное окно (не важно какое именно).

Пример:

Если у вас перед показом основного UI нужно отобразить окно логина, то так и напишите в коде. Смысл заворачивания абстракций в том чтобы была гибкость в разработке. Чем меньше связаны между собой компоненты приложения – тем легче их разрабатывать не ломая связи. Об этом, как мне кажется и говорить MVVM и прочие шаблоны.

Попробуйте действительно передать управление экземплярами окон в IoC контейнер, оберните его в фабрику окошек и пусть она по типу VM будет вам нужное окно выдавать, там же можно и смапить пары VM-View.

WindowFactory.Instance.Display<LoginViewModel>();

Как-то так я это вижу. Эта фабрика и будет развязкой для колбэков из VM во View. Далее уже вам решать, нужны ли вам только пары или нужно несколько вьюх на одну VM или наоборот.


Кстати, string это sealed класс, можно не опасаться, что кто-то вам поломает операции сравнения строк полиморфизмом. str1 == str2 выглядит намного лучше, чем констркукция string.Compare в вашем случае. Есть еще .Equals и несколько других вариантов, возвращающих bool.

Авторизация в субд mysql в связке с [wpf mvvm entity framework]

Есть приложение WPF, разделенное на три части согласно паттерну MVVM. Приложение работает с БД MySql средствами Entity Framework 6.
Появилась необходимость авторизации пользователей в приложении. Каждый пользователь – это не просто запись в таблице Users базы данных, он также является пользователем на уровне СУБД (сделал так для разграничения прав на определенные таблицы и для записи в логи).
Так вот, как реализовать следующий функционал в соответствии с MVVM:

  • при запуске выводить окно авторизации, где пользователь вводит свои логин и пароль;
  • потом по введеным логину и паролю генерировать строку подключения, по которой и будет подключаться контекст EF к базе данных. Важно делать это именно таким образом, чтобы можно было в коде быстро и удобно передавать эту строку в контекст;
  • дальше пытаться уже, собсно, авторизоваться в СУБД и, если авторизация успешна, работать под этим пользователем (в БД есть триггеры, пишущие логи изменений, в которых много раз используется функция CURRENT_USER);
  • хранить строку подключения где-то в файле крайне нежелательно, т.к. это плохо скажется на безопасности да и на скорости.

Поведение модели

Теперь нам осталось только реализовать поведение модели при вносе наличности, покупке и требовании сдачи.

Как видим, до настоящего момента мы практически не принимали проектных решений. Наша программа с неизбежностью произростала из разработанного нами интерфейса пользователя и нужд MVVM. В разработке интерфейса мы делали творческое усилие, а VM у нас получиласть практически автоматом.

Заодно появились точки соприкосновения VM с моделью, которые стали опорными точками для дальнейшего построения модели. Такой подход (View first) может применяться и в реальных проектах, после того, как будет сделан эскиз или каркас разрабатываемого модуля, так сказать, широкими мазками.

Сейчас, для того, чтобы продолжить разработку, нам необходимо объединить пользователя и автомат в рамках одной сущности. Это объединение обычно диктуется этим предварительным эскизом. Мы же, в нашем случае — просто создадим жесткое соединение одного произвольного пользователя и одного автомата в рамках объекта класса, например, PurchaseManager. Соответственно, объекту именно этого класса мы будем адресовать наши, еще не реализованные, запросы на поведение модели.

Последняя функциональность — получение сдачи


Алгоритм очень простой — необходимо посмотреть, есть ли у автомата достаточно денег для сдачи, и если да — собрать набор купюр (сначала крупные, потом все мельче) и передать пользователю.

Реализация примера №1 в wpf

Конкретно в WPF реализована «аппаратная поддержка» паттерна MVVM. View реализуется в XAML. Т.е. зеленый слой (View) будет написана на XAML. Зеленые точки — это будут текстовые поля. А зеленые линии, соединяющиеся с синими — будут реализованы через механизм Binding.

Рисуем View:

Теперь выполняем последний пункт методики — реализуем VM. Чтобы наша VM «автоматически» обновляла View, требуется реализовать интерфейс INotifyPropertyChange. Именно посредством него View получает уведомления, что во VM что-то изменилось и требуется обновить данные.

Делается это следующим образом:

public class MainVM : INotifyPropertyChange
{
  public event PropertyChangedEventHandler PropertyChanged;
  protected virtual void OnPropertyChanged(string propertyName) {
      PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
  }
}

Теперь снабдим VM тремя необходимыми свойствами. (Требования для установления связи VM и View такое, что это должны быть открытые свойства)

private int _number1;
public int Number1 { get {return _number1;}
  set { _number1 = value;
    OnPropertyChanged("Number3"); // уведомление View о том, что изменилась сумма
  }
}

private int _number2;
public int Number2 { get {return _number2;}
  set { _number1 = value; OnPropertyChanged("Number3"); } }

Последнее свойство — это линия пунктирная синяя линия связи VM и модели:

//свойство только для чтения, оно считывается View каждый раз, когда обновляется Number1 или Number2
public int Number3 { get; } => MathFuncs.GetSumOf(Number1, Number2);


Мы реализовали полноценное приложение с применением паттерна MVVM.

Рассмотрение паттерна на примере №2:

Теперь усложним наше задание. В программе будет текстовое поле для ввода числа. Будет ListBox с коллекцией значений. Кнопка «Добавить», по нажатию на которую число в текстовом поле будет добавлено в коллекцию значений. Кнопка удалить, по нажатию на которую выделенное в ListBox’е число будет удалено из коллекции. И текстовое поле с суммой всех значений в коллекции.

c# - Авторизация в СУБД MySQL в связке с [WPF   MVVM   Entity Framework] - Stack Overflow на русском
Изображение 4: Интерфейс для Примера №2

Согласно методике — необходимо сначала разработать модель. Теперь модель не может быть stateless и должна хранить состояние. Значит в модели будет коллекция элементов. Это раз. Затем — операция добавление некоторого числа в коллекцию — это обязанность модели.

VM не может залезать во внутренность модели и самостоятельно добавлять в коллекцию модели число, она обязана просить сделать это саму модель. В противном случае это будет нарушение принципа инкапсуляции. Это как если бы водитель не заливал, как положено, топливо в бензобак и т.д. — а лез бы под капот и впрыскивал топливо непосредственно в цилиндр.

То есть будет метод «добавить число в коллекцию». Это два. И третье: модель будет предоставлять сумму значений коллекции и точно также уведомлять об ее изменении через интерфейс INotifyPropertyChanged. Не будем разводить споры о чистоте модели, а будем просто использовать уведомления.

Давайте сразу реализуем модель:

Коллекция элементов должна уведомлять подписчиков о своем изменении. И она должна быть только для чтения, чтобы никто, кроме модели, не могли ее как-либо изменить. Ограничение доступа — это выполнение принципа инкапсуляции, оно должно соблюдаться неукоснительно, чтобы: а) самому случайно не создать ситуацию трудноуловимого дебага, б) вселить уверенность, что поле не изменяется извне — опять же, в целях облегчения отладки.

Кроме того, так так мы далее все равно подключим Prism для DelegateCommand, то давайте сразу использовать BindableBase вместо самостоятельной реализации INotifyPropertyChange. Для этого надо подключить через NuGet библиотек Prism.Wpf (на момент написания 6.3.0). Соответственно OnPropertyChanged() измениться на RaisePropertyChanged().

public class MyMathModel : BindableBase
{
  private readonly ObservableCollection<int> _myValues = new ObservableCollection<int>();
  public readonly ReadOnlyObservableCollection<int> MyPublicValues;
  public MyMathModel() {
    MyPublicValues = new ReadOnlyObservableCollection<int>(_myValues);
  }
  //добавление в коллекцию числа и уведомление об изменении суммы
  public void AddValue(int value) {
    _myValues.Add(value);
    RaisePropertyChanged("Sum");
  }
  //проверка на валидность, удаление из коллекции и уведомление об изменении суммы
  public void RemoveValue(int index) {
      //проверка на валидность удаления из коллекции - обязанность модели
    if (index >= 0 && index < _myValues.Count) _myValues.RemoveAt(index);
    RaisePropertyChanged("Sum");
  }
  public int Sum => MyPublicValues.Sum(); //сумма
}

Согласно методике — рисуем View. Перед этим несколько необходимых пояснений. Для того, чтобы создать связь кнопки и VM, необходимо использовать DelegateCommand. Использование для этого событий и кода формы, для чистого MVVM — непозволительно. Используемые события необходимо обрамлять в команды. Но в случае с кнопкой такого обрамления не требуется, т.к. существует специальное ее свойство Command.

Кроме того, число, которое мы будем добавлять, используя DelegateCommand, мы будем не биндить к VM, а будем передавать в качестве параметра этого DelegateCommand, чтобы не загромождать VM и избежать рассинхронизации непосредственного вызова команды и использования параметра. Обратите внимание на получившуюся схему и, особенно, на место, обведенное красной линией.

c# - Авторизация в СУБД MySQL в связке с [WPF   MVVM   Entity Framework] - Stack Overflow на русском
Изображение 5: Схема для Примера №2

Здесь привязка на View происходит не вида View <=> ViewModel, а вида View <=> View. Для того, чтобы этого добиться используется второй вид биндинга, где указывается имя элемента и его свойства, к которому осуществляться привязка — “{Binding ElementName=TheNumber, Path=Text}”.

Создание viewmodels


Мы уже создали (создайте, если еще не) классы MainViewVM, ProductVM и MoneyVM.

Рассмотрение паттерна на примере №1: сложение двух чисел с выводом результата

Методика:

Методика написания программы используя подход «ModelFirst».

Однако творчество — это довольно затратный по ресурсам мыслительный процесс, и следует избегать излишнее обращение к нему. Во-первых — чтобы не перенапрягаться. Во-вторых — чтобы ваш собрат по программированию (да и вы сами, через пару недель) заглянув к вам в код, не был бы принужден к, излишне увлекательному порой, следованию за творческим полетом мысли.

Итак, моделью в нашей задаче будет сложение чисел с возвратом результата. Модель, в принципе, может не хранить никакого состояния. Т.е. она может вполне быть реализована статическим методом статического класса. Примерно так:

 static class MathFuncs {
    public static int GetSumOf(int a, int b) => a   b;
  }

Следующий шаг — (см. методику «ModelFirst») — создать View или, проще — нарисовать интерфейс. Это тоже часть, которая может содержать творчество. Но, опять же, не стоить с ним перебарщивать. Пользователь не должен быть шокирован неожиданностями интерфейса.

Заключительный шаг — соединение View и модели через VM. VM — это такое место, которое вообще не должно содержать творческого элемента. Т.е. эта часть паттерна железно обуславливается View и не должна содержать в себе НИКАКОЙ «бизнес логики». Что значит обусловленность от View?

Следовательно два свойства принимают из View число номер один и два, а третье свойство — вызывает нашу модель для выполнения бизнес-логики нашей программы. VM ни в коем случае не выполняет сложение чисел самостоятельно, оно для этого действия только вызывает модель!

В этом и состоит функция VM — соединять View (которое тоже ничем иным, кроме как приема ввода от пользователя и предоставления ему вывода не занимается) и Модель, в которой происходит все вычисление. Если нарисовать картинку нашей задачки, то получиться нечто такое:

c# - Авторизация в СУБД MySQL в связке с [WPF   MVVM   Entity Framework] - Stack Overflow на русском
Изображение 3: Схема Примера №1

Зеленое — это View, три зеленые точки в которой — это наши три текстовые поля. Синее — это VM, к которой эти три зеленых точки железно прибиты (прибиндены), ну а красное облачко — это модель, которая занимается вычислением.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *