如何在 Avalonia.ReactiveUI 中使用 Autofac 作为 DI 容器?

Posted

技术标签:

【中文标题】如何在 Avalonia.ReactiveUI 中使用 Autofac 作为 DI 容器?【英文标题】:How to use Autofac as DI container in Avalonia.ReactiveUI? 【发布时间】:2021-03-14 13:16:45 【问题描述】:

我正在尝试使用 Autofac 覆盖在 Avalonia 和 ReactiveUI 中使用的 DI 容器。到目前为止,我已经尝试关注instructions in the Splat.Autofac repository,但我无法让 Avalonia 工作。

作为一个工作示例,我采用了HelloWorld example from the ReactiveUI.Samples 存储库。

我可以想到两个地方来覆盖 Splat。在App.xaml.csOnFrameworkInitializationCompleted 方法中:

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);
        
    

但是在运行时,启动失败并出现以下异常:

System.ArgumentException: '不知道如何检测 ReactiveAvalonia.HelloWorld.MainView 何时激活/停用,您可能需要实现 IActivationForViewFetcher'

我的另一个想法是在Program.csMain 中进行覆盖:

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);
        
    

但这失败了,还有一个例外:

System.Exception: '容器已经构建并且生命周期范围设置,所以不能再修改它。'

我该怎么做才能让它发挥作用?

【问题讨论】:

【参考方案1】:

通常,Avalonia 文档会告诉您使用 AppBuilder 上的 UseReactiveUI extension method 才能使用 ReactiveUI。它的作用是将一些 Avalonia 组件注册到 DI 容器:

    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));
        );
    

对于问题中的两种解决方案,出现问题的可能方式有两种:

App 中设置 Autofac

当您在 OnFrameworkInitializationCompleted 中设置 Autofac 时,这会发生在 UseReactiveUI 中的注册设置完成之后,基本上会覆盖它们。

如果您在覆盖 DI 容器后再次添加这些代码行,应用程序将启动:

    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);
    

Main 中设置 Autofac

在这里,我们已经在 Avalonia 尝试注册其组件之前覆盖了 DI 容器,此时,Autofac 容器已经创建,并且它是只读的,这会导致第二个异常。同样,这可以通过与其他代码一起进行相同的注册来解决。那么AppBuilder.UseReactiveUI扩展方法可以省略:

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);
        
    

扩展方法

现在,在类似于我们用来设置 Splat 和 ReactiveUI 的扩展方法中,这看起来会更好:

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

    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);
    

【讨论】:

以上是关于如何在 Avalonia.ReactiveUI 中使用 Autofac 作为 DI 容器?的主要内容,如果未能解决你的问题,请参考以下文章

如何在图像中找到明亮区域(以及如何在图像中找到阴影区域)

在QGIS中如何添加天地图的WMTS

如何在异步任务中调用意图?或者如何在 onPostExecute 中开始新的活动?

如何在Allegro中测量距离

如何在Allegro中测量距离

如何在Vue中嵌入React组件?如何在React中嵌入Vue组件?