پایان یک انتظار طولانی: اعتبارسنجی ناهمگام (Async Validation) در NET 11.
نویسنده: وحید نصیری
تاریخ: ۱۴۰۵/۰۴/۱۴ ۰۸:۴۵
آدرس: www.dntips.ir
System.ComponentModel.DataAnnotations استوار است، ساختاری کاملاً همگام (Synchronous) داشت. این طراحی سنتی، پیادهسازی قواعد اعتبارسنجی پیچیدهای را که به عملیاتهای ورودی/خروجی (I/O-bound) وابسته بودند با چالش مواجه میکرد؛ سناریوهایی مانند بررسی منحصربهفرد بودن نام کاربری در پایگاه داده، استعلام وضعیت یک شناسه از طریق Web API خارجی یا سنجش موجودی انبار..Result یا .Wait() فراخوانی کنند. این رویکرد نامناسب، مستقیماً ریسک بروز گلوگاه در پایگاه ریسمانها (Thread Pool Starvation) را افزایش میداد. در پی درخواستهای مکرر جامعه توسعهدهندگان، مایکروسافت بالاخره در .NET 11 Preview 6 زیرساخت اعتبارسنجی ناهمگام را به صورت بومی به هسته فریمورک اضافه کرد تا به یکی از پررایترین درخواستهای گیتهاب (Issue #31905) پاسخ دهد.System.ComponentModel.DataAnnotations اضافه شده است که به توسعهدهندگان اجازه میدهد منطق ناهمگام خود را بدون مسدود کردن ریسمانها پیادهسازی کنند:AsyncValidationAttribute: این کلاس پایه جدید، جایگزین مستقیم ValidationAttribute برای سناریوهای ناهمگام است و امکان نوشتن اتریبیوتهای سفارشی را با بازنویسی (Override) متد IsValidAsync فراهم میکند.IAsyncValidatableObject: همتای ناهمگام اینترفیس شناختهشدهی IValidatableObject که به خودِ مدل یا DTO اجازه میدهد منطق اعتبارسنجی درونیاش را به صورت اسنک (Async) پیادهسازی کند.Validator): معرفی متدهای جدید از جمله ValidateObjectAsync، TryValidateObjectAsync، ValidatePropertyAsync و ValidateValueAsync جهت اجرای غیرمسدودکننده عملیات سنجش صحت دادهها.IsValid به صورت پیشفرض برای سازگاری عقبرو (Backward Compatibility) مقدار ValidationResult.Success را برمیگرداند و منطق اصلی در IsValidAsync به همراه پشتیبانی از CancellationToken پیادهسازی میشود.using System.ComponentModel.DataAnnotations;
using Microsoft.Extensions.DependencyInjection;
public sealed class UniqueUserNameAttribute : AsyncValidationAttribute
{
// سازگاری با خط لولههای همگام قدیمی
protected override ValidationResult? IsValid(object? value, ValidationContext context)
=> ValidationResult.Success;
// پیادهسازی اصلی برای خط لوله ناهمگام داتنت ۱۱
protected override async Task<ValidationResult?> IsValidAsync(
object? value, ValidationContext context, CancellationToken cancellationToken)
{
// دریافت مستقیم سرویسها از کانتینر DI با استفاده از Context موجود
var users = context.GetRequiredService<IUserStore>();
bool isTaken = await users.ExistsAsync((string?)value, cancellationToken);
return isTaken
? new ValidationResult("این نام کاربری قبلاً توسط کاربر دیگری انتخاب شده است.")
: ValidationResult.Success;
}
}
public class RegistrationRequest
{
[Required]
[UniqueUserName]
public string UserName { get; set; } = "";
}Validator متدهای الحاقی جدید خود را ارائه میدهد:// اجرای فرآیند اعتبارسنجی ناهمگام بدون بلاک کردن Thread var context = new ValidationContext(model, serviceProvider, items: null); await Validator.ValidateObjectAsync(model, context, validateAllProperties: true);
نکته: یکی از نکات کلیدی هنگام استفاده ازValidationContext.GetRequiredServiceدر محیطهای ناهمگام، طول عمر (Lifetime) سرویس تزریقشده است. اگر سرویس شما دارای طول عمر محدوده (Scoped) باشد (مانندDbContextدر Entity Framework Core)، اطمینان حاصل کنید که نمونه کانتینیشن جاری در محدوده لایفتایم درخواست وب (HTTP Request Scope) اجرا میشود تا با خطای دسترسی همزمان (Concurrency) مواجه نشوید.
Microsoft.Extensions.Options نیز گسترش داده است.IAsyncStartupValidator، اپلیکیشنها میتوانند کدهای مربوط به تنظیمات وب و سرویسهای ابری خود را که نیازمند اعتبارسنجی شبکهای هستند (مانند بررسی صحت خط اتصال پایگاه داده، صحت کلیدهای API متصل به درگاههای مالی، یا در دسترس بودن سرور کش رادیس) در زمان بوت شدن برنامه به صورت ناهمگام بررسی کنند.DataAnnotations در .NET 11 یکی از بنیادینترین اصلاحات ساختاری در زیرسیستم اعتبارسنجی در چند سال گذشته است. این قابلیت با آزادسازی پتانسیل مسدودکننده کدهای قدیمی، مقیاسپذیری (Scalability) و نرخ پاسخدهی سرور تحت بارهای ترافیکی شدید را به شکل چشمگیری ارتقا میدهد. با کاهش وابستگی به کتابخانههای فرعی و افزایش انعطافپذیری Options Pattern در زمان استارتآپ، توسعهدهندگان داتنت اکنون ابزاری بومی، قدرتمند و فوقالعاده بهینه برای مدیریت صحت دادهها در اختیار دارند.dotnet/aspnetcore #66487 و #67183) توسعه یافته است.AddValidation امکانپذیر شده است. فریمورک از طریق Microsoft.Extensions.Validation به طور خودکار قبل از اجرای هر اِندپوینت، مدلهای ورودی را سنجش کرده و در صورت بروز خطا، پاسخ 400 Bad Request برمیگرداند.var builder = WebApplication.CreateBuilder(args);
// اضافه کردن سرویسهای اعتبارسنجی توکار به کانتینر DI
builder.Services.AddValidation();
builder.Services.AddScoped<ICatalogService, CatalogService>();
var app = builder.Build();
// فریمورک به طور خودکار قبل از اجرای اِندپوینت، ساختار ورودی را ناهمگام میسنجد
app.MapPost("/products", (Product product) => Results.Ok(product));
app.Run();AsyncValidationAttribute و IAsyncValidatableObject در بستر Minimal APIs، نحوه برخورد با متدهای همگامِ قدیمی (IsValid و Validate) است. از آنجایی که فریمورک همواره مسیر ناهمگام (Async Path) را فراخوانی میکند، در متدهای سنتی همگام با قاطعیت اقدام به پرتاب InvalidOperationException میکنیم.internal sealed class UniqueSkuAttribute : AsyncValidationAttribute
{
// متد همگام عمداً خطا پرتاب میکند تا از دور زدن صامت اعتبارسنجی جلوگیری شود
protected override ValidationResult? IsValid(object? value, ValidationContext validationContext) =>
throw new InvalidOperationException($"اعتبارسنجی این اتریبیوت باید با متد '{nameof(IsValidAsync)}' انجام شود.");
protected override async Task<ValidationResult?> IsValidAsync(
object? value, ValidationContext validationContext, CancellationToken cancellationToken)
{
var catalogService = validationContext.GetRequiredService<ICatalogService>();
if (value is string sku && await catalogService.SkuExistsAsync(sku, cancellationToken))
{
return new ValidationResult("محصولی با این شناسه کالا (SKU) از قبل وجود دارد.");
}
return ValidationResult.Success;
}
}IAsyncValidatableObject بروید. این اینترفیس با بازگرداندن IAsyncEnumerable امکان تولید جریان پویایی از خطاها را با استفاده از کلمهکلیدی yield return مهیا میکند.using System.ComponentModel.DataAnnotations;
using System.Runtime.CompilerServices;
public record OrderRequest(
[Range(1, int.MaxValue)] int ProductId,
[Range(1, int.MaxValue)] int Quantity,
string PromoCode) : IAsyncValidatableObject
{
// پیادهسازی متد همگام و پرتاب استثنا برای امنیت بیشتر
public IEnumerable<ValidationResult> Validate(ValidationContext context) =>
throw new InvalidOperationException($"این نوع داده باید با متد '{nameof(ValidateAsync)}' سنجیده شود.");
// پیادهسازی جریان ناهمگام خطاها
public async IAsyncEnumerable<ValidationResult> ValidateAsync(
ValidationContext context,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var catalogService = context.GetRequiredService<ICatalogService>();
// بررسی موجودی انبار به صورت ناهمگام
if (!await catalogService.HasStockAsync(ProductId, Quantity, cancellationToken))
{
yield return new ValidationResult(
$"محصول شناسه '{ProductId}' به تعداد '{Quantity}' واحد در انبار موجود نیست.",
[nameof(Quantity)]);
}
// بررسی کد تخفیف به صورت ناهمگام
if (!await catalogService.IsPromoCodeValidAsync(PromoCode, cancellationToken))
{
yield return new ValidationResult(
$"کد تخفیف '{PromoCode}' معتبر نیست.",
[nameof(PromoCode)]);
}
}
}IsValidAsync آنها همزمان آغاز میشود تا زمان انتظار کاهش یابد.IAsyncValidatableObject مورد ارزیابی قرار میگیرند تا ساختار اولویتبندی از بین نروید.ValidateOnStart() و رابط IValidateOptions را برای بررسی زودهنگام ارائه داده بود. اما متد Validate کاملاً همگام (Synchronous) طراحی شده بود. این طراحی دو مشکل اساسی ایجاد میکرد:task.Result یا task.Wait() میشدند که خطر رخداد Sync-over-Async و بنبست رشتهها (Thread Pool Starvation / Deadlock) را به همراه داشت.IHost.StartAsync() ناهمگام است، عدم پشتیبانی چرخه حیات اعتبارسنجی از فراخوانیهای Asynchronous یک خلاء معماری بهشمار میرفت.IAsyncValidateOptions به همراه بازنگری در مکانیسمهای اعتبارسنجی اولیه به هسته داتنت افزوده شده است.IAsyncValidateOptionsValidateAsync را در دسترس قرار میدهد که به برنامه اجازه میدهد اعتبارسنجیهای متکی به I/O را بدون مسدود کردن ترد اجرا کند:public interface IAsyncValidateOptions<TOptions> where TOptions : class
{
Task<ValidateOptionsResult> ValidateAsync(
string? name,
TOptions options,
CancellationToken cancellationToken = default);
}IStartupValidatorو جایگزینی باIAsyncStartupValidatorIStartupValidator که وظیفه جمعآوری و اجرای اعتبارسنجیها در زمان شروع هاست را بر عهده داشت، اکنون با شناسه منسوخسازی (Obsolete) نشاندار شده و جای خود را به IAsyncStartupValidator داده است. این تغییر به هاست اجازه میدهد تمامی متدهای ValidateAsync را به شکل کاملاً ناهمگام در حین اجرای پایپلاین آغازین به سرانجام برساند.IValidateOptions) و ناهمگام (IAsyncValidateOptions) را تولید میکنند. این کار باعث میشود:#:sdk Microsoft.NET.Sdk.Web
using System.Net;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
// پیکربندی و فعالسازی اعتبارسنجی در فاز راهاندازی
builder.Services
.AddOptions<BackendOptions>()
.Configure(options =>
{
options.HostName = "api.internal.network";
})
.Validate<BackendOptionsValidator>()
.ValidateOnStart();
using IHost host = builder.Build();
await host.StartAsync();
public sealed class BackendOptions
{
public string HostName { get; set; } = string.Empty;
}
public sealed class BackendOptionsValidator :
IAsyncValidateOptions<BackendOptions>,
IValidateOptions<BackendOptions>
{
public async Task<ValidateOptionsResult> ValidateAsync(
string? name,
BackendOptions options,
CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace(options.HostName))
{
return ValidateOptionsResult.Fail("نام میزبان سرویس بکاند مشخص نشده است.");
}
// بررسی ناهمگام آدرس IP متناظر با هاست
DnsResult<AddressRecord> result =
await Dns.ResolveAddressesAsync(options.HostName, cancellationToken);
return result.ResponseCode == DnsResponseCode.NoError && result.Records.Count > 0
? ValidateOptionsResult.Success
: ValidateOptionsResult.Fail(
$"امکان ارزیابی آدرس IP برای دامنه '{options.HostName}' وجود ندارد.");
}
// متد همگام به منظور ممانعت از فراخوانی همگام غیراصولی خطای معنادار صادر میکند
public ValidateOptionsResult Validate(string? name, BackendOptions options) =>
ValidateOptionsResult.Fail("اعتبارسنجی این تنظیمات صرفاً باید به صورت ناهمگام اجرا شود.");
}ValidateOnStart(): در طول فراخوانی host.StartAsync()، سامانه هاست ارائهدهنده IAsyncStartupValidator را فراخوانی کرده و متد ValidateAsync را با CancellationToken مجاز اجرا میکند.Validate همگام: در صورتی که بخشی از کدهای قدیمی یا کتابخانههای ثالث سعی کنند با متد همگام این نمونه را ارزیابی کنند، بهجای بلاک کردن ترد با خطای صریح مواجه میشوند تا توسعهدهنده به استفاده از مدل ناهمگام هدایت شود.CancellationToken ورودی متد را به متدهای داخلی پاس دهید و در صورت نیاز از CancellationTokenSource.CreateLinkedTokenSource با بازه زمانی مشخص (مانند ۳ الی ۵ ثانیه) استفاده کنید:using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); cts.CancelAfter(TimeSpan.FromSeconds(3));
string? name در متدهای اعتبارسنجی نشاندهنده نام نمونه تنظیمات است. اگر از الگوهای چندگانه (Named Options) استفاده میکنید، همیشه بررسی کنید که آیا نام ارسالی با مورد مدنظر تطابق دارد یا برای تمام نمونهها (name == Options.DefaultName) معتبر است.IAsyncValidateOptions و معرفی IAsyncStartupValidator گام مهمی در تکامل معماری پیکربندی داتنت است. این قابلیت به سیستمها اجازه میدهد بدون قربانی کردن کارایی تردها یا نقض اصول غیرهمگام، با اطمینان بالا وابستگیهای کلیدی را در بدو شروع راستیآزمایی کرده و در صورت بروز خطا بلافاصله متوقف شوند. با استفاده اصولی از این قابلیت همراه با رعایت سقف زمانی (Timeout)، تابآوری و سلامت معماری برنامههای داتنت بهطور چشمگیری ارتقا مییابد.