2

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

Krusty
  • 955
  • 1
  • 12
  • 26
  • The question in its current state is incomplete and therefore unclear. Where does `Host` in the example come from? – Nkosi Oct 22 '20 at 12:43
  • I think this would require a rewrite of MvxSetup to not call `BuildServiceProvider()` for every call. Adding support or replacing the IoC in MvvmCross with Microsoft DI is something that I have thought of, just not had the time to play with yet. – Cheesebaron Oct 22 '20 at 14:19
  • @Nkosi `Host` is a .NET Core static class. Why unclear? You can understand almost everything from the title, the rest is just how I have approached the problem. @Cheesebaron Is it something that I can do client side or shall I wait for the Mvx support? BTW I would like to open a PR on the official Mvx repo but unfortunately I am not able to :/ – Krusty Oct 22 '20 at 18:05

1 Answers1

2

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");
                }
            }
        }
    }
Krusty
  • 955
  • 1
  • 12
  • 26
  • You may want to change `var instance = serviceDescriptor.ImplementationFactory(serviceProvider); Mvx.IoCProvider.RegisterSingleton(serviceDescriptor.ServiceType, instance);` to this `MvvmCross.Mvx.IoCProvider.RegisterSingleton(serviceDescriptor.ServiceType,()=> serviceDescriptor.ImplementationFactory(serviceProvider));` to avoid eager loading. Also before registering to the IocProvider, you need to check the `LifeTime` and register accordingly – zafar Jul 14 '22 at 13:17