.NET Core单例创建被多次调用

15
我正在.NET Core中将一个服务注册为单例。但我发现该单例的构造函数被调用了多次。
services.AddSingleton<DbAuthorizationOptions, ContextAuthorizationOptions>();

我的上下文授权选项只是实体类型到IValidators的字典,这些上下文授权选项通过DBContext传递,以自动运行验证。

在注册我的服务时,我还使用DI注册了动态验证器。

var useDynamicValidator = serviceOption.ValidatorOptions != null;
if(useDynamicValidator)
{
    //TODO: Extract this to before the register service no sense in building the provider each time
    //TODO: Make this cleaner don't be dependent on Authorization options
    var provider = services.BuildServiceProvider();
    var authOptions = provider.GetService<DbAuthorizationOptions>();
    var validator = BuildDynamicValidatorFactory(serviceOption).Invoke(provider, null);
    authOptions.ValidatorOptions.AddValidatorForSet(validator);
}

我注意到当我在提供程序上调用GetService时,我收到了一个新的单例而不是现有的单例。是否建立提供程序会创建新的容器,使得所有服务都重新注册?

如果是这样,那么我如何调用一个方法将我的动态验证器注册到现有的单例容器中的IServiceProvider,是否有一种方法可以在服务容器构建后调用一次注册方法?

2个回答

6

构建提供程序是否会创建一个新容器,以便重新注册所有服务?

是的。请参阅源代码

如果是这样,我该如何调用方法将我的动态验证器注册到现有的IServiceProvider的单例容器中,有没有一种方法可以在构建servicecontainer后调用某些注册?

我真的不明白为什么这是个问题。您应该在组合根中在应用程序启动时注册所有服务一次

然后,DI容器负责解析应用程序的对象图。应用程序本身不应该依赖它,也不需要更新它。

您应该在需要使用它的地方注入DbAuthorizationOptions

public class Foo : IFoo
{
    private readonly DbAuthorizationOptions authOptions;

    public Foo(DbAuthorizationOptions authOptions) // <-- Inject parameters
    {
        this.authOptions = authOptions ??
            throw new ArgumentNullException(nameof(authOptions));
    }

    public void DoSomething()
    {
        // TODO: Inject the type that has the BuildDynamicValidatorFactory
        // method and the serviceOption (whatever type that is) here
        // either as a method parameter of this method, or a constructor
        // parameter of this class.
        var validator = BuildDynamicValidatorFactory(serviceOption).Invoke(provider, null);
        // Now we have an instance of authOptions that can be used
        authOptions.ValidatorOptions.AddValidatorForSet(validator);
    }
}

请注意,如果将DI容器注入到另一个通过DI解析的类型(例如控制器或过滤器)中,则DI容器会自动提供DbAuthorizationOptions

注意:从您的问题中不太清楚需要在哪里执行此操作。您提到希望它只发生一次,这通常意味着将其放置在应用程序启动时。但是,用户无法与在启动时运行的代码交互。因此,您可以使用过滤器。这完全取决于它必须发生在应用程序的生命周期中的位置。


我有一个组件,它依赖于访问上下文中任何实体的所有验证器。我注册了一个EntityType到Validator的单例字典。当用户向服务工厂注册时,如果没有声明具体的验证器,则用户可以通过传递选项来提供创建动态验证器的选项。这个动态验证器需要在单例中注册,以便上下文可以访问所有验证器。因此,我需要在服务容器构建后仅注册我的动态验证器一次,如何做到这一点? - johnny 5
请看我的编辑。如果您注入了authOptions并且它是单例的,那么您应该能够在任何服务中访问它。因此,在运行时,您可以构建工厂并添加验证。我不确定serviceOption来自哪里,所以您需要通过构造函数注入它或将其作为方法参数传递。如果这没有意义,您可能需要编辑问题以提供有关设置的更多上下文(更好的方法是,由于这似乎是一个XY问题,请提出一个新问题)。 - NightOwl888
1
@johnny5 ... NightOwl888的工厂模式是正确的方法,但直接回答你的问题,.NET Core DI容器不支持后续添加服务。我之前关于注入IServiceProvider的答案可以解决你的非单例问题,但这是“服务定位器”反模式,通常应该避免使用。 - McGuireV10
@NightOwl888 我不确定这个解决方案是否适用于我的实现,但是如果不倾泻出大量代码,我很难解释清楚我的完整问题。我会调查两种解决方案,看哪一个更好。 - johnny 5
@NightOwl888 谢谢,最终我使用了这个修改版的。 - johnny 5
我很难理解单例的意义,如果它在每个使用它的地方都被重新创建并且不在用途之间共享。 - foxtrotuniform6969

0

你可以声明对 IServiceProvider 的依赖 -- 不要构建它,注入它。

public class SomeController
{
    DbAuthorizationOptions authOptions;
    public SomeController(IServiceProvider provider)
    {
        authOptions = provider.GetSerivce<DbAuthorizationOptions>();
    }
}

但这是服务定位器反模式。正如我在你提供更多细节后评论NightOwl888的帖子时所说,工厂可能是更好的方法。


你的意思是不要构建它,只需注入它?我怎么注入呢? - johnny 5
有趣,我需要调查一下在我的情况下是否可以使用工厂方法,不过这应该是可行的。 - johnny 5
实际上,我提供的解决方案中没有工厂(除了我复制和粘贴的一个)。我的解决方案注入DbAuthorizationOptions。这个解决方案使用服务定位器解析DbAuthorizationOptions。在纸面上看起来一样,但不同之处在于,当你注入IServiceProvider时,你将代码紧密耦合IServiceProvider上,所以它不能在没有这个额外接口的情况下运行。如果你想用另一个实现或模拟替换DbAuthorizationOptions,使用服务定位器方法需要跳过更多的障碍。 - NightOwl888

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接