【问题标题】:How to change configuration at runtime in ASP.NET Core如何在 ASP.NET Core 运行时更改配置
【发布时间】:2021-06-29 19:13:50
【问题描述】:

我希望能够在运行时更改配置设置,因为我将处理包含某个自定义标头的请求,该标头将从我的接受/冒烟测试中发送。其他传入请求将需要使用默认配置。

对于命中 API 的测试,我想加载包含将返回模拟数据的 URL 的 JSON 文件并在设置中替换它们。

我尝试了不同的方法,在我需要设置的地方注入了 IOptionsSnapshot 或 IOptionsMonitor,但它似乎没有获取新值,试图在执行中间件时替换配置值,但没有运气。

我看到了不同的方法,它们将应用程序设置文件的配置与我想要加载和丢弃的配置文件合并,您能否告诉我,最有效的方法是什么?难道没有比读取 JSON 文件、更新值和保存新的应用程序设置文件更简单的事情了吗?谢谢!

【问题讨论】:

  • 为什么不在测试中加载不同的配置?这就是environments 的用途。
  • 您需要在每个环境中拥有不同的 appetttngs 文件并覆盖它们。这就是它在 .net 核心中的完成方式。
  • 您使用同一个应用程序实例进行产品和集成测试?如果是 - 尽可能停止这样做。如果不是 - 那么您可以使用环境设置文件的概念(第 3 点 here)。
  • @Crowcoder,设置有点不同,我知道你在说什么,想法是测试会触及实际的 API,更像是验收测试而不是集成测试,我例如,认为该术语应该是验收测试,并且能够动态交换配置,以返回我们使用的第 3 方 URL 的模拟数据。
  • 你可以将这些json文件分开并使用config.AddJsonFile("json_array.json", optional: false, reloadOnChange: true);

标签: c# asp.net-core


【解决方案1】:

选项 A 如果您正在使用 Microsoft.AspNetCore.Mvc.Testing 库运行测试,您可以使用不同的环境、环境变量或 appsettings 文件,正如一些在 cmets 中提到的那样。 我在 xUnit 下的所有合同/组件/功能(无论您想如何称呼它们)测试都继承自一个基类,该基类抽象出配置,以便测试可以专注于给定/何时/然后部分。

public abstract class Given_When_Then_Test_Async 
    : IAsyncLifetime
{
    public async Task InitializeAsync()
    {
        await this.Given();
        await this.When();
    }

    public async Task DisposeAsync() => await this.Cleanup();

    protected virtual Task Cleanup() => Task.CompletedTask;

    protected abstract Task Given();

    protected abstract Task When();
}

public static class ServiceCollectionExtensions
{
    public static IServiceCollection Replace<TService>(
        this IServiceCollection services,
        Func<IServiceProvider, TService> implementationFactory,
        ServiceLifetime lifetime)
        where TService : class
    {
        var descriptorToRemove = services.FirstOrDefault(d => d.ServiceType == typeof(TService));

        services.Remove(descriptorToRemove);

        var descriptorToAdd = new ServiceDescriptor(typeof(TService), implementationFactory, lifetime);

        services.Add(descriptorToAdd);

        return services;
    }
}

public abstract class FunctionalTest
    : Given_When_Then_Test_Async
{
    private readonly IServiceProvider _serviceProvider;
    protected HttpClient HttpClient { get; }
    protected IConfiguration Configuration { get; }
    protected FunctionalTest()
    {
        var server =
            new TestServer(
                new WebHostBuilder()
                    .UseStartup<Startup>()
                    .UseCommonConfiguration()
                    .UseEnvironment("Test") // here I specify that my environment is called Test, so it'll look for appsettings.Test.json
                    .ConfigureTestServices(ConfigureTestServices));

        HttpClient = server.CreateClient();
        _serviceProvider = server.Services;
        Configuration = _serviceProvider.GetService<IConfiguration>();
    }

    protected T GetService<T>() where T : class
    {
        return _serviceProvider.GetService<T>();
    }

    protected virtual void ConfigureTestServices(IServiceCollection services)
    {
        // replace here whichever service you want for all tests
    }
}

因此,在您的测试干净的所有样板之后,使用真正的实现(相同的 Startup),仅使用您指定的模拟(如果有)。

public class Given_Clients_In_Database_When_Getting_Filtered_Clients_By_Name
    : FunctionalTest
{
    private string _url;
    private HttpResponseMessage _result;

    protected override void ConfigureTestServices(IServiceCollection services)
    {
        base.ConfigureTestServices(services);
        // replace here the service you use to read settings with mocks
        // e.g: services.Replace(typeof(IConfiguration),...)
    }

    protected override async Task Given()
    {
        _url = "api/clients?name=foo";
    }

    protected override async Task When()
    {
        _result = await HttpClient.GetAsync(_url);
    }

    [Fact]
    public void Then_It_Should_Return_200_Ok()
    {
        _result.StatusCode.Should().Be(HttpStatusCode.OK);
    }
}

重要提示:以上配置允许覆盖已注册的服务、appsettings 文件等。还可以通过使用环境变量来覆盖特定的设置值。

只需设置任何环境变量,如Environment.SetEnvironmentVariable("Whichever__Whatever", "strongerPreferredValue");appsettings.Test.jsonappsettings.json 中的Whichever:Whatever 设置就会被替换。

选项 B 看起来您可能希望在“真实”环境中运行这些测试,也许您不使用 TestServer。不过我建议不要这样做。

如果是烟雾测试或刺激测试,您需要真实的东西,没有假货。否则,它就不是真正的冒烟测试,因为它不能保证它对最终用户运行良好。

如果它是在受控环境下进行的某种集成测试,但像任何生产应用程序通常那样作为进程运行,我会考虑为这些事情提供一个单独的环境,您不必动态更改任何东西,你只需在哪里配置那个环境的设置。

如果您仍然认为最好运行应用程序并动态修改设置,那么您能做的最好的事情就是抽象出读取设置的功能,并确保您的应用程序中的所有内容都使用这种新的抽象来检索设置。

例如,您可以为IConfiguration 创建一个包装器

public interface IMySettings
{
    object GetSettings(string name);
}

并且实现可以是

public class MySettings : IMySettings
{
   private readonly IConfiguration _configuration;
   
   public MySettings(IConfiguration configuration)
   {
      _configuration = configuration;
   }

   public object GetSettings(string name)
   {
      //Here your logic to return the real setting value as per environment, environment variable or appsettings. Or a custom one.
      // or delegate in the IConfiguration as normal
      return _configuration.GetValue<object>("Whichever:Whatever");
   }
}

不要忘记注册services.AddTransient&lt;IMySettings, MySettings&gt;(); 并将IMySettings 注入您需要访问设置的任何控制器或服务中。

PS:对于 OptionA 中的测试方法,我有一个视频(西班牙语,抱歉),通过该方法。 https://www.youtube.com/watch?v=dyVEayGwU3I 还有一个代码示例位于https://gitlab.com/diegosasw/arquitectura-software/-/tree/master/src/testing

【讨论】:

    猜你喜欢
    • 2019-06-07
    • 1970-01-01
    • 1970-01-01
    • 2021-06-21
    • 1970-01-01
    • 2019-02-02
    • 2018-04-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多