Route parametrlari — {id}, [FromRoute], [FromQuery], [FromBody]

~20 daq · .NET Backend · Web API — Controller va Routing

Muammodan boshlaylik

Bitta so'rovda ma'lumot har xil joydan keladi:

PUT /api/dokonlar/7/mahsulotlar/42?bildirishnoma=true
Authorization: Bearer eyJ...
X-Til: uz
Content-Type: application/json

{ "narx": 135000, "qoldiq": 8 }
  • 7 va 42 — URL yo'lida
  • bildirishnoma — query string da
  • X-Til — sarlavhada
  • Narx va qoldiq — body da (JSON)

Har birini Request.Query["..."], Request.Headers["..."] bilan qo'lda o'qib, turga o'girish — takroriy va xatoga moyil.


Model binding nima?

Model binding — ASP.NET Core so'rovning turli qismlaridagi qiymatlarni action parametrlariga avtomatik, kerakli turga o'girib beradi:

[HttpPut("/api/dokonlar/{dokonId:int}/mahsulotlar/{id:int}")]
public IActionResult Yangila(
    [FromRoute] int dokonId,
    [FromRoute] int id,
    [FromQuery] bool bildirishnoma,
    [FromHeader(Name = "X-Til")] string? til,
    [FromBody] MahsulotYangilaDto dto)
{
    // hammasi tayyor, to'g'ri turda
    return NoContent();
}

Hayotiy o'xshatma

Pochta posilkasi. Manzil konvert ustida (route), "tez yetkazish" belgisi — yorliqda (query), jo'natuvchi muhri — burchakda (header), asosiy narsa — qutining ichida (body). Pochtachi har birini o'z joyidan o'qiydi — siz hammasini bitta varaqqa ko'chirib yozishingiz shart emas.


Manba atributlari

Atribut Qayerdan Misol
[FromRoute] URL yo'li /mahsulotlar/{id}
[FromQuery] Query string ?sahifa=2&kategoriya=telefon
[FromBody] Body (JSON) POST/PUT ma'lumoti
[FromHeader] Sarlavha X-Til: uz
[FromForm] Forma, fayl yuklash multipart/form-data
[FromServices] DI konteyner servis (38-dars)

[ApiController] taxminlari

Atribut yozilmasa, [ApiController] taxmin qiladi:

  • Murakkab tur (klass, record) → [FromBody]
  • Route shablonida nomi bor parametr → [FromRoute]
  • Qolgan oddiy turlar (int, string, bool) → [FromQuery]
  • IFormFile → [FromForm]
  • DI da ro'yxatdan o'tgan tur → [FromServices]
[HttpGet("{id:int}")]
public IActionResult Ol(int id, bool batafsil) { }   // id — route, batafsil — query

[!TIP] Aniqlik uchun murakkab holatlarda atributlarni yozing. Ayniqsa query dan murakkab obyekt olishda: [FromQuery] MahsulotFiltr filtr — busiz u body dan qidiriladi.


Route parametrlari

[HttpGet("{id:int}")]                              // majburiy
[HttpGet("{slug}")]
[HttpGet("kategoriya/{kategoriya}/{sahifa:int?}")] // ? — ixtiyoriy
[HttpGet("{*yol}")]                                // catch-all: qolgan butun yo'l

Constraint lar (41-dars) — :int, :guid, :min(1), :length(3,50). Ular validatsiya emas, marshrut tanlash: mos kelmasa — 404.


Query string

Oddiy

// GET /api/mahsulotlar?kategoriya=telefon&min=1000000&sahifa=2
[HttpGet]
public IActionResult Royxat(string? kategoriya, decimal? min, decimal? max, int sahifa = 1, int hajm = 20)

Klass bilan — ko'p filtrlar

public class MahsulotFiltr
{
    public string? Kategoriya { get; init; }
    public decimal? Min { get; init; }
    public decimal? Max { get; init; }
    public string Saralash { get; init; } = "yangi";

    [Range(1, int.MaxValue)]
    public int Sahifa { get; init; } = 1;

    [Range(1, 100)]
    public int Hajm { get; init; } = 20;
}

[HttpGet]
public async Task<IActionResult> Royxat([FromQuery] MahsulotFiltr filtr, CancellationToken ct) =>
    Ok(await service.RoyxatAsync(filtr, ct));

[Range(1, 100)] — ?hajm=1000000 bilan bazani bitta so'rovda to'liq yuklab olishning oldini oladi.

Massiv

// GET /api/mahsulotlar?id=1&id=5&id=9
[HttpGet]
public IActionResult BirNechta([FromQuery(Name = "id")] int[] idlar)

Body — JSON

[HttpPost]
public IActionResult Yarat([FromBody] MahsulotYaratDto dto)
  • Bitta action da faqat bitta [FromBody] parametr bo'lishi mumkin — butun body bitta ob'ektga
  • JSON maydon nomlari katta-kichik harfga sezgir emas (nomi = Nomi)
  • Noto'g'ri JSON yoki tur mos emas ("narx": "qimmat") → avtomatik 400 ([ApiController])

Fayl yuklash — [FromForm]

[HttpPost("{id:int}/rasm")]
[RequestSizeLimit(5 * 1024 * 1024)]
public async Task<IActionResult> RasmYukla(int id, IFormFile fayl, CancellationToken ct)
{
    if (fayl.Length == 0) return BadRequest("Fayl bo'sh");
    if (!new[] { "image/jpeg", "image/png", "image/webp" }.Contains(fayl.ContentType))
        return BadRequest("Faqat rasm");

    var nom = 
quot;{Guid.NewGuid()}{Path.GetExtension(fayl.FileName)}"; await using var oqim = System.IO.File.Create(Path.Combine("uploads", nom)); await fayl.CopyToAsync(oqim, ct); return Ok(new { url =
quot;/uploads/{nom}" }); }

[!WARNING] Foydalanuvchi bergan fayl.FileName ni to'g'ridan-to'g'ri saqlash yo'li sifatida ishlatmang (../../appsettings.json — path traversal). O'zingiz yangi nom yarating (GUID), hajm va turni cheklang. Production da fayllarni server diskida emas — S3/Blob kabi saqlash xizmatida saqlang.


Tez-tez uchraydigan xatolar

Query dan murakkab tur, atributsiz — [ApiController] uni body dan kutadi → 415 Unsupported Media Type yoki 400.

Ikki [FromBody]:

InvalidOperationException: Action '...' has more than one parameter that was specified or inferred as bound from request body.

Ularni bitta DTO ga birlashtiring.

Route dagi nom va parametr nomi mos emas:

[HttpGet("{mahsulotId}")]
public IActionResult Ol(int id)    // ❌ id doim 0 — nomlar mos emas

Xulosa

Model binding — so'rov qismlari action parametrlariga avtomatik va to'g'ri turda keladi. Manbalar: [FromRoute] (yo'l), [FromQuery] (query string), [FromBody] (JSON, bitta), [FromHeader], [FromForm] (fayl), [FromServices].

[ApiController] taxmini: murakkab tur — body, route nomi — route, oddiy — query. Ko'p filtrlar — [FromQuery] bilan klass va [Range] cheklovlari. Route parametr nomi action parametri nomi bilan bir xil bo'lsin. Fayl — IFormFile, nom va hajmni o'zingiz nazorat qiling.


Keyingi dars

45-dars: DTO — nima uchun, Entity dan ajratish, request/response DTO