I'm trying to pass a service to a component constructor in MAUI:
public partial class MyComponent: ContentView
{
public MyComponent(MyService service)
{
InitializeComponent();
data = service.getData();
}
}
But it throws error and asks to add a default constructor:
public partial class MyComponent: ContentView
{
public MyComponent() { }
public MyComponent(MyService service)
{
InitializeComponent();
data = service.getData();
}
}
I added singletons to pass the service to the component:
builder.Services.AddSingleton<MyComponent>();
builder.Services.AddSingleton<MyService>();
I also tried this method:
builder.Services.AddSingleton(sp => new MyComponent(sp.GetService<MyService>()));
None of them worked. Only default constructor called
I use the component in a page like this:
<ContentPage ...
xmlns:components="clr-namespace:MyApp.Components">
<components:MyComponent/>
</ContentPage>
How do I pass a service to a component?
You can also use dependency injection like in there.
Create a ServiceProvider class :
public static class ServiceProvider
{
public static TService GetService<TService>()
=> Current.GetService<TService>();
public static IServiceProvider Current
=>
#if WINDOWS10_0_17763_0_OR_GREATER
MauiWinUIApplication.Current.Services;
#elif ANDROID
MauiApplication.Current.Services;
#elif IOS || MACCATALYST
MauiUIApplicationDelegate.Current.Services;
#else
null;
#endif
}
Then you can simply use it in any component constructor :
_Contexte = ServiceHelper.GetService<Contexte>();
As for why you can't pass parameter to XAML components it's because they are converted to C# code and then automatically process. If the constructor has argument the mechanism handling the conversion wont know which instance to pass to the constructor (unless microsoft decide to handle DI, which may happend at some point).
You could not directly use dependency injection in a custom control.
See this issue on Github: Dependency Injection for Custom Controls. It's still enhancement under consideration and you could follow this issue.
There should be some workarounds. Just as tripjump comment in above issue, you could attach a bindable property and inject the viewModel from MainPage through this property. I just made a small demo for you.
For MyComponent control:
public partial class MyComponent : ContentView
{
public static readonly BindableProperty ServiceProperty = BindableProperty.Create(nameof(Service),typeof(MyService), typeof(MyComponent),propertyChanged: OnServiceChanged);
static void OnServiceChanged(BindableObject bindable, object oldValue, object newValue)
{
// Property changed implementation goes here
MyService a = newValue as MyService;
}
public MyService Service
{
get => (MyService)GetValue(MyComponent.ServiceProperty);
set => SetValue(MyComponent.ServiceProperty, value);
}
public MyComponent()
{
InitializeComponent();
}
}
For MainPage which consumes MyComponent:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
...
x:Name="this">
<components:MyComponent BindingContext="{x:Reference this}" MyService="{Binding BindingContext.service}"/>
For MainPageViewModel which is BindingContext of MainPage:
public class MainPageViewModel
{
public MyService service { get; set; }
public MainPageViewModel(MyService myService)
{
service = myService;
}
}
And also add service in MauiProgram:
builder.Services.AddSingleton<MainPage>();
builder.Services.AddSingleton<MainPageViewModel>();
builder.Services.AddSingleton<MyService>();
Hope it works for you.
Related
I use unity container in my WinForms application and register interfaces and classes. but when open other forms it's not working for fetching data. It's just working for form1.
How to resolve all forms?
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
RegisterType(new UnityContainer());
}
public static void RegisterType(IUnityContainer container)
{
container.RegisterType<IBlogRepository, BlogRepository>();
container.RegisterType<IPostRepository, PostRepository>();
Application.Run(container.Resolve<Form1>());
}
}
This is constructor injection in form1():
public partial class Form1: Form
{
public Form1()
{
InitializeComponent();
}
private readonly IBlogRepository _blogRepository;
public Form1(IBlogRepository blogRepository) : this()
{
_blogRepository = blogRepository;
}
}
and this is constructor injection in formAddUpdate():
public partial class FormAddUpdate : Form
{
public FormAddUpdate()
{
InitializeComponent();
}
private readonly IBlogRepository _blogRepository;
private readonly IPostRepository _postRepository;
public FormAddUpdate(IBlogRepository blogRepository, IPostRepository postRepository) : this()
{
_blogRepository = blogRepository;
_postRepository = postRepository;
}
}
now when the run application I can retrieve data from from1 but when switch to add/update form, it returns error: {"Object reference not set to an instance of an object."}
How to resolve all forms in my application?
If you look at your code, you resolve exactly one type (Form1) from your container. After that, poor container dies.
RegisterType(new UnityContainer());
}
public static void RegisterType(IUnityContainer container)
{
container.RegisterType<IBlogRepository, BlogRepository>();
container.RegisterType<IPostRepository, PostRepository>();
Application.Run(container.Resolve<Form1>());
}
When you create another container somewhere else to resolve, say, Form2 it knows nothing of the registrations made with the first container, so it can resolve neither BlogRepository nor PostRepository.
So the solution is to keep the one and only container around, the one that you do all registrations with. And use that one to do all your resolving, preferentially not by passing the container around or referencing a static service locator, but instead resolve just one root object and inject factories that do all the resolving needed.
I am having to re-write a large WinForms application and I want to use MVC to allow increased testing capability etc. I want to also adopt Ninject as my IoC container as it is lightweight, fast and will increase the exstensibility of my application going forward.
I have done a great deal of reading and I have managed to make a start on the arcitecture of this new application. However, I am not sure i have the right idea when using Ninject. The code...
Starting with Program.cs and related classes...
static class Program
{
[STAThread]
static void Main()
{
FileLogHandler fileLogHandler = new FileLogHandler(Utils.GetLogFilePath());
Log.LogHandler = fileLogHandler;
Log.Trace("Program.Main(): Logging initialized");
CompositionRoot.Initialize(new ApplicationModule());
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(CompositionRoot.Resolve<ApplicationShellView>());
}
}
public class CompositionRoot
{
private static IKernel _ninjectKernel;
public static void Initialize(INinjectModule module)
{
_ninjectKernel = new StandardKernel(module);
}
public static T Resolve<T>()
{
return _ninjectKernel.Get<T>();
}
}
public class ApplicationModule : NinjectModule
{
public override void Load()
{
Bind(typeof(IApplicationShellView)).To(typeof(ApplicationShellView));
}
}
An my ApplicationShellView is
public partial class ApplicationShellView : Form, IApplicationShellView
{
public ApplicationShellView()
{
InitializeComponent();
}
public void InitializeView()
{
dockPanel.Theme = vS2012LightTheme;
}
}
with interface
public interface IApplicationShellView
{
void InitializeView();
}
The controller for this view is
public class ApplicationShellController
{
private IApplicationShellView view;
public ApplicationShellController(IApplicationShellView view)
{
view.InitializeView();
}
}
Currently the controller is redundant, and although this code works and my view displays, I have some important questions...
Should I be using the ApplicationShellController to initialize my form, currently this is not using MVC "pattern"?
It feels like I have written a Service Locator, and from what I have read, this is bad. How else should I be using Ninject for IoC to initialize my application?
Any other advice as to what I am doing right[if anything!]/wrong?
Thanks very much for your time.
No you should not be initializing your controller, this exactly what IoC and Ninject are for. When you initialize your view/form, Ninject should make the view fetch the controller it depends on, which will auto fetch controllers it depends on and so on.
Of course this won't work like you've set it up right now. For starters, your view needs to know the controller it depends on.
public partial class ApplicationShellView : Form, IApplicationShellView
{
private IApplicationShellController _controller;
public ApplicationShellView()
{
InitializeComponent();
init();
//InitializeView()
}
private void init() {
_controller = NinjectProgram.Kernel.Get<IApplicationShellController>();
//Because your view knows the controller you can always pass himself as parameter or even use setter to inject
//For example: _controller.SetView1(this);
}
public void InitializeView()
{
dockPanel.Theme = vS2012LightTheme;
}
}
public class ApplicationShellController : IApplicationShellController
{
//Implementes functionality for the MainForm.
public ApplicationShellController()
{
//Also possible to add other controllers with DI
}
}
This does indeed look like a Service Locator, simply initializing your view should do be sufficient.
public class NinjectProgram
{
//Gets the inject kernal for the program.
public static IKernel Kernel { get; protected set; }
}
public class Program : NinjectProgram
{
[STAThread]
private static void Main()
{
Kernel = new StandardKernel();
Kernel.Load(new ApplicationModule());
Application.Run(new ApplicationShellView());
}
}
public class ApplicationModule : NinjectModule
{
public override void Load()
{
//Here is where we define what implementations map to what interfaces.
Bind<IApplicationShellController>().To<ApplicationShellController>();
//We can also load other modules this project depends on.
Kernel.Load(new NinjectModule());
}
}
Don't try and make it too complicated, a good start is important but you can always apply changes when and where needed during development.
I believe the following GitHub project might be a good starting point: Example of how you might use Ninject within a WinForms application.
If you have any more questions, just leave a comment and I'll try to answer them as soon as possible
I'm using ninject's kernel as a viewmodel locator in a WPF application.
The kernel helper class:
public static class IoCKernel
{
private static IKernel kernel;
public static void Init(params NinjectModule[] modules)
{
if (kernel == null)
{
kernel = new StandardKernel(modules);
}
}
public static T Get<T>()
{
return kernel.Get<T>();
}
}
And the ViewModelLocator exposes the Get method like:
public class ViewModelLocator : IViewModelLocator
{
public MainWindowViewModel MainWindowViewModel
{
get
{
return IoCKernel.Get<MainWindowViewModel>();
}
}
}
And when the instance is needed, it's called like:
IoCKernel.Get<IViewModelLocator>().MainWindowViewModel;
However, the IoCKernel.Get<MainWindowViewModel>() will always return a new instance. Is there a way to make it only work with one instance?
If you set up a binding in Ninject, you can call the InSingletonScope method:
Bind<IYourInterface>().To<YourClass>().InSingletonScope();
In your case (you do not have an interface for the view model) it might be:
Bind<MainWindowViewModel>().ToSelf().InSingletonScope();
See here for more info : Object Scopes in Ninject
Using MVVM Light in a WPF MVVM application.
I want to use Ninject instead of SimpleIOC.
Even in a brand new WPF/MVVM Light v4 project, I get a null reference for MainViewModel when the Main Property in the ViewModelLocator is called by the XAML.
private static readonly StandardKernel kernel;
static ViewModelLocator()
{
if (ViewModelBase.IsInDesignModeStatic)
{
}
else
{
kernel = new StandardKernel(new mymodule());
}
}
public MainViewModel Main
{
get { return kernel.Get<MainViewModel>(); }
}
MyModule looks like this:
public class mymodule:NinjectModule
{
public override void Load()
{
Bind<MainViewModel>().ToSelf();
}
}
I've also tried
public class mymodule:NinjectModule
{
public override void Load()
{
Bind<MainViewModel>().To<MainViewModel();
}
}
Ninject kernel's .Get<T> does not return null.
Except in case you explicitly tell it to by doing:
Bind<T>().ToConstant(null);
Bind<T>().ToMethod(x => null);
Bind<T>().ToProvider<TProvider>() --> and TProvider.Create(...) returns null
It's very unlikely you have any of these.
So if there's a NullReferenceException when accessing the Main property, it must be because private static readonly StandardKernel kernel is null.
Now if the code you've provided us is a Minimal, Complete, and Verifiable example, that means ViewModelBase.IsInDesignModeStatic returns true.
In my NinjectWebCommon.cs file, under the RegisterServices method I have the following:
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<IProfileRepository>().To<ProfileRepository>();
kernel.Bind<IMatchUpService>().To<MatchUpService>();
kernel.Bind<ISoloUserRepository>().To<SoloUserRepository>();
SignalR.GlobalHost.DependencyResolver = new SignalR.Ninject.NinjectDependencyResolver(kernel);
}
I am trying to inject SoloUserRepository into my hub class, here is my hub class:
public class MatchMaker : Hub
{
[Inject]
private ISoloUserRepository soloUsers { get; set; }
}
For some reason when I try to use the soloUsers object in my Hub class, i get object reference not set to instance of an object because the soloUsers object is never being instantiated or, in other words, not injected. Am I doing something wrong?
Your problem is that you have a private property, and Ninject by default doesn't inject private properties.
So either you make your property public or you can enable non public property injection with:
kernel.Settings.InjectNonPublic = true;
I'm not familiar with the dependency injection in SignalR (so maybe it's not supported) but you should always prefer constructor injection so your Hub should be like:
public class MatchMaker : Hub
{
private readonly ISoloUserRepository soloUsers;
public MatchMaker(ISoloUserRepository soloUsers)
{
this.soloUsers = soloUsers;
}
}