.NET Core позволяет добавлять множество параметров (переменные окружения, файлы json, аргументы командной строки).
Я просто не могу понять и найти ответ, как заполнить его с помощью кода.
Я пишу модульные тесты для методов расширения конфигураций, и я подумал, что заполнить их в модульных тестах с помощью кода будет проще, чем загружать выделенные файлы json для каждого теста.
Мой текущий код:
[Fact]
public void Test_IsConfigured_Positive()
{
// test against this configuration
IConfiguration config = new ConfigurationBuilder()
// how to populate it via code
.Build();
// the extension method to test
Assert.True(config.IsConfigured());
}
Обновление:
Одним из особых случаев является «пустая секция», которая будет выглядеть так в json.
{
"MySection": {
// the existence of the section activates something triggering IsConfigured to be true but does not overwrite any default value
}
}
Обновление 2:
Как указал Мэтью в комментариях, пустой раздел в json дает тот же результат, что и отсутствие раздела вообще. Я привел пример и да, это так. Я был неправ, ожидая другого поведения.
Итак, что мне делать и чего я ожидал:
Я пишу модульные тесты для 2 методов расширения для IConfiguration (на самом деле, потому что привязка значений в методе Get ... Settings не работает по какой-то причине (но это другая тема). Они выглядят так:
public static bool IsService1Configured(this IConfiguration configuration)
{
return configuration.GetSection("Service1").Exists();
}
public static MyService1Settings GetService1Settings(this IConfiguration configuration)
{
if (!configuration.IsService1Configured()) return null;
MyService1Settings settings = new MyService1Settings();
configuration.Bind("Service1", settings);
return settings;
}
Мое недоразумение состояло в том, что, если я помещу пустой раздел в настройки приложения, метод IsService1Configured()
вернет true
(что сейчас явно не так). Разница, которую я ожидал, состоит в том, что теперь с пустым разделом метод GetService1Settings()
возвращает null
, а не как я ожидал, MyService1Settings
со всеми значениями по умолчанию.
К счастью, это все еще работает для меня, так как у меня не будет пустых разделов (или теперь я знаю, что должен избегать этих случаев). Это был только один теоретический случай, с которым я столкнулся при написании модульных тестов.
Далее по дороге (для тех, кто интересуется).
Для чего я это использую? Активация / деактивация услуги на основе конфигурации.
У меня есть приложение, в которое встроена служба / некоторые службы. В зависимости от развертывания мне нужно полностью активировать / деактивировать сервисы. Это связано с тем, что некоторые (локальные или тестовые настройки) не имеют полного доступа к полной инфраструктуре (вспомогательные сервисы, такие как кэширование, метрики ...). И я делаю это через appsettings. Если служба настроена (раздел config существует), она будет добавлена. Если раздел конфигурации отсутствует, он не будет использоваться.
Полный код приведенного ниже примера приведен ниже.
- в Visual Studio создайте новый API с именем WebApplication1 из шаблонов (без HTTPS и аутентификации)
- удалить класс Startup и appsettings.Development.json
- заменить код в Program.cs на код ниже
- теперь в appsettings.json вы можете активировать / деактивировать сервисы, добавив / удалив
Service1
и Service2
раздел
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System;
namespace WebApplication1
{
public class MyService1Settings
{
public int? Value1 { get; set; }
public int Value2 { get; set; }
public int Value3 { get; set; } = -1;
}
public static class Service1Extensions
{
public static bool IsService1Configured(this IConfiguration configuration)
{
return configuration.GetSection("Service1").Exists();
}
public static MyService1Settings GetService1Settings(this IConfiguration configuration)
{
if (!configuration.IsService1Configured()) return null;
MyService1Settings settings = new MyService1Settings();
configuration.Bind("Service1", settings);
return settings;
}
public static IServiceCollection AddService1(this IServiceCollection services, IConfiguration configuration, ILogger logger)
{
MyService1Settings settings = configuration.GetService1Settings();
if (settings == null) throw new Exception("loaded MyService1Settings are null (did you forget to check IsConfigured in Startup.ConfigureServices?) ");
logger.LogAsJson(settings, "MyServiceSettings1: ");
// do what ever needs to be done
return services;
}
public static IApplicationBuilder UseService1(this IApplicationBuilder app, IConfiguration configuration, ILogger logger)
{
// do what ever needs to be done
return app;
}
}
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureLogging
(
builder =>
{
builder.AddDebug();
builder.AddConsole();
}
)
.UseStartup<Startup>();
}
public class Startup
{
public IConfiguration Configuration { get; }
public ILogger<Startup> Logger { get; }
public Startup(IConfiguration configuration, ILoggerFactory loggerFactory)
{
Configuration = configuration;
Logger = loggerFactory.CreateLogger<Startup>();
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// flavour 1: needs check(s) in Startup method(s) or will raise an exception
if (Configuration.IsService1Configured()) {
Logger.LogInformation("service 1 is activated and added");
services.AddService1(Configuration, Logger);
} else
Logger.LogInformation("service 1 is deactivated and not added");
// flavour 2: checks are done in the extension methods and no Startup cluttering
services.AddOptionalService2(Configuration, Logger);
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment()) app.UseDeveloperExceptionPage();
// flavour 1: needs check(s) in Startup method(s) or will raise an exception
if (Configuration.IsService1Configured()) {
Logger.LogInformation("service 1 is activated and used");
app.UseService1(Configuration, Logger); }
else
Logger.LogInformation("service 1 is deactivated and not used");
// flavour 2: checks are done in the extension methods and no Startup cluttering
app.UseOptionalService2(Configuration, Logger);
app.UseMvc();
}
}
public class MyService2Settings
{
public int? Value1 { get; set; }
public int Value2 { get; set; }
public int Value3 { get; set; } = -1;
}
public static class Service2Extensions
{
public static bool IsService2Configured(this IConfiguration configuration)
{
return configuration.GetSection("Service2").Exists();
}
public static MyService2Settings GetService2Settings(this IConfiguration configuration)
{
if (!configuration.IsService2Configured()) return null;
MyService2Settings settings = new MyService2Settings();
configuration.Bind("Service2", settings);
return settings;
}
public static IServiceCollection AddOptionalService2(this IServiceCollection services, IConfiguration configuration, ILogger logger)
{
if (!configuration.IsService2Configured())
{
logger.LogInformation("service 2 is deactivated and not added");
return services;
}
logger.LogInformation("service 2 is activated and added");
MyService2Settings settings = configuration.GetService2Settings();
if (settings == null) throw new Exception("some settings loading bug occured");
logger.LogAsJson(settings, "MyService2Settings: ");
// do what ever needs to be done
return services;
}
public static IApplicationBuilder UseOptionalService2(this IApplicationBuilder app, IConfiguration configuration, ILogger logger)
{
if (!configuration.IsService2Configured())
{
logger.LogInformation("service 2 is deactivated and not used");
return app;
}
logger.LogInformation("service 2 is activated and used");
// do what ever needs to be done
return app;
}
}
public static class LoggerExtensions
{
public static void LogAsJson(this ILogger logger, object obj, string prefix = null)
{
logger.LogInformation(prefix ?? string.Empty) + ((obj == null) ? "null" : JsonConvert.SerializeObject(obj, Formatting.Indented)));
}
}
}