Not
Bu sayfaya erişim yetkilendirme gerektiriyor. Oturum açmayı veya dizinleri değiştirmeyi deneyebilirsiniz.
Bu sayfaya erişim yetkilendirme gerektiriyor. Dizinleri değiştirmeyi deneyebilirsiniz.
Seçenekler deseninde, seçenekleri doğrulamaya yönelik çeşitli yöntemler sunulur. Bu yöntemler, veri ek açıklaması özniteliklerini kullanmayı veya özel bir doğrulayıcı kullanmayı içerir. Veri ek açıklaması öznitelikleri çalışma zamanında doğrulanır ve performans maliyetlerine neden olabilir. Bu makalede, derleme zamanında iyileştirilmiş doğrulama kodu üretmek için seçenekler doğrulama kaynak oluşturucusunun nasıl kullanıldığı gösterilmektedir.
Otomatik IValidateOptions uygulama oluşturma
Seçenekler deseni makalesi, seçenekleri doğrulamak için IValidateOptions<TOptions> arabiriminin nasıl uygulanacağını göstermektedir. Seçenekler doğrulama kaynak oluşturucu, seçenekler sınıfındaki IValidateOptions veri ek açıklaması özniteliklerinden yararlanarak arabirim uygulamasını otomatik olarak oluşturabilir.
Aşağıdaki içerik, Seçenekler deseni'nde gösterilen not ekleme öznitelikleri örneğini alır ve bunu, seçenek doğrulama kaynak oluşturucusunu kullanacak şekilde dönüştürür.
Aşağıdaki appsettings.json dosyasını göz önünde bulundurun:
{
"MyCustomSettingsSection": {
"SiteTitle": "Amazing docs from awesome people!",
"Scale": 10,
"VerbosityLevel": 32
}
}
Aşağıdaki sınıf yapılandırma bölümüne bağlanır "MyCustomSettingsSection" ve birkaç DataAnnotations kural uygular:
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; }
}
Önceki SettingsOptions sınıfta, ConfigurationSectionName özelliği bağlanacak yapılandırma bölümünün adını içerir. Bu senaryoda options nesnesi yapılandırma bölümünün adını sağlar. Aşağıdaki veri ek açıklaması öznitelikleri kullanılır:
- RequiredAttribute: özelliğinin gerekli olduğunu belirtir.
- RegularExpressionAttribute: Özellik değerinin belirtilen normal ifade deseni ile eşleşmesi gerektiğini belirtir.
- RangeAttribute: Özellik değerinin belirtilen aralık içinde olması gerektiğini belirtir.
Tavsiye
özelliklerine RequiredAttributeek olarak gerekli değiştiriciyi de kullanır. Bu, doğrulama kaynağı oluşturma özelliğiyle ilgili olmasa da seçenekler nesnesinin tüketicilerinin özellik değerini ayarlamayı unutmamasını sağlamaya yardımcı olur.
Aşağıdaki kod, yapılandırma bölümünün seçenekler nesnesine nasıl bağlanacağını ve veri ek açıklamalarını doğrulamayı örneklemektedir:
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();
Tavsiye
<PublishAot>true</PublishAot> dosyasına eklenerek etkinleştirildiğinde, kod IL2025 ve IL3050 gibi uyarılar oluşturabilir. Bu uyarıları azaltmak için yapılandırma kaynak oluşturucusunun kullanılması önerilir. Yapılandırma kaynağı oluşturucuyu etkinleştirmek için özelliğini <EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator> proje dosyasına ekleyin.
Seçenekler doğrulaması için derleme zamanı kaynak oluşturma işleminden yararlanarak performans açısından iyileştirilmiş doğrulama kodu oluşturabilir ve yansıma gereksinimini ortadan kaldırarak daha sorunsuz AOT uyumlu uygulama derlemesi elde edebilirsiniz. Aşağıdaki kod, seçenekler doğrulama kaynak oluşturucusunun nasıl kullanılacağını gösterir:
using Microsoft.Extensions.Options;
namespace ConsoleJson.Example;
[OptionsValidator]
public partial class ValidateSettingsOptions : IValidateOptions<SettingsOptions>
{
}
Boş bir kısmi sınıfta OptionsValidatorAttribute varlığı, seçenekleri doğrulama kaynak oluşturucusuna IValidateOptions öğesini doğrulayan SettingsOptions arabirim uygulamasını oluşturmasını bildirir. Seçenekler doğrulama kaynak oluşturucu tarafından oluşturulan kod aşağıdaki örneğe benzer:
// <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;
}
}
}
Oluşturulan kod performans için iyileştirilmiştir ve yansımaya dayanmaz. Ayrıca AOT uyumlu. Oluşturulan kod , Validators.g.cs adlı bir dosyaya yerleştirilir.
Uyarı
Seçenek doğrulama kaynak oluşturucuyu etkinleştirmek için ek adımlar uygulamanız gerekmez. Projeniz Microsoft.Extensions.Options sürüm 8 veya sonraki bir sürüme başvurduğunda veya bir ASP.NET uygulaması oluştururken varsayılan olarak otomatik olarak etkinleştirilir.
Yapmanız gereken tek adım, başlangıç koduna aşağıdakileri eklemektir:
builder.Services
.AddSingleton<IValidateOptions<SettingsOptions>, ValidateSettingsOptions>();
Uyarı
Seçenekler kaynağı doğrulama oluşturucusu kullanılırken OptionsBuilderDataAnnotationsExtensions.ValidateDataAnnotations<TOptions>(OptionsBuilder<TOptions>) çağrılması gerekmez.
Uygulama seçenekler nesnesine erişmeye çalıştığında, seçenekler nesnesi doğrulamak için oluşturulan seçenek doğrulaması kodu yürütülür. Aşağıdaki kod parçacığında options nesnesine nasıl erişildi gösterilmektedir:
var settingsOptions =
app.Services.GetRequiredService<IOptions<SettingsOptions>>().Value;
Değiştirilen veri açıklama öznitelikleri
Oluşturulan kodu yakından incelediğinizde, başlangıçta özelliğe RangeAttribute uygulanan SettingsOptions.Scale gibi özgün veri açıklama özniteliklerinin, __SourceGen__RangeAttribute gibi özel özniteliklerle değiştirildiğini fark edeceksiniz. Bu değişiklik, RangeAttribute doğrulaması yansımaya bağlı olduğundan yapılır. Buna karşılık, __SourceGen__RangeAttribute performans için iyileştirilmiş özel bir özniteliktir ve yansımaya bağımlı değildir ve kodu AOT uyumlu hale getirir. Aynı öznitelik değiştirme deseni, MaxLengthAttribute'ün yanı sıra MinLengthAttribute, LengthAttribute ve RangeAttribute üzerinde uygulanacaktır.
Özel veri ek açıklaması öznitelikleri geliştiren herkes için doğrulama için yansıma kullanmaktan kaçınmanız önerilir. Bunun yerine, yansımaya dayanmayan kesin türemiş kodlar oluşturmak önerilir. Bu yaklaşım, AOT derlemeleriyle sorunsuz uyumluluk sağlar.