I know the question has been asked 2 years ago, but I might have a solution that could match what you are asking for.
In the past few months I've been working on apps using Xamarin and WPF and I used the Microsoft.Extensions.DependencyInjection
package to add constructor dependency injection to my view models, just like an ASP.NET Controller. Which means that I could have something like:
public class MainViewModel : ViewModelBase
{
private readonly INavigationService _navigationService;
private readonly ILocalDatabase _database;
public MainViewModel(INavigationService navigationService, ILocalDatabase database)
{
_navigationService = navigationService;
_database = database;
}
}
To implement this kind of process I use the IServiceCollection
to add the services and the IServiceProvider
to retrieve the registered services.
What is important to remember, is that the IServiceCollection
is the container where you will register your dependencies. Then when building this container, you will obtain a IServiceProvider
that will allow you to retrieve a service.
To do so, I usually create a Bootstrapper
class that will configure the services and initialize the main page of the application.
The basic implementation
This example show how to inject dependencies into a Xamarin page. The process remains the same for any other class. (ViewModels or other classes)
Create a simple class named Bootstrapper
in your project and intitialize a IServiceCollection
and IServiceProvider
private fields.
public class Bootstrapper
{
private readonly Application _app;
private IServiceCollection _services;
private IServiceProvider _serviceProvider;
public Bootstrapper(Application app)
{
_app = app;
}
public void Start()
{
ConfigureServices();
}
private void ConfigureServices()
{
_services = new ServiceCollection();
// TODO: add services here
_serviceProvider = _services.BuildServiceProvider();
}
}
Here in the ConfigureServices()
method we just create a new ServiceCollection
where we are going to add our services. (See https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.servicecollection?view=dotnet-plat-ext-3.1)
Once our services have been added, we build the service provider that will allow us to retrieve the previously registered services.
Then in your App
class constructor, create a new Bootstrapper
instance and call the start method to initialize the application.
public partial class App : Application
{
public App()
{
InitializeComponent();
var bootstrapper = new Bootstrapper(this);
bootstrapper.Start();
}
...
}
With this piece of code, you have setup your service container, but we still need to initialize the MainPage
of the application. Go back to the bootstrapper's Start()
method and create a new instance of the wanted main page.
public class Bootstrapper
{
...
public void Start()
{
ConfigureServices();
// Real magic happens here
var mainPageInstance = ActivatorUtilities.CreateInstance<MainPage>(_serviceProvider);
_app.MainPage = new NavigationPage(mainPageInstance);
}
}
Here we use the ActivatorUtilities.CreateInstance<TInstance>()
method to create a new MainPage
instance. We give the _serviceProvider
as parameter, because the ActivatorUtilities.CreateInstance()
method will take care of creating your instance and inject the required services into your object.
Note that this is what ASP.NET Core using to instanciate the controllers with contructor dependency injection.
To test this, create a simple service and try to inject it into your MainPage
contructor:
public interface IMySimpleService
{
void WriteMessage(string message);
}
public class MySimpleService : IMySimpleService
{
public void WriteMessage(string message)
{
Debug.WriteLine(message);
}
}
Then register it inside the ConfigureServices()
method of the Bootstrapper
class:
private void ConfigureServices()
{
_services = new ServiceCollection();
_services.AddSingleton<IMySimpleService, MySimpleService>();
_serviceProvider = _services.BuildServiceProvider();
}
And finally, go to your MainPage.xaml.cs
, inject the IMySimpleService
and call the WriteMessage()
method.
public partial class MainPage : ContentPage
{
public MainPage(IMySimpleService mySimpleService)
{
mySimpleService.WriteMessage("Hello world!");
}
}
There you go, you have successfully registered a service and injected it into your page.
The real magic with constructor injection really occurs using the ActivatorUtilities.CreateInstance<T>()
method by passing a service provider. The method will actually check the parameters of your constructor and try to resolve the dependencies by trying to get them from the IServiceProvider
you gave him.
Bonus : Register platform specific services
Well this is great right? You are able to inject services into any classes thanks to the ActivatorUtilities.CreateInstance<T>()
method, but sometimes you will also need to register some platform specific services (Android or iOS).
With the previous method is not possible to register platform-specific services, because the IServiceCollection
is initialized in the Bootstrapper
class. No worries, the workaround is really simple.
You just need to extract the IServiceCollection
initialization to the platform-specific code. Simply initialize the service collection on the MainActivity.cs
of your Android project and in the AppDelegate
of your iOS project and pass it to your App
class that will forward it to the Bootstrapper
:
MainActivity.cs (Android)
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
protected override void OnCreate(Bundle savedInstanceState)
{
...
var serviceCollection = new ServiceCollection();
// TODO: add platform specific services here.
var application = new App(serviceCollection);
LoadApplication(application);
}
...
}
AppDelegate.cs (iOS)
public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
{
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
global::Xamarin.Forms.Forms.Init();
var serviceCollection = new ServiceCollection();
// TODO: add platform specific services here.
var application = new App(serviceCollection);
LoadApplication(application);
return base.FinishedLaunching(app, options);
}
}
App.xaml.cs (Common)
public partial class App : Application
{
public App(IServiceCollection services)
{
InitializeComponent();
var bootstrapper = new Bootstrapper(this, services);
bootstrapper.Start();
}
...
}
Bootstrapper.cs (Common)
public class Bootstrapper
{
private readonly Application _app;
private readonly IServiceCollection _services;
private IServiceProvider _serviceProvider;
public Bootstrapper(Application app, IServiceCollection services)
{
_app = app;
_services = services;
}
public void Start()
{
ConfigureServices();
var mainPageInstance = ActivatorUtilities.CreateInstance<MainPage>(_serviceProvider);
_app.MainPage = new NavigationPage(mainPageInstance);
}
private void ConfigureServices()
{
// TODO: add services here.
_serviceCollection.AddSingleton<IMySimpleService, MySimpleService>();
_serviceProvider = _services.BuildServiceProvider();
}
}
And that's all, you are now able to register platform-specific services and inject the interface into your pages / view models / classes easily.
Microsoft.Extensions.DependencyInjection
is an independent module. While it is used as the out of the box DI container in ASP.Net-Core. It can be used on its own in any other solution that supports it. I believe is should be able to be used in Xamarin. Note however that the built-in services container is meant to serve the basic needs of the framework and most consumer applications built on it. – DougaldContainerBuilder
? I couldn't find it online. I didn't want to lengthen my question by adding that fact – NertContainerBuilder
. Should be able to find it on Nuget easily. – Dougald