استانداردسازی ورودیها در WebAPI — مدیریت صحیح Input و Validation در طراحی API
در هر سیستم نرمافزاری، کیفیت و امنیت دادههایی که وارد برنامه میشود تأثیر مستقیم بر رفتار سیستم دارد. بسیاری از باگهای جدی، ناپایداری سرویسها، سوءاستفادههای امنیتی و حتی مشکلات عملکردی، ناشی از مدیریت نادرست ورودیهای API هستند.
در مقاله قبلی درباره استانداردسازی خروجیها و Status Codeها صحبت کردیم؛ اما استانداردسازی ورودیها نیز به همان اندازه اهمیت دارد. در این مقاله تمرکز کاملاً بر روی ورودیهای WebAPI و روشهای صحیح تعریف، کنترل، محدودسازی و اعتبارسنجی آنهاست.
هدف این مقاله ارائه یک الگوی کاربردی و استاندارد برای:
- دریافت ورودیهای تمیز و معتبر
- جلوگیری از دادههای مخرب
- یکسانسازی رفتار API
- تستپذیر کردن همه لایههای Validation
۲. چرا استانداردسازی ورودیها مهم است؟
امنیت: اگر ورودیها درست کنترل نشوند، راه برای انواع حملات باز میشود:
- SQL Injection
- XSS
- Mass Assignment
- Over-posting
- Bypass کردن Ruleها
در بسیاری از APIها دادههای مخرب بدون فیلتر وارد سیستم شده و کل اکوسیستم را تحت تأثیر قرار میدهد.
یکپارچگی دادهها: ورودیهای ناسازگار در نهایت به دادههای ناسازگار در دیتابیس تبدیل میشوند.
کاهش باگها: هرچه Validation نزدیکتر به ورودی انجام شود، هزینهٔ رفع باگ کمتر است.
سادگی تستنویسی: استانداردسازی ورودیها یعنی API با ورودی مشخص و پیشبینیپذیر کار میکند؛ این یعنی تستها سادهتر و قابل اعتمادتر.
۳. معماری صحیح مدیریت ورودی در WebAPI
استفاده از DTO بهعنوان تنها مدل ورودی: هیچگاه نباید Entityهای Domain یا EF Core مستقیم از کلاینت ورودی بگیرند.
- افزایش امنیت
- محدودسازی فیلدها
- جلوگیری از over-posting
- جلوگیری از نشت ساختار دیتابیس به API
DTO تنها سطحی است که API با آن ورودی دریافت میکند.
Ruleهای Validation خارج از کنترلر: کنترلر باید فقط یک کار انجام دهد: دریافت درخواست و ارسال پاسخ. منطق اعتبارسنجی نباید داخل کنترلر باشد؛ این منطق باید کاملاً جدا، مستقل و تستپذیر باشد.
۴. انتخاب فریمورک مناسب Validation
Data Annotations: ساده، سریع و مناسب برای اعتبارسنجیهای پایه است.
RequiredEmailAddressRangeStringLength
اما برای سناریوهای پیچیده، خوانایی و انعطاف لازم را ندارد و تستنویسی مستقل آن سختتر است.
FluentValidation (پیشنهادی): بهترین انتخاب برای APIهای مدرن است.
- خوانایی بالا و Ruleهای زنجیرهای
- تستپذیری فوقالعاده
- Ruleهای قابل ترکیب و شرطی
- پشتیبانی از Ruleهای Async (مثلاً چک یکتایی ایمیل)
- یکپارچه با ASP.NET Core Pipeline
۵. استانداردسازی Validation با FluentValidation
هر DTO باید یک Validator مخصوص خود داشته باشد. این کار باعث میشود ورودیها مستقل از کنترلر و سرویس، در یک لایه متمرکز و تستپذیر اعتبارسنجی شوند.
public class RegisterUserDtoValidator : AbstractValidator<RegisterUserDto>
{
public RegisterUserDtoValidator()
{
RuleFor(x => x.FullName)
.NotEmpty().WithMessage("FullName is required.")
.MaximumLength(100);
RuleFor(x => x.Email)
.NotEmpty()
.EmailAddress();
RuleFor(x => x.Password)
.NotEmpty()
.MinimumLength(8);
RuleFor(x => x.Age)
.GreaterThanOrEqualTo(18);
}
}
۶. اجرای خودکار Validation در Pipeline
با یکپارچهسازی FluentValidation و ASP.NET Core، هر ورودی قبل از رسیدن به اکشن کنترلر، بهصورت خودکار اعتبارسنجی میشود. در صورت بروز خطا:
- کد وضعیت مناسب (معمولاً
400 BadRequest) برگردانده میشود. - ساختار خطا میتواند مطابق الگوی استاندارد ApiResponse (تعریفشده در مقاله قبلی) باشد.
همچنین میتوان رفتار Serializer را برای فیلدهای ناشناخته تنظیم کرد تا از ورود دادههای اضافه جلوگیری شود:
builder.Services.Configure<JsonOptions>(options =>
{
options.SerializerOptions.UnknownTypeHandling = JsonUnknownTypeHandling.Fail;
});
۷. جلوگیری از ورودیهای خطرناک و Extra Fields
محدودسازی به فقط فیلدهای مورد اعتماد: در ASP.NET Core، مدلسازی JSON در صورت استفاده از Entityهای خام میتواند منجر به over-posting شود.
- استفاده از DTOهای کوچک و سناریو-محور (Create، Update، Filter و ...)
- عدم استفاده از نوعهای کلی مثل
dynamicوobjectبرای Body
علاوه بر این، با تنظیم رفتار Serializer (همانطور که در بخش قبل دیدیم) میتوان فیلدهای اضافی را رد کرد تا کلاینت نتواند دادههای خارج از قرارداد را ارسال کند.
۸. ترکیب Validation با تستنویسی
یکی از مزیتهای بزرگ FluentValidation این است که Validatorها کاملاً مستقل از API قابل تست هستند. بهکمک تستهای واحد (Unit Test)، میتوان رفتار Ruleها را بدون نیاز به اجرای وبسرور بررسی کرد.
نمونهای ساده از تست واحد برای Validator:
public class RegisterUserValidatorTests
{
private readonly RegisterUserDtoValidator _validator = new();
[Fact]
public void Should_Fail_When_Email_Is_Invalid()
{
var dto = new RegisterUserDto
{
FullName = "Test User",
Email = "invalid",
Password = "12345678",
Age = 25
};
var result = _validator.Validate(dto);
result.IsValid.Should().BeFalse();
}
}
در کنار تستهای واحد، میتوان با تستهای یکپارچه (Integration Tests) مسیر کامل HTTP Request تا Response را نیز تست کرد و مطمئن شد که:
- Pipeline بهدرستی خطاهای اعتبارسنجی را به ۴۰۰ تبدیل میکند.
- ساختار خروجی خطا با ApiResponse استاندارد شما یکسان است.
- ورودیهای معتبر منجر به ۲۰۰/۲۰۱ و خروجی صحیح میشوند.
۹. استانداردسازی ورودیها بر اساس Verbهای HTTP
انتخاب صحیح HTTP Verb بخش مهمی از طراحی API است و مستقیماً روی نوع و محل ورودیها تأثیر میگذارد. یکی از رایجترین اشتباهات، استفادهٔ دائمی از POST برای هر نوع عملیات است. طراحی درست Verbها باعث میشود ورودیها معنیدار، قابل پیشبینی و قابل تست باشند.
۹.۱. GET — دریافت داده (بدون Body)
در درخواستهای GET، ورودیها فقط باید از طریق:
- Query String
- Route Parameters
ارسال Body در GET غیراستاندارد است و نباید استفاده شود.
مثال صحیح:
GET /api/users?search=mehrdad&page=1
مثال غیراستاندارد:
GET /api/users
Body: { "search": "mehrdad" }
۹.۲. POST — ایجاد داده جدید (Create)
POST Verb اصلی برای ایجاد موجودیت جدید است. ورودی باید در Body و با DTO مخصوص Create ارسال شود.
POST /api/users
Content-Type: application/json
{
"fullName": "Mehrdad",
"email": "test@example.com",
"password": "12345678"
}
۹.۳. PUT — بروزرسانی کامل (Full Update)
PUT برای بروزرسانی کامل یک موجودیت استفاده میشود. یعنی تمام فیلدهای قابل ویرایش باید همراه درخواست ارسال شوند، نه فقط بخشی از آنها.
PUT /api/users/10
Content-Type: application/json
{
"fullName": "Mehrdad Updated",
"email": "updated@example.com",
"age": 30
}
۹.۴. PATCH — بروزرسانی جزیی (Partial Update)
برای بروزرسانی جزیی، PATCH انتخاب مناسب است. در این حالت فقط فیلدهایی ارسال میشوند که قرار است تغییر کنند، و Validation باید بهصورت شرطی روی همان فیلدها اعمال شود.
PATCH /api/users/10
Content-Type: application/json
{
"email": "new@example.com"
}
۹.۵. DELETE — حذف موجودیت
در DELETE معمولاً تنها ورودی، شناسهٔ موجودیت است که در Route قرار میگیرد. استفاده از Body برای DELETE توصیه نمیشود.
DELETE /api/users/10
۹.۶. خطاهای رایج در استفاده از Verbها
- ارسال Body در GET
- استفاده از POST برای Update بهجای PUT/PATCH
- استفاده از PUT برای عملیات غیر CRUD
- استفاده از DELETE همراه با Body
- تعریف ورودیهای نامشخص با نوعهای کلی (مثل
objectیاdynamic)
با استانداردسازی Verbها، ورودیها نیز در مسیر مشخصی قرار میگیرند (Route / Query / Body) و این موضوع هم امنیت و هم تستپذیری و هم خوانایی API را بهطور محسوسی بهبود میدهد.
۱۰. نکات مهم در طراحی ورودیهای API
استفاده از DTOهای متفاوت برای Create و Update: برای عملیات ایجاد و بروزرسانی، DTOهای جدا تعریف کنید تا:
- برخی فیلدها فقط در Create مجاز باشند (مثلاً Password)
- برخی فیلدها در Update قابل تغییر نباشند (مثل Id یا CreatedAt)
- امنیت و خوانایی قرارداد API افزایش یابد
کنترل تاریخها و اعداد: تاریخهای نامعتبر، مقادیر منفی یا خارج از Range میتوانند منجر به رفتارهای غیرمنتظره شوند. Validation باید این موارد را بهروشنی پوشش دهد.
Normalize کردن ورودیها: قبل از رسیدن داده به لایه بیزینس:
- رشتهها را
Trimکنید. - ایمیلها را lowercase کنید.
- کاراکترهای غیرقابل نمایش و ناایمن را حذف یا escape کنید.
این کارها باید در لایه ورودی (DTO + Validator) انجام شود تا Domain همیشه دادههای تمیز دریافت کند.
۱۱. جمعبندی
استانداردسازی ورودیها یکی از مهمترین ستونهای طراحی یک WebAPI حرفهای است. با ترکیبی از:
- استفاده از DTOهای جداگانه و سناریو-محور
- بهکارگیری Ruleهای دقیق در FluentValidation
- یکپارچهسازی Validation در Pipeline
- جلوگیری از فیلدهای اضافی و ورودیهای خطرناک
- رعایت استاندارد در استفاده از HTTP Verbها
- نوشتن تستهای واحد و یکپارچه برای پوشش سناریوهای ورودی
میتوانید APIهایی ایمن، پایدار، تستپذیر و قابل نگهداری بسازید. این مقاله در کنار مقالهٔ قبلی درباره استانداردسازی خروجیها، میتواند پایهای برای طراحی یک لایه API تمیز و حرفهای در سیستمهای واقعی باشد.