Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
In het optiespatroon worden verschillende methoden voor het valideren van opties weergegeven. Deze methoden omvatten het gebruik van kenmerken van gegevensaantekeningen of het gebruik van een aangepaste validator. Attributen voor gegevensaantekening worden tijdens runtime gevalideerd en kunnen prestatienadelen hebben. In dit artikel wordt gedemonstreerd hoe u de validatiebrongenerator voor opties gebruikt voor het produceren van geoptimaliseerde validatiecode tijdens het compileren.
Automatisch gegenereerde implementatie van IValidateOptions
In het artikel over het optiespatroon ziet u hoe u de IValidateOptions<TOptions> interface implementeert voor het valideren van opties. De generator voor optiesvalidatiebron kan automatisch de IValidateOptions interface-implementatie maken door gebruik te maken van gegevensaantekeningskenmerken in de optiesklasse.
De volgende inhoud neemt het voorbeeld van annotatie-attributen dat wordt weergegeven in het patroon opties en converteert het om de optiesvalidatiebrongenerator te gebruiken.
Houd rekening met het volgende appsettings.json-bestand :
{
"MyCustomSettingsSection": {
"SiteTitle": "Amazing docs from awesome people!",
"Scale": 10,
"VerbosityLevel": 32
}
}
De volgende klasse bindt aan de "MyCustomSettingsSection" configuratiesectie en past een aantal DataAnnotations regels toe:
using System.ComponentModel.DataAnnotations;
namespace ConsoleJson.Example;
public sealed class SettingsOptions
{
public const string ConfigurationSectionName = "MyCustomSettingsSection";
[Required]
[RegularExpression(@"^[a-zA-Z''!'\s]{1,40}$")]
public required string SiteTitle { get; set; }
[Required]
[Range(0, 1_000,
ErrorMessage = "Value for {0} must be between {1} and {2}.")]
public required int? Scale { get; set; }
[Required]
public required int? VerbosityLevel { get; set; }
}
In de voorgaande SettingsOptions klasse bevat de ConfigurationSectionName eigenschap de naam van de configuratiesectie waaraan moet worden gekoppeld. In dit scenario bevat het optiesobject de naam van de configuratiesectie. De volgende kenmerken voor gegevensaantekening worden gebruikt:
- RequiredAttribute: Hiermee geeft u op dat de eigenschap is vereist.
- RegularExpressionAttribute: Hiermee geeft u op dat de eigenschapswaarde moet overeenkomen met het opgegeven reguliere expressiepatroon.
- RangeAttribute: Hiermee geeft u op dat de eigenschapswaarde binnen een opgegeven bereik moet liggen.
Aanbeveling
Naast de RequiredAttributeeigenschappen gebruiken de eigenschappen ook de vereiste modifier. Dit helpt ervoor te zorgen dat consumenten van het optiesobject niet vergeten de eigenschapswaarde in te stellen, hoewel deze niet betrekking heeft op de functie voor het genereren van validatiebronnen.
De volgende code illustreert hoe u de configuratiesectie verbindt met het optiesobject en de gegevensaantekeningen valideert:
using ConsoleJson.Example;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services
.AddOptions<SettingsOptions>()
.Bind(builder.Configuration.GetSection(SettingsOptions.ConfigurationSectionName));
builder.Services
.AddSingleton<IValidateOptions<SettingsOptions>, ValidateSettingsOptions>();
using IHost app = builder.Build();
var settingsOptions =
app.Services.GetRequiredService<IOptions<SettingsOptions>>().Value;
await app.RunAsync();
Aanbeveling
Wanneer AOT-compilatie is ingeschakeld door het <PublishAot>true</PublishAot> op te slaan, kan de code waarschuwingen genereren, zoals IL2025 en IL3050. Als u deze waarschuwingen wilt beperken, is het raadzaam om de configuratiebrongenerator te gebruiken. Als u de configuratiebrongenerator wilt inschakelen, voegt u de eigenschap <EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator> toe aan het projectbestand.
Door gebruik te maken van het genereren van compilatietijdbronnen voor optiesvalidatie, kunt u validatiecode genereren die is geoptimaliseerd voor prestaties en de noodzaak van weerspiegeling elimineren, wat resulteert in soepeler AOT-compatibele app-gebouw. De volgende code laat zien hoe u de validatiebrongenerator voor opties gebruikt:
using Microsoft.Extensions.Options;
namespace ConsoleJson.Example;
[OptionsValidator]
public partial class ValidateSettingsOptions : IValidateOptions<SettingsOptions>
{
}
De aanwezigheid van de OptionsValidatorAttribute op een lege gedeeltelijke klasse geeft de optie validatiebrongenerator opdracht om de IValidateOptions interface-implementatie te maken die valideert SettingsOptions. De code die wordt gegenereerd door de validatiebrongenerator voor opties, lijkt op het volgende voorbeeld:
// <auto-generated/>
#nullable enable
#pragma warning disable CS1591 // Compensate for https://github.com/dotnet/roslyn/issues/54103
namespace ConsoleJson.Example
{
partial class ValidateSettingsOptions
{
/// <summary>
/// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
/// </summary>
/// <param name="name">The name of the options instance being validated.</param>
/// <param name="options">The options instance.</param>
/// <returns>Validation result.</returns>
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "8.0.9.3103")]
[System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode",
Justification = "The created ValidationContext object is used in a way that never call reflection")]
public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::ConsoleJson.Example.SettingsOptions options)
{
global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
context.MemberName = "SiteTitle";
context.DisplayName = string.IsNullOrEmpty(name) ? "SettingsOptions.SiteTitle" : $"{name}.SiteTitle";
validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.SiteTitle, context, validationResults, validationAttributes))
{
(builder ??= new()).AddResults(validationResults);
}
context.MemberName = "Scale";
context.DisplayName = string.IsNullOrEmpty(name) ? "SettingsOptions.Scale" : $"{name}.Scale";
validationResults.Clear();
validationAttributes.Clear();
validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A3);
if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Scale, context, validationResults, validationAttributes))
{
(builder ??= new()).AddResults(validationResults);
}
context.MemberName = "VerbosityLevel";
context.DisplayName = string.IsNullOrEmpty(name) ? "SettingsOptions.VerbosityLevel" : $"{name}.VerbosityLevel";
validationResults.Clear();
validationAttributes.Clear();
validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.VerbosityLevel, context, validationResults, validationAttributes))
{
(builder ??= new()).AddResults(validationResults);
}
return builder is null ? global::Microsoft.Extensions.Options.ValidateOptionsResult.Success : builder.Build();
}
}
}
namespace __OptionValidationStaticInstances
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "8.0.9.3103")]
file static class __Attributes
{
internal static readonly global::System.ComponentModel.DataAnnotations.RequiredAttribute A1 = new global::System.ComponentModel.DataAnnotations.RequiredAttribute();
internal static readonly global::System.ComponentModel.DataAnnotations.RegularExpressionAttribute A2 = new global::System.ComponentModel.DataAnnotations.RegularExpressionAttribute(
"^[a-zA-Z''-'\\s]{1,40}$");
internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute A3 = new __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute(
(int)0,
(int)1000)
{
ErrorMessage = "Value for {0} must be between {1} and {2}."
};
}
}
namespace __OptionValidationStaticInstances
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "8.0.9.3103")]
file static class __Validators
{
}
}
namespace __OptionValidationGeneratedAttributes
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "8.0.9.3103")]
[global::System.AttributeUsage(global::System.AttributeTargets.Property | global::System.AttributeTargets.Field | global::System.AttributeTargets.Parameter, AllowMultiple = false)]
file class __SourceGen__RangeAttribute : global::System.ComponentModel.DataAnnotations.ValidationAttribute
{
public __SourceGen__RangeAttribute(int minimum, int maximum) : base()
{
Minimum = minimum;
Maximum = maximum;
OperandType = typeof(int);
}
public __SourceGen__RangeAttribute(double minimum, double maximum) : base()
{
Minimum = minimum;
Maximum = maximum;
OperandType = typeof(double);
}
public __SourceGen__RangeAttribute(global::System.Type type, string minimum, string maximum) : base()
{
OperandType = type;
NeedToConvertMinMax = true;
Minimum = minimum;
Maximum = maximum;
}
public object Minimum { get; private set; }
public object Maximum { get; private set; }
public bool MinimumIsExclusive { get; set; }
public bool MaximumIsExclusive { get; set; }
public global::System.Type OperandType { get; }
public bool ParseLimitsInInvariantCulture { get; set; }
public bool ConvertValueInInvariantCulture { get; set; }
public override string FormatErrorMessage(string name) =>
string.Format(global::System.Globalization.CultureInfo.CurrentCulture, GetValidationErrorMessage(), name, Minimum, Maximum);
private bool NeedToConvertMinMax { get; }
private bool Initialized { get; set; }
public override bool IsValid(object? value)
{
if (!Initialized)
{
if (Minimum is null || Maximum is null)
{
throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values.");
}
if (NeedToConvertMinMax)
{
System.Globalization.CultureInfo culture = ParseLimitsInInvariantCulture ? global::System.Globalization.CultureInfo.InvariantCulture : global::System.Globalization.CultureInfo.CurrentCulture;
Minimum = ConvertValue(Minimum, culture) ?? throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values.");
Maximum = ConvertValue(Maximum, culture) ?? throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values.");
}
int cmp = ((global::System.IComparable)Minimum).CompareTo((global::System.IComparable)Maximum);
if (cmp > 0)
{
throw new global::System.InvalidOperationException("The maximum value '{Maximum}' must be greater than or equal to the minimum value '{Minimum}'.");
}
else if (cmp == 0 && (MinimumIsExclusive || MaximumIsExclusive))
{
throw new global::System.InvalidOperationException("Cannot use exclusive bounds when the maximum value is equal to the minimum value.");
}
Initialized = true;
}
if (value is null or string { Length: 0 })
{
return true;
}
System.Globalization.CultureInfo formatProvider = ConvertValueInInvariantCulture ? global::System.Globalization.CultureInfo.InvariantCulture : global::System.Globalization.CultureInfo.CurrentCulture;
object? convertedValue;
try
{
convertedValue = ConvertValue(value, formatProvider);
}
catch (global::System.Exception e) when (e is global::System.FormatException or global::System.InvalidCastException or global::System.NotSupportedException)
{
return false;
}
var min = (global::System.IComparable)Minimum;
var max = (global::System.IComparable)Maximum;
return
(MinimumIsExclusive ? min.CompareTo(convertedValue) < 0 : min.CompareTo(convertedValue) <= 0) &&
(MaximumIsExclusive ? max.CompareTo(convertedValue) > 0 : max.CompareTo(convertedValue) >= 0);
}
private string GetValidationErrorMessage()
{
return (MinimumIsExclusive, MaximumIsExclusive) switch
{
(false, false) => "The field {0} must be between {1} and {2}.",
(true, false) => "The field {0} must be between {1} exclusive and {2}.",
(false, true) => "The field {0} must be between {1} and {2} exclusive.",
(true, true) => "The field {0} must be between {1} exclusive and {2} exclusive.",
};
}
private object? ConvertValue(object? value, System.Globalization.CultureInfo formatProvider)
{
if (value is string stringValue)
{
value = global::System.Convert.ChangeType(stringValue, OperandType, formatProvider);
}
else
{
value = global::System.Convert.ChangeType(value, OperandType, formatProvider);
}
return value;
}
}
}
De gegenereerde code is geoptimaliseerd voor prestaties en is niet afhankelijk van weerspiegeling. Het is ook AOT-compatibel. De gegenereerde code wordt in een bestand met de naam Validators.g.cs geplaatst.
Opmerking
U hoeft geen extra stappen uit te voeren om de validatiebrongenerator voor opties in te schakelen. Deze functie wordt standaard automatisch ingeschakeld wanneer uw project verwijst naar Microsoft.Extensions.Options versie 8 of hoger, of wanneer u een ASP.NET toepassing bouwt.
De enige stap die u moet uitvoeren, is door het volgende toe te voegen aan de opstartcode:
builder.Services
.AddSingleton<IValidateOptions<SettingsOptions>, ValidateSettingsOptions>();
Opmerking
Aanroepen OptionsBuilderDataAnnotationsExtensions.ValidateDataAnnotations<TOptions>(OptionsBuilder<TOptions>) is niet vereist wanneer u de validatiebrongenerator voor opties gebruikt.
Wanneer de toepassing probeert toegang te krijgen tot het optiesobject, wordt de gegenereerde code voor optiesvalidatie uitgevoerd om het optiesobject te valideren. In het volgende codefragment ziet u hoe u toegang hebt tot het optiesobject:
var settingsOptions =
app.Services.GetRequiredService<IOptions<SettingsOptions>>().Value;
Kenmerken van vervangende data-annotatie
Wanneer u de gegenereerde code goed bekijkt, ziet u dat de oorspronkelijke kenmerken van de gegevensaantekening, zoals RangeAttribute, die oorspronkelijk op de eigenschap SettingsOptions.Scalezijn toegepast , zijn vervangen door aangepaste kenmerken, zoals __SourceGen__RangeAttribute. Deze vervanging wordt gemaakt omdat het RangeAttribute afhankelijk is van reflectie voor validatie. Het is daarentegen __SourceGen__RangeAttribute een aangepast kenmerk dat is geoptimaliseerd voor prestaties en is niet afhankelijk van weerspiegeling, waardoor de code compatibel is met AOT. Hetzelfde patroon van kenmerkvervanging wordt toegepast op MaxLengthAttribute, MinLengthAttributeen LengthAttribute naast RangeAttribute.
Voor iedereen die aangepaste kenmerken voor gegevensaantekening ontwikkelt, is het raadzaam om af te zien van weerspiegeling voor validatie. In plaats daarvan is het raadzaam om sterk getypte code te maken die niet afhankelijk is van weerspiegeling. Deze aanpak zorgt voor een soepele compatibiliteit met AOT-builds.