Как получить список IOptions, доступных из DI в c # Core? - PullRequest
0 голосов
/ 08 января 2019

После этого поста: https://blog.bredvid.no/validating-configuration-in-asp-net-core-e9825bd15f10

Теперь я могу проверить настройки, когда сервису это нужно. То, что я хотел бы сделать, это проверить непосредственно при запуске сервера в program.cs.

Я не уверен, как это сделать? Есть ли способ получить список служб, введенных в DI, затем проверить, можно ли назначить тип из IOption, и зарегистрировать его?

Вот как я могу добавить в DI Настройки:

    //App settings
    services.ConfigureAndValidate<AuthenticationSettings>(Configuration);
    services.ConfigureAndValidate<SmtpSettings>(Configuration);

Код расширения:

public static class IServiceCollectionExtensions
    {
        public static IServiceCollection ConfigureAndValidate<T>(
            this IServiceCollection serviceCollection,
            IConfiguration config,
            string section = null
        ) where T : class
        {
            var configType = typeof(T).Name;
            if (string.IsNullOrEmpty(section)) { 
                section = configType;
            }

            return serviceCollection
                .Configure<T>(config.GetSection(section))
                .PostConfigure<T>(settings =>
                {
                    var configErrors = settings.ValidationErrors().ToArray();
                    if (configErrors.Any())
                    {
                        var aggrErrors = string.Join(",", configErrors);
                        var count = configErrors.Length;
                        throw new ApplicationException($"Found {count} configuration error(s) in {configType}: {aggrErrors}");
                    }
                });
        }

        private static IEnumerable<string> ValidationErrors(this object obj)
        {
            var context = new ValidationContext(obj, serviceProvider: null, items: null);
            var results = new List<ValidationResult>();
            Validator.TryValidateObject(obj, context, results, true);
            foreach (var validationResult in results)
            {
                yield return validationResult.ErrorMessage;
            }
        }
    }

Вот мой текущий лаунчер:

public class Program
{
    public static async Task Main(string[] args)
    {
        var webHost = new WebHostBuilder()
            .UseKestrel()
            .UseContentRoot(Directory.GetCurrentDirectory())
            .ConfigureAppConfiguration((hostingContext, config) =>
            {
                config.AddEnvironmentVariables();

                var env = hostingContext.HostingEnvironment;

                config.SetBasePath(env.ContentRootPath)
                      .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                      .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
            })
            .ConfigureLogging((hostingContext, logging) =>
            {
                logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
                logging.AddConsole();
                logging.AddDebug();
            })
            .UseStartup<Startup>()
            .Build();

        using (var scope = webHost.Services.CreateScope())
        {
            var services = scope.ServiceProvider;

            /// <---- BEGIN / AN IDEA OF WHAT I WOULD LIKE TO DO ---->
            /// <---- THIS CODE IS NOT WORKING ---->
            var allServices = services.GetAllServices();
            if (allServices != null)
            {
                foreach (var service in allServices )
                {
                    if (service.ServiceType.IsAssignableFrom(IOptions))
                    {
                       services.GetRequiredService<service.ServiceType>()
                    }
                }
            }
            /// <---- END ---->
        }

        await webHost.RunAsync();
    }
}

Дайте мне знать, если у вас есть какие-либо предложения в комментарии.

Спасибо за вашу помощь.


РЕДАКТИРОВАТЬ 1: Спасибо Стивену за вашу помощь, с вашим ответом, он помог мне продолжить находить ответ, но вещи все еще отсутствуют.

теперь все мои настройки наследуются от ISettings, например:

public class AuthenticationSettings : ISettings
{
    [Required]
    public string Issuer { get; set; }
    [Required]
    public string Audience { get; set; }
    [Required]
    public string SecretKey { get; set; }
    [Required]
    public int ExpirationDurationInDays { get; set; }
}

Я обновляю Program.cs как:

using Autofac;
using Autofac.Core;



var options = services.GetService<ILifetimeScope>()
   .ComponentRegistry
   .Registrations.SelectMany(e => e.Services)
   .Select(s => s as TypedService)
   .Where(s => s.ServiceType.IsGenericType && s.ServiceType.GetGenericTypeDefinition() == typeof(IConfigureOptions<>))
   .Select(s => s.ServiceType.GetGenericArguments()[0])
   .Where(s => typeof(ISettings).IsAssignableFrom(s))
   .ToList();

так что теперь мне нужно создать экземпляр каждой опции в опциях и получить значение. Я все еще работаю над этим. дайте мне знать, если у вас есть предложение или решение:)

Ответы [ 2 ]

0 голосов
/ 08 января 2019

Следуя предложениям Стивена, вот мое решение: Служба проверки моих настроек

    public SettingsValidator(
        IServiceProvider services,
        ILifetimeScope scope
    )
    {
        var types = scope.ComponentRegistry.Registrations
            .SelectMany(e => e.Services)
            .Select(s => s as TypedService)
            .Where(s => s.ServiceType.IsAssignableToGenericType(typeof(IConfigureOptions<>)))
            .Select(s => s.ServiceType.GetGenericArguments()[0])
            .Where(s => typeof(ISettings).IsAssignableFrom(s))
            .ToList();

        foreach (var t in types)
        {
            var option = services.GetService(typeof(IOptions<>).MakeGenericType(new Type[] { t }));
            option.GetPropertyValue("Value");
        }
    }

при запуске:

        builder.RegisterType<SettingsValidator>();

пример настроек

public class AzureStorageSettings : ISettings
{
    [Required]
    public string ConnectionString { get; set; }
    [Required]
    public string Container { get; set; }
    [Required]
    public string Path { get; set; }
}

1010 * Расширения *

public static class TypeExtensions
{
    public static bool IsAssignableToGenericType(this Type givenType, Type genericType)
    {
        foreach (var it in givenType.GetInterfaces())
        {
            if (it.IsGenericType && it.GetGenericTypeDefinition() == genericType)
                return true;
        }

        if (givenType.IsGenericType && givenType.GetGenericTypeDefinition() == genericType)
            return true;

        Type baseType = givenType.BaseType;
        if (baseType == null) return false;

        return IsAssignableToGenericType(baseType, genericType);
    }
}

в программе.cs

using (var scope = webHost.Services.CreateScope())
        {
            var services = scope.ServiceProvider;
            var logger = services.GetRequiredService<ILogger<Program>>();
            try
            {
                logger.LogInformation("Starting settings validation.");
                services.GetRequiredService<SettingsValidator>();
                logger.LogInformation("The settings have been validated.");
            }
            catch (Exception ex)
            {
                logger.LogError(ex, "An error occurred while validating the settings.");
            }
        }

Дайте мне знать, если это работает и у вас:)

0 голосов
/ 08 января 2019

Вы можете получить список настроенных типов опций, повторяя экземпляр IServiceCollection:

var configuredOptionTypes =
    from descriptor in services
    let serviceType = descriptor.ServiceType
    where serviceType.IsGenericType
    where serviceType.GetGenericTypeDefinition() == typeof(IConfigureNamedOptions<>)
    let optionType = serviceType.GetGenericArguments()[0]
    select optionType;
...