Search code examples
xamarin.net-coreinversion-of-controlmvvmcross

Replace default IoC container in MvvmCross


Since MvvmCross v7 sticks on its own IoC container, I would like to replace it with the .NET Core one in order to have an easier life when registering third party libraries such as IHttpClientFactory, Polly, Automapper, etc. through already built-in extensions methods.

To achieve this, I've successfully created a class that implementas MvxSingleton<IMvxIoCProvider> described as follow:

public class HostingAdapter : MvxSingleton<IMvxIoCProvider>, IMvxIoCProvider
{
    private IServiceProvider ServiceProvider;
    private IServiceCollection ServiceCollection;

    public HostingAdapter()
    {
        var host = Host               
            .ConfigureServices((context, serviceCollection) =>
            {
                // Configure local services
                ConfigureServices(context, serviceCollection);

                ServiceCollection = serviceCollection;
                ServiceProvider = ServiceCollection.BuildServiceProvider();
            })
            .Build();
    }

    public void RegisterType<TFrom, TTo>() where TFrom : class where TTo : class, TFrom
    {
         ServiceCollection.AddTransient<TFrom, TTo>();
         ServiceProvider = ServiceCollection.BuildServiceProvider();
    }

    public T GetSingleton<T>() where T : class
    {
        return ServiceProvider.GetRequiredService<T>();
    }

    public object GetSingleton(Type type)
    {
        return ServiceProvider.GetRequiredService(type);
    }

.. and all the required methods requested by the interface. Then on the platform specific side I override the IoC creation as follow:

protected override IMvxIoCProvider CreateIocProvider()
{
    var hostingAdapter = new HostingAdapter();
    return hostingAdapter;
}

The code seems to work but as soon as the app starts Mvx registers its own "extra" services such as the IMvxLoggerProvider, IMvxSettings and so on. And here issues come:

  1. ServiceProvider = ServiceCollection.BuildServiceProvider(); is called during the Host initialization but Mvx still continue to register services after that. This means IServiceProvider is not 'in sync' with IServiceCollection and a new ServiceCollection.BuildServiceProvider(); call is needed. I temporarily solved updating the provider at each collection registration (like the code above) but I'm aware this affects performances. Anyone knows how to workaround this?

  2. There are plenty of Mvx services that are not registered so the app fails to start. These are the IMvxLogProvider, IMvxAndroidLifetimeMonitor, IIMvxSettings, IMvxStart, etc. I just wonder, why? How can let Mvx handle the registration in my container of all what it needs to start? I partially solved some of them such as the logger thing replacing the default with a custom one, but other callbacks like InitializeLifetimeMonitor are called too late for being registered.

  3. Do I need to change anything in my MvxApplication than the most standard implementation?

  4. Am I really forced to replace the standard IoC container? How can I handle the IServiceCollection's extension methods that 3rd party libraries expose like services.AddHttpClient();?

If it needs, I am on Xamarin classic using the Droid platform. Thanks


Solution

  • Deliberately inspired by Unity.Microsoft.DependencyInjection repository I've workarounded this approaching the problem the other way round: instead of replacing the default IoC container, I manually initialize an IServiceCollection instance and I add it to the Mvx's IoC provider.

    To achieve this, I've used the following code:

    public class App : MvxApplication
        {
            public override void Initialize()
            {
                base.Initialize();
    
                InitializeServiceCollection();
    
                CreatableTypes()
                    .EndingWith("Service")
                    .AsInterfaces()
                    .RegisterAsLazySingleton();
    
                RegisterAppStart<HomeViewModel>();
            }
    
            private static void InitializeServiceCollection()
            {
                IServiceCollection serviceCollection = new ServiceCollection();
                ConfigureServices(serviceCollection);
    
                IServiceProvider serviceProvider = serviceCollection.BuildServiceProvider();
    
                MapServiceCollectionToMvx(serviceProvider, serviceCollection);
            }
    
            private static void ConfigureServices(IServiceCollection serviceCollection)
            {
                serviceCollection.AddHttpClient();
            }
    
            private static void MapServiceCollectionToMvx(IServiceProvider serviceProvider,
                IServiceCollection serviceCollection)
            {
                foreach (var serviceDescriptor in serviceCollection)
                {
                    if (serviceDescriptor.ImplementationType != null)
                    {
                        Mvx.IoCProvider.RegisterType(serviceDescriptor.ServiceType, serviceDescriptor.ImplementationType);
                    }
                    else if (serviceDescriptor.ImplementationFactory != null)
                    {
                        var instance = serviceDescriptor.ImplementationFactory(serviceProvider);
                        Mvx.IoCProvider.RegisterSingleton(serviceDescriptor.ServiceType, instance);
                    }
                    else if (serviceDescriptor.ImplementationInstance != null)
                    {
                        Mvx.IoCProvider.RegisterSingleton(serviceDescriptor.ServiceType, serviceDescriptor.ImplementationInstance);
                    }
                    else
                    {
                        throw new InvalidOperationException("Unsupported registration type");
                    }
                }
            }
        }