M
M
Maxim Grekhov2016-09-22 14:51:13
WPF
Maxim Grekhov, 2016-09-22 14:51:13

How to organize architecture when using C#, WPF, MVVM?

Hello. Sorry for war and peace. I don't know how to briefly ask about architecture.
I often have problems with misunderstanding how it is possible / necessary to build interaction between different parts of the application. Let's say we have:
1. BackgroundWorker - some kind of background service (device, parsing bot, etc.).
2. StorageService - data storage layer (sql, nosql, serialization).
3.UI - wpf, mvvm.
Now I will describe how I usually do it and what I think about it.
1. BackgroundWorker - I create a separate classlibrary where I put everything related to this worker. I create independent data models (POCO, no gui's IPropertyChanged, etc.), add events using these models. That is, api is built on the principle of calling a method for control, an event for informing in the opposite direction. I build it as independent as possible, so that if something happens, it can be used in various external conditions (console, gui, service).
2. StorageService - just like in the previous case, I am building the most independent solution. All models describe strictly the data layer, no left properties / methods, again POCO. I usually use Unite of Work/Repository for a large solution, or one class (DbService) to work with all entities for a small solution. I don't like ActiveRecord.
3. Ui - this is where my problems begin. Since to display all this you have to fence a bunch, as it seems to me, crutches. For example, I will describe the latest development for the house. Wrote a bot for taking honey. coupon. All three layers have their own

class Job //Общий класс описывающий задание
{ 
  Guid PersonId {get;set;}
  bool Enabled {get;set;}
  List<Period> Periods{get;}
//Другие поля
} 
class Period //Информация когда брать талончик
{
//Другие поля
}
class Person //Человек для кого берем
{
Guid Id{get;set;}
//Другие поля
}

That is, we have 3 * 3 data structures similar to 90% in their group. To transfer from one layer to another, we perform the conversion each time. I do it in ViewModels. It's okay little things, although verbose enough. Which increases the volume and impairs readability. The removal of the conversion logic into a service or the use of AutoMapper helps.
Lately I've been using a TabControl where each panel is a separate ViewModel and View. We have JobsViewModel and PeopleViewModel. We look (mentally) at the JobsViewModel, it stores tasks (ObservableCollection) that are displayed in the ListView, there is also a link to the bot (BackgroundWorker). Periods (ObservableCollection) and person id are stored in each task.
And then there are a lot of dilemmas of who should do what and how.
1st problem - duplication, tracking and cohesion.
When adding (removing) to tasks in parallel, we duplicate this for the bot. If I do not want to make a "Save" button, but simply change all the data in the database on the fly. Then the database is added to the duplication of calls. Plus, a subscription to the propertychanged event of each JobViewModel, and it, in turn, should somehow signal to me about changes in each PeriodViewModel. In general, you will need a lot of connections and crutches. Or use IEventAggregator and make a call for each sneeze. This will at least reduce the coupling. But you still have to keep track of all this mess (many points of change). For more complex structures, this will become much more difficult. The code will become more "magical".
2nd problem - adding new dependencies for each sneeze.
I'm using a beautiful ToggleSwitch to toggle the execution status of a task (the Enabled property). To pass this to the bot (BackgroundWorker), I either need to have a link to it for each JobViewModel (which increases connectivity), or the JobsViewModel must subscribe to the propertychanged event for all JobViewModels when added and unsubscribe when removed, it seemed to me a more correct solution.
The 3rd problem is passing data from one ViewModel to another.
An ObservableCollection is formed in the PeopleViewModel. This collection is needed in the JobViewModel to output to the combobox and select the PersonId. Here I see 4 ways:
a. I throw the entire PeopleViewModel into each JobViewModel. (connectedness)
b. I create a wrapper by type
PeopleService {public ObservableCollection<Person>{get;set;}}
and inject it into the PeopleViewModel and each JobViewModel. Or throw an ObservableCollection from the PeopleViewModel only into the JobsViewModel and from there slip each JobViewModel (but less connected)
into. Through the same IEventAggregator, I implement duplicate additions / deletions to all ObservableCollections in all ViewModels. (For ten tasks, ten ObservableCollection + 1 in PeopleViewModel)
d. When switching tabs JobsViewModel update all JobViewModel ObservableCollection from the database. Complete decoupling from PeopleViewModel. (cons as in point c, or dependence on the ObservableCollection in the parent JobsViewModel. It must be saved on the fly in the PeopleViewModel, and the third minus is that if everything is located in one window (without tabs), then it is not clear which event to update)
For a small project, you can get along with this. But I don’t understand how to live with this if there are tens and hundreds of models / view models. All these connections \ events and so on. How do senior software engineer\architect solve such problems?

Answer the question

In order to leave comments, you need to log in

1 answer(s)
A
Andrei Smirnov, 2016-09-22
@Sterk

Have you read about PRIMS yet?
If you want in Russian: https://habrahabr.ru/post/176851 If you
want in the original language: MSDN.
Also, if you haven't read about RX (Reactive Extensions) yet, it's time.
All together will give you a complete set for building a UI of any complexity.

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question