简体   繁体   中英

How to use Autofac as DI container in Avalonia.ReactiveUI?

I am trying to override the DI container used in Avalonia and ReactiveUI with Autofac. So far I have tried following the instructions in the Splat.Autofac repository , but I can't get Avalonia to work.

As a working example, I took the HelloWorld example from the ReactiveUI.Samples repository.

I could think of two places to override Splat. In App.xaml.cs in the OnFrameworkInitializationCompleted method:

using System.Reflection;

using Autofac;

using Avalonia;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
using Avalonia.Threading;

using ReactiveUI;

using Splat;
using Splat.Autofac;

namespace ReactiveAvalonia.HelloWorld {
    public class App : Application {
        public override void Initialize() {
            AvaloniaXamlLoader.Load(this);
        }

        public override void OnFrameworkInitializationCompleted()
        {
            // Build a new Autofac container.
            var builder = new ContainerBuilder();
            builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()).AsImplementedInterfaces();

            // Use Autofac for ReactiveUI dependency resolution.
            // After we call the method below, Locator.Current and
            // Locator.CurrentMutable start using Autofac locator.
            AutofacDependencyResolver resolver = new AutofacDependencyResolver(builder);
            Locator.SetLocator(resolver);

            // These .InitializeX() methods will add ReactiveUI platform 
            // registrations to your container. They MUST be present if
            // you *override* the default Locator.
            Locator.CurrentMutable.InitializeSplat();
            Locator.CurrentMutable.InitializeReactiveUI();

            var container = builder.Build();
            resolver.SetLifetimeScope(container);
        }
    }
}

But when running this, the startup fails with the following exception:

System.ArgumentException: 'Don't know how to detect when ReactiveAvalonia.HelloWorld.MainView is activated/deactivated, you may need to implement IActivationForViewFetcher'

My other idea was to do the overriding in Program.cs 's Main :

using System.Reflection;

using Autofac;

using Avalonia;
using Avalonia.Controls;
using Avalonia.Logging.Serilog;
using Avalonia.ReactiveUI;
using Avalonia.Threading;

using ReactiveUI;

using Splat;
using Splat.Autofac;

namespace ReactiveAvalonia.HelloWorld {

    // You may want to start here:
    // https://reactiveui.net/docs/getting-started/

    class Program {
        // http://avaloniaui.net/docs/reactiveui/
        // https://github.com/AvaloniaUI/Avalonia/wiki/Application-lifetimes
        public static AppBuilder BuildAvaloniaApp() {
            return AppBuilder
                .Configure<App>()
                .UseReactiveUI()
                .UsePlatformDetect()
                .LogToDebug();
        }

        private static void AppMain(Application app, string[] args) {
            app.Run(new MainView());
        }

        public static void Main(string[] args) {
            // Build a new Autofac container.
            var builder = new ContainerBuilder();
            builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()).AsImplementedInterfaces();

            // Use Autofac for ReactiveUI dependency resolution.
            // After we call the method below, Locator.Current and
            // Locator.CurrentMutable start using Autofac locator.
            AutofacDependencyResolver resolver = new AutofacDependencyResolver(builder);
            Locator.SetLocator(resolver);

            // These .InitializeX() methods will add ReactiveUI platform 
            // registrations to your container. They MUST be present if
            // you *override* the default Locator.
            Locator.CurrentMutable.InitializeSplat();
            Locator.CurrentMutable.InitializeReactiveUI();

            var container = builder.Build();
            resolver.SetLifetimeScope(container);

            BuildAvaloniaApp().Start(AppMain, args);
        }
    }
}

But this fails with another exception:

System.Exception: 'Container has already been built and the lifetime scope set, so it is not possible to modify it anymore.'

What can I do to get this to work?

Normally, the Avalonia docs tell you to use the UseReactiveUI extension method on the AppBuilder to be able to use ReactiveUI. What this does is registering some Avalonia components to the DI container:

    public static TAppBuilder UseReactiveUI<TAppBuilder>(this TAppBuilder builder)
        where TAppBuilder : AppBuilderBase<TAppBuilder>, new()
    {
        return builder.AfterPlatformServicesSetup(_ =>
        {
            RxApp.MainThreadScheduler = AvaloniaScheduler.Instance;
            Locator.CurrentMutable.RegisterConstant(new AvaloniaActivationForViewFetcher(), typeof(IActivationForViewFetcher));
            Locator.CurrentMutable.RegisterConstant(new AutoDataTemplateBindingHook(), typeof(IPropertyBindingHook));
        });
    }

With the two solutions in the question, there are two possible ways things go wrong:

Setting up Autofac in App

When you set up Autofac in OnFrameworkInitializationCompleted , this happens after the registrations setup in UseReactiveUI are made, essentially overriding them.

If you add those lines of code again after overriding the DI container, the application boots:

    public override void OnFrameworkInitializationCompleted()
    {
        // Build a new Autofac container.
        var builder = new ContainerBuilder();
        builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()).AsImplementedInterfaces();

        // Use Autofac for ReactiveUI dependency resolution.
        // After we call the method below, Locator.Current and
        // Locator.CurrentMutable start using Autofac locator.
        AutofacDependencyResolver resolver = new AutofacDependencyResolver(builder);
        Locator.SetLocator(resolver);

        // These .InitializeX() methods will add ReactiveUI platform 
        // registrations to your container. They MUST be present if
        // you *override* the default Locator.
        Locator.CurrentMutable.InitializeSplat();
        Locator.CurrentMutable.InitializeReactiveUI();

        Locator.CurrentMutable.RegisterConstant(new AvaloniaActivationForViewFetcher(), typeof(IActivationForViewFetcher));
        Locator.CurrentMutable.RegisterConstant(new AutoDataTemplateBindingHook(), typeof(IPropertyBindingHook));
        RxApp.MainThreadScheduler = AvaloniaScheduler.Instance;

        var container = builder.Build();
        resolver.SetLifetimeScope(container);
    }

Setting up Autofac in Main

Here we have overridden the DI container already before Avalonia attempts to register its components, at this time, the Autofac container is already created though, and with it being read-only, that causes the second exception. Again this can be fixed by making those same registrations along with the other code. Then the AppBuilder.UseReactiveUI extension method can be omitted:

using System.Reflection;

using Autofac;

using Avalonia;
using Avalonia.Controls;
using Avalonia.Logging.Serilog;
using Avalonia.ReactiveUI;
using Avalonia.Threading;

using ReactiveUI;

using Splat;
using Splat.Autofac;

namespace ReactiveAvalonia.HelloWorld {

    // You may want to start here:
    // https://reactiveui.net/docs/getting-started/

    class Program {
        // http://avaloniaui.net/docs/reactiveui/
        // https://github.com/AvaloniaUI/Avalonia/wiki/Application-lifetimes
        public static AppBuilder BuildAvaloniaApp() {
            return AppBuilder
                .Configure<App>()
                //.UseReactiveUI()
                .UsePlatformDetect()
                .LogToDebug();
        }

        private static void AppMain(Application app, string[] args) {
            app.Run(new MainView());
        }

        public static void Main(string[] args) {
            // Build a new Autofac container.
            var builder = new ContainerBuilder();
            builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()).AsImplementedInterfaces();

            // Use Autofac for ReactiveUI dependency resolution.
            // After we call the method below, Locator.Current and
            // Locator.CurrentMutable start using Autofac locator.
            AutofacDependencyResolver resolver = new AutofacDependencyResolver(builder);
            Locator.SetLocator(resolver);

            // These .InitializeX() methods will add ReactiveUI platform 
            // registrations to your container. They MUST be present if
            // you *override* the default Locator.
            Locator.CurrentMutable.InitializeSplat();
            Locator.CurrentMutable.InitializeReactiveUI();

            Locator.CurrentMutable.RegisterConstant(new AvaloniaActivationForViewFetcher(), typeof(IActivationForViewFetcher));
            Locator.CurrentMutable.RegisterConstant(new AutoDataTemplateBindingHook(), typeof(IPropertyBindingHook));
            RxApp.MainThreadScheduler = AvaloniaScheduler.Instance;

            var container = builder.Build();
            resolver.SetLifetimeScope(container);
            BuildAvaloniaApp().Start(AppMain, args);
        }
    }
}

Extension method

Now, this would look a lot nicer in an extension method similar to what we use to setup Splat and ReactiveUI:

    public static void InitializeAvalonia(this IMutableDependencyResolver resolver)
    {
        resolver.RegisterConstant(new AvaloniaActivationForViewFetcher(), typeof(IActivationForViewFetcher));
        resolver.RegisterConstant(new AutoDataTemplateBindingHook(), typeof(IPropertyBindingHook));
        RxApp.MainThreadScheduler = AvaloniaScheduler.Instance;
    }  

and

    public static void Main(string[] args) {
        // Build a new Autofac container.
        var builder = new ContainerBuilder();
        builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()).AsImplementedInterfaces();

        // Use Autofac for ReactiveUI dependency resolution.
        // After we call the method below, Locator.Current and
        // Locator.CurrentMutable start using Autofac locator.
        AutofacDependencyResolver resolver = new AutofacDependencyResolver(builder);
        Locator.SetLocator(resolver);

        // These .InitializeX() methods will add ReactiveUI platform 
        // registrations to your container. They MUST be present if
        // you *override* the default Locator.
        Locator.CurrentMutable.InitializeSplat();
        Locator.CurrentMutable.InitializeReactiveUI();
        Locator.CurrentMutable.InitializeAvalonia();

        var container = builder.Build();
        resolver.SetLifetimeScope(container);
        BuildAvaloniaApp().Start(AppMain, args);
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM