Проблемы понимания использования MVVM при использовании WCF DTO в приложении умного клиента WPF - PullRequest
14 голосов
/ 23 января 2012

Имея очень небольшой опыт в этой области, я пишу умное клиентское приложение WPF, взаимодействующее с бэкэндом WCF с использованием MVVM, и действительно изо всех сил пытаюсь принять правильные решения из всей имеющейся информации. Это приводит меня к ряду вопросов, которые, я надеюсь, могут быть решены здесь людьми, более опытными в этой области.

Например, один из экранов позволяет вводить заказ и добавлять в него строки заказа.

Что используется в качестве модели?

В службе WCF у меня есть следующее упрощенное DTO:

public OrderDTO
{
   string orderDetails { get; set; }
   List<OrderLineDTO> OrderLines { get; set; }
}

public OrderLineDTO
{
   int customerId { get; set; }
   int productId { get; set; }
   double quantity { get; set; }
}

И служба WCF, которая имеет следующий метод:

public OrderService Order
{
    CreateOrderResponse CreateOrder(OrderDTO order) 
}

В моем интеллектуальном клиенте WPF у меня есть ссылка на DTO, но он явно не реализует INotifyPropertyChanged, поскольку он предназначен исключительно для транспорта.

Вопросы

Будет ли рекомендуемый подход преобразовывать эти DTO в модель, которая реализует INotifyPropertyChanged с использованием Automapper или аналогичного? Или DTO следует использовать в качестве модели непосредственно в ViewModel?

Связь между моделями представления

В настоящее время у меня есть представление заказа с 2 вкладками (Order и OrderLines) с ViewModels OrderViewModel и OrderLineViewModel. На вкладке заказа у меня есть ComboBox, содержащий идентификаторы и имена клиентов. Когда я выбираю клиента на OrderView, мне нужно сообщить OrderLineView, что клиент был выбран, чтобы ComboBox показывал только продукты, принадлежащие этому клиенту.

Вопросы

Как OrderViewModel будет общаться с OrderLineViewModel в этом сценарии?

Добавление строки заказа и применение логических / бизнес-правил

Поскольку приложение уровня сервера будет использоваться несколькими клиентами, например ПК, мобильными устройствами. Я хотел бы убедиться, что в приложении уровня сервера применяются все бизнес-правила. Как пример, когда строка заказа добавлена. если он относится к определенному типу продукта, его можно добавить, только если у клиента есть определенная сертификация.

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

Вопросы

Разрешаете ли вы пользователю добавлять недействительные строки, отправлять запрос на сервер, разрешать серверу применять соответствующие правила и возвращать ответ? Или вы как-то применяете логику в приложении умного клиента перед отправкой запроса на сервер?

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

Спасибо

Alex

Edit: Спасибо всем за ваш вклад, так как он помог мне стать немного более ясным с точки зрения лучшего пути вперед. Все ответы были хорошими, но я решил принять ответ Ури, так как он лучше всего соответствует моим мыслям на данном этапе. Тем не менее, я до сих пор не уверен, что лучший способ справиться с преобразованием из идентификатора DTO в SelectedItem в ItemsSource, который представляет собой список ViewModels. Я вижу, что конвертер может работать, но я собираюсь попытаться найти другое решение. Спасибо, Алекс

Ответы [ 4 ]

4 голосов
/ 24 января 2012

Вот мои мысли о ваших вопросах:

Вопрос: Будет ли рекомендуемый подход преобразовывать эти DTO в модель, в которой реализован INotifyPropertyChanged с использованием Automapper или аналогичного?Или DTO должен использоваться в качестве модели непосредственно в модели представления?

Ответ: Мой подход, который мне нравится больше всего, - это сдерживание.Я согласен с вами, у DTO не должно быть ничего, кроме добытчиков и сеттеров.Держите его как можно более чистым, поэтому он не должен запускать INotifyPropertyChanged.Я также не думаю, что представление должно иметь прямой доступ к объектной модели (если по какой-либо другой причине вы не изменили преимущество свойства).Недостатком моего подхода является дополнительный код в ViewModel, но я думаю, что он того стоит.

public class VmBase : INotifyPropertyChanged {

    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void raise( string propName )
    {
        if( PropertyChanged ) {
            PropertyChanged( this, new PropertyChangedEventArgs(propName) );
        }
    }
}

public class OrderLineVm : VmBase {
    private OrderLineDTO orderLine;

    public OrderLineVm( OrderLineDTO ol ) {
        orderLine = ol;
    }

    public OrderLineVm( ) {
        orderLine = new OrderLineDTO();
    }

    int customerId {
        get { return orderLine.customerId; }
        set { orderLine.customerId=value; raise("customerId"); }
    }

    int productId {
        get { return orderLine.productId; }
        set { orderLine.productId=value; raise("productId"); }
    }

    double quantity {
       ...
    }
}

Благодаря чуду сборки мусора OrderLineDTO будет создан только один раз (когда он поступит с сервера).) и живите столько, сколько нужно.Существует два общедоступных конструктора: один с DTO (обычно, когда объекты поступают с сервера), а другой создан на клиенте.

Для OrderVm это немного сложнее, поскольку вы хотели быиметь ObservableCollection (по сравнению со списком) OrderLineVm (по сравнению с OrderLineDTO), поэтому ограничение не будет работать.Также обратите внимание, что orderLines имеет только геттер (вы добавляете и удаляете из него строки заказа, но не меняете весь список. Выделяете его один раз во время создания).

public class OrderVm : VmBase {
    private string _orderDetails;
    public string orderDetails {
        get { return _orderDetails;
        set { _orderDetails=value; raise("orderDetails"); }
    }

    private ObservableCollection<OrderLineVm> _orderLines;
    public ObservableCollection<OrderLineVm> orderLines { 
        get { return _orderLines; }
    }
}

Вопрос: Как бы OrderViewModel взаимодействовал с OrderLineViewModel в этом сценарии?

Ответ: Если требуется связь, действительно, вы должны сделать это самым простым способом.Оба класса модели представления находятся на одном и том же уровне.OrderVm ссылается на список OrderLineVm, и если вам нужно сообщение из класса OrderLineVm для заказа, просто сохраните ссылку.

Однако я бы настоятельно сказал, что сообщение не нужно.Как только представление будет связано должным образом, я не вижу причин для такого общения.Свойство Mode привязки должно быть «двусторонним», поэтому все, что изменилось в пользовательском интерфейсе, будет изменено в модели представления.Кроме того, удаление в список строк заказа будет автоматически отображаться в представлении благодаря уведомлениям, отправленным из ObservableCollection.

Вопросы: Разрешить ли пользователю добавлять недопустимую строку, отправьтезапрос к серверу разрешить серверу применить соответствующие правила и вернуть ответ?Или вы каким-то образом применяете логику в приложении «умный клиент» перед отправкой запроса на сервер?

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

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

РЕДАКТИРОВАТЬ: (продолжение комментария от Алекса):

Отображение раскрывающегося спискаЯ думаю, что источник вашей путаницы заключается в том, что на самом деле существует два независимых ItemsSource (и, следовательно, два отдельных контекста данных): существует один список строк заказа, и в каждой строке заказа находится список ProductID, которые представляют собой заполненные элементы.комбинированный ящикТолько SelectedItem является свойством ProductLine.Как правило, список возможных ProductID должен быть глобальным для приложения (или заказа).У вас будет ProductIDs свойство всей формы, и вы дадите ему имя (например, x: Key или x: Name).Затем в элементе ComboBox просто укажите этот список:

<ComboBox ItemsSource="{Binding Source={StaticResource ProductIDs}}"
          SelectedItem="{Binding Path=productId}"
          />
1 голос
/ 25 января 2012

Я полагаю, что реальный вопрос в том, насколько верным вам будет шаблон MVVM?

Идея, лежащая в основе MVVM, а также аналогичных шаблонов, таких как MVC и MVP, заключается в разделении интересов. Хотя я тоже трудился над этой темой, я более внимательно посмотрел на то, что шаблон пытается достичь, и выбор стал легче.

С MVVM у вас есть три проблемы: View (V), Model (M) и ViewModel (VM). Кажется довольно очевидным, верно? Но спросите себя, о чем действительно беспокоится каждый и что произойдет, если мы начнем смешивать проблемы - так же, как это происходит, когда мы смешиваем проблемы в других местах. Наш код становится все труднее изменить.

Имея это в виду, рассмотрим случай, когда вы позволяете пользовательскому интерфейсу проникать в вашу модель представления, раскрывая свойство, которое использует тип пользовательского интерфейса. Это часто встречается при работе с диалогами (основной источник головной боли в MVVM). Допустим, вы разрабатываете свое приложение с использованием стороннего набора элементов управления, и тип интерфейса пользователя - один из их. Теперь вам нужно внести несколько изменений, если вы меняете наборы элементов управления, а не просто меняете разметку пользовательского интерфейса (или это делает дизайнер).

(Это свежо, на мой взгляд, потому что я только что предпринял такую ​​попытку, и настоящие MVVM-приложения было несложно изменить, в то время как другим потребовалось в 10-25 раз больше времени!)

Этот же сценарий влияет и на «конец» шаблона.

Целью модели является передача данных в / из любого механизма персистентности, который вы используете со своим приложением. Это может быть веб-служба, база данных, текстовый файл и т. Д. Тот факт, что WCF добавляет такие возможности, как INotifyPropertyChanged, не означает, что их использование рекомендуется. Помните, что Microsoft занимается разработкой инструментов. Чтобы продавать эти инструменты, они должны работать в различных ситуациях и на разных уровнях. RIA Services, например, отлично подходит для быстрых и грязных приложений, но быстро ломается при применении к реальным решениям (по моему опыту, по крайней мере).

Так что же произойдет, если вы используете модель сдерживания и все ваши свойства будут делегированы объекту Model, который находится в состоянии в вашей ViewModel, и характер модели изменится? Или Модель не делает все, что вам нужно. Факт заключается в том, что ViewModel предназначен для адаптера, который дает UI то, что ему нужно для работы. Редко должны быть отношения 1: 1 с Моделью, но я знаю, что это происходит.

Что произойдет, если через 6 месяцев вы решите воспользоваться услугой REST вместо WCF? Теперь у вас нет поддержки INPC в вашей модели, потому что вы не имеете дело с автоматически генерируемыми прокси-классами. Хотя это и не так ощутимо, как меняется пользовательский интерфейс, здесь применимы те же идеи, и именно поэтому шаблоны разделяют модель.

Мой совет: следуйте своему первому инстинкту и используйте AutoMapper для отображения данных, содержащихся в вашем объекте Model, в вашу ViewModel и наоборот. AutoMapper позволяет очень легко справляться с проблемами несоответствия импеданса, с которыми вы можете столкнуться, и дает вам единое место для внесения изменений в случае изменения одной или другой стороны договора.

2

То, что у вас есть, является объектной моделью, и в этом случае наличие событий, обратных вызовов и т. Д. Совершенно законно. Я бы сказал, что ваш OrderViewModel содержит коллекцию объектов OrderLineViewModel. Дочерние объекты могут содержать ссылку на родителя (OrderViewModel) и извлекать оттуда выбранного клиента.

3

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

Что касается вопроса о правилах делового обхода, я бы сказал, нет. Я стараюсь применять столько бизнес-правил в клиентском приложении, сколько имеет смысл. Во-первых, это улучшает взаимодействие с пользователем и уменьшает сетевой трафик, необходимый клиенту. Я придерживаюсь одного практического правила: никогда не позволяю пользователю сохранять недопустимый объект. Примечание: неточные или неполные данные не совпадают с недействительными. Неверные данные вызывают исключения.

1 голос
/ 23 января 2012

Чтобы ответить на ваши вопросы по очереди ...

1) Если вам не нужно, чтобы ваши свойства уведомляли пользовательский интерфейс при их изменении, тогда нет необходимости использовать INotifyPropertyChanged - и, по моему мнению, вы можете привязать модель напрямую к представлению. Нет необходимости добавлять дополнительный слой, если он не добавляет никаких дополнительных функциональных возможностей. Однако в большинстве приложений вы захотите изменить состояние объекта модели через пользовательский интерфейс. В этом случае вам нужно будет добавить объекты View Model, которые реализуют INotifyPropertyChanged. Вы можете либо создать модель представления, которая адаптирует модель, то есть делегировать свойства базовой модели, либо скопировать состояние объекта модели в эквивалентную модель представления.

Чтобы избежать написания большого количества довольно похожего кода, то есть одного и того же объекта домена, представленного в качестве объекта модели и объекта модели представления, я стараюсь по возможности использовать генерацию кода. Мне нравится использовать XML для описания моей модели и шаблоны T4 для codegen.

2) Как OrderViewModel должен общаться с OrderLineViewModel? напрямую! Понятия звучат довольно тесно, я бы предположил, что заказ имеет несколько строк заказа? В этом случае просто сделайте так, чтобы каждая модель представления ссылалась на другую. Нет необходимости в причудливых посредниках, если они тесно связаны в вашем домене.

3) Хороший вопрос! Я согласен, что сервер должен применить проверку. Дублируете ли вы часть этой проверки в клиенте, зависит от ваших требований. Если ваше общение с сервером происходит быстро и часто, вы можете обеспечить хороший пользовательский интерфейс, общаясь с сервером, когда пользователь редактирует заказы, и проверяя их при переходе от поля к полю. Однако во многих случаях это не практично. Весьма распространено применять простую проверку в клиентском приложении, но разрешать серверу выполнять более сложные проверки, например, проверять уникальность и т. Д. *

Надеюсь, это поможет.

0 голосов
/ 24 января 2012

У меня 2-летний опыт работы с "богатыми клиентами" (в WPF).

В моем интеллектуальном клиенте WPF у меня есть ссылка на DTO, но ясно он не реализует INotifyPropertyChanged, поскольку это чисто для транспорт.

Неправильно
По умолчанию WCF автоматически реализует INPC в каждом DTO.
Лучше всего использовать DTO как ViewModel для простых представлений, а для более сложных представлений использовать композицию «pattern».

Связь между моделями представлений

«Лучшая» практика (читай: чем в основном занимаются все) - это использовать слабый шаблон событий, чтобы держать вещи слабо связанными. Наиболее известным из них является IEventAggregator из библиотеки PRISM, но существует несколько реализаций.

Добавление строки заказа и применение логических / бизнес-правил

Думайте о клиенте так же, как о веб-странице: не доверяйте ему. Это код .NET, и мы все знаем, как легко взломать.
Вот почему у вас должны быть реализованы проверки безопасности в вашей службе WCF.

НТН,

Бабы.

...