Постепенно начал изучать IoC и всю эту кухню и вот не как не могу понять, как работать с ними в WPF приложение по правилам MVVM.
Допустим я делаю некий класс настроек контейнера (использую Autofac):
class ContainerConfig
{
public static IContainer Configure()
{
ISettings settings = new ConfigurationBuilder<ISettings>().UseJsonFile("Settings.json").Build();
var builder = new ContainerBuilder();
builder.RegisterType<MainViewModel>().SingleInstance();
builder.RegisterInstance(settings).SingleInstance();
return builder.Build();
}
}
В нем я регистрирую пока 2 объекта:
MainViewModel
- главная VM приложения, она я как понял должна быть в едином экземпляре.Далее переопределяю OnStartup
, для того, что бы создать окно, задать ему DataContext
и все это вывести:
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var container = ContainerConfig.Configure();
using var scope = container.BeginLifetimeScope();
var mainViewModel = scope.Resolve<MainViewModel>();
new MainWindow() { DataContext = mainViewModel }.Show();
}
}
Вроде пока я двигаюсь в правильном направление, или нет?
Ок, дальше для проверки работы я просто привяжу свойство из настроек, написав в MainViewModel
следующее:
class MainViewModel
{
public ISettings Settings { get; }
public MainViewModel(ISettings settingsModel)
{
Settings = settingsModel;
}
}
И сама привязка:
<TextBlock Text="{Binding Settings.SomeValue}"/>
Вроде все работает, все хорошо.
Теперь допустим мне надо сделать еще одну VM, которой например нужна главная VM, я делаю:
class SecondViewModel
{
private MainViewModel main;
public SecondViewModel(MainViewModel mainViewModel)
{
main = mainViewModel;
}
public int Test { get; set; } = 33;
private void SomeMethod()
{
main.SomeProperty = false;
}
}
Регистрирую его:
builder.RegisterType<SecondViewModel>().SingleInstance();
Ну и дописываю в MainViewModel
новую VM:
class MainViewModel
{
public ISettings Settings { get; }
public SecondViewModel Second { get; }
public MainViewModel(ISettings settingsModel, SecondViewModel second)
{
Settings = settingsModel;
Second = second;
}
}
В итоге получаю ошибку зацикленности и тут явно понимаю, что делаю что-то не так.
Немного поискав информацию, нашел способ обхода.
Короче как видите, я не совсем до конца понимаю как все это должно работать и возникает куча вопросов, например:
В общем, помогите разобраться, как все-же правильно реализовывать IoC в WPF приложение, да еще и с MVVM?
DI контейнер - это инструмент, который делает за вас работу, которую вы и сами можете сделать, не нарушая никаких принципов.
Как вы знаете, контейнер - это такая вещь, которая создает обекты и следит за их временем жизни за вас.
Вы в своем коде создали 2 класса с цикличной зависимостью в конструкторе, что делает невозможным создание таких классов (если только в конструкторы NULL не передавать). Попробуйте создать ваши классы вручную и вы увидите, что у вас ничего не получится, так как, чтобы создать класс А, вам надо предоставить ему класс Б, а чтобы создать класс Б - вам надо предоставить класс А. Этот цикл и называется циклической зависимостью и его не так и просто побороть.
Но, как я сказал, побороть его не просто, но можно, например если определить фабрику для классов и передавать уже фабрику в конструктор. И то, это сработает, только если вы не попытаетесь обратиться к фабрике прямо внутри конструктора.
Например
class A
{
IBFactory _bfactory;
public A(IBFactory bfactory)
{
_bfactory = bfactory;
}
void Foo()
{
var b = _bfactory.GetB();
// do stuff
}
}
class B
{
A _a;
public B(A a)
{
_a = a;
}
}
interface IBFactory{
B GetB();
}
Реализация IBFactory может быть самой простой
class BFactory : IBFactory
{
IContainer _container;
public BFactory(IContainer container)
{
_container = container;
}
public B GetB(){
return _container.Resolve<B>();
}
}
Некоторые контейнеры, если мне память не изменяет, сами по себе поддерживают создание подобных фабрик. Ленивые же программисты в таком случае, вместо фабрики, пробрасывают просто весь контейнер в А класс, что черевато и является анти паттерном - service loсator.
Однако, также хочется сказать, что такие ситуации, когда подчиненная модель хочет знать о главной, в идеале, не должны существовать. Старайтесть проектировать такие VM, которые были бы максимально самодостаточные. В случае, если должна происходить комуникация между VM, то в общем случае для этого есть шина сообщений, или вы можете пробрасывать какие то свои интерфейсы, но вообще такая коммуникация должна быть ограничена. Чем меньше VM общаются друг с другом, тем меньше у вас головной боли при отладке.
Теперь давайте пойдем по вопросам:
Правильно я сделал выше?
Нет ничего правильного в нашей профессии, но я бы решил вашу задачу иначе.
class SecondViewModel
{
public SecondViewModel()
{
}
event EventHandler<MyEventArgs> SomethingHappened;
private void OnSomethingHappened(...) => SomethingHappened?.Invoke(....);
///......
private void SomeMethod()
{
OnSomethingHappened(...);
}
}
class MainViewModel
{
public ISettings Settings { get; }
public SecondViewModel Second { get; }
public MainViewModel(ISettings settingsModel, SecondViewModel second)
{
Settings = settingsModel;
Second = second;
Second.SomethingHappened += Second_SomethingHappened;
}
private void Second_SomethingHappened(object sender, MyEventArgs args)
{
this.SomeProperty = false;
}
}
Таким образом, вторая модель знать не знает ни о какой главной модели, а логика реакции главной модели на подчиненные модели хранится в одном месте - в главной модели.
Что должно регистрироваться в контейнере?
Всё, что вы планируете использовать. Ваша проблема не в контейнере, а в орагнизации ваших классов - в циклической зависимости.
Необходимы-ли для VM слоев интерфейсы?
Зависит от вашей органищации классов. Когда то это имеет смысл, когда то - не имеет (чаще не имеет). В вашей ситуации интерфейсы вам не помогут.
Как не нарушить MVVM?
Вы оперируете здесь только с VM слоем, потому как бы вы этот слой не сделали, сам по себе он не нарушает MVVM.
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Окно WPF запускается с WindowState="Maximized"Если сразу после запуска посмотреть Left или Top окна то они будут равны -8
Нужно ли вообще корректировать верстку так, чтоб при масштабировании нигде ничего не выпирало и тд? Вот как выглядит отверстанная часть...
Есть ли разница в производительности между тегами? Например у меня (образно) 10000 слов данных, каждое слово нужно обернуть в тег Влияет ли выбор...