Skip to content

17 • Rest API v ASP.NET ​

Implementace REST API v ASP.NET, routování, controller, metody HTTP (GET, POST, PUT, DELETE)

Formát: 30 min praktická úloha, 15 min obhajoba + teorie. Podle informací z minulých let se u praktiky dodělává endpoint do existujícího starter projektu. Připraveno na to.


Část 1: Teorie ​

Co je REST ​

REST (Representational State Transfer) je architektonický styl pro komunikaci klient-server přes HTTP. Každý zdroj (resource) má svou URL a operace s ním odpovídají HTTP metodám.

Klasický chyták: REST není protokol, je to architektonický styl. HTTP je protokol, REST se postavil nad ním jako konvence "jak ho používat".

Klíčové vlastnosti ​

VlastnostCo znamená
StatelessServer si nepamatuje stav klienta. Každý request obsahuje vše potřebné (token, ID, atd.)
Client-ServerKlient a server jsou odděleny, komunikují přes HTTP
Uniform InterfaceJednotné konvence pro URL a metody
CacheableOdpovědi lze cachovat (GET typicky ano, POST ne)
Layered SystemKlient nemusí vědět, jestli komunikuje přímo se serverem nebo přes proxy/CDN
Resource-basedVše je "zdroj" identifikovaný URL

Stateless je důležitý pojem: server si nic nepamatuje mezi requesty. Proto se token posílá v hlavičce každého requestu, ne v session.


HTTP metody + CRUD ​

HTTP MetodaCRUDAtribut v ASP.NETStav úspěchuVrátí tělo
GETRead[HttpGet]200 OKAno
POSTCreate[HttpPost]201 CreatedAno (nový záznam)
PUTUpdate (celý)[HttpPut("{id}")]200 OK nebo 204 No ContentVolitelné
PATCHUpdate (část)[HttpPatch("{id}")]200 OKAno
DELETEDelete[HttpDelete("{id}")]204 No ContentNe

Idempotence: důležitý pojem ​

Idempotentní operace = můžeš ji volat víckrát se stejným výsledkem.

MetodaIdempotentníBezpečná
GETAnoAno (nemění data)
POSTNe (vytvoří nový záznam pokaždé)Ne
PUTAno (nahradí stejně)Ne
PATCHZáleží na implementaciNe
DELETEAno (smazáno = smazáno)Ne

Konvence URL endpointů ​

GET    /api/produkty          → seznam všech
GET    /api/produkty/{id}     → jeden záznam
POST   /api/produkty          → vytvoř nový
PUT    /api/produkty/{id}     → nahraď celý
PATCH  /api/produkty/{id}     → uprav část
DELETE /api/produkty/{id}     → smaž

GET    /api/produkty/{id}/recenze    → vnořený zdroj
GET    /api/produkty?skladem=true    → filtrace přes query string

HTTP stavové kódy (nejdůležitější) ​

2xx: Success ​

KódNázevKdy
200OKGET nebo PUT úspěšný
201CreatedPOST: záznam vytvořen
204No ContentDELETE, nebo PUT bez těla odpovědi

4xx: Client Error (chyba klienta) ​

KódNázevKdy
400Bad RequestŠpatný request (chybí pole, špatný formát)
401UnauthorizedNení přihlášen (žádný token)
403ForbiddenPřihlášen, ale nemá oprávnění
404Not FoundZáznam neexistuje
409ConflictKonflikt (duplicitní email atd.)
422Unprocessable EntityValidace selhala

5xx: Server Error (chyba serveru) ​

KódNázevKdy
500Internal Server ErrorNeošetřená výjimka v kódu
502Bad GatewayProxy nedostala odpověď
503Service UnavailableServer přetížený nebo údržba

Pamatuj si: 4xx = něco udělal špatně klient, 5xx = něco udělal špatně server. Když máš 500, je to bug v tvém kódu.


Struktura projektu ASP.NET Web API ​

MujApi/
├── Controllers/
│   └── ProduktyController.cs    ← controllery (logika endpointů)
├── Models/
│   └── Produkt.cs               ← datové třídy
├── Services/                    ← business logika (volitelné)
│   └── ProduktyService.cs
├── Program.cs                   ← konfigurace, DI, middleware pipeline
└── MujApi.csproj                ← konfigurace projektu

Vytvoření projektu ​

bash
dotnet new webapi -n MujApi --no-openapi
cd MujApi
dotnet run

Model ​

Jednoduchá POCO třída (Plain Old CLR Object):

csharp
public class Produkt
{
    public int Id { get; set; }
    public string Nazev { get; set; } = "";
    public decimal Cena { get; set; }
    public int Skladem { get; set; }
}

ASP.NET automaticky serializuje/deserializuje do/z JSON díky System.Text.Json.

Z C# objektu:

csharp
new Produkt { Id = 1, Nazev = "Notebook", Cena = 25000, Skladem = 5 }

Vznikne JSON:

json
{
    "id": 1,
    "nazev": "Notebook",
    "cena": 25000,
    "skladem": 5
}

Default jsou jména camelCase v JSONu (i když v C# jsou PascalCase). System.Text.Json to dělá automaticky.


Controller: základ ​

csharp
using Microsoft.AspNetCore.Mvc;

[ApiController]                        // automatická validace modelu
[Route("api/[controller]")]            // URL = api/produkty
public class ProduktyController : ControllerBase
{
    // "databáze" v paměti (pro maturitu stačí)
    private static List<Produkt> _produkty = new()
    {
        new Produkt { Id = 1, Nazev = "Notebook", Cena = 25000, Skladem = 5 },
        new Produkt { Id = 2, Nazev = "Myš",      Cena = 350,   Skladem = 20 }
    };
}

Klíčové atributy ​

AtributCo dělá
[ApiController]Zapne automatickou validaci, binding z body, ProblemDetails chyby
[Route("api/[controller]")]URL prefix, [controller] se nahradí jménem třídy bez "Controller"
[HttpGet]Mapuje na GET /api/produkty
[HttpGet("{id}")]Mapuje na GET /api/produkty/5
[HttpPost]Mapuje na POST /api/produkty
[HttpPut("{id}")]Mapuje na PUT /api/produkty/5
[HttpDelete("{id}")]Mapuje na DELETE /api/produkty/5
[FromBody]Čte parametr z těla requestu
[FromRoute]Čte parametr z URL (default pro {id})
[FromQuery]Čte parametr z query string (?key=value)

Co dělá [ApiController] ​

Šetří hromadu práce, zapne:

  • Automatickou validaci ModelState: pokud model selže validací (např. chybí required pole), automaticky vrátí 400 Bad Request
  • Implicitní [FromBody] pro komplexní typy (nemusí se psát)
  • Implicitní [FromRoute] pro parametry odpovídající template
  • ProblemDetails chybové odpovědi (standardní RFC 7807 formát)

Bez [ApiController] bys musel psát validaci ručně.


GET: čtení dat ​

GET všechny ​

csharp
// GET /api/produkty
[HttpGet]
public ActionResult<List<Produkt>> GetAll()
{
    return Ok(_produkty);     // 200 OK + JSON
}

GET podle ID ​

csharp
// GET /api/produkty/1
[HttpGet("{id}")]
public ActionResult<Produkt> GetById(int id)
{
    var produkt = _produkty.FirstOrDefault(p => p.Id == id);
    if (produkt == null)
        return NotFound();        // 404
    return Ok(produkt);           // 200 + JSON
}

POST: vytvoření záznamu ​

csharp
// POST /api/produkty
// Tělo requestu: { "nazev": "Klávesnice", "cena": 800, "skladem": 10 }
[HttpPost]
public ActionResult<Produkt> Create([FromBody] Produkt novy)
{
    novy.Id = _produkty.Count > 0 ? _produkty.Max(p => p.Id) + 1 : 1;
    _produkty.Add(novy);

    // 201 Created + Location header (kde najít nový záznam)
    return CreatedAtAction(nameof(GetById), new { id = novy.Id }, novy);
}

[FromBody]: čti data z těla HTTP requestu (JSON). S [ApiController] je implicitní pro komplexní typy, takže nemusíš psát.

CreatedAtAction: vrátí 201 Created plus Location header, který říká, kde nový záznam najít (typicky URL GET endpointu). Tohle je důležitý detail REST API.


PUT: nahrazení celého záznamu ​

csharp
// PUT /api/produkty/1
// Tělo: { "nazev": "Notebook Pro", "cena": 30000, "skladem": 3 }
[HttpPut("{id}")]
public IActionResult Update(int id, [FromBody] Produkt updated)
{
    var index = _produkty.FindIndex(p => p.Id == id);
    if (index == -1)
        return NotFound();           // 404

    updated.Id = id;                 // id z URL má přednost
    _produkty[index] = updated;

    return NoContent();              // 204: úspěch, bez těla
}

Důležitý detail: ID se bere z URL, ne z těla. Klient by neměl měnit ID přes PUT, jen ostatní pole.


DELETE: smazání ​

csharp
// DELETE /api/produkty/1
[HttpDelete("{id}")]
public IActionResult Delete(int id)
{
    var produkt = _produkty.FirstOrDefault(p => p.Id == id);
    if (produkt == null)
        return NotFound();           // 404

    _produkty.Remove(produkt);
    return NoContent();              // 204
}

Co vrátitMetodaKód
Data (objekt)Ok(data)200
Nový záznamCreatedAtAction(...)201
Prázdná odpověďNoContent()204
NenalezenoNotFound()404
Špatný requestBadRequest("zpráva")400
Vlastní kódStatusCode(418, "I'm a teapot")libovolný

IActionResult vs ActionResult<T> ​

csharp
// IActionResult: flexibilní (různé typy odpovědí)
[HttpGet("{id}")]
public IActionResult GetById(int id) { /* může vrátit Ok, NotFound, atd. */ }

// ActionResult<T>: navíc TYPUJE výstup pro Swagger a klienty
[HttpGet("{id}")]
public ActionResult<Produkt> GetById(int id) { /* explicitně říká, že vrací Produkt */ }

Pro maturitu: ActionResult<T> u GET (vrací data), IActionResult u DELETE/PUT (vrací jen status).


Routování: shrnutí ​

csharp
[Route("api/[controller]")]   →  /api/produkty

[HttpGet]                     →  GET  /api/produkty
[HttpGet("{id}")]             →  GET  /api/produkty/5
[HttpGet("aktivni")]          →  GET  /api/produkty/aktivni    (pojmenovaná trasa)
[HttpGet("{id}/detail")]      →  GET  /api/produkty/5/detal
[HttpGet("kategorie/{kat}/produkty")]  →  GET  /api/produkty/kategorie/elektronika/produkty

Vícenásobné routy ​

csharp
[Route("api/[controller]")]
[Route("api/v1/[controller]")]      // dvě cesty na stejný controller
public class ProduktyController : ControllerBase { }

Program.cs: registrace a pipeline ​

csharp
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();          // přidá podporu controllerů
builder.Services.AddEndpointsApiExplorer(); // pro Swagger
builder.Services.AddSwaggerGen();           // Swagger UI

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();                     // dostupné na /swagger
}

app.MapControllers();                       // mapuje controller endpoints
app.Run();

Co je Swagger ​

Swagger (dnes OpenAPI) je automaticky vygenerované interaktivní UI pro testování API. Najdeš ho na /swagger. Generuje se ze atributů controllerů.

Pro maturitu ideální místo pro ověření funkčnosti API: vidíš všechny endpointy, můžeš je zkusit kliknutím (jako Postman v prohlížeči).


Dependency Injection (briefly) ​

V reálném projektu se logika z controlleru přesouvá do services, které se injektují přes konstruktor:

csharp
public class ProduktyController : ControllerBase
{
    private readonly IProduktyService _service;

    public ProduktyController(IProduktyService service)   // DI
    {
        _service = service;
    }

    [HttpGet]
    public ActionResult<List<Produkt>> GetAll()
        => Ok(_service.GetAll());
}

Registrace v Program.cs:

csharp
builder.Services.AddScoped<IProduktyService, ProduktyService>();

Lifetimy:

  • AddSingleton: jedna instance pro celou aplikaci
  • AddScoped: jedna instance na request
  • AddTransient: nová instance při každém vyžádání

Pro maturitu stačí static List přímo v controlleru. DI je bonus, který by se mohli zeptat.


Testování API ​

Swagger (nejjednodušší) ​

Otevři https://localhost:5001/swagger, vyber endpoint, "Try it out", napiš parametry, "Execute".

Postman / Bruno (desktop klient) ​

GUI klient pro REST API. Snadné posílání requestů s autentizací, history, kolekce.

cURL (terminál) ​

bash
# GET
curl https://localhost:5001/api/produkty

# POST
curl -X POST https://localhost:5001/api/produkty \
     -H "Content-Type: application/json" \
     -d '{"nazev":"Klávesnice","cena":800,"skladem":10}'

# PUT
curl -X PUT https://localhost:5001/api/produkty/1 \
     -H "Content-Type: application/json" \
     -d '{"nazev":"Notebook Pro","cena":30000,"skladem":3}'

# DELETE
curl -X DELETE https://localhost:5001/api/produkty/1

Časté chyby ​

ChybaDůsledekŘešení
[Route("api/produkty")] místo [controller]Hard-coded URLPoužít [controller] token
Zapomenutý [ApiController]Žádná auto-validaceVždy přidat
POST vrací Ok(novy) (200)Špatný stavový kódCreatedAtAction (201)
DELETE vrací Ok() (200)Špatný stavový kódNoContent() (204)
Použití static List v produkciSdílí se mezi requesty, ztratí se při restartuPoužít databázi (EF Core)
Chybí check existence v PUT/DELETE500 místo 404Vždy zkontrolovat null
Použití string id místo int idChybný typový bindingDefinovat správný typ
Pojmenování endpointu velbloudemEndpoint /api/Produkty (uppercase)URL jsou case-sensitive, držet se camelCase
POST endpoint s ID v URLŠpatné RESTPOST do /api/resource, ID generuje server
Smíchání [FromBody] a [FromQuery]KonfliktKomplexní typy z body, primitivní z query/route

Část 2: Praktická úloha ​

Co může praktická úloha obsahovat ​

Podle leaku bude úloha typicky doplnit endpoint do existujícího starter projektu. To znamená:

  1. Otevřít poskytnutý starter projekt
  2. Přečíst si existující kód a model
  3. Spustit projekt přes dotnet run a otevřít Swagger
  4. Doplnit chybějící endpointy podle TODO komentářů
  5. Otestovat každý endpoint ve Swaggeru po implementaci
  6. Případně doplnit bonus endpoint

Workflow při zadání ​

1. ls Controllers/                 → kolik tříd
2. Otevřít existující Controller   → vidět konvence
3. Otevřít Model                   → znát fieldy
4. Spustit dotnet run + /swagger
5. Začít s prvním TODO
6. Otestovat ve Swaggeru
7. Pokračovat dalšími TODOs
8. Bonus pokud zbude čas

Příklad zadání: Knihovna API ​

Máš API pro správu knih (KnihyController). Projekt je z části hotový, máš model, router a 2 funkční endpointy (GET all a GET by id). Tvůj úkol: doplnit chybějící endpointy (POST, PUT, DELETE) přesně podle TODO komentářů + bonus endpoint pro dostupné knihy.

Starter kód ​

Models/Kniha.cs ​

csharp
namespace KnihovnaApi.Models;

public class Kniha
{
    public int Id { get; set; }
    public string Nazev { get; set; } = "";
    public string Autor { get; set; } = "";
    public int RokVydani { get; set; }
    public bool Dostupna { get; set; } = true;
}

Controllers/KnihyController.cs (starter, neúplný) ​

csharp
using Microsoft.AspNetCore.Mvc;
using KnihovnaApi.Models;

namespace KnihovnaApi.Controllers;

[ApiController]
[Route("api/[controller]")]
public class KnihyController : ControllerBase
{
    private static List<Kniha> _knihy = new()
    {
        new Kniha { Id = 1, Nazev = "Stopařův průvodce galaxií", Autor = "Douglas Adams", RokVydani = 1979, Dostupna = true },
        new Kniha { Id = 2, Nazev = "1984", Autor = "George Orwell", RokVydani = 1949, Dostupna = false },
        new Kniha { Id = 3, Nazev = "Pán prstenů", Autor = "J. R. R. Tolkien", RokVydani = 1954, Dostupna = true }
    };

    // ✓ HOTOVO: GET všechny knihy
    [HttpGet]
    public ActionResult<List<Kniha>> GetAll()
    {
        return Ok(_knihy);
    }

    // ✓ HOTOVO: GET kniha podle ID
    [HttpGet("{id}")]
    public ActionResult<Kniha> GetById(int id)
    {
        var kniha = _knihy.FirstOrDefault(k => k.Id == id);
        if (kniha == null) return NotFound();
        return Ok(kniha);
    }

    // TODO 1: POST - přidat novou knihu
    //   Přijme Kniha z těla, přidělí Id, přidá do seznamu.
    //   Vrátí 201 Created s novou knihou.

    // TODO 2: PUT - aktualizovat celou knihu
    //   Přijme id z URL a Kniha z těla.
    //   Pokud Id neexistuje → 404. Jinak nahraď → 204.

    // TODO 3: DELETE - smazat knihu
    //   Přijme id z URL.
    //   Pokud Id neexistuje → 404. Jinak smaž → 204.

    // BONUS: GET pouze dostupné knihy
    //   GET /api/knihy/dostupne
    //   Vrátí seznam kde Dostupna == true.
}

Řešení: kompletní KnihyController.cs ​

csharp
using Microsoft.AspNetCore.Mvc;
using KnihovnaApi.Models;

namespace KnihovnaApi.Controllers;

[ApiController]
[Route("api/[controller]")]
public class KnihyController : ControllerBase
{
    private static List<Kniha> _knihy = new()
    {
        new Kniha { Id = 1, Nazev = "Stopařův průvodce galaxií", Autor = "Douglas Adams", RokVydani = 1979, Dostupna = true },
        new Kniha { Id = 2, Nazev = "1984", Autor = "George Orwell", RokVydani = 1949, Dostupna = false },
        new Kniha { Id = 3, Nazev = "Pán prstenů", Autor = "J. R. R. Tolkien", RokVydani = 1954, Dostupna = true }
    };

    // ===== GET všechny =====
    [HttpGet]
    public ActionResult<List<Kniha>> GetAll()
    {
        return Ok(_knihy);
    }

    // ===== GET podle ID =====
    [HttpGet("{id}")]
    public ActionResult<Kniha> GetById(int id)
    {
        var kniha = _knihy.FirstOrDefault(k => k.Id == id);
        if (kniha == null)
            return NotFound();
        return Ok(kniha);
    }

    // ===== TODO 1: POST nová kniha =====
    [HttpPost]
    public ActionResult<Kniha> Create([FromBody] Kniha nova)
    {
        // Přidělíme nové Id (max existující + 1, nebo 1 pokud prázdné)
        nova.Id = _knihy.Count > 0 ? _knihy.Max(k => k.Id) + 1 : 1;
        _knihy.Add(nova);

        // 201 Created + Location header
        return CreatedAtAction(nameof(GetById), new { id = nova.Id }, nova);
    }

    // ===== TODO 2: PUT aktualizace =====
    [HttpPut("{id}")]
    public IActionResult Update(int id, [FromBody] Kniha aktualizovana)
    {
        var index = _knihy.FindIndex(k => k.Id == id);
        if (index == -1)
            return NotFound();

        aktualizovana.Id = id;             // ID z URL má přednost
        _knihy[index] = aktualizovana;

        return NoContent();                // 204
    }

    // ===== TODO 3: DELETE =====
    [HttpDelete("{id}")]
    public IActionResult Delete(int id)
    {
        var kniha = _knihy.FirstOrDefault(k => k.Id == id);
        if (kniha == null)
            return NotFound();

        _knihy.Remove(kniha);
        return NoContent();                // 204
    }

    // ===== BONUS: GET dostupné =====
    [HttpGet("dostupne")]
    public ActionResult<List<Kniha>> GetDostupne()
    {
        var dostupne = _knihy.Where(k => k.Dostupna).ToList();
        return Ok(dostupne);
    }
}

Program.cs (beze změny) ​

csharp
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();
app.UseSwagger();
app.UseSwaggerUI();
app.MapControllers();
app.Run();

Testování ve Swaggeru ​

  1. Spustit přes dotnet run

  2. Otevřít https://localhost:5001/swagger v prohlížeči

  3. GET /api/knihy → vrátí seznam 3 knih

  4. GET /api/knihy/1 → vrátí "Stopařův průvodce galaxií"

  5. GET /api/knihy/999 → vrátí 404 Not Found

  6. POST /api/knihy s tělem: → vrátí 201 Created s knihou ID 4

    json
    { "nazev": "Hobit", "autor": "J. R. R. Tolkien", "rokVydani": 1937, "dostupna": true }
  7. PUT /api/knihy/1 s tělem: → vrátí 204 No Content

    json
    { "nazev": "Stopařův průvodce galaxií", "autor": "Douglas Adams", "rokVydani": 1979, "dostupna": false }
  8. DELETE /api/knihy/2 → vrátí 204 No Content

  9. GET /api/knihy/dostupne → vrátí jen knihy s Dostupna: true

Co se v řešení děje ​

TODO 1 (POST): [FromBody] čte JSON z těla requestu (s [ApiController] implicitně). Vygenerujeme nové ID jako max existujícího + 1. Přidáme do listu. Vrátíme CreatedAtAction, což vytvoří 201 Created a do hlavičky Location dá URL nově vytvořené knihy (/api/knihy/{novéId}).

TODO 2 (PUT): Najdeme index podle ID. Pokud neexistuje, 404. Jinak nastavíme ID z URL (klient by ho neměl měnit) a nahradíme záznam celý. Vrátíme 204 No Content (úspěch bez těla).

TODO 3 (DELETE): Najdeme knihu. Neexistuje → 404. Existuje → odebereme a 204.

Bonus (GET dostupné): Atribut [HttpGet("dostupne")] mapuje na /api/knihy/dostupne. LINQ Where filtruje, ToList materializuje. Ok vrátí 200 s JSON polem.


Další bonusy ​

Bonus A: Filtrace přes query string ​

csharp
// GET /api/knihy/hledat?autor=Tolkien
[HttpGet("hledat")]
public ActionResult<List<Kniha>> Hledej([FromQuery] string autor)
{
    var vysledky = _knihy
        .Where(k => k.Autor.Contains(autor, StringComparison.OrdinalIgnoreCase))
        .ToList();
    return Ok(vysledky);
}

Bonus B: Validace s ModelState ​

csharp
public class Kniha
{
    public int Id { get; set; }

    [Required, MinLength(1)]
    public string Nazev { get; set; } = "";

    [Required]
    public string Autor { get; set; } = "";

    [Range(1000, 2100)]
    public int RokVydani { get; set; }

    public bool Dostupna { get; set; } = true;
}

S [ApiController] se validace provede automaticky a vrátí 400 Bad Request s detaily, pokud něco neprojde.

Bonus C: PATCH endpoint (částečný update) ​

csharp
public class KnihaUpdate
{
    public string? Nazev { get; set; }
    public string? Autor { get; set; }
    public bool? Dostupna { get; set; }
}

[HttpPatch("{id}")]
public IActionResult Patch(int id, [FromBody] KnihaUpdate update)
{
    var kniha = _knihy.FirstOrDefault(k => k.Id == id);
    if (kniha == null) return NotFound();

    if (update.Nazev != null) kniha.Nazev = update.Nazev;
    if (update.Autor != null) kniha.Autor = update.Autor;
    if (update.Dostupna != null) kniha.Dostupna = update.Dostupna.Value;

    return NoContent();
}

Část 3: Tipy pro obhajobu ​

Co u obhajoby říct ​

"V zadání jsem dostal hotový starter projekt s controllerem pro správu knih a doplnil chybějící endpointy podle TODO komentářů. POST endpoint přijímá knihu z těla requestu přes FromBody (s ApiController atributem implicitně), generuje nové ID a vrací CreatedAtAction s 201 Created a Location headerem. PUT najde knihu podle ID, vrátí 404 pokud neexistuje, jinak nahradí celý záznam a vrátí 204 No Content. DELETE pracuje obdobně. Bonus endpoint dostupne používá LINQ Where pro filtraci. Všechno jsem testoval ve Swaggeru, který je integrovaný v projektu."

Klíčové pojmy pro teorii ​

PojemRychlá odpověď
RESTArchitektonický styl pro komunikaci klient-server přes HTTP
StatelessServer si nepamatuje stav klienta, každý request je samostatný
CRUDCreate, Read, Update, Delete (POST, GET, PUT/PATCH, DELETE)
IdempotenceVolání víckrát má stejný výsledek (GET, PUT, DELETE ano, POST ne)
HTTP metodyGET, POST, PUT, PATCH, DELETE
HTTP status kódy200 OK, 201 Created, 204 No Content, 400/404, 500
[ApiController]Auto-validace, FromBody binding, ProblemDetails
[Route("api/[controller]")]URL prefix, [controller] nahradí jméno třídy
[FromBody]Čte parametr z těla JSON requestu
[FromRoute], [FromQuery]Z URL, z query stringu
Ok()200 OK + data
CreatedAtAction()201 Created + Location header
NoContent()204 (úspěch bez těla)
NotFound()404
BadRequest()400
ActionResult<T>Typovaný návratový typ pro Swagger
IActionResultNetypovaný (flexibilní)
Swagger / OpenAPIAuto-generated dokumentace + testovací UI
DI (Dependency Injection)Vstřikování závislostí přes konstruktor
Middleware pipelinePosloupnost zpracování requestu v Program.cs

Časté chytáky ​

OtázkaOdpověď
Co je REST?Architektonický styl pro komunikaci klient-server přes HTTP. Stateless, resource-based, používá HTTP metody pro CRUD.
Rozdíl PUT a PATCH?PUT nahrazuje celý objekt, PATCH mění jen některá pole. PUT je idempotentní vždy, PATCH záleží na implementaci.
Co dělá [ApiController]?Auto-validaci ModelState, implicit FromBody pro komplexní typy, ProblemDetails chybové odpovědi.
Jaký status vrátí NoContent?204 No Content (úspěch, ale nic v těle).
Proč POST vrací 201 a ne 200?200 znamená OK obecně, 201 explicitně "vytvořeno". REST konvence. Plus 201 dovoluje Location header.
Co je [FromBody]?Čte parametr z těla HTTP requestu (JSON). Potřeba pro POST/PUT s objekty.
Co znamená "REST je stateless"?Server si nepamatuje stav klienta mezi requesty. Každý request musí obsahovat vše potřebné (token v hlavičce, ne v session).
Jak funguje [Route("api/[controller]")]?[controller] se nahradí jménem třídy bez sufixu "Controller". ProduktyController → /api/produkty.
Co je idempotence?Volání operace víckrát má stejný výsledek. GET, PUT, DELETE jsou idempotentní. POST není.
Co je Swagger?OpenAPI dokumentace plus interaktivní UI pro testování API. Auto-generated z atributů controlleru.

Časté chyby v praktické úloze ​

  • POST vrací Ok(novy) (200) místo CreatedAtAction (201)
  • DELETE vrací Ok() (200) místo NoContent() (204)
  • Chybí check null v PUT/DELETE (500 Internal Server Error místo 404)
  • Hardcoded route [Route("api/knihy")] místo [controller] tokenu
  • ID v POST endpointu (klient by neměl posílat ID, generuje server)
  • Použití string pro ID místo int (špatný binding)
  • Zapomenutý [ApiController] (žádná auto-validace, žádný implicit FromBody)
  • Špatný pořadí atributů u routy ([HttpGet("dostupne")] před [HttpGet("{id}")] může vést k matching dostupne jako ID parametr)
  • Forgotten [FromBody] u PATCH (binduje špatně)
  • Použití IActionResult všude (lépe ActionResult<T> pro typování Swageru)
  • Generování ID bez Max + 1 (např. Count + 1, problém po smazání)
  • _knihy.Remove(kniha) pak _knihy.Add(kniha) v PUT (špatný způsob update, použít [index] = ...)
  • Vrácení 200 OK z DELETE (REST konvence je 204)
  • Změna ID v PUT z těla, ne z URL (klient může vytvořit nesoulad)