Skip to content

Commit

Permalink
Demo IOptionsSnapshot, IOptionsMonitor, IOptions
Browse files Browse the repository at this point in the history
  • Loading branch information
tgharold committed Mar 5, 2020
1 parent 8ac221f commit b77e814
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,38 @@ public class WeatherForecastController : ControllerBase
private readonly ILogger<WeatherForecastController> _logger;

private readonly DatabaseOptions _databaseOptions;
private UnvalidatedOptions _unvalidatedOptions;
private readonly UnvalidatedOptions _unvalidatedOptions;
private readonly MonitoredSettingsOptions _monitoredSettingsOptions;
private readonly UnmonitoredButValidatedOptions _unmonitoredButValidatedOptionsAccessor;

public WeatherForecastController(
ILogger<WeatherForecastController> logger,
IOptionsSnapshot<DatabaseOptions> databaseOptionsAccessor,
IOptions<UnvalidatedOptions> unvalidatedOptionsAccessor
IOptions<UnvalidatedOptions> unvalidatedOptionsAccessor,
IOptionsMonitor<MonitoredSettingsOptions> monitoredSettingsOptionsAccessor,
IOptions<UnmonitoredButValidatedOptions> unmonitoredButValidatedOptionsAccessor
)
{
_logger = logger;

_unvalidatedOptions = unvalidatedOptionsAccessor.Value;

/* if IOptions validation fails, code will error out here (first access)
/* If options-pattern validation fails, code will error out here. However, it will error out on the
* first object that fails to validate. You could wrap a try-catch around all of the calls.
*
* example:
* - OptionsValidationException: Comment1 is required.; DatabaseType is required.; Comment2 is required.;
* SchemaNames.Schema1 is required.; SchemaNames.Schema2 is required.
*/

// There's no validator for this one.
_unvalidatedOptions = unvalidatedOptionsAccessor.Value;

// IOptions<T> is a singleton that only validates on the first-use (anywhere).
_unmonitoredButValidatedOptionsAccessor = unmonitoredButValidatedOptionsAccessor.Value;

// IOptionsMonitor<T> only runs when the underlying file has actually changed (still a singleton).
_monitoredSettingsOptions = monitoredSettingsOptionsAccessor.CurrentValue;

// IOptionsSnapshot<T> runs validation on every new request.
_databaseOptions = databaseOptionsAccessor.Value;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Example3Api.Attributes;

namespace Example3Api.Options
{
[ConfigurationSectionName("MonitoredSettings")]
public class MonitoredSettingsOptions
{
public string MonitorA { get; set; }
public string MonitorB { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Example3Api.Attributes;

namespace Example3Api.Options
{
[ConfigurationSectionName("UnmonitoredButValidated")]
public class UnmonitoredButValidatedOptions
{
public string OptionA { get; set; }
public string OptionB { get; set; }
public string OptionC { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Example3Api.Attributes;
using Example3Api.Options;
using Microsoft.Extensions.Options;

namespace Example3Api.OptionsValidators
{
public class MonitoredSettingsOptionsValidator : IValidateOptions<MonitoredSettingsOptions>
{
public ValidateOptionsResult Validate(
string name,
MonitoredSettingsOptions options
)
{
var sectionName = typeof(MonitoredSettingsOptions).GetCustomAttribute<ConfigurationSectionNameAttribute>()?.SectionName
?? throw new ArgumentNullException(nameof(ConfigurationSectionNameAttribute));

if (options is null)
return ValidateOptionsResult.Fail($"Configuration object is null for section '{sectionName}'.");

var failureMessages = new List<string>();

if (string.IsNullOrWhiteSpace(options.MonitorA))
failureMessages.Add($"{sectionName}.{nameof(options.MonitorA)} is required.");

if (string.IsNullOrWhiteSpace(options.MonitorB))
failureMessages.Add($"{sectionName}.{nameof(options.MonitorB)} is required.");

return failureMessages.Any()
? ValidateOptionsResult.Fail(failureMessages)
: ValidateOptionsResult.Success;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Example3Api.Attributes;
using Example3Api.Options;
using Microsoft.Extensions.Options;

namespace Example3Api.OptionsValidators
{
public class UnmonitoredButValidatedOptionsValidator : IValidateOptions<UnmonitoredButValidatedOptions>
{
public ValidateOptionsResult Validate(
string name,
UnmonitoredButValidatedOptions options
)
{
var sectionName = typeof(UnmonitoredButValidatedOptions).GetCustomAttribute<ConfigurationSectionNameAttribute>()?.SectionName
?? throw new ArgumentNullException(nameof(ConfigurationSectionNameAttribute));

if (options is null)
return ValidateOptionsResult.Fail($"Configuration object is null for section '{sectionName}'.");

var failureMessages = new List<string>();

if (string.IsNullOrWhiteSpace(options.OptionA))
failureMessages.Add($"{sectionName}.{nameof(options.OptionA)} is required.");

if (string.IsNullOrWhiteSpace(options.OptionB))
failureMessages.Add($"{sectionName}.{nameof(options.OptionB)} is required.");

if (string.IsNullOrWhiteSpace(options.OptionC))
failureMessages.Add($"{sectionName}.{nameof(options.OptionC)} is required.");

return failureMessages.Any()
? ValidateOptionsResult.Fail(failureMessages)
: ValidateOptionsResult.Success;
}
}
}
2 changes: 2 additions & 0 deletions Example3-IValidateOptions/src/Example3Api/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ public void ConfigureServices(IServiceCollection services)
{
services.ConfigureAndValidateSection<DatabaseOptions, DatabaseOptionsValidator>(_configuration);
services.ConfigureAndValidateSection<ConnectionStringsOptions, ConnectionStringsOptionsValidator>(_configuration);
services.ConfigureAndValidateSection<MonitoredSettingsOptions, MonitoredSettingsOptionsValidator>(_configuration);
services.ConfigureAndValidateSection<UnmonitoredButValidatedOptions, UnmonitoredButValidatedOptionsValidator>(_configuration);
services.ConfigureSection<UnvalidatedOptions>(_configuration);

services.AddControllers();
Expand Down
9 changes: 9 additions & 0 deletions Example3-IValidateOptions/src/Example3Api/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,19 @@
}
},
"AllowedHosts": "*",
"UnmonitoredButValidated": {
"OptionA": "Apple",
"OptionB": "Peach",
"OptionC": "Panda"
},
"Unvalidated": {
"ParameterA": null,
"ParameterB": "Chekov"
},
"MonitoredSettings": {
"MonitorA": "Francis",
"MonitorB": "Frannie"
},
"Database": {
"Comment1": null,
"DatabaseType": "Charlie",
Expand Down

0 comments on commit b77e814

Please sign in to comment.