Почему IOptions разрешается, даже если он не зарегистрирован

В моем проекте.NET Core у меня есть следующие настройки в методе Configure:

public void ConfigureServices(IServiceCollection services)
{
    services
        .AddMvc()
        .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

    //services.AddOptions<UploadConfig>(Configuration.GetSection("UploadConfig"));
}

Я не зарегистрировал ни одного IOptions и я впрыскиваю это в контроллере

[Route("api/[controller]")]
[ApiController]
public class HelloWorldController : ControllerBase
{
    public HelloWorldController(IOptions<UploadConfig> config)
    {
        var config1 = config.Value.Config1;
    }
 }

IOptions разрешается с экземпляром по умолчанию, и я узнаю об ошибке только тогда, когда пытаюсь ее использовать (и когда я ожидаю, что значение будет не нулевым).

Можно ли как-то заставить его выйти из строя, указав, что тип экземпляра не зарегистрирован или что-то подобное? Я просто хочу ловить ошибки как можно раньше.

2 ответа

Решение

Инфраструктура опций устанавливается компоновщиком хоста по умолчанию как часть его настройки, поэтому вам не нужно AddOptions() сам. Это, однако, также гарантирует, что вы можете использовать IOptions<T> везде, где вы хотите, так как фреймворк предоставит вам этот объект опций.

Работа опций заключается в том, что фреймворк всегда даст вам T (до тех пор, пока он может построить один). Когда вы настраиваете конфигурацию, используя, например, AddOptions<T> или же Configure<T> на самом деле происходит то, что действие конфигурации регистрируется для этого типа T, И когда IOptions<T> позднее разрешается, все эти зарегистрированные действия будут выполняться в той последовательности, в которой они зарегистрированы.

Это означает, что допустимо не настраивать тип параметров. В этом случае будут использоваться значения по умолчанию для объекта. Конечно, это также означает, что вы не можете определить, действительно ли вы настроили тип параметров и является ли конфигурация действительной. Обычно это нужно делать, когда вы используете значения.

Например, если вам требуется Config1 чтобы быть настроенным, вы должны явно искать это:

public HelloWorldController(IOptions<UploadConfig> config)
{
    if (string.IsNullOrEmpty(config.Value.Config1))
        throw ArgumentException("Config1 is not configured properly");
}

Альтернативой было бы зарегистрировать действие проверки для типа, используя OptionsBuilder.Validate, Затем он будет вызываться автоматически, когда вы повторно определяете объект параметров для проверки содержащего его значения. Таким образом, вы можете настроить проверку в центральном месте:

services.AddOptions<UploadConfig>()
    .Bind(Configuration.GetSection("UploadConfig"))
    .Validate(c => !string.IsNullOrEmpty(c.Config1));

К сожалению, это также означает, что вы можете обнаружить эти проблемы, только когда вы действительно используете значения, которые могут быть пропущены, если вы не проводите тщательное тестирование приложения. Обходным путем было бы разрешить параметры один раз при запуске приложения и проверить их там.

Например, вы можете просто ввести IOptions<T> в вашем стартапе Configure метод:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, IOptions<UploadConfig> uploadOptions)
{
    // since the options are injected here, they will be constructed and automatically
    // validated if you have configured a validate action

    // …
    app.UseMvc();
}

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

public class OptionsValidator
{
    private readonly IOptions<UploadConfig> uploadOptions;
    public OptionsValidator(IOptions<UploadConfig> uploadOptions)
    {
        _uploadOptions = uploadOptions;
    }

    public void Validate()
    {
        if (string.IsNullOrEmpty(_uploadOptions.Value.Config1))
            throw Exception("Upload options are not configured properly");
    }
}

А потом вколоть это в свой Configure:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, OptionsValidator optionsValidator)
{
    // validate options explicitly
    optionsValidator.Validate();

    // …
    app.UseMvc();
}

Что бы вы ни делали, имейте в виду, что по умолчанию источники конфигурации настроены для поддержки обновления конфигурации во время выполнения. Таким образом, у вас всегда будет ситуация, когда конфигурация может быть временно недействительной во время выполнения.

Отличный ответ от poke, я просто хочу завершить это тем, что вы можете быстро завершить работу с ошибкой в ​​вашем файле запуска, когда не предоставлена ​​конфигурация:

public class MyOptions
{
    public string MyValue { get; set; }
}

public void ConfigureServices(IServiceCollection services)
{
    var options = Configuration.GetSection("MyOptions").Get<MyOptions>();
    if (string.IsNullOrWhiteSpace(options?.MyValue))
    {
        throw new ApplicationException("MyValue is not configured!");
    }
}

Значения конфигурации IOptions читаются лениво. Хотя файл конфигурации может быть прочитан при запуске приложения, требуемый объект конфигурации создается только при первом вызове IOptions.Value.

При сбое десериализации из-за неправильной конфигурации приложения такая ошибка появится только после вызова IOptions.Value. Это может привести к тому, что неправильные конфигурации останутся незамеченными гораздо дольше, чем требуется. С помощью чтения и проверки значений конфигурации при запуске приложения эту проблему можно предотвратить.

Эта статья также поможет вам понять:

IOptions - это плохо?

ASP.NET Core 2.2 - проверка параметров

Другие вопросы по тегам