【问题标题】:How to perform a call to an Initialize method of a service with Microsoft DI如何使用 Microsoft DI 调用服务的 Initialize 方法
【发布时间】:2021-12-28 07:10:19
【问题描述】:

我以前用过其他的DI框架,现在我必须使用微软的(.NET Core 3.0),并且在使用服务时我需要调用一个InitializeAsync方法(它是一个单例所以我只有一个整个应用程序中的实例)。我不想在 Constructor 中执行这样的操作,因为它必须调用 web api ,我也不想在服务实现的方法中放入变量并检查它是否刚刚初始化。

可以考虑下面sn-p的代码

  class Program
    {
        static void Main(string[] args)
        {
            using IHost host = CreateHostBuilder(args).Build();

            ExemplifyScoping(host.Services, 1);
            ExemplifyScoping(host.Services, 88);

            host.RunAsync();
        }

        private static void ExemplifyScoping(IServiceProvider hostServices, int scope)
        {
            var service = hostServices.GetService<IDummyService>();

          var str =  service.PerfomSomething(scope);

          Console.WriteLine($"RES : {str}");

        }

        static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureServices((_, services) =>
                {
                    services.AddTransient<IDummyService, DummyService>();

                }
                );
    }

和简化的服务类

    public class DummyService : IDummyService
    {
        private IDictionary<int, string> _dictionary;
        public Task InitializeAsync()
        {
            _dictionary = new Dictionary<int, string>();

            _dictionary.Add(1,"1");
            _dictionary.Add(2,"2");
            _dictionary.Add(3,"3");
            _dictionary.Add(4,"4");

            return  Task.CompletedTask;
        }

        public string PerfomSomething(int id)
        {
            if (_dictionary.ContainsKey(id))
                return _dictionary[id];

            return string.Empty;
        }
    }

    public interface IDummyService
    {
        Task InitializeAsync();

        string PerfomSomething(int id);
    }

我看到DI框架有一个PostConfigure方法但是不知道是不是我需要用的。

有什么建议吗? 谢谢

【问题讨论】:

    标签: c# .net-core dependency-injection


    【解决方案1】:

    简短的回答是您不使用容器调用InitializeAsync 方法。相反,您可以从 Composition Root 中手动调用它。

    让您的 DI 容器调用异步方法(或任何为此执行 I/O 的方法)通常是一个坏主意,因为组合对象图应该是 fast 和 reliable。这就是为什么 DI 容器通常不公开 ResolveAsync 方法的原因,因为这在它们的上下文中没有意义。

    在您的情况下,您需要对组件进行一次性应用程序范围的初始化。这可以通过解析和调用目标组件来有效地完成所有组件都已注册,并且容器已构建。

    这里有一个简单的例子来说明这一点:

    static async Task Main(string[] args)
    {
        using IHost host = CreateHostBuilder(args).Build();
    
        await host.Services.GetRequiredService<IDummyService>().InitializeAsync();
    
        ...
    
        host.RunAsync();
    }
    

    考虑从IDummyService 抽象中删除InitializeAsync 方法,并使其仅可用于实现。 InitializeAsync 方法是一个实现细节,只有组合根需要知道它,而组合根已经知道实现的存在(它注册了那个实现)。

    有关异步初始化和 DI 容器主题的更详细讨论,请参阅 Stephen Cleary、Mark Seemann 和我本人之间的 this q/a。

    【讨论】:

    • 您好 Steven,感谢您的回复... 删除 InitalizeAsync 的实现应该是什么?你通常如何处理这种情况?因为我认为我不是世界上唯一一个试图完成一些初始化的人
    • 嗨@advapi,我不是说 不 进行任何初始化,但我想说的是:不要将此实现细节泄漏到您的IDummyService 抽象。只需让DummyService 实现 将InitializeAsync 作为public 方法,然后让您的Composition Root 解析该实现并调用该方法。
    • 但是 Composition Root 是如何知道调用 InitializeMethod 的呢?我的意思是,如果我按照我的方式注册服务,然后从接口中删除 InitializeAsync,将其留在 DummyService 类中,我何时以及如何调用它?
    • 在这种情况下,您基本上有两个选择:要么解析接口并将其转换回实现,要么将抽象 和 的实现都注册到容器并在您的 main.xml 中解析实现。如果是后一种选择,请查看this answer 如何进行此类注册。
    【解决方案2】:

    我鼓励你在构造函数中调用它,也许是并行异步。但是,我想您有理由不这样做。 这是你可以做到的。

    1. 向自己注册实现

      services.AddTransient&lt;DummyService, DummyService&gt;();

    2. 将接口注册到工厂方法

        services.AddSingleton<IDummyService>(sp =>
        {
            var service = sp.GetService<DummyService>();
            service.InitializeAsync();
            return service;
        });
    

    【讨论】:

    • 您的建议是,IMO,这是一种危险的做法,因为当应用程序开始使用 DummyService 时,初始化仍将在后台运行。如果不仔细设计,这可能会导致各种竞争条件。它还会在对象图组合期间引起 I/O,从而使组合 slow 和 unreliable。
    • 谢谢@Steven,但这正是我从问题中所理解的。我不推荐这种模式,但我试图从技术上回答如何做到这一点。
    猜你喜欢
    • 2012-04-04
    • 1970-01-01
    • 2023-03-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-14
    • 2010-11-16
    • 2012-12-24
    相关资源
    最近更新 更多