MvvmCross icon indicating copy to clipboard operation
MvvmCross copied to clipboard

Replacing IoC with .net core DI

Open enizvk opened this issue 5 years ago • 11 comments

🚀 Feature Requests

Contextualize the feature

Many libraries starting with AutoMapper, MediatR, Ef Core, .net core IOptions and many more are providing .net core DI registration extensions.

To use these we should write our custom registration extensions and this is often error prone, because of the often changes in the libraries and causing incompatibilities with MvvmCross DI mechanism. Most of the ime heavry Rtti is involved. Another option we have is to provide custom IMvxIocProvider to replace default DI container of MvvmCross. But this is not very viable option because we have to support all these extensions. and very often we find various compatibility problems. Also EF Core uses scoped DbContext registration which seems is not easy to port to MvvmCross. One use case of it is when you use Medaitor pattern you can provide scoped DbContext to Handlers using constructor injection.

Is this request feasible? I am sure that many developers will benefit from this, but maybe I am wrong. This is why I am opening this discussion.

Describe the feature

Would be very nice to be able to replace default MvvmCross DI mechanism with the default .net core DI. This would save tons of time and problems. I made several attempts to fully replace IMvxIocProvider, but facing many problems and gave up. We had discussions with several guys from Xamarin forms experiencing same problems. I am mainly WPF involved.

Platforms affected (mark all that apply)

  • [x] :robot: Android
  • [x] :checkered_flag: WPF
  • [x] :earth_americas: UWP
  • [x] :monkey: Xamarin.Forms

enizvk avatar Jun 15 '20 07:06 enizvk

I have the same problem. I want to use SimpleInjector IoC, but for a MvvmCross beginner it is not obvious how to properly implement IMvxIoCProvider

Shumigaj avatar Jun 16 '20 18:06 Shumigaj

Steven sent me a conforming-container article that describes the importance of writing an application independent from container. It says that it is better to use the Constructor Injection pattern instead of the Service Locator anti-pattern.

Shumigaj avatar Jun 17 '20 18:06 Shumigaj

for a MvvmCross beginner it is not obvious how to properly implement IMvxIoCProvider

Like anything else in MvvmCross, impement the interface and register it in Setup.cs in the appropriate override. What you probably are looking for is to override CreateIocProvider

Not sure which Steven you are referring to. However, you can write your MvvmCross application with Constructor Injection, no one forces you to use Service Locator. Not sure where you are going with your comment...

As for OPs statement:

I made several attempts to fully replace IMvxIocProvider, but facing many problems and gave up. We had discussions with several guys from Xamarin forms experiencing same problems.

I can't find any questions or attempts to contact us about this. Annotation 2020-06-17 204400

Please enlighten me how a different interface to implement, would have changed that at all?

Anyways. For the feature request itself. I am not dismissing it. We have been looking at replacing the TinyIoC inspired IoC container we use in MvvmCross with Microsoft.Extensions.DependencyInjection. Just haven't gotten around to much than some minor experimenting with it.

Cheesebaron avatar Jun 17 '20 18:06 Cheesebaron

I can't find any questions or attempts to contact us about this.

Yes, that`s why I opened this feature request, otherwise i would follow already opened one. We have a Slack group where the discussions are not very formal.

Please enlighten me how a different interface to implement, would have changed that at all?

My mistake, I was thinking about IMvxIocProvider adapter. I`ll edit the title.

Basically i need to use Microsoft.DependancyInjection constructor injection, preventing MvvmCross from injecting ViewModel`s constructors by default.

enizvk avatar Jun 18 '20 09:06 enizvk

Any news on this?

bdovaz avatar Sep 30 '20 07:09 bdovaz

Nope. Haven't had the time to do it.

Cheesebaron avatar Sep 30 '20 07:09 Cheesebaron

In Microsoft.Extensions.DependencyInjection there is IServiceCollection where you add dependencies and a IServiceProvider where you resolve dependencies.

I'm trying to replace IMvxIoCProvider but I have some problems:

  1. In this code it's where supposedly attempts to inject dependencies:

https://github.com/MvvmCross/MvvmCross/blob/develop/MvvmCross/Core/MvxSetup.cs#L77

So in my MXSetup subclass I'm subscribed to RegisterSetupDependencies event where I build a IServiceProvider from the IServiceCollection class.

But my problem is that later it's still adding dependencies. An example:

https://github.com/MvvmCross/MvvmCross/blob/develop/MvvmCross/Core/MvxSetup.cs#L411

And that's not possible with Microsoft DI, there are two separate phases:

  1. Adding dependencies to IServiceCollection.
  2. Building IServiceProvider via IServiceCollection BuildServiceProvider() method and then resolving dependencies via IServiceProvider instance.

Example code:

public class CustomSetup : MvxSetup {

    public CustomSetup() {
        RegisterSetupDependencies += provider => {
            MicrosoftMvxIoCProvider microsoftMvxIoCProvider = provider as MicrosoftMvxIoCProvider;
            microsoftMvxIoCProvider.ExecuteDelayedCallback();

            var services = microsoftMvxIoCProvider.Services;
            var serviceProvider = services.BuildServiceProvider();

            microsoftMvxIoCProvider.ServiceProvider = serviceProvider;
        };
    }

    protected override IMvxApplication CreateApp() => throw new NotImplementedException();

    protected override IMvxIoCProvider CreateIocProvider() {
        var iocProvider = new MicrosoftMvxIoCProvider(ServiceLifetime.Transient);
        iocProvider.Services = new ServiceCollection();

        return iocProvider;
    }

    protected override IMvxViewDispatcher CreateViewDispatcher() => throw new NotImplementedException();
    protected override IMvxViewsContainer CreateViewsContainer() => throw new NotImplementedException();
    protected override IMvxNameMapping CreateViewToViewModelNaming() => throw new NotImplementedException();

}
public class MicrosoftMvxIoCProvider : MvxSingleton<IMvxIoCProvider>, IMvxIoCProvider {

    internal IServiceCollection Services { get; set; }
    internal IServiceProvider ServiceProvider { get; set; }

    private readonly ServiceLifetime serviceLifetime;

    private readonly Queue<ActionContainer> _delayedCallbacks = new Queue<ActionContainer>();

    class ActionContainer {
        public Type Type { get; set; }
        public Action Action { get; set; }
    }

    public MicrosoftMvxIoCProvider(ServiceLifetime serviceLifetime) {
        this.serviceLifetime = serviceLifetime;
    }

    public void CallbackWhenRegistered(Type type, Action action) {
        _delayedCallbacks.Enqueue(new ActionContainer() { Action = action, Type = type });
    }

    public void CallbackWhenRegistered<T>(Action action) {
        CallbackWhenRegistered(typeof(T), action);
    }

    public bool CanResolve<T>() where T : class => ServiceProvider.GetService<T>() != null;
    public bool CanResolve(Type type) => ServiceProvider.GetService(type) != null;
    public T Create<T>() where T : class => ServiceProvider.GetRequiredService<T>();
    public object Create(Type type) => ServiceProvider.GetRequiredService(type);
    public IMvxIoCProvider CreateChildContainer() => throw new NotImplementedException();
    public T GetSingleton<T>() where T : class => ServiceProvider.GetRequiredService<T>();
    public object GetSingleton(Type type) => ServiceProvider.GetRequiredService(type);

    public T IoCConstruct<T>() where T : class => ActivatorUtilities.CreateInstance<T>(ServiceProvider);
    public T IoCConstruct<T>(IDictionary<string, object> arguments) where T : class => throw new NotImplementedException();
    public T IoCConstruct<T>(object arguments) where T : class => ActivatorUtilities.CreateInstance<T>(ServiceProvider, arguments);
    public T IoCConstruct<T>(params object[] arguments) where T : class => ActivatorUtilities.CreateInstance<T>(ServiceProvider, arguments);

    public object IoCConstruct(Type type) => ActivatorUtilities.CreateInstance(ServiceProvider, type);
    public object IoCConstruct(Type type, IDictionary<string, object> arguments) => throw new NotImplementedException();
    public object IoCConstruct(Type type, object arguments) => ActivatorUtilities.CreateInstance(ServiceProvider, type, arguments);
    public object IoCConstruct(Type type, params object[] arguments) => ActivatorUtilities.CreateInstance(ServiceProvider, type, arguments);

    public void RegisterSingleton<TInterface>(TInterface theObject) where TInterface : class => Services.AddSingleton<TInterface>(theObject);
    public void RegisterSingleton(Type tInterface, object theObject) => Services.AddSingleton(tInterface, theObject);
    public void RegisterSingleton<TInterface>(Func<TInterface> theConstructor) where TInterface : class => Services.AddSingleton(provider => theConstructor());
    public void RegisterSingleton(Type tInterface, Func<object> theConstructor) => Services.AddSingleton(tInterface, provider => theConstructor());

    public void RegisterType<TInterface>(Func<TInterface> constructor) where TInterface : class => Services.Add(ServiceDescriptor.Describe(typeof(TInterface), provider => constructor(), serviceLifetime));
    public void RegisterType(Type t, Func<object> constructor) => Services.Add(ServiceDescriptor.Describe(t, provider => constructor(), serviceLifetime));
    public void RegisterType(Type tFrom, Type tTo) => Services.AddSingleton(tFrom, serviceProvider => serviceProvider.GetRequiredService(tTo));

    public T Resolve<T>() where T : class => ServiceProvider.GetRequiredService<T>();
    public object Resolve(Type type) => ServiceProvider.GetRequiredService(type);

    public bool TryResolve<T>(out T resolved) where T : class {
        resolved = ServiceProvider.GetService<T>();

        return resolved != null;
    }
    public bool TryResolve(Type type, out object resolved) {
        resolved = ServiceProvider.GetService(type);

        return resolved != null;
    }

    void IMvxIoCProvider.RegisterType<TFrom, TTo>() {
        Services.AddSingleton<TFrom>(serviceProvider => (TFrom)serviceProvider.GetRequiredService<TTo>());
    }

    public void ExecuteDelayedCallback() {
        while (_delayedCallbacks.Any()) {
            var dequeuedAction = _delayedCallbacks.Dequeue();

            dequeuedAction.Action.Invoke();
        }
    }

}
GitHub
The .NET MVVM framework for cross-platform solutions, including Xamarin.iOS, Xamarin.Android, Windows and Mac. - MvvmCross/MvvmCross
GitHub
The .NET MVVM framework for cross-platform solutions, including Xamarin.iOS, Xamarin.Android, Windows and Mac. - MvvmCross/MvvmCross

bdovaz avatar Sep 30 '20 09:09 bdovaz

@Cheesebaron, @bdovaz Have you ever thought of publishing a package similar to Autofac.Extras.MvvmCross that may enable Microsoft.Extensions.DependencyInjection support? I've come across the same need to replace the container and seeking a solution :)

LDan1117 avatar Dec 09 '20 18:12 LDan1117

Has anybody got this working using Simple Injector? As a first time user of MvvmCross there doesn't appear to be a user friendly way of bypassing the default container.

Forgive my ignorance, but why is there built in container? At first glance it looks like the Conforming Container anti-pattern @Shumigaj linked.

Amith211 avatar Jul 21 '21 01:07 Amith211

Forgive my ignorance, but why is there built in container? At first glance it looks like the Conforming Container anti-pattern @Shumigaj linked.

For historical reasons. MvvmCross is quite old (but of course constantly updated) library. It was one of the first mobile application frameworks when C# DI containers primarily were for WPF and Silverlight so MvvmCross introduced it's own crossplatform solution.

Paul-N avatar Jul 21 '21 01:07 Paul-N

If you're developer who want to adopt MS DI for MvvmCross take note, that just implementing IMvxIoCProvider is not enough. Since in MS DI all "register" work should be done exactly before any "resolve" actions you need to refactor App/Setup files for keeping right order.

Paul-N avatar Jul 21 '21 01:07 Paul-N