WPF, MVVM, бизнес-объект с проверкой не очень хорошо сочетаются

Для своего WPF-приложения я выбрал MVVM. Вот моя концепция, как я буду реализовывать этот паттерн.

  • Мои модели (бизнес-объекты) несут ответственность за проверку (это обязательно для меня).
  • ViewModels отвечает за обертку моей модели для удобства взаимодействия с пользователем и некоторых аспектов безопасности.

Мой первый вопрос был о переносе или не переносе моей модели во ViewModel.

  • Когда я не оборачиваю свою модель в ViewModel и не открываю модель непосредственно для представления - тогда я не понимаю, зачем мне ViewModel (это кажется бессмысленным)
  • ViewModel должен оборачивать Модель по разным причинам:

    1. Мне не нравится прямая привязка к строго типизированным свойствам в модели (DateTime, int,…), потому что, когда я это делаю, => WPF берет на себя мою проверку для этих типов. Это действительно плохо, потому что, когда пользователь пишет «aaaa» в Datepicker, моя модель действительна (моя модель никогда не знает об этом, потому что WPF берет на себя контроль над строго типизированными свойствами), а кнопка «Сохранить» включена - это действительно неправильно.

    2. Я не показываю все свойства моей модели представлению, мой ViewModel должен защищать мою модель (у меня есть некоторые свойства, которые должны иметь на уровне представления только геттер, а не сеттер)

Мое решение таково, что ViewModel обязательно следует обернуть Модель. Итак, ViewModel реализует INotifyPropertyChanged.

Но теперь у меня проблема с проверкой бизнеса.

Когда я беру красивый IDataErrorInfo, тогда у меня есть все бизнес-правила в ViewModel, что нарушает мою концепцию. Бизнес-правила обязательно должны быть в модели.

Пример: когда пользователь выбирает тип A, тогда поля 1 и 2 являются обязательными. Когда пользователь выбирает тип B, тогда поле 3 является обязательным - это поле должно быть помечено красным, а кнопка «Сохранить» неактивна, если оно недействительно. Также более тяжелые вещи, такие как свободные / занятые DateTime-Ranges.

Это определенно плохо, когда я делаю это во ViewModel, потому что большинство вещей - это бизнес-часть.

Итак, как я могу этого добиться?

На данный момент у меня есть обходной путь:

Все ValidationRules находятся в модели как простые методы, например

public string ValidateBirthday(string birthay)
{
    if (...)
    {
        return "Birthday should be…";
    }
    return string.Empty;
}

В моей ViewModel я реализовал IDataErrorInfo и перенаправлял на мою проверку модели следующим образом:

public string this[string columnName]
{
    get
    {
        switch (columnName)
        {
            case "Birthday":
                return Model.ValidateBirthday(Birthday);
            case "XXX":
                return Model.ValidateXXX(XXX);
            case "YYY":
                return Model.ValidateYYY(YYY);
            break;
        }
    }
}

Я никогда не видел ничего подобного (перенаправление на модель) в примере, поэтому очень сомневаюсь в своей реализации.

Мой способ обхода в порядке, или вы видите какие-либо проблемы по этому поводу?

Я пытаюсь дать больше информации о том, что я имею в виду ...

Я знаю о реализации INotifyPropertyChanged и IDataErrorInfo в Модели.

Это хорошо работает с прямым связыванием от представления к модели.

  1. Прямая привязка от представления к модели:

    открытый класс PersonViewModel: INotifyPropertyChanged {частное лицо _personModel; общедоступный человек PersonModel {получить {вернуть _personModel; } установить {если (_personModel! = значение) {_personModel = значение; NotifyPropertyChanged (); }}}

    public PersonViewModel(Person person)
    {
        PersonModel = person;
    }
    …
    

    }

Вид:

<DatePicker Text="{Binding PersonModel.Birthday}"/>

Большой недостаток: WPF берет на себя управление всеми свойствами со строгим типом.

Пример: пользователь ввел 20.07.2008 в datepicker, поэтому PersonModel будет проинформирован, и PersonModel может это проверить, когда OK, тогда PersonModel действителен => SaveButton включен.

Теперь пользователь ввел «aaa» в datepicker, WPF берет на себя управление этой проверкой, потому что это привязка к строго типизированному свойству (DateTime). PersonModel не будет проинформирован об этом, поэтому PersonModel все еще действителен => SaveButton включен!

Так что для этой «проблемы» мне нужен ViewModel правильно.

  1. ViewModel оберните модель следующим образом:

    открытый класс PersonViewModel: INotifyPropertyChanged {частное лицо _personModel;

    public string Birthday
    {
        get
        {
            if (_personModel. Birthday!= null)
            {
                return ((DateTime) _personModel. Birthday).ToShortDateString();
            }
            else
            {
                return String.Empty;
            }
        }
        set
        {
            if (_personModel. Birthday.ToString() != value)
            {
                DateTime dateValue;
                if (DateTime.TryParse(value, out dateValue))
                {
                    _personModel.Birthday = dateValue;
                    …
                }
                else
                {
                    …
                }
            }
        }    
    }
    
    public PersonViewModel(Person person)
    {
        _personModel = person;
    }
    …
    

    }

Теперь я не привязываю Модель напрямую из View. Я привязываю Свойства из ViewModel, который обернул Модель.

<DatePicker Text="{Binding Birthday}"/>

Большое преимущество: теперь у меня есть полный контроль над тем, что пользователь вводит в поля. Когда пользователь вводит строки типа «aaa» в Datepicker, я могу поймать это => установить состояние недействительным, а SaveButton отключен.

Это одна из причин, по которой я не использую прямую привязку из представления к модели. Другая причина - это свойство только для чтения. В модели я получил и установил каждое свойство, но из соображений безопасности я не буду предлагать все свойства из модели с получением и установкой. Таким образом, это также можно решить с помощью ViewModel, заключив в свойства только get. С прямым связыванием вы не можете делать всего этого.

Я хочу сказать, что я обязательно перенесу все свойства из моей модели в ViewModel, но как я могу использовать красивый IDataErrorInfo в модели (он работает только с прямым связыванием)?


person user3607263    schedule 06.05.2014    source источник
comment
Попробуйте реализовать интерфейс IDataErrorInfo в классах модели или типа данных, а не в модели представления. При правильном написании WPF, MVVM и IDataErrorInfo интерфейс на самом деле прекрасно сочетаются друг с другом. Кроме того, модель представления не просто оборачивает класс модели ... вместо этого она должна предоставлять все необходимые данные и функции для связанного с ним представления. Пожалуйста, пойдите и прочитайте об этих технологиях / методологиях, прежде чем приходить сюда, чтобы жаловаться на них, не понимая. Это не веб-сайт, на который вы можете прийти, чтобы узнать об этом.   -  person Sheridan    schedule 06.05.2014
comment
Благодарю за ваш ответ. Но я не выставляю модель прямо на свой взгляд. Это из-за строго типизированных свойств в Model (DateTime, int,…). Пример: День рождения - это DateTime. Поэтому, когда я напрямую привязываюсь к представлению, wpf берет на себя управление этим свойством. Когда пользователь переопределяет дату с помощью «aaa» в datepicker, моя модель никогда не информирует об этом, и моя модель находится в допустимом состоянии - это неправильно. Когда я оборачиваю свою модель в viewmodel, днем ​​рождения является DateTime в Model и строка в ViewModel, поэтому теперь я могу контролировать это свойство, а не wpf.   -  person user3607263    schedule 06.05.2014
comment
И как упаковка вашей модели в модель представления поможет вам в этой конкретной ситуации? Как это предотвратит рассинхронизацию вашей модели с пользовательским интерфейсом?   -  person Sheridan    schedule 06.05.2014
comment
Я расширил свой вопрос дополнительной информацией об этом.   -  person user3607263    schedule 06.05.2014
comment
Ух ты! Итак, вы хотите заключить все свойства в strings, а? Вы потеряете все встроенные функции типа .NET, такие как string.Format и т. Д., Так что удачи вам с этой идеей! Это так не лучший вариант. Интерфейс IDataErrorInfo предназначен для ошибок данных, а не ошибок пользовательского интерфейса ... ключ кроется в названии. Если пользователь вводит «AAA» в DatePicker, зачем вам знать это недопустимое значение в модели? «большой недостаток», о котором вы говорите, на самом деле вовсе не недостаток ... по крайней мере, ни я, ни многие мои пользователи моих приложений WPF никогда не считали это проблемой.   -  person Sheridan    schedule 06.05.2014
comment
Мы проверяем ошибки данных из модели, используя интерфейс IDataErrorInfo, и мы проверяем ошибки пользовательского интерфейса (например, пользователь пытается ввести букву в числовое поле) в пользовательском интерфейсе, обрабатывая события.   -  person Sheridan    schedule 06.05.2014
comment
А, ладно, большое спасибо.   -  person user3607263    schedule 06.05.2014
comment
Поэтому я буду реализовывать IDataErrorInfo и INotifyPropertyChanged в модели. Затем напрямую привяжите вид к модели через viewmodel (например, 1. Прямая привязка от вида к модели). И событие для изменения текста в Datapicker, когда пользователь вводит «aaa», поэтому я могу обрабатывать эти вещи в модели просмотра и отключать SaveButton. Как вы обрабатываете DateTime без DefaultValue? Пример нового человека - значение в datepicker должно быть пустым - через конвертер?   -  person user3607263    schedule 06.05.2014
comment
Я бы установил значение DateTime по умолчанию как DateTime.MinValue или DateTime.MaxValue, а затем использовал бы Converter для обнаружения любого из этих значений и вывода сообщения Not set в фактическом элементе управления.   -  person Sheridan    schedule 06.05.2014
comment
Обычно модель реализует IDataErrorInfo, а viewmodel реализует INotifyPropertyChanged в этом стиле архитектуры. Обычно.   -  person    schedule 07.05.2014
comment
Это MVVM, а не VVM. Модели несут данные, но также могут выполнять проверку - в любом случае, это их данные. ViewModels является промежуточным звеном между пользователем (с помощью пользовательского интерфейса) и бизнес-логикой приложения, представляя модели с логическими обработанными данными для отображения и передавая изменения этих моделей обратно в логику. Это не так уж и сложно.   -  person    schedule 27.08.2015


Ответы (1)


Здесь вы смешиваете две концепции: бизнес-объекты и валидацию.

Практически каждая система в настоящее время использует архитектуру клиент-сервер, даже если это отдельное приложение.

В таком сценарии у вас есть два места для проверки:

  • клиент отвечает за правильность введенных данных перед отправкой чего-либо на сервер, чтобы улучшить взаимодействие с пользователем и избежать перегрузок сервера и проблем с безопасностью.

  • сервер отвечает за проверку входящих данных, чтобы избежать искаженных, искаженных данных и проблем с безопасностью.

Также:

  • Бизнес-объекты (BO) - это классы, используемые сервером, типично представляющие базу данных.

  • Объекты передачи данных (DTO) - это классы, которые сервер отправляет клиенту.

  • ViewModels - это как внутренний код для пользовательского интерфейса, так и оболочки для DTO.

У ваших объектов модели не должно быть никакой логики, поскольку вы испортите их некоторым кодом, который в какой-то момент вам нужно будет использовать повторно.

Как показано здесь, вы должны разделить эту логику проверки на службы, которые знают только об этом объекте и о том, как их проверять. Таким образом, вы можете использовать службы проверки из пользовательского интерфейса.

Ваша кнопка Save должна реагировать только на изменения пользовательского интерфейса, и вы получите их только из ViewModel.

По сути, здесь вы будете применять принципы SOLID: каждый слой имеет очень четкое обязанности (модель -> данные, услуги -> проверка, dto -> данные, готовые для клиента, модели просмотра -> взаимодействие с пользовательским интерфейсом). Со всем кодом будет легко работать, легко расширять и легко рефакторинг.

Редактировать

1-й и 2-й вопросы: пользовательский интерфейс проверяет только ввод: нет случайных символов в числовых полях, нет символов sql в текстовых полях, дата имеет правильный формат и т. д.

Считает, что «если это, то это» должно обрабатываться серверной частью, как вы описываете:

  1. Нажата кнопка "Сохранить".
  2. Данные пользовательского интерфейса действительны.
  3. DTO отправлено на бэкэнд.
  4. Backend анализирует DTO, и это недействительно.
  5. Backend отправляет обратно обнаруженные ошибки.
  6. Пользовательский интерфейс показывает найденные ошибки.

3-й вопрос: мне кажется, это правильно.

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

Вам следует начать развиваться в этом направлении, а затем посмотреть, что вам больше подходит.

person JoanComasFdz    schedule 07.05.2014
comment
Спасибо за подробный ответ. Мне это кажется правильным. BO и DTO не знают о своем собственном действительном состоянии? Это всегда знает другой объект (ViewModel в PL с простыми правилами, Service в BL с более сложными правилами). Пример 1: Когда пользователь выбирает Тип A, тогда поля 1 и 2 являются обязательными. Когда пользователь выбирает тип B, тогда поле 3 является обязательным. Это простые глупые правила проверки, и я могу это сделать во ViewModel? - person user3607263; 07.05.2014
comment
Пример 2: DateFrom - DateTo должен быть в диапазоне дат, который является бесплатным (например, бронирование в ресторане). Это не правило проверки, а бизнес-правило (?), Поэтому пользователь пытается сохранить - запрос отправляется в бизнес, а бизнес отправляет сообщение обратно, когда диапазон не является свободным - верно? Это накладные расходы, когда служба снова проверяет правила валидации из примера 1 (для большей стабильности) перед сохранением? - person user3607263; 07.05.2014
comment
А как насчет ListViewModel и EditViewModel? У меня есть список с людьми (только для чтения). Пользователь может добавлять / обновлять / удалять лиц (с новой формой, а не в самой сетке). Когда пользователь сохранит изменения, запрос сначала отправляется в BO-Service, а затем BO-Service отправляет сообщение. ListPersonVM, который прослушивает это сообщение, обновит этого человека в списке (или добавит / удалит) - верно? Нет связи между EditPersonVM и ListPersonVM - всегда через BO-Layer (может быть, бизнес делает какие-то вычисления или что-то еще - так что это имеет смысл). - person user3607263; 07.05.2014
comment
Мое приложение wpf - это толстый клиент, поэтому PL и BL находятся на одной машине, поэтому я могу делать все без DTO (?). Извините за многие вопросы, я впервые делаю это наслоение. - person user3607263; 07.05.2014