Parameter Binding (اتصال پارامتر) و اعتبارسنجی ورودی‌ها

Parameter Binding (اتصال پارامتر) و اعتبارسنجی ورودی‌ها

Parameter Binding (اتصال پارامتر) و اعتبارسنجی ورودی‌ها

منبع: Coding Clean, Reliable, and Safe REST APIs with ASP.NET Core 8 — Anthony Giretti

اعتبار ترجمه: ترجمه با کمک هوش مصنوعی

Parameter Binding (اتصال پارامتر) و Input Validation

ادامهٔ RouteGroups

Listing 4-6. مشترک‌کردن Constraint مربوط به ID در GroupCountries

var idGroup = group.MapGroup("/{id}");
    idGroup.MapGet("/", (int id) => countries[id]);
    idGroup.MapGet("/languages", (int id) =>
    {
          var country = countries[id];
          return languages[country];
    });

کافی است Variable مربوط به idGroup را برای Endpointهای دیگر نیز استفاده کنید تا همگی Constraint مربوط به ID را به ارث ببرند. Route Grouping اجازه می‌دهد Constraintها و قطعه‌های URL را چند سطح Chain کنید، اما زیاده‌روی در آن Readability و Maintainability را کاهش می‌دهد. نویسنده این Feature را برای مجموعهٔ بزرگ Endpointها بسیار عملی می‌داند، زیرا از تکرار Code و Route Name جلوگیری می‌کند و با اصل KISS سازگار است.

Parameter Binding

در بخش قبل دیدیم اگر Route Parameter نتواند به Parameter Function مسئول پردازش Request Bind شود، Response برابر 400 Bad Request است. اکنون Fundamentals این فرایند را دقیق‌تر بررسی می‌کنیم.

Parameter Binding دقیقاً چیست؟

Parameter Binding یعنی ASP.NET Core مقدارهای موجود در HTTP Request را می‌گیرد و آن‌ها را به Parameterهای Typed مورد نیاز Function پردازش‌کننده تبدیل می‌کند. علاوه بر Primitive Typeها، ASP.NET Core 8 می‌تواند موارد پیچیده‌تری را نیز Bind کند:

  • Collectionها مانند List، Dictionary و Array.
  • تقریباً هر Complex Object، به‌جز Objectهای Recursive در Minimal API؛ یعنی Objectای که Property از Type خودش دارد.
  • Serviceهایی که از Dependency Injection قابل Inject هستند.

Listing 4-7 نمونه‌ای از Address را نشان می‌دهد که به دلیل وجود Property از Type خودش، در Minimal API قابل Binding نیست.

Listing 4-7. کلاس Address دارای Recursion

public class Address
    {
        public int StreetNumber { get; set; }
        public string streetName { get; set; }
        public string StreetType { get; set; }
        public string City { get; set; }
        public string Country { get; set; }
        public int PostalCode { get; set; }
        public Address AlternateAddress { get; set; }
    }

Parameter Binding با مثال

Minimal API در ASP.NET Core 8 می‌تواند Parameter را از Sourceهای زیر Bind کند:

  • Route
  • QueryString
  • Body با Data از نوع JSON
  • Body به‌شکل Form Data با Key/Value
  • Headerها
  • Sourceهای دیگر، شامل Instanceهای Dependency Injection و Custom Binding

Binding می‌تواند Explicit باشد؛ یعنی Source هر Parameter با Attribute مشخص شود. در Function دارای چند Source، Explicit Binding خوانایی و ابهام‌زدایی را بهتر می‌کند.

جدول ۴-۴. Attributeهای Parameter Binding در Minimal API
Data SourceBinding Attribute
RouteFromRoute
QueryStringFromQuery
HeaderFromHeader
BodyFromBody
FormFromForm

Listing 4-8 یک POST Request برای ساخت Address از JSON Body را نشان می‌دهد. نسخهٔ این مثال Property بازگشتی AlternateAddress را ندارد.

Listing 4-8. ساخت Address از JSON Body

app.MapPost("/Addresses", ([FromBody] Address address) => {
        return Results.Created();
    });
تصویر منبع — صفحهٔ 122Visual واقعی استخراج‌شده از PDF، مربوط به صفحهٔ 122.
تصویر منبع — صفحهٔ 122Visual واقعی استخراج‌شده از PDF، مربوط به صفحهٔ 122.

فعلاً از Results.Created() صرف‌نظر کنید؛ بعداً Class ایستا Results را برای بازگرداندن HTTP Status مناسب بررسی می‌کنیم. اگر Breakpoint قرار دهید، می‌بینید JSON Request به Object از نوع Address Bind شده است.

شکل ۴-۷. Binding شیء Address از JSON Body
شکل ۴-۸. POST Request ساخت Address در Postman
تصویر منبع — صفحهٔ 123Visual واقعی استخراج‌شده از PDF، مربوط به صفحهٔ 123.
تصویر منبع — صفحهٔ 123Visual واقعی استخراج‌شده از PDF، مربوط به صفحهٔ 123.

اکنون Route Parameter و Form Data را با هم ترکیب می‌کنیم. Listing 4-9 یک PUT Request برای Update کردن Address نشان می‌دهد: addressId از Route و Data مربوط به Address از Form می‌آیند.

Listing 4-9. Update آدرس با Route و Form

app.MapPut("/Addresses/{addressId}", ([FromRoute] int
    addressId, [FromForm] Address address) => {
        return Results.NoContent();
    }).DisableAntiforgery();

Sourceها Explicit و جدا هستند و Binding به‌درستی انجام می‌شود.

شکل ۴-۹. Bind شدن صحیح addressId به‌عنوان Route Parameter
شکل ۴-۱۰. Bind شدن Address به‌عنوان Form Parameter

در مثال از DisableAntiforgery استفاده شده است. Requestهایی که از HTML Form و FromForm استفاده می‌کنند باید AntiForgery را مدیریت کنند. این Feature برای جلوگیری از Cross-Site Request Forgery (XSRF/CSRF) طراحی شده است. در ASP.NET Core 8 اگر AntiForgery را نه پیکربندی و نه غیرفعال کنید، Endpoint مبتنی بر Form Data در Development می‌تواند Error ایجاد کند و در Production Warning بدهد. راه اصولی دیگر تولید و اعتبارسنجی AntiForgery Token است. نویسنده برای ساده نگه داشتن مثال و جلوگیری از HTTP Call اضافی، در اینجا Feature را غیرفعال کرده است.

Minimal API نمی‌تواند Sourceهای مختلف را در Propertyهای یک Object به‌طور هم‌زمان Bind کند؛ اگر مثل Listing 4-10 Attributeها را Property-by-Property از Sourceهای گوناگون قرار دهید، Body/Form اولویت می‌گیرد و بعضی Parameterها Resolve نمی‌شوند.

Listing 4-10. Binding Propertyها از Sourceهای متفاوت

public class Address
    {
        [FromRoute]
        public int AddressId { get; set; }

        [FromForm]
        public int StreetNumber { get; set; }
    
    
تصویر منبع — صفحهٔ 125Visual واقعی استخراج‌شده از PDF، مربوط به صفحهٔ 125.
[FromForm] public string StreetName { get; set; } [FromForm] public string StreetType { get; set; } [FromForm] public string City { get; set; } [FromForm] public string Country { get; set; } [FromForm] public int PostalCode { get; set; } }

در این حالت AddressId Bind نمی‌شود و با مقدار پیش‌فرض صفر باقی می‌ماند.

شکل ۴-۱۱. Bind نشدن AddressId هنگام ترکیب Sourceهای متفاوت در یک Object

در Lambda می‌توان Attribute Binding را روی Parameter اصلی قرار داد و لازم نیست Attributeها حتماً روی خود Propertyها باشند. نویسنده ترجیح می‌دهد تا حد امکان یک Attribute روی Object Parameter در Lambda داشته باشد.

تصویر منبع — صفحهٔ 126Visual واقعی استخراج‌شده از PDF، مربوط به صفحهٔ 126.
شکل ۴-۱۲. PUT Request با Form Data در Postman

اکنون QueryString و Header را با GET ترکیب می‌کنیم. فرض کنید Endpointی فهرست Addressها را بر اساس Coordinate ارسال‌شده در Header و حداکثر تعداد نتیجهٔ اختیاری در Query String برمی‌گرداند.

Listing 4-11. GET با Header و QueryString

app.MapGet("/Addresses", ([FromHeader] string coordinates,
    [FromQuery] int? limitCountSearch) => {
    
    
تصویر منبع — صفحهٔ 127Visual واقعی استخراج‌شده از PDF، مربوط به صفحهٔ 127.
تصویر منبع — صفحهٔ 127Visual واقعی استخراج‌شده از PDF، مربوط به صفحهٔ 127.
return Results.Ok(); });

Query Parameterها غالباً Optional هستند. اگر Type به‌طور طبیعی Nullable نیست، بهتر است آن را Nullable تعریف کنید؛ در مثال، int? برای limitCountSearch استفاده شده است.

شکل ۴-۱۳. Bind شدن coordinates از Header
شکل ۴-۱۴. Bind شدن limitCountSearch از QueryString

Listing 4-12 نحوهٔ Bind کردن Array از Query String و Header را نشان می‌دهد.

Listing 4-12. Array در Query و Header

// Represents ?id=1&id=2
    app.MapGet("/Ids", ([FromQuery] int[] id) =>
    {
        return Results.Ok();
    });
    
    
تصویر منبع — صفحهٔ 128Visual واقعی استخراج‌شده از PDF، مربوط به صفحهٔ 128.
تصویر منبع — صفحهٔ 128Visual واقعی استخراج‌شده از PDF، مربوط به صفحهٔ 128.
app.MapGet("/Languages", ([FromHeader(Name = "lng")] string[] lng) => { return Results.Ok(); });

برای IDها می‌توان Query را به شکل ?id=1&id=2&id=3 ارسال کرد. در مثال Header، مقدار Name = "lng" به ASP.NET Core می‌گوید Array پارامتر lng از Header با همان نام خوانده شود. امکان Customize کردن نام روی دیگر Binding Attributeها نیز وجود دارد.

شکل ۴-۱۵. Bind شدن Array مربوط به id از Query String
شکل ۴-۱۶. Bind شدن Array مربوط به lng از Header
تصویر منبع — صفحهٔ 129Visual واقعی استخراج‌شده از PDF، مربوط به صفحهٔ 129.
تصویر منبع — صفحهٔ 129Visual واقعی استخراج‌شده از PDF، مربوط به صفحهٔ 129.
شکل ۴-۱۷. ارسال Array شناسه‌ها در Query String با Postman
شکل ۴-۱۸. ارسال Array رشته‌ها در Header با Postman

در نمونهٔ شکل ۴-۱۸، یکی از Headerها عمداً با نام lang به‌جای lng ارسال شده و Bind نمی‌شود؛ این موضوع نشان می‌دهد Custom Name دقیقاً چگونه عمل می‌کند.

مثال‌های بالا رایج‌ترین سناریوها هستند. در حالت‌های خاص می‌توان Object کامل را نیز از Header یا Query String ساخت، مثلاً برای جست‌وجوی چندمعیاره. Explicit Binding به‌جز در بعضی موارد مانند FromForm و FromHeader همیشه اجباری نیست، اما نویسنده توصیه می‌کند برای Readability Source Parameterها را صریح بنویسید.

Validating Inputs (اعتبارسنجی ورودی‌ها)

پس از Routing و Binding، مرحلهٔ حیاتی بعدی Input Validation است. هرگز نباید ورودی User را بدون بررسی قابل اعتماد فرض کرد. Validation دو هدف مهم دارد:

  1. اطمینان از رعایت Business Ruleها؛ برای مثال Email باید واقعاً قالب Email داشته باشد یا اگر فقط HTTPS مجاز است، URL مبتنی بر HTTP رد شود.
  2. محافظت در برابر Data مخرب؛ مثلاً Dataای که بعداً در Web Page نمایش داده می‌شود می‌تواند شامل HTML/JavaScript برای XSS باشد. همان‌طور که در فصل ۳ دیدیم، ورودی کنترل‌نشده همچنین زمینهٔ Injection را فراهم می‌کند.

DataAnnotations که در MVC، Razor Pages یا Web API استفاده می‌شوند، در Minimal API به همان صورت پشتیبانی نمی‌شوند. Listing 4-13 یک Country با Ruleهای Required و RegularExpression را نشان می‌دهد.

Listing 4-13. کلاس Country با DataAnnotation

using System.ComponentModel.DataAnnotations;

    namespace AspNetCore8MinimalApis.Models;

    public class Country
    {
        [Required]
        [RegularExpression("^[a-zA-Z0-9]+$")]
    
        public string Name { get; set; }

        public string Description { get; set; }

        [Required]
        [RegularExpression("^(https:\\/\\/.)[-a-zA-Z0-9@:%._\\+~#=]
         {2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)$")]
        public string FlagUri { get; set; }
    }

Name اجباری است و فقط Alphanumeric را می‌پذیرد؛ در نتیجه HTML Tag Match نمی‌شود. FlagUri نیز اجباری است و باید HTTPS URL باشد. چون DataAnnotation در Minimal API محدودیت دارد و برای برخی «عدم Match»ها مناسب نیست، نویسنده از Library با نام FluentValidation استفاده می‌کند.

FluentValidation امکان Ruleهای Built-in یا Custom و Error Message سفارشی می‌دهد. Package مربوط به Dependency Injection با فرمان زیر نصب می‌شود:

Install-Package FluentValidation.DependencyInjectionExtensions
تصویر منبع — صفحهٔ 133Visual واقعی استخراج‌شده از PDF، مربوط به صفحهٔ 133.
شکل ۴-۱۹. باز کردن Package Manager Console در Visual Studio 2022
تصویر منبع — صفحهٔ 134Visual واقعی استخراج‌شده از PDF، مربوط به صفحهٔ 134.
شکل ۴-۲۰. اجرای فرمان نصب NuGet Package مربوط به FluentValidation.DependencyInjection

پس از نصب، Validator از AbstractValidator<T> ارث می‌برد. Methodهای مهم عبارت‌اند از:

  • RuleFor: Property هدف Rule را انتخاب می‌کند.
  • NotEmpty: خالی نبودن مقدار.
  • WithMessage: Error Message در زمان شکست Rule؛ می‌تواند از Placeholder برابر {PropertyName} استفاده کند.
  • Custom: Rule سفارشی، مثلاً Regex و ایجاد Error با AddFailure.
  • Matches: Match کردن مقدار با Regular Expression.

این Ruleها—به‌جز AddFailure—به‌صورت Extension Method قابل Chain هستند.

Listing 4-14. FluentValidation برای Country

using AspNetCore8MinimalApis.Models;
    using FluentValidation;
    using FluentValidation.Results;
    using System.Text.RegularExpressions;

    namespace AspNetCore8MinimalApis.Validators;

    public class CountryValidator : AbstractValidator<Country>
    {
        public CountryValidator()
        {
            RuleFor(x => x.Name)
            .NotEmpty()
    
            .WithMessage("{PropertyName} is required")
            .Custom((name, context) =>
            {
               Regex rg = new Regex("<.*?>"); // Matches HTML tags
               if (rg.Matches(name).Count > 0)
               {
                  // Raises an error
                  context.AddFailure(
                     new ValidationFailure(
                     "Name",
                     "The parameter has invalid content"
                     )
                  );
            }});

            RuleFor(x => x.FlagUri)
            .NotEmpty()
            .WithMessage("{PropertyName} is required")
            .Matches("^(https:\\/\\/.)[-a-zA-Z0-9@:%._\\+~#=]
            {2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)$")
            .WithMessage("{PropertyName} must match an HTTPS URL");
        }
    }

برای استفاده از Validator در Dependency Injection، همهٔ Validatorهای Assembly با یک Scan ثبت می‌شوند.

Listing 4-15. ثبت Validatorهای FluentValidation در Assembly برنامه

var builder = WebApplication.CreateBuilder(args);
    builder.Services.AddValidatorsFromAssemblyContaining<P
    rogram>();
    var app = builder.Build();

استفاده از Program به‌عنوان Generic Parameter باعث می‌شود Assembly مربوط به ASP.NET Core Application شناسایی و Validatorهای موجود در آن پیدا شوند.

Listing 4-16. POST /countries با Validation

app.MapPost("/countries", ([FromBody] Country country,
    IValidator<Country> validator) => {
        var validationResult = validator.Validate(country);

        if (validationResult.IsValid)
        {
            //Do something
            return Results.Created();
        }
        return Results.ValidationProblem(validationResult.
         ToDictionary(), statusCode: (int)HttpStatusCode.
         BadRequest);
    });

Dependency Injection به‌صورت خودکار Implementation مناسب IValidator<Country> یعنی CountryValidator را فراهم می‌کند. Validate روی Object اجرا می‌شود و IsValid نتیجه را مشخص می‌کند. در صورت شکست، Errorها به Dictionary تبدیل و با ValidationProblem به Client برگردانده می‌شوند.

در نمونهٔ کتاب، قرار دادن JavaScript Alert داخل Name و حذف HTTPS از FlagUri باعث Validation Error می‌شود.

تصویر منبع — صفحهٔ 139Visual واقعی استخراج‌شده از PDF، مربوط به صفحهٔ 139.
شکل ۴-۲۱. ValidationProblem هنگام نامعتبر بودن Name و FlagUri

Errorها با جزئیات مناسب برگردانده می‌شوند؛ در صورت موفقیت Validation، Endpoint Response موفق خواهد داشت.

منبع: Coding Clean, Reliable, and Safe REST APIs with ASP.NET Core 8 — Anthony Giretti.

فروش یا انتشار این ترجمه منوط به داشتن مجوز لازم از صاحب حقوق اثر است.

امتیاز کاربران به این مقاله

☆☆☆☆☆

0 نفر امتیاز داده اند. میانگین: 0.0 از 5

 

0 نظر

نظر محترم شما در مورد مقاله های وب سایت برنامه نویسی و پایگاه داده

نظرات محترم شما در خدمات رسانی بهتر ما را یاری می نمایند. لطفا اگر مایل بودید یک نظر ما را مهمان فرمائید. آدرس ایمیل و وب سایت شما نمایش داده نخواهد شد.

0 / 500

اطلاعات تماس

  • آدرس:اصفهان-خیابان ام کلثوم غربی - بعد خیابان تخم چی - بیست متر بعد از پیتزا ننه شب - کوچه تعمیر گاه سمار زغالی - پلاک 354 - درب مشکی - طبقه هفتم
  • آدرس ایمیل:najafzade@gmail.com
  • وب سایت:http://www.a00b.com/
  • تلفن ثابت:(+98)9131253620
  • تلفن همراه:09131253620