I'm developing my own application to learn something new. It's WPF .net Core 3.1 app using MVVM pattern.
Recently I've decided to include Microsoft DependencyInjection library.
I've removed StartupUri and modifiec app.xaml.cs:
public IServiceProvider ServiceProvider { get; private set; }
public IConfiguration Configuration { get; private set; }
protected override void OnStartup(StartupEventArgs e)
{
AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve;
InitializeCefSharp();
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
Configuration = builder.Build();
var serviceCollection = new ServiceCollection();
ConfigureServices(serviceCollection);
ServiceProvider = serviceCollection.BuildServiceProvider();
var mainWindow = ServiceProvider.GetRequiredService<MainWindow>();
mainWindow.Show();
}
private void ConfigureServices(IServiceCollection services)
{
services.Configure<AppSettings>
(Configuration.GetSection(nameof(AppSettings)));
// Views
services.AddSingleton<MainWindow>();
services.AddSingleton<SettingsView>();
services.AddSingleton<WwwView>();
services.AddSingleton<BuildingView>();
services.AddSingleton<TroopsMovementsView>();
// ViewModels
services.AddSingleton<MainWindowViewModel>();
services.AddSingleton<SettingsViewModel>();
services.AddSingleton<WwwViewModel>();
services.AddSingleton<DomainViewModel>();
services.AddSingleton<WorldViewModel>();
services.AddSingleton<RegisterViewModel>();
}
I'm setting DataContext inside Views' constructors. All views except MainWindow are UserControl type.
private readonly MainWindowViewModel _mainWindowViewModel;
public MainWindow(MainWindowViewModel mainWindowViewModel)
{
_mainWindowViewModel = mainWindowViewModel;
DataContext = _mainWindowViewModel;
InitializeComponent();
}
private readonly SettingsViewModel _settingsViewModel;
public SettingsView(SettingsViewModel settingsViewModel)
{
_settingsViewModel = settingsViewModel;
DataContext = settingsViewModel;
InitializeComponent();
}
All Views are embedded in MainWindow like this:
<dz:DockControl Grid.Row="3" Loaded="FrameworkElement_OnLoaded">
<dz:DockItem x:Name="Settings" TabText="Settings" ShowAction="{dz:ShowAsDockPositionAction DockPosition=RightAutoHide}">
<views:SettingsView/>
</dz:DockItem>
<dz:DockItem x:Name="WWW" TabText="WWW" ShowAction="{dz:ShowAsDockPositionAction DockPosition=Document}" DefaultDockPosition="Document" >
<views:WwwView/>
</dz:DockItem>
</dz:DockControl>
It's visual studio-like docking library.
The problem is that I got exception during startup, that there's no parameterless constructor. But I can not have any parameterless constructors as I need Views to get ViewModels injected. ViewModels to get repositories injected.
When I've created 2nd ocnstructor which is parameterless, there's strange things happening - Views doesn't load inside MainWindow or load, but doesn't use ViewModels.
If I'm setting DataContext in xaml file, Views got parameterless constructors, but then ViewModels must have parameterless constructors...
<UserControl.DataContext>
<vm:SettingsViewModel/>
</UserControl.DataContext>
How to correctly use Dependency injection in my case?
WPF requires objects which are instantiated in XAML to define a parameterless constructor.
There are many ways to accomplish Dependency Injection in WPF using MVVM. When searching the internet the most wide spread solution seems to be the ViewModelLocator, another Service Locator implementation which is considered widely an anti-pattern (like the infamous static Singleton IoC container).
A simple solution is to use composition. You create a main view model which is composed of other view models where each is dedicated to a certain view.
MainViewModel.cs
class MainViewModel
{
public MainViewModel(IFirstControlViewModel firstControlViewModel ,
ISecondControlViewModel secondControlViewModel)
{
this.FirstControlViewModel = firstControlViewModel;
this.SecondControlViewModel = secondControlViewModel;
}
public IFirstControlViewModel FirstControlViewModel { get; }
public ISecondControlViewModel SecondViewModel { get; }
}
FirstViewModel.cs
class FirstViewModel : IFirstViewModel
{
}
SecondViewModel.cs
class SecondViewModel : ISecondViewModel
{
public SecondVieModel(IThirdViewModel thirdViewModel) => this.ThirdViewModel = thirdViewModel;
public IThirdViewModel ThirdViewModel { get; }
}
MainWindow.xaml
<Window>
<StackPanel>
<FirstUserControl DataContext="{Binding FirstViewModel}" />
<SecondUserControl DataCOntext="{Binding SecondViewModel}" />
</StackPanel>
</Window>
SecondUserControlxaml
<UserControl>
<Grid>
<ThirdUserControl DataContext="{Binding ThirdViewModel}" />
</Grid>
</UserControl>
App.xaml.cs
private void Run(StartupEventArgs e)
{
IMainViewModel viewModel = container.GetExportedValue<IMainViewModel>();
var mainWindow = new MainWindow { DataContext = viewModel };
mainWindow.Show();
}
Or use only top-level composition:
MainViewModel.cs
class MainViewModel
{
public MainViewModel(IFirstControlViewModel firstControlViewModel ,
ISecondControlViewModel secondControlViewModel,
IThirdViewModel thirdViewModel)
{
this.FirstControlViewModel = firstControlViewModel;
this.SecondControlViewModel = secondControlViewModel;
this.ThirdViewModel = thirdViewModel;
}
public IFirstControlViewModel FirstControlViewModel { get; }
public ISecondControlViewModel SecondViewModel { get; }
public IThirdViewModel ThirdViewModel { get; }
}
App.xaml.cs
private void Run(StartupEventArgs e)
{
IMainViewModel viewModel = container.GetExportedValue<IMainViewModel>();
// For simplicity, you can add the view model to the globally accessible App.xaml ResourceDictionary
this.Resources.Add("MainViewModel", viewModel);
var mainWindow = new MainWindow { DataContext = viewModel };
mainWindow.Show();
}
SecondUserControlxaml
<UserControl>
<Grid>
<ThirdUserControl DataContext="{Binding Source="{StaticResource MainViewModel}", Path=ThirdViewModel}" />
</Grid>
</UserControl>
Composition is a very simple solution to use Dependency Injection with views. If performance e.g. classes with a big dependency tree is an issue many DI frameworks like MEF support Lazy<T> exports.
Related
I have recently parameterized my xViewModel's constructor.
Before that, I was doing this in my window:
<Window.DataContext>
<vm:xViewModel />
</Window.DataContext>
Now I have two parameters :
using Prism.Services.Dialogs;
using Prism.Regions;
.
.
.
protected readonly IDialogService _dialogService;
private readonly IRegionManager _regionManager;
public xViewModel(
IDialogService dialogService,
IRegionManager regionManager)
{
_dialogService = dialogService;
_regionManager = regionManager;
}
How can I instantiate DataContext with my xViewModel?
You can use the ViewModelLocator to wire the DataContext of a view to an instance of a view model using a standard naming convention:
<Window x:Class="Demo.Views.MainWindow"
...
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True">
There are multiple ways to do this in without or with Prism.
You can instantiate all classes in the code-behind constructor of the Window, pass the dependencies as parameters to the window when it is instantiated or assign the view model from outside to the DataContext property of the window.
public MyWindow()
{
InitializeComponent();
// ...instantiate the dialog service and region manager
DataContext = xViewModel(dialogService, regionManager);
}
Prism can make this a lot easier, if you use one of its supported dependency injection containers. Then you can use the container to resolve an instance of your view model and it will automatically inject all dependencies like IDialogService and IRegionManager into its constructor.
In the code-behind contructor, resolve an instance of your xViewModel using IContainerExtension
public MyWindow(IContainerExtension containerExtension)
{
InitializeComponent();
DataContext = containerExtension.Resolve<xViewModel>();
}
If you use the built-in ViewModelLocator, Prism will automatically resolve an instance of the view model for a view and assign it as DataContext if you follow its naming convention. All you have to do is set the AutoWireViewModel attached property to True in you view.
<Window x:Class="Demo.Views.MainWindow"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True">
Read more about the ViewModelLocator, customization and examples in the documentation.
Set it in code behind instead of XAML:
public MainWindow()
{
InitializeComponent();
// add code to initialize the dialogService and regionManager variables
DataContext = new xViewModel(dialogService, regionManager);
}
I have my MainView and an associated MainViewViewModel which are linked by ViewModelLocator.
Within MainViewViewModel there is a command which should trigger a new Window to open which has it's own View and ViewModel (NewView and NewViewViewModel).
In a lot of the examples I've seen it is suggested to use Mvvmlight's Messenger to do something like this:
public class MainViewViewModel
{
private void OpenNewWindow()
{
Messenger.Default.Send(new NotificationMessage("NewView"));
}
}
And then register the NewViewViewModel and handle the message like this:
public class NewViewViewModel
{
public NewViewViewModel()
{
Messenger.Default.Register<NotificationMessage>(this, NotificationMessageReceived);
}
private void NotificationMessageReceived(NotificationMessage obj)
{
if (obj.Notification == "NewView")
{
NewView view = new NewView();
view.Show();
}
}
}
However, this doesn't work because the NewViewViewModel isn't yet instantiated (so isn't registered with Messenger). Additionally, this doesn't fit with MVVM because NewViewViewModel is responsible for creating NewView.
What is the correct way to achieve a simple command which instantiates and opens a new View and ViewModel pair which are linked via ViewModelLocator and setting of DataContext="{Binding NewView, Source={StaticResource Locator}}" in NewView.xml?
Use a window service:
MVVM show new window from VM when seperated projects
You may either inject the view model to with an IWindowService implementation or use a static WindowService class:
public static class WindowService
{
public static void OpenWindow()
{
NewView view = new NewView();
view.Show();
}
}
Dependency injection is obviously preferable for being able to unit test the view model(s) and switch implementations of IWindowService at runtime.
This question already has an answer here:
Why my Subscribe method is not called when using Prism EventAggregator?
(1 answer)
Closed 3 years ago.
In my solution I am using Autofac, and Prism as well. Below is a simplified project that explains what happens.
I am registering my views, view models and EventAggregator in Autofac's container class like that:
public class BootStrapper
{
public IContainer BootStrap()
{
var builder = new ContainerBuilder();
builder.RegisterType<EventAggregator>()
.As<IEventAggregator>().SingleInstance();
builder.RegisterType<MainWindow>().AsSelf();
builder.RegisterType<ChildView1>().AsSelf();
builder.RegisterType<MainViewModel>().AsSelf();
builder.RegisterType<Child1ViewModel>().AsSelf();
return builder.Build();
}
}
Note, that when registering view models as a singletons, effect was the same. I am injecting EventAggregator into my VM like that:
public class MainViewModel
{
private IEventAggregator _eventAggregator;
public MainViewModel(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
UpdateName("Name1");
}
public void UpdateName(string name)
{
ChildView1 win1 = new ChildView1(); //in the backend Child1ViewModel is assigend to its DataContext
win1.Show();
_eventAggregator.GetEvent<UpdateNameEvent>().Publish(name); //this does not work
}
}
Code above does not work. Because of some reason (I hope that you will tell me why), when executing UpdateName method, this dependency does not work, and inside of Child1ViewModel class UpdateName method is not executed:
public class Child1ViewModel : ViewModelBase
{
private IEventAggregator _eventAggregator;
public Child1ViewModel(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
_eventAggregator.GetEvent<UpdateNameEvent>().Subscribe(UpdateName);
}
private string _name;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
OnPropertyChanged();
}
}
private void UpdateName(string name)
{
this.Name = name; //debug does not hit this code line
}
}
Constructor of Child1ViewModel is hiten during debug, just UpdateName is not executed. BUT, if I use direct call of the EventAggregator, like this:
Utility.EventAggregator.GetEvent<UpdateNameEvent>().Subscribe(UpdateName);
or this:
Utility.EventAggregator.GetEvent<UpdateNameEvent>().Publish(name);
it works! When assuming, that Utility class looks like:
public class Utility
{
public static EventAggregator EventAggregator { get; set; }
static Utility()
{
EventAggregator = new EventAggregator();
}
}
I suspect, that there is some problem with registering the aggregator in Autofac, but I have no idea what is the problem, I just used it as per odl exaples I found.
Resolving Child1ViewModel and MainViewModel:
public partial class ChildView1 : Window
{
public ChildView1()
{
var bootStrapper = new BootStrapper();
var container = bootStrapper.BootStrap();
Child1ViewModel vm = container.Resolve<Child1ViewModel>();
InitializeComponent();
DataContext = vm;
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
var bootStrapper = new BootStrapper();
var container = bootStrapper.BootStrap();
MainViewModel vm = container.Resolve<MainViewModel>();
InitializeComponent();
DataContext = vm;
}
}
I'm assuming you are using Prism's ViewModelLocator mechanism. My assumption is based on that you are manually instantiating ChildView1 rather than resolving it with dependency injection container and state in the comment that viewModel is resolved in the background.
You must change the container of Prism ViewModelLocator used for resolving the ViewModel instances as shown below.
ViewModelLocationProvider.SetDefaultViewModelFactory(viewModelType) =>
{
return YourAutofacContainer.Resolve<viewModelType>();
});
For further information see Control how ViewModels are Resolved.
Keep in mind that for keeping single instance lifetime scope of object instances consistent throughout your whole application, you must use the same dependency injection container instance for resolving objects and this objects' parent objects all the way up to the root object.
Trying to inject dependencies to usercontrols in WinForms. As the instantiation of controls is generated by designer, only setter injection seems to be possible.
My objective is to directly reference DI container only at the form level:
public void Form(StructureMap.Container container)
{
InitializeComponent();
container.BuildUp(this); // this should also traverse Controls and their subcontrols
}
The problem is how to force DI container to traverse the Controls collection hierarchically and inject dependencies. Is this possible with any DI container? Trying with StructureMap and so far no luck:
Container container = new Container(delegate (ConfigurationExpression e)
{
InjectedClass c = new InjectedClass();
e.Policies.SetAllProperties(delegate (StructureMap.Configuration.DSL.SetterConvention x)
{
x.OfType<InjectedClass>();
});
e.For<InjectedClass>().Use(c);
});
Form form1 = new Form(container);
// here the form.Controls[0].Controls[0].MyInjectedClass has no instance
StructureMap seems to stop at the first level (injecting to Form.MyInjectedClass is working)
I Couldn't do it either.
I used structuremap with setter injection and called ObjectFactory.BuildUp in every control.
Define a CustomInjectAttribute
public class CustomInjectAttribute : Attribute
{
}
Define a Registry
public class MyRegistry : Registry
{
public MyRegistry()
{
this.Policies.SetAllProperties(by => by.Matching(prop => prop.HasAttribute<CustomInjectAttribute>()));
}
}
Then call BuildUp on Control's constructor:
public partial class MyControl : UserControl
{
public MyControl()
{
this.InitializeComponent();
ObjectFactory.BuildUp(this);
}
[CustomInject, Browsable(false)]
public IInjectable Injectable { protected get; set; }
}
Im pretty new to modern ui framework. I'm adding new page(usercontroller) as ContentSource page.
Im using IOC framework (IviewModels and ViewModels). I'm getting error saying no maching constructor found. because usercontroll default constructor injected with Iviewmodel object.
i'm pretty stuck here, it would be great some one can help this matter
thanks
this is my main window code + this is my usercontroll cs file
this is the error
As you found out, you can't use parameterized constructors because they break the framework.
Navigation use just the page URI, no other extra parameters.
So, how do you use IoC without parameterized constructors?
You should use a Dependency Injection Container.
Something like this:
public partial class MyPage: UserControl
{
private MyViewModel: IViewModel;
public MyPage()
{
MyViewModel = MyViewModelFactory.Create(IViewModel);
InitializeComponent();
}
}
MyVewModelFactory is an object which create other objects.
You dont have to code it by yourself.
Some common IoC containers are:
Unity
MEF
Using Unity your code would be:
public partial class MyPage: UserControl
{
private MyViewModel: IViewModel;
public MyPage()
{
MyViewModel = container.Resove<IViewModel>();
InitializeComponent();
}
}
Using MEF your code would be:
public partial class MyPage: UserControl
{
[Import(GetType(IViewModel))]
private MyViewModel: IViewModel;
public MyPage()
{
InitializeComponent();
}
}