在Startup.ConfigureServices方法期间访问依赖项注入对象

时间:2019-11-29 00:34:20

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

我有一个ASP.NET Core 3应用程序,并且我正在使用AzureAD进行身份验证。我的Startup.ConfigureSerivces方法中包含以下几行,其目的是在附加Cookie时执行一些业务规则。

services.Configure<CookiePolicyOptions>(options => {
    options.CheckConsentNeeded = ctx => true;
    options.MinimumSameSitePolicy = SameSiteMode.None;
    options.OnAppendCookie = ctx => {
        var svc = ctx.Context.RequestServices.GetRequiredService<IUserInformationService>();
        // do something with svc        
    };
});
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
    .AddAzureAD(options => {
        Configuration.Bind("AzureAd", options);
    });

这很好,并且在IUserInformationService方法中按预期方式获得了注入的OnAppendCookie对象,并且从appsettings.json中获取了AzureAD信息。

但是,最近,有关AzureAD租户的信息一定不能驻留在appsettings.json中,而是我现在必须查阅数据库。我有一项已经查询数据库并获取AD设置的服务。像这样:

public interface IAzureADConfiguration {
    void Configure(AzureADOptions options);
}

但是,在调用AddAzureAD时,我找不到一种方法来检索注入的服务。我想要的是这样的:

services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
    .AddAzureAD(options => {
        var svc = ???
        svc.Configure(options);
        Configuration.Bind("AzureAd", options);
    });

由于我无法像在HttpContext中那样在AddAzureAD方法中访问OnAppendCookie。在此阶段是否有办法获取注入的对象?我不想对实例进行硬编码,因为此要求将来可能会更改(即添加另一种方法来配置从何处获取Azure广告设置)。预先感谢!

2 个答案:

答案 0 :(得分:2)

选项1:配置回调

请@Nkosi指出相关文档:Use DI services to configure options

他们的答案使我正确地开始了,但是所提供的代码示例对我而言不起作用,因为它没有说明命名选项(AzureAD配置使用该选项)。在弄清楚并简化一点之后,我得到了以下内容...

对于AzureADOptions,最好的选择似乎是添加后配置以在提供给AddAzureAD的现有(必需)配置回调之后运行。两个回调都将传递到相同的AzureADOptions实例,因此可以按原样保留现有回调以加载“基本”值,并且配置后回调可以根据需要覆盖它们。

// Configure Azure AD to load settings from the app config.
// This is unchanged (apart from syntax) from the original question.
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
    .AddAzureAD(options => Configuration.Bind("AzureAd", options));

// Add a post-configure callback to augment the app config-provided values
// with service-provided values.
services.AddOptions<AzureADOptions>(AzureADDefaults.AuthenticationScheme)
    .PostConfigure<IAzureADConfiguration>((options, service) =>
        service.Configure(options));

如果您不想从应用程序配置中加载基本值,只需将Configuration.Bind("AzureAD", options)替换为空块({ })。

AddAzureAD使用命名的选项实例,因此请确保将相同的名称传递给PostConfigureAddAuthentication。如果两个地方都没有使用相同的名称,则配置后回调将不会配置正确的选项实例(或根本不会运行)。

注意:如果有人想使用此答案来配置Azure AD以外的其他东西...如果没有注册现有的选项实例或回调,则应使用Configure方法而不是PostConfigure,并且您可能不需要提供名称。

services.AddOptions<MyOptionsType>()
    .Configure<IMyConfigurationService>((options, service) =>
        service.Configure(options));

选项2:自定义配置提供程序

虽然这不是您问题的直接答案,但这可能是解决引发您问题的问题的可行解决方案。

您可以实现custom configuration provider来从数据库中加载设置,而不是编写配置服务。然后,您应该可以使用AddAzureAD离开Configuration.Bind通话。

文档new中的示例建立了EF数据上下文,而不是由DI容器提供它。这违反了DI的直觉,但我认为在这种情况下还可以。示例提供程序是一个小型且功能齐全的子系统。它的数据库配置仍然被注入(尽管不是通过DI容器),并且仍然可以通过注入EF内存数据库的配置来对其进行单元测试。它没有其他依赖项可以交换。 DI容器不会增加太多价值-系统已经具有它需要的所有依赖项注入。

答案 1 :(得分:2)

内部AddAzureAD最终将在Configure上调用AzureADOptions

要进入该过程,请根据您的服务创建自定义IConfigureOptions<T>

public class AzureADOptionsSetup : IConfigureOptions<AzureADOptions> {
    private readonly IAzureADConfiguration service;

    public AzureADOptionsSetup(IAzureADConfiguration service) { 
        this.service = service;
    }

    public void Configure(AzureADOptions options) {
        service.Configure(options);
    }
}

并确保将其注册到服务集合中

services.AddTransient<IConfigureOptions<AzureADOptions>, AzureADOptionsSetup>();

这将确保您的自定义选项配置有机会在options类上进行操作。

参考Use DI services to configure options