Dependency Injection to Page ViewModels in WPF - c#

I am creating a WPF application using .NET core 3.1
I have developed ASP.Net applications in the past and I was excited to use this in WPF. I did some searching and realized DI in WPF isn't as straightforward as it is in ASP.Net, meaning you have to register Views and ViewModels.
My structure is like this
MainWindow
|---BalanceIntegrationPage
|---BalanceIntegrationViewModel
Everything is handled in XAML with the MainWindow.xaml.cs having only generated code, and the BalanceIntegrationPage.xaml.cs has a single line added to it in the constructor
DataContext = new ScaleIntegrationViewModel();
That could not be handled in the xaml because DI requires parameters in the constructor.
Here is my 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();
}
My ScaleIntegrationViewModel looks like:
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";
}
I also attempted to use the pattern described here
When I step through the code, my IScale object in the ViewModel constructor is always null.
Any Suggestions??
edit:
Based off a comment, I removed the ViewModel call in the page constructor and instead assigned it on the .xaml
This forced me to create a default paramaterless constructor which then breaks DI.
It almost starts to seem like I need to inject the services into the MainWindow ctor and then pass them down to everything I call from there. Makes no sense to me, since at that point, I may as well throw away DI and just new them up when I need them.

You are missing the configuration for some dependencies. From the code you've posted, you missed to configure IJMDataIntegration and 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();
}
Also, as already mentioned, you have to inject the view model into the MainWindow too. This where the dependency graph starts, the root of the application:
partial class MainWindow : Window
{
public MainWindow(ScaleIntegrationViewModel viewModel)
{
this.DataContext = viewModel;
}
}
To enable the full power of Dependency Injection (and make mocking easier) you should make use of Dependency Inversion throughout the application. This means you should only depend on interfaces and therefore only have interface types in your constructors:
partial class MainWindow : Window
{
public MainWindow(IScaleIntegrationViewModel viewModel)
{
this.DataContext = viewModel;
}
}
Controls like pages should be generated via DataTemplate and not instantiated directly in XAML. All you need to do, is to inject the page view models e.g. into another view model. Bind them to a ContentPresenter and define an implicit DataTemplate which targets the type of the page view model. This template contains the actual page. See this example.
Search for View-Model-First pattern if you need more details. Basically, a view can be defined as a data template and associated with a view model type. Data templates can be defined as resources, or they can be defined inline within the control that will display the view model. The content of the control is the view model instance, and the data template is used to visually represent it. This technique is an example of a situation in which the view model is instantiated first, followed by the creation of the view.
This is the preferred way, especially in conjunction with Dependency Injection.

Dependency injection means that you inject the dependencies, but don't construct them by yourself.
In your example, you construct the viewmodel manually inside of your page. This is not DI. You have to inject an instance of the viewmodel into the page.
You don't show us how you inject the page into the main window. I am not sure that using DI to inject a page into a window is a good decision. This is UI, and you can describe everything without DI (by using the data templating and pure XAML).
Anyway, to inject your viewmodel into the page, just introduce a constructor parameter in your page:
public ScaleInterfacePage(ScaleIntegrationViewModel vm)
{
InitializeComponent();
DataContext = vm;
}
That's it, you're all set.

Related

DevExpress MVVM - Shared ViewModel & EventToCommand

I'm using DevExpress to add MVVM to my WinForms application. I have a form that contains a ViewModel, which is given to all of its user controls via constructor parameter.
DataBindings are working fine, but I want to add functionality to a ribbonbar by adding EventToCommand behaviour to the usercontrols toolbar item (BarButtonItem).
I do my bindings like this:
MVVMContext context = new MVVMContext();
context.ViewModelType = typeof(MyViewModel);
context.WithEvent<MyViewModel, EventArgs>(cmdA, "ItemClick")
.EventToCommand(x => x.SomeMethod());
context.WithEvent<MyViewModel, EventArgs>(cmdB, "ItemClick")
.EventToCommand(x => x.SomeOtherMethod());
context.SetViewModel(typeof(MyViewModel), viewModel);
viewModel is an instance of MyViewModel that contains the data and is received in the user controls constructor from the form.
However, when running the application, both buttons work but seem to bind to a separate instance of MyViewModel. How can I use the instance that I already have? Thanks!
Since you have passed the ViewModel instance from the external binding context you should set up your MVVMContext as follows:
// View(UserControl) side:
MVVMContext context = new MVVMContext();
// make sure that the MVVMContext will be destroyed when the UserControl destroyed
context.ContainerControl = this; // your View(UserControl)
context.SetViewModel(typeof(MyViewModel), viewModel);
This way prevent the automatic creation of the MyViewModel instance (the automatic creation is used when the context.ViewModelType is specified).
Then you can use the MVVMContext API as usual.
In your case, you can use the BindCommand instead of EventToCommand because of it specially designed to work with button-objects(like a BarButtonItems).
And, I strongly recommend you use the Fluent API which provides very clean and maintainable code:
var fluent = context.OfType<MyViewModel>();
fluent.BindCommand(cmdA, x => x.SomeMethod());
fluent.BindCommand(cmdB, x => x.SomeOtherMethod());

View model not resolved when using an interface with Xamarin Forms and Caliburn Micro

I am attempting to register an interface for a viewmodel, so that I can inject the interface into another view model. I have something similar to the following code in the app constructor in a shared .net standard class library in a Xamarin Forms app using Caliburn Micro.
public App(SimpleContainer container)
{
this._container = container;
_container.Singleton<ISetupViewModel, SetupViewModel>();
InitializeComponent();
DisplayRootView<SetupView>();
}
Using this will display the correct view is displayed, however the SetupViewModel doesn’t get created. But when I change the registration to not use the interface as below, then the SetupViewModel is created and resolved.
_container.Singleton<SetupViewModel>();
Is there any reason that I can't register a view model with an interface?

Automatic Resolving from Castle Winsdor

Im trying to seperate my Model,View and ViewModel in different assemblys and instantiate them with Castle Windsor.
I have my app.config
<components>
<component id="ViewModel.SomeViewModel" service="TEST.Business.IViewModel, TEST.Business" type="TEST.ViewModel.SomeViewModel, Test.ViewModel" />
<component id="ViewModel.SomeView" service="TEST.Business.IView, TEST.Business" type="TEST.View.SomeView, Test.View" />
</components>
and resolve it by
IoC.Configure();
var viewModel = IoC.Resolve<IViewModel>();
var view = IoC.Resolve<IView>();
view.ShowDialog();
my static IoC class
public static class IoC
{
private static IWindsorContainer container;
public static void Configure()
{
IResource resource = new ConfigResource("castle");
container = new WindsorContainer(new XmlInterpreter(resource));
}
public static TService Resolve<TService>()
{
return container.Resolve<TService>();
}
}
really simple until yet.
But i would love to do it like this:
naming have to be like this: I[someName]ViewModel and I[someName]View
and then resolve every component in my app.config thus for each pair of View and ViewModel resolve and associate them.
I guess there are many solutions for my problem but i dont know which keywords to use.
btw: I[someName]ViewModel and View are ofc IViewModels and IViews
I think you're doing it wrong.
Do not abstract your views and view models. It gives you no benefit. Therefore the problem is an architectural one, not a technical one.
Use reflection to iterate over the types in the assemblies you want to resolve. You can use Classes for registrations.
var assembly = Assembly.GetExecutingAssembly(); // Replace with the assembly you want to resolve for.
var exports = assembly.ExportedTypes;
var viewTypes = exports.Where(t => t.GetInterface(typeof(IView).FullName) != null);
foreach (var viewType in viewTypes)
{
var viewModelType = assembly.GetType(viewType.FullName.Replace("View", "ViewModel"));
var viewModel = container.Resolve(viewModelType);
var view = container.Resolve(viewType);
view.ShowDialog();
}
In your example, I can't see any dependency between IViewModel and IView so your code doesn't make sense. If the view model is injected as a parameter of the constructor it will be automatically resolved.
I would not recommend doing this technique. It is probably more complicated than it needs to be. Are you sure you really understand how to use an IoC container/Castle Windsor?
Ioc containers are great once you get used to them. In general you only want to use your container from your main/bootstrapping code of your application. To me it seems you try to make the resolve function for the container static to allow resolving compoents anywhere. This should not be required.
If you are looking for a method to bind views and view models in nice way, take a look at caliburn micro. You can combine this with most Ioc containers, including windsor
Kind regards,
Marwijn.

IoC - Constructor takes a runtime value as one parameter and a service as another

I have a WPF app which, when it starts, looks at the file system for some config files
For each config file it finds, it displays some info in a different window
Each window has an associated ViewModel object which is bound to the windows datacontext
So a new ViewModel is created for each config file. An object representing the data in the config file is passed into the viewmodels constructor
However, the View model also has other dependancies passed into the constructor
The code looks something like this (in a bootstrapper initiated from app.xaml)
foreach (WindowConfig config in ConfigManager.GetConfigs())
{
IMyService svc = new MyService();
//change to resolve from IoC container
MyViewModel vm = new MyViewModel(config, svc);
Window1 view = new Window1();
view.DataContext = vm;
window.show();
}
I want to use Castle IoC contaoiner resolve these dependancies. I know how to do that for IMyService, but how can I do it for the specific class that has been created from the config file ?
thanks
Always remember that in the application code, pulling from the container is never the solution. Application code should be unaware that there's a DI container in play.
The general solution when you need to resolve a dependency based on a run-time value is to use an Abstract Factory.
In your case, the factory might look like this (assuming that your config variables are strings:
public interface IViewModelFactory
{
IViewModel Create(string configuration);
}
Now you can inject the IViewModelFactory as a single dependency into the class that loops through the configuration files.
To implement IViewModelFactory you can either do it by hand or use Castle Windsor's Typed Factory Facility to implement it for you.
You can pass parameters to Windsor, that it should use when resolving the constructor, by using the overload of IWindsorContainer.Resolve that takes an IDictionary as a parameter. In this dictionary, the key should be the parameter name, and the value should be the object to use as the parameter value:
var arguments = new Dictionary<string,object> {{ "config", config }, { "service", svc } };
var viewModel = container.Resolve<MyViewModel>(arguments);

How View is resolved in Prism

I am newbie in using Prism. I have created a sample application to understand it. I have some queries on, how it is working.
Question 1:
public class HelloModuleCls : IModule
{
IRegionManager regionManager;
IUnityContainer container;
public void Initialize()
{
RegisterViewAndServices(); // Injecting occurs here
if (regionManager.Regions.ContainsRegionWithName("ViewHolder"))
{
regionManager.Regions["ViewHolder"].Add
(
***container.Resolve().View***
);
}
}
protected void RegisterViewAndServices()
{
//since angular brace is not appearing, I am using ( , )....
container.RegisterType(IHelloView, HelloViewUC)();
container.RegisterType(IHelloViewModel, EmployeeVM)();
}
}
public class EmployeeVM : INotifyPropertyChanged, IHelloViewModel
{
public EmployeeVM(IHelloView targetview)
{
}
}
Upon hitting the line, container.Resolve().View,
the ctor of the VM, gets executed with, view type got injected in the param "targetview".
1. So, what will happen behind the scene, when that line got a hit...?
Question 2:
Project Name:- HelloModule (Another Silvelight Class Library referred in the startup Silverlight Application)
public class HelloModuleCls : IModule
{
public HelloModuleCls(IRegionManager rm, IUnityContainer um)
{
regionManager = rm;
container = um;
}
}
(Shell.Xaml) (In Startup Silverlight Application)
<ContentControl Prism:RegionManager.RegionName="ViewHolder" Grid.Row="0">
</ContentControl>
This Module is in another project. I am having the Region in the startup project. I have seen that, in the ctor of the HelloModuleCls, the region got which is used in the Startup project, got injected perfectly...
2. So, how the prism is passing those regions..... Is it like, once the region got created, then it will be injected to all the available Modules ctor or some other concept.
May I kindly know, how this is working out, So I can able to understand more.
Thanks in advance.
Answer 1: it resolve the one view, normally you want to register views with some key to be able to differentiate them.
Answer 2: it's the other way round, the regions exist in the application and the modules use them. They need to somehow know the name of the region, of course.
Unsorted comments:
RegisterType<ISomeInterface, OneOfItsImplementations>() lives in the Microsoft.Practices.Unity namespace, so add using Microsoft.Practices.Unity; to "see the angular brace appearing"
use something like regionManager.RegisterViewWithRegion("MyRegion", typeof(AView)) to make the view automatically appear in the region
to switch views in a region, have a look at prism's navigation features, like RegisterTypeForNavigation and RequestNavigate
the view uses the viewmodel, not the other way round. The viewmodel normally doesn't know about the view.

Categories

Resources