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; }
}
Related
I have Login window and a Home window, inside the login class the Home is opened and the Login is closed:
Home home = new Home(user, _gitApiService);
home.Show();
Close();
Because the Home class relies on a dependency of IGitApiService, I am passing the dependency via the window class's constructor:
public partial class Home : Window
{
private readonly IGitApiService _gitApiService;
public Home(User user, IGitApiService gitApiService)
{
_gitApiService = gitApiService;
...etc
This seems like bad practice to me, is there any cleaner way of accessing/instaniating the IGitApiService?
(For context the GitApiService is just a class with api calls using HttpClient)
Assuming that there are only a few dependencies then such poor man's/pure DI isn't something really bad.
But if it is a common scenario and there are many dependencies, then by all means register a factory for the Home page (as user seems to be some domain object that can't be registered in CompositionRoot):
services.Register<Func<User, Home>>(context =>
user => new Home(user, context.Resolve<IGitApiService>());
or however explicitly or implicitly it is done in the DI framework used in the application.
Slight design change to Home window
public partial class Home : Window {
private readonly IGitApiService _gitApiService;
public Home(IGitApiService gitApiService) {
_gitApiService = gitApiService;
}
public User User { get; set; }
//...
}
I would have a window service responsible for showing a desired window
public interface IWindowService {
public void Show<TWindow>(Action<TWindow> configure = null) where TWindow : Window;
}
public class WindowService : IWindowService {
private readonly IServiceProvider services;
public WindowService(IServiceProvider services) {
this.services = services
}
public void Show<TWindow>(Action<TWindow> configure = null) where TWindow : Window {
var window = services.GetService<TWindow>();
if(configure != null) {
configure(window);
}
window.Show();
}
}
With that in place you inject your window service and use it like
windowSevie.Show<Home>(window => window.User = user);
Close();
Any explicit dependencies are injected when the window is resolved, and the configure delegate allows flexibility to populate any other members as needed
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.
I'm currently using WPF and .NET 4.5, my purpose is to build a desktop app that supports plugins using MVVM.
Using MEF, I can import plugins with
pluginLoader = new PluginLoader<IPlugin>(path);
IPlugin is the interface that expose the method that app itself should use, plus the view object that app dinamically shows on the main window.
public interface IPlugin
{
[...]
object View { get; }
}
Here's my doubts:
How can I use IoC with this architecture? I'm currently using TinyIOC and there's no reason to change it so far.
By the way, since I'm using MVVM, I need both viewmodel and view components.
For this purpose, I thought about exposing IPlugin so made:
public interface IPlugin
{
object View { get; }
object ViewModel { get; } // called after Init()
void Init(); // called after plugin resolution
}
public class MyPlugin : IPlugin
{
MyViewModel vm;
MyView view;
void Init()
{
// use TinyIOC to init service and resolve viewmodel
}
object ViewModel
{
get
{
return vm;
}
}
object View
{
get
{
return view;
}
}
}
What do you think about this? Can it works?
I am currently learning Catel+Orchestra using MahApps Metro.
I am doing the Authentication example from the Catel.Examples project using the MetroUI.
My problem is when i create a new MainWindow in my MahAppsService
public FrameworkElement GetMainView()
{
return new MainWindow();
}
The constructor of the MainWindowViewModel is never called
public MainWindowViewModel(UIVisualizerService uiVisualizarService, IAuthenticationProvider authenticationProvider)
{
_uiVisualizerService = uiVisualizarService;
_authenticationProvider = authenticationProvider;
RoleCollection = new ObservableCollection<string>(new[] { "Read-Only", "Administrator" });
ShowView = new Command(OnShowViewExecute, OnShowViewCanExecute, "ShowView");
}
I have narrowed it down to the 2 dependencies of the constructor. If i remove the UIVisualizerService and IAuthenticacionProvider dependencies the constructor is properly called but the ModelView needs those two services later on.
I am lost at what can i do to get this working.
You must register the IAuthenticationProvider in the ServiceLocator:
var serviceLocator = ServiceLocator.Default;
serviceLocator.RegisterType<IAuthenticationProvider, MyAuthenticationProvider>();
Note that all services inside Catel are automatically registered for you, but you must register your own services yourself (for example, by using ModuleInit or another entry point in your assembly).
I solved the problem by adding a explicit injection of the viewmodel into the mainwindow constructor.
public MainWindow(MainWindowViewModel _mainwindowviewmodel):base(_mainwindowviewmodel)
{
InitializeComponent();
}
Declaring the field for the AuthenticationProvider interface to the MahAppsService class.
private readonly IAuthenticationProvider _authenticationProvider;
Also adding the dependency of the AuthenticationProvider interface to the constructor.
public MahAppsService(ICommandManager commandManager, IMessageService messageService, IUIVisualizerService uiVisualizerService, IAuthenticationProvider authenticationProvicer)
{
Argument.IsNotNull(() => commandManager);
Argument.IsNotNull(() => messageService);
Argument.IsNotNull(() => uiVisualizerService);
Argument.IsNotNull(() => authenticationProvicer);
_commandManager = commandManager;
_messageService = messageService;
_uiVisualizerService = uiVisualizerService;
_authenticationProvider = authenticationProvicer;
}
And the last step is creating an instance of the viewmodel in the GetMainView in the MahAppsService class.
public FrameworkElement GetMainView()
{
var mainwindowViewModel = TypeFactory.Default.CreateInstanceWithParametersAndAutoCompletion<MainWindowViewModel>(_uiVisualizerService, _authenticationProvider);
return new MainWindow(mainwindowViewModel);
}
Please note that this might not be the best way to do it but it gets the work done. If someone has better way feel free to share it.
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();
}
}