Внедрение зависимостей в модели представления страниц в WPF

Я создаю приложение WPF с использованием ядра .NET 3.1. В прошлом я разрабатывал приложения ASP.Net, и я был рад использовать это в WPF. Я немного поискал и понял, что DI в WPF не так прост, как в ASP.Net, а это означает, что вы должны регистрировать Views и ViewModels.

Моя структура такая

MainWindow
|---BalanceIntegrationPage
   |---BalanceIntegrationViewModel

Все обрабатывается в XAML, при этом MainWindow.xaml.cs имеет только сгенерированный код, а BalanceIntegrationPage.xaml.cs имеет одну строку, добавленную к нему в конструкторе.

DataContext = new ScaleIntegrationViewModel();  

Это нельзя было обработать в xaml, потому что DI требует параметров в конструкторе.

Вот мой app.xaml.cs:

protected override async void OnStartup(StartupEventArgs startupEventArgs)
        {
            base.OnStartup(startupEventArgs);
            ServiceCollection services = new ServiceCollection();
            services.AddScoped<MainWindow>();
            services.AddScoped<ScaleInterfacePage>();
            services.AddScoped<ScaleIntegrationViewModel>();
            services.AddScoped<IScale>(provider => new Scale("1234"));

            ServiceProvider serviceProvider = services.BuildServiceProvider();
            MainWindow mainWindow = serviceProvider.GetService<MainWindow>();
            mainWindow.Show();

        }

Моя ScaleIntegrationViewModel выглядит так:

public ScaleIntegrationViewModel(IJMDataIntegration jmContext = null, IBalanceIntegrationContext localContext = null, IScale scale = null)
        {
            _jmContext = jmContext ?? new JMDataIntegration();
            _localContext = localContext ?? new BalanceIntegrationContext();
            _scale = scale ?? new Scale("1234");
            //JK read from config
            _commPort = "1234";
        }

Я также попытался использовать шаблон, описанный здесь

Когда я выполняю код, мой объект IScale в конструкторе ViewModel всегда имеет значение null.

Какие-либо предложения??

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

Основываясь на комментарии, я удалил вызов ViewModel в конструкторе страницы и вместо этого назначил его в .xaml. Это вынудило меня создать конструктор без параметров по умолчанию, который затем прерывает DI.

Почти начинает казаться, что мне нужно внедрить службы в ctor MainWindow, а затем передать их всему, что я вызываю оттуда. Для меня это не имеет смысла, так как в этот момент я могу также выбросить DI и просто обновить их, когда они мне понадобятся.


person Joe K    schedule 10.03.2020    source источник
comment
Вы создаете свою модель представления вручную и не предоставляете значения для аргументов конструктора, поэтому используются значения по умолчанию null. Что именно вам непонятно?   -  person dymanoid    schedule 10.03.2020
comment
@dymanoid, как мне тогда настроить это для использования DI? Я отредактировал вопрос, основываясь на том, что я пробовал из вашего комментария. Какие-либо предложения?   -  person Joe K    schedule 10.03.2020
comment
Вам действительно следует использовать инфраструктуру MVVM, большинство из которых имеют встроенный DI.   -  person mxmissile    schedule 10.03.2020
comment
Внедрение зависимостей @mxmissile — это функция .NET Core 3.1, поэтому это должно быть сделано изначально. Если бы я хотел использовать фреймворк или что-то в этом роде, я бы вернулся к использованию UnityContainers и реализации внедрения зависимостей таким образом, как я это делаю в приложениях с полным фреймворком. Цель состоит в том, чтобы использовать собственный DI для этого проекта. Спасибо хоть!   -  person Joe K    schedule 10.03.2020
comment
это означает, что вы должны зарегистрировать представления и модели представления — это зависит от используемой вами структуры и от того, полагаетесь ли вы на сопоставление зависимостей по соглашению, а не определяете его явно. Использование условного метода просто означает, что волшебство происходит в фоновом режиме.   -  person slugster    schedule 11.03.2020


Ответы (2)


Вам не хватает конфигурации для некоторых зависимостей. Из кода, который вы опубликовали, вы пропустили настройку IJMDataIntegration и IBalanceIntegrationContext:

protected override async void OnStartup(StartupEventArgs startupEventArgs)
{
  base.OnStartup(startupEventArgs);
  ServiceCollection services = new ServiceCollection();
  services.AddScoped<MainWindow>();
  services.AddScoped<ScaleInterfacePage>();
  services.AddScoped<IJMDataIntegration, JMDataIntegration>();
  services.AddScoped<IBalanceIntegrationContext, BalanceIntegrationContext>();
  services.AddScoped<IScale>(provider => new Scale("1234"));
  services.AddScoped<ScaleIntegrationViewModel>();

  ServiceProvider serviceProvider = services.BuildServiceProvider();
  MainWindow mainWindow = serviceProvider.GetService<MainWindow>();
  mainWindow.Show();

}

Кроме того, как уже упоминалось, вы также должны внедрить модель представления в MainWindow. Здесь начинается граф зависимостей, корень приложения:

partial class MainWindow : Window
{
  public MainWindow(ScaleIntegrationViewModel viewModel)
  {
    this.DataContext = viewModel;
  }
}

Чтобы включить полную мощь внедрения зависимостей (и упростить насмешки), вы должны использовать инверсию зависимостей во всем приложении. Это означает, что вы должны зависеть только от интерфейсов и, следовательно, иметь только типы интерфейсов в своих конструкторах:

partial class MainWindow : Window
{
  public MainWindow(IScaleIntegrationViewModel viewModel)
  {
    this.DataContext = viewModel;
  }
}

Элементы управления, такие как страницы, должны создаваться с помощью DataTemplate, а не создаваться непосредственно в XAML. Все, что вам нужно сделать, это внедрить модели просмотра страниц, например. в другую модель представления. Свяжите их с ContentPresenter и определите неявный DataTemplate, который нацелен на тип модели просмотра страницы. Этот шаблон содержит реальную страницу. См. этот пример.

Найдите шаблон View-Model-First, если вам нужна дополнительная информация. По сути, представление можно определить как шаблон данных и связать с типом модели представления. Шаблоны данных могут быть определены как ресурсы или встроены в элемент управления, который будет отображать модель представления. Содержимое элемента управления — это экземпляр модели представления, а шаблон данных используется для его визуального представления. Этот метод является примером ситуации, в которой сначала создается экземпляр модели представления, а затем создается представление.
Это предпочтительный способ, особенно в сочетании с внедрением зависимостей.

person BionicCode    schedule 11.03.2020
comment
Это помогает иметь немного больше смысла, по крайней мере, больше, чем другие, спасибо! Я не пропустил конфигурацию для контекстов данных, они настроены точно так же, как интерфейс IScale, я просто закомментировал их, пока работал над частью масштаба. Я думаю, что сейчас я просто оставлю модели просмотра с необязательными параметрами, чтобы я мог издеваться над интерфейсами, которые мне нужны, когда я тестирую. Основная причина, по которой я использовал DI, заключалась в том, чтобы немного упростить тестирование. Таким образом, мне не нужно менять всю свою парадигму для представлений. Спасибо еще раз! - person Joe K; 11.03.2020

Внедрение зависимостей означает, что вы внедряете зависимости, но не создаете их самостоятельно.

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

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

В любом случае, чтобы внедрить свою модель представления на страницу, просто введите параметр конструктора на своей странице:

public ScaleInterfacePage(ScaleIntegrationViewModel vm)
{
    InitializeComponent();
    DataContext = vm;
}

Вот и все, все готово.

person dymanoid    schedule 10.03.2020
comment
Моя страница не вводится в окно. Он объявлен в XAML с помощью тега ``` ‹views:ScaleInterfacePage/›```. Если я удалю конструктор страницы без параметров, он перестанет загружаться. Я предполагаю, что здесь моя разобщенность. В ASP.Net казалось, что все, что я зарегистрировал как сервис, я мог внедрить в конструктор. Кажется, это не работает в WPF, я думаю? Кажется, что нужна сложная цепочка зависимостей, я вызываю главное окно, затем ввожу в него страницу, затем ввожу в него модель представления и так далее и тому подобное. - person Joe K; 11.03.2020
comment
@JoeK, нет, DI работает не так - нет необходимости перенаправлять зависимости сверху вниз. Поскольку вы не показываете всю настройку, я не могу указать, где именно ваши проблемы. - person dymanoid; 11.03.2020
comment
@dyamanoid, что ты имеешь в виду, я не знаю всей настройки? Был ли у вас какой-то вопрос, на который я не ответил в ответе? - person Joe K; 11.03.2020