首页 > 解决方案 > 你如何获得 IOptions来自服务的价值?

问题描述

我在开发时尝试做 TDD,但我很挣扎。我的第一个测试我可以成功获得在 services.AddAuthentication(...) 中设置的选项,但是我无法从添加到上一个调用中的 .AddCookie(..) 中获得选项。在调试期间,我确实看到为 CookieAuthenticationOptions 添加了一个 IPostConfigureOptions,我怀疑它以某种方式改变了默认选项值,但我不知道如何获取它。

要测试的代码:

public static void AddOpenIdConnect(this IServiceCollection services)
{
    services.Configure<CookiePolicyOptions>(options => { options.MinimumSameSitePolicy = SameSiteMode.None; });

    services.AddAuthentication(sharedOptions =>
    {
        sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
    })
    .AddCookie(
        CookieAuthenticationDefaults.AuthenticationScheme,
        options =>
        {
            options.ExpireTimeSpan = TimeSpan.FromHours(2);
            options.SlidingExpiration = true;
            options.AccessDeniedPath = new PathString("/Error/403");
        }
    );
}

我的第一个测试确保设置了 AuthenticationOptions,并且它通过了:

[TestMethod]
public void AddOpenIdConnect_Should_AddAuthenticationOptions()
{
    //  arrange
    var services = new ServiceCollection();

    //  act
    services.AddOpenIdConnect();

    //  assert
    var provider = services.BuildServiceProvider();
    var authOptions = provider.GetRequiredService<IOptions<AuthenticationOptions>>();

    authOptions.Value.DefaultScheme.Should().Be(CookieAuthenticationDefaults.AuthenticationScheme);
    authOptions.Value.DefaultChallengeScheme.Should().Be(OpenIdConnectDefaults.AuthenticationScheme);
}

此方法失败,因为 AccessDeniedPath 返回默认设置,而不是正在测试的代码设置的设置(您可以看到我注释掉了对 ExpireTimeSpan 的检查,因为它也失败了。)

[TestMethod]
public void AddOpenIdConnect_Should_AddCookieAuthenticationOptions()
{
    //  arrange
    var services = new ServiceCollection();

    //  act
    services.AddOpenIdConnect();

    //  assert
    var provider = services.BuildServiceProvider();
    var authCookieOptions = provider.GetRequiredService<IOptions<CookieAuthenticationOptions>>();

    //authCookieOptions.Value.ExpireTimeSpan.Should().Be(TimeSpan.FromHours(2));
    authCookieOptions.Value.SlidingExpiration.Should().BeTrue();
    authCookieOptions.Value.AccessDeniedPath.Should().Be("/Error/403");
}

任何帮助将不胜感激,因为在此之后我仍然需要添加“.AddOpenIdConnect(options =>”。

谢谢!

标签: c#unit-testingasp.net-coredependency-injection

解决方案


扩展程序的源代码AddCookie显示,在添加时使用身份验证方案配置命名选项CookieAuthenticationOptions

public static AuthenticationBuilder AddCookie(this AuthenticationBuilder builder, 
string authenticationScheme, string displayName, Action<CookieAuthenticationOptions> configureOptions)
{
    builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<CookieAuthenticationOptions>, PostConfigureCookieAuthenticationOptions>());
    builder.Services.AddOptions<CookieAuthenticationOptions>(authenticationScheme).Validate(o => o.Cookie.Expiration == null, "Cookie.Expiration is ignored, use ExpireTimeSpan instead.");
    return builder.AddScheme<CookieAuthenticationOptions, CookieAuthenticationHandler>(authenticationScheme, displayName, configureOptions);
}

源代码

注意

builder.Services.AddOptions<CookieAuthenticationOptions>(authenticationScheme) //<-- Named option

因此,需要使用注册选项时使用的相同方案才能访问 name 选项

这可以使用IOptionsSnapshot<TOptions>.Get(String)方法来完成

//...omitted for brevity

// Assert
var provider = services.BuildServiceProvider();
IOptionsSnapshot<CookieAuthenticationOptions> namedOptionsAccessor = 
    provider.GetRequiredService<IOptionsSnapshot<CookieAuthenticationOptions>>();

CookieAuthenticationOptions options = 
    namedOptionsAccessor.Get(CookieAuthenticationDefaults.AuthenticationScheme);

options.ExpireTimeSpan.Should().Be(TimeSpan.FromHours(2));
options.SlidingExpiration.Should().BeTrue();
options.AccessDeniedPath.Should().Be("/Error/403");

推荐阅读