ASP.NET esetén két lehetőségünk is van REST API projektek létrehozására: A tradicionális MVC megközelítés és az újabb minimal API megközelítés. Mindkettőnek meg van az előnye és a hátránya.
Az MVC akkor jön jól, ha egy sok entitással rendelkező API-t kell fejlesztenünk. Ebben a megközelítésben minden egyes entitás leírásához egy dedikált kontroller tartozik, ami implementálja REST felett a CRUD műveleteket. Ilyen módon a kódunk rendezett és karbantarthatóbb lesz hosszú távon. Az MVC megközelítés hátránya, hogy jelen pillanatban nem támogat natív fordítást, vagyis az ilyen módon elkészített API projektünk mindenképpen reflection-re fog támaszkodni a betöltéskor. Ez azzal jár, hogy egy ilyen alkalmazás indítása több ideig fog tartani, mint egy minimal API-val szerelt projekt.
A minimal API-t a Python Flask keretrendszere inspirálta és azért találták ki, hogy egy gyorsabb, kötöttségek nélküli API fejlesztési sémát kínáljon. Az MVC megközelítés nagyon hasznos és jól tud működni, de egy olyan API esetén, ahol pár entitás van csak és/vagy a szolgáltatás egy mikroszerviz rendszerben van, ott felesleges. Ez a megközelítés nem reflection alapú, így natív fordításra is alkalmazható, ami még gyorsabb kódot eredményez.
Egy új projekt esetén mindig kérdéses, hogy melyiket érdemes választani? Jelen projektben az állatorvosi ló mivolta és a kis mérete miatt a minimal API megközelítés jobbnak tűnik.
Új minimal API projektet a dotnet new webapi parancsok létrehozásával tudunk készíteni. Ez egy minta API-t implementál, ami egy időjárás lekérdező API-t szimulál.
A kódolás előtt azonban érdemes megterveznünk az API-t, hogy milyen útvonalon lesznek elérhetők a végpontok és milyen HTTP metódussal, illetve milyen státusz kódokat fognak visszaadni.
Egy REST API tervezésében az a nehéz, hogy a HTTP alapvetően nem erre lett kitalálva és nagyon sok flexibilitást kínál, így igazából nincs egyetlen egy igazság, amit mindenkinek követnie kellene. Ez API fejlesztői oldalról nézve jó, mert lényegében úgy szabom a végpontjaimat ahogy nekem kényelmes, ahogy a frontend igényli. Azonban, ha az API publikus és nem csak a frontend fejlesztők használják, akkor fogyasztói oldalról nézve viszont már nem annyira jó a dolog, mivel minden esetben dokumentáció olvasással és értelmezéssel kezdődik egy API használata.
Gondok persze akkor vannak, ha nincs dokumentációnk, mert az API-t nem arra tervezték, hogy majd bárki használhassa.
Egy ilyen API használata jogilag eléggé szürke zóna és az API gyártója bármikor ellehetetlenítheti a használatát, ezért egy ilyenre alkalmazást építeni minimum kockázatos.
Az elkészített API-t annak ellenére, hogy a leírás alapján csak belsőleg fogjuk használni, mégis úgy fogjuk elkészíteni, mint ha publikusan bárki által használható lenne, mégpedig azért, mert ha dokumentálva van minden rendesen, akkor a frontend csapatnak is könnyebb dolga van.
Ha UI számára fejlesztünk API-t, akkor kifejezetten fontos és hasznos, ha a végpontok megalkotása előtt egyeztetünk azokkal, akik ténylegesen használni is fogják, mert a választott UI technológiának lehetnek limitációi vagy nehezen megvalósítható részei, ami mondjuk egy okos API tervezéssel elkerülhető.
Az elkészítendő API az alábbi végpontokat fogja használni:
| Endpoint | Metódus | Státuszkód siker esetén | Státuszkód hiba esetén |
|---|---|---|---|
/v1/urls |
POST | 201 Created | 401 Unauthorized |
/v1/urls |
GET | 200 OK | 401 Unauthorized |
/v1/{shortcode} |
PUT | 204 No content | 401 Unauthorized vagy 400 Bad Request |
/v1/{shortcode} |
DELETE | 204 No content | 401 Unauthorized vagy 404 Not found |
/v1/{shortcode} |
GET | 307 Temporary Redirect | 404 Not found vagy 400 Bad Request |
Az útvonalban a /v1 verziózásra szolgál. A verziózásra a kezdetek kezdetén érdemes gondolni, mivel, ha később bővül az alkalmazás szkópja, akkor egy új végpont útvonalon elkezdhetjük fejleszteni az új API-t anélkül, hogy a régit eltörnénk, vagy a klienseinket azonnali adaptációra kényszerítenénk. Egy tipikus egy klienses API esetén, ha egy csapat vagy cégen belül készül mindkettő, akkor nem biztos, hogy van értelme verziózni.
A táblázatot megnézve lehetnek furcsaságok. Legelőször a választott metódusokban a létrehozásnál és a frissítésnél. Elsőre logikusnak tűnhet, hogy a PUT utasítással hozzunk létre, hiszen ez az ige konkrétan azt jelenti, hogy valamit valahova teszünk, például egy URL-t. Azonban a POST erre alkalmasabb.
Mégpedig azért, mert a létrehozás a követelmények alapján nem idempotens művelet. ugyanaz a kérés más rövid URL-t fog generálni. A generált URL pedig a válaszban fog érkezni. Ezzel szemben a PUT HTTP ige idempotens, vagyis a kliens a létrehozás pillanatában tudja, hogy hol lesz elérhető az erőforrás később. Ezért jelen API esetén a POST-ot alkalmazzuk létrehozásra, a PUT-ot pedig frissítésre, mivel a frissítés nem engedi a rövid URL megváltoztatását.
URL lekérésénél érdekes válaszkódnak tűnhet továbbá a 307-es átmeneti átirányítás. Átirányításból kétféle létezik. Átmeneti és végleges. Utóbbi a 308-as kóddal rendelkezik. Jelen API-ban azért küldünk 307-es kódot minden esetben, mert a követelmények része, hogy a kattintásokat tároljuk. Ha 308-as kódot küldenénk, akkor a kliens implementációtól függően dönthet úgy az első feloldás után, hogy eltárolja az új URL-t és legközelebb nem fog a szerverhez fordulni. Ez pedig félrevezető statisztikát jelentene.
A használható HTTP státusz kódokról a MDN rendelkezik egy nagyon jó leírással, ami a https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status címen található meg.
A statisztika kapcsán érdemes megjegyezni, hogy az URL feloldások gyorsítótárazását megnehezíti, hiszen minden egyes feloldás valójában egy írás is, mert a régi értéket írni kell. Ezért egy szimpla eltároljuk a rövid URL-hez tartozó hosszú URL-t megoldás, ami minden esetben nem fordul az adatbázis felé, nem lesz jó. A dolog kivitelezése nem lehetetlen, lehet gyorsítótárazni a hívásokat, de periodikusan vissza kell íri az adatbázisba a frissített értékeket, illetve gondoskodni kell a gyorsítótár perzisztenciájáról is. Csak memória alapú gyorsítótárazást nem alkalmazhatunk, mert ha áramszünet van, vagy a szolgáltatás frissítése miatt újraindul a szerviz, akkor a csak memória cache-ben tárolt értékek elvesznek. Ha ez úgy hangzik, hogy probléma helyett problémáink vannak, akkor jó a megérzésünk.
A probléma áthidalására több lehetőségünk is van. A legkönnyebb megoldás, hogy nem foglalkozunk vele. Ez később visszaüthet, ha skálázási gondok lesznek, mert akkor kezdenünk kell majd vele valamit.
A második megoldás, hogy 0. pillanattól kezdve megoldjuk a problémát és felkészítjük a rendszert erre. Ezen megoldás értelemszerűen több erőforrást fog igényelni, mint az első, de ha csak tényleg annyian használják az alkalmazást, mint amit eredetileg mondtak, akkor meg valószínűleg feleslegesen építettünk űrhajót.
A harmadik megoldás pedig az, hogy visszamegyünk a követelményekhez és újra átrágjuk az igényeket a megrendelővel és miért kérdésekkel próbáljuk megérteni, hogy miért is kell neki az URL szintű statisztika. Elképzelhető, hogy csupán félreértésről van szó vagy egy rosszul megfogalmazott igényről és valójában a felhasználó csak azt szeretné tudni, hogy mennyi alkalmazott használja rendszeresen a rendszerét linkek létrehozására. Ez az igény teljesen más, mint ami eredetileg volt és nagyságrendileg könnyebben kivitelezhető, mint az eredeti követelmény. Éppen ezért fontos, hogy kommunikáljunk és kommunikáljunk az ügyféllel és igenis tegyük fel a miért kérdéseket, mert könnyen előfordulhat, hogy elbeszélünk egymás mellett.
Jelen alkalmazás esetén az első megoldást választottam, mégpedig azért, mert egy remek továbbfejlesztési ötlet a Kedves Olvasónak, illetve azért, mert a könyv médiumából adódóan hülyén venné ki magát az, hogy ha a 3. megoldással mennénk és át kellene írni egy csomó kódot. Természetesen a 2. opció lefejlesztése is lehetőség lenne, de akkor nem maradna házi feladat.
Végpontok dokumentálása
Az API végpontok dokumentálása egy szerteágazó téma. Egyrészt jelentheti azt, hogy egy felhasználói dokumentációt gyártunk a kódunkhoz, illetve azt is, hogy valami leírófájlt adunk az API mellé, ami kommunikációban használatos be és kimeneti objektumok struktúráját leírja.
Ha XML-t használunk, akkor erre való az XSD (XML Schema Definition), amiből programozási nyelvtől függetlenül generálhatunk objektumokat és validálhatjuk vele a kapott adatokat. Az XML legnagyobb baja nem az, hogy senki sem szereti, hanem az, hogy nem a web nyelve. A JSON, mivel a JavaScript részhalmaza, ezért közvetlenül vagy minimális feldolgozással fogyasztható a JavaScript alapú kliensek által.
A JSON-hoz is létezik egy séma definíciós nyelv, de ezt az elmúlt 10 évben nem sikerült még véglegesíteni, hivatalosan még mindig vázlat állapotú a dokumentum. Ebből adódóan N+1 eltérő implementáció van N+2 eltérő funkció támogatással, ami miatt nem egységes a fejlesztői eszközök támogatása.
A JSON Schema egy részhalmazát, vagy pontosabban egy változatát használja az OpenAPI specifikáció. Ezt kifejezetten arra találták ki, hogy API-k fejlesztői dokumentációját és a dokumentációból API fogyasztó és kiszolgáló kód generálását lehetővé tegye. Ez egy véglegesített, verziózott specifikáció, amit természetesen az ASP.NET is támogat. A végpontok leírásához generál egy OpenAPI dokumentációt, amit felhasználva egy OpenAPI kompatibilis kód generátorral kliens kódot tudunk generálni.
Ha pedig OpenAPI dokumentációból szeretnénk C# kódot generálni, azt a Microsoft.dotnet-openapi generátorral tudjuk megtenni. Ez az alábbi parancs segítségével telepíthető:
dotnet tool install -g Microsoft.dotnet-openapi
Minimal API
Az ASP.NET új megoldása Web API létrehozásra az úgynevezett Minimal API. Nevét onnan kapta, hogy ez egy könnyed, minimalista lehetőséget biztosít API fejlesztésre, a „tradícionális” MVC-hez hasonlítva. Ennek megvan az az előnye, hogy kevesebb kötöttséggel szembesülünk, ami egyben a hátránya is tud lenni ennek a megközelítésnek. Ez a „hátrány” akkor tud megmutatkozni, ha az általunk fejlesztett A0PI túlnő az egy kézen megszámolható entitásokon. A hátrányt az előző mondatomban szándékosan tettem idézőjelbe, mert ez csak akkor hátrány, ha nem teszünk az API implementálása mögé szabályokat, valamilyen architektúrát. Simán lehet, hogy ez a vélt hátrány valójában egy előnnyé növi ki magát.
A Minimal API megközelítés Map metódusokkal operál, amivel egy adott útvonalhoz és HTTP metódushoz egy kezelő metódust tudunk hozzárendelni. Ez a kezelő metódus lehet egy lambda is, vagy egy osztály metódusa. Utóbbi talán szervezettség szempontjából előnyösebb, de ízlések és pofonok. A kezelő metódusok tekintetében hatalmas a flexibilitásunk és a lehetőségeink szerteágazóak. A https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis oldal részletes példák tömkelegével van tele.
Jelen alkalmazásban azt a megodást választottam, hogy a kezelő függvényeimet egy Endpoints osztályban implementáltam. Mielőtt azonban megnézzük a kódót, beszélnünk kell pár elméleti koncepcióról: bejelentkeztetésről, naplózásról és validációról.
Bejelentkeztetés – Authentikáció és Authorizáció
Biztonsági szempontból az authentikáció az, amikor valamilyen információk alapján egy felhasználót bejelentkeztetünk egy rendszerbe. Az authorizáció pedig annak a folyamatnak a megnevezése, amikor egy ismert felhasználóhoz jogosultságokat rendelünk. Fontos, hogy a kettő nem ugyanaz és sose legyen ugyanaz! Egy rendszerben, ahol attól mindenható adminisztrátor valaki, mert a felhasználóneve „Admin”, azonnal tálcán kínálja a támadóknak a lehetőségeket, mert csak azzal kell bajlódniuk, hogy a jelszót kitalálják. De tételezzük fel nagylelkűen, hogy nincs rosszakaratú támadója a rendszerünknek. Ebben az esetben is problémás egy ilyen rendszer, főleg akkor, ha több embernek is adminisztrátornak kellene lennie. Adódhat a kérdés, hogy ez miért probléma? Adunk egy jelszót a fiókhoz több embernek és meg van oldva minden, egészen addig, amíg mondjuk az egyik emberrel nem kell bontani a munkaszerződést. Ebben az esetben dönthet úgy, hogy „bosszúból” töröl mindent a még meglévő jogosultságával és egyértelműen nem is lesz bizonyítható, hogy ő volt, hiszen több ember is megtehette ugyanezt.
Szeretném azt mondani, hogy ez egy állatorvosi ló vagy kitalált példa, de sajnos nem. Sajnos még mindig számos rendszer üzemel a nagyvilágban a fentebb leírt abszurd elv mentén és időről időre előkerül a fenti koncepció, mint „jó ötlet”.
Saját authentikációt és authorizációt nem egyszerű írni, mivel nagyjából végtelen mennyiségű dologra gondolnunk kell és ha még gondoltunk is mindenre, akkor az implementáció fázisában egy apróságon nagyon félremehet a dolog. Éppen ezért sajátot nem érdemes fejleszteni. Nem véletlen trend egy ideje, hogy az azonosítás „nyűgjét” kiszervezzük egy nagy szolgáltatónak, akik erre felhős termékeket építettek, mint a Google, Microsoft vagy Amazon.
Ha nem szeretnénk külső autentikációt alkalmazni, akkor lehetőségünk van használni a Microsoft.AspNetCore.Identity csomagot, ami helyben tárolt felhasználói adatos bejelentkezést tesz lehetővé. Az alkalmazásunkban ezt fogjuk használni, mivel az adatbázis esetén a felhasználó modellezéséhez már ezt kezdtük el alkalmazni.
A bejelentkeztetést az teszi igazán problémássá, hogy a HTTP alapvetően állapotmentes, de mégis valamilyen módon állapotot kell tárolnunk a felhasználókhoz, ezért a bejelentkezés után valami azonosítót kell rendelnie a szervernek klienshez, ami alapján majd később azonosítani tudja. A kliensnek meg ezen kapott azonosítóval kell ellátnia a további kéréseit, hogy a szerver tudja, hogy melyik ismert klienssel is beszélget.
Annak függvényében, hogy ez az azonosító hogyan kerül a klienshez, három fő megoldás alkalmazott: session, JWT alapú és Bearer token alapú. Ezek közül a legrégebbi megoldás, amit a böngészők alkalmaznak, az a session alapú tárolás. Ennek lényege, hogy az azonosítás után kap a kliens egy azonosítót a szervertől, aminek van egy fix lejárati ideje. Ezt az azonosítót pedig a kliens elteszi egy tárba és minden további kérés mellé továbbítja. Ezen információkat nevezzük sütiknek, mivel minden oldal, ami bejelentkeztet bennünket, egy ilyen sütit készít. Ezen sütik minden oldal meglátogatásakor továbbítódhatnak bármilyen oldal felé és ezt a múltban ki is használták bőven. Például a Meta a Facebook termékével azért tudott nagyra nőni, mert a bejelentkeztetett felhasználóik sütijeit a Like gomb és egyéb publikált integrációs szolgáltatásuk ki tudja olvasni, így nyomon követve az egyes felhasználóikat a világban.
De tekintsünk el ettől és fókuszáljunk csak az előttünk lévő technikai kihívásokra kliens fejlesztői szempontból. Egy süti lejárati ideje akár több hónap is lehet, éppen ezért biztonságosan kellene tárolni őket. Ezt úgy kellene, hogy az alkalmazásomból egy harmadik fél ne tudja ezt kiolvasni, mert ha sikerül neki, akkor tök mindegy, milyen biztonságosan tárolom az adatokat a szerveren, nem fogom tudni megkülönböztetni a hamisított felhasználót a valóditól a szerver oldalon. De tegyük fel, hogy megoldottam ezt a kihívást. Ebben az esetben a következő megoldandó feladat az, hogy csak a megfelelő oldalaknak küldjem a megfelelő sütit.
Ezen két probléma megoldása jelentős infrastrukturális kódot jelent egy kliens számára. Ezt a böngészők megoldják, de ha azt szeretnénk, hogy a HTTP végpontjaink konzol alkalmazásból, vagy akár egy mikrovezérlőről is használhatóak legyenek, akkor ez nem a legjobb megoldás. Éppen ezért megszületett a JWT token.
Ezen autentikáció folyamán a szerver egy titkosított JSON dokumentumot ad a kliensnek, ami a rá vonatkozó minden információt tárol. Ez azért jó, mert a token elegendő ahhoz, hogy az API azonosítsa a felhasználót és a jogosultságait anélkül, hogy ezt a memóriából vagy adatbázisból kéne kikeresnie. A token egy szavatossági időt is kódol. Ennek a lejárta után a szerver visszautasítja a kapcsolatot. A folyamatos azonosítás megőrzéséhez a kliensnek a tokent időközönként meg kell újítania. A gondok akkor kezdődnek, amikor a token lejárati ideje hosszú és napok nagyságrendjében mozog. Ez azért problémás, mert ebben az esetben ismételten a biztonságos tárolás problémája előjön, amit a kliens vagy meg tud oldani, vagy nem. Illetve egy másik probléma, hogy a token nem vonható vissza menet közben. Vagyis ha kap egy munkavállaló egy olyan tokent, aminek az érvényessége 3 nap és közben megszűnik a munkaviszonya, akkor lényegében a lejáratáig hozzáférése van a rendszerhez.
Éppen ezért a token alapú azonosítás egyik biztonsági alappillére, hogy a lejárati idő nem túl hosszú és inkább a percek nagyságrendjében mozogjon, mint az órákban vagy napokban.
A bearer token megoldás a süti alapú megoldásra hasonlít. A kliens itt is kap egy azonosítót, amit ha mellékel a kérése mellé, tudni fogja a szerver, hogy ő kicsoda. Azonban azért token, mert van egy rövid lejárati ideje és a token cserélhető a lejárati ideje előtt egy újra. A JWT-vel ellentétben ez azonban „csak” egy azonosító, ami valamilyen módon előállt. Ez nem kódol semmit, a szervernek ellenőriznie kell a hitelességét minden kérés végrehajtása előtt. Ennek előnye, hogy a token érvényessége visszavonható a lejárati ideje előtt is, a hátránya viszont az, hogy minden esetben a szervernek ellenőriznie kell, ami egy limitált erőforrású hardveren nem biztos, hogy a legjobb.
Éppen ezért azonosítás tekintetében nincs egy ezüstgolyó vagy egy mindenkire jó méretű kabát. Szituációfüggő, hogy melyiket érdemes alkalmazni. Jelen alkalmazás esetén a választásom a Bearer megoldásra esett.
Naplózás
Naplózás esetén két külön alkalmazási területről érdemes beszélni: fejlesztői szempontú és biztonsági célzatú naplózásról.
Fejlesztői szempontból hasznosak a naplófájlok, mert ha történik egy olyan esemény, amire nem gondoltunk vagy nem megfelelően kezeltük le, akkor a naplók segítségével visszakövethető a folyamat és könnyebben megtalálhatjuk a hiba okát. Ezen naplók esetén az a jó, ha minél több információ van, mivel ez segíti a visszakeresést. Azonban ennek a hátránya az, hogy ha minden apróságot naplózunk, akkor a napló írása és kezelése több időt fog elvinni a program végrehajtásából, mint a tényleges munka elvégzése. Éppen ezért érdemes olyan napló megoldást választani, ami különböző szinteket tud kezelni konfigurálható módon. Például teszt környezetben engedélyezünk minden log üzenetet, míg éles rendszernél csak a figyelmeztetéseket és a hibákat. Opcionálisan ez megtoldható telemetriával is.
A naplózás szolgálhat biztonsági célokat is. Tételezzük fel, hogy feltörik az alkalmazásunkat, mert egy felhasználó jelszava kikerül. Ebben az esetben, ha minden műveletet naplózunk, például azt hogy milyen adatbázis rekordokhoz fért hozzá a felhasználó, miket módosított, akkor könnyen beazonosíthatóak a kompromittált és kiszivárgott adatok. Az ilyen jellegű naplózás történhet ugyanazzal a naplózó megoldással, mint amivel a hibakeresési naplókat gyártjuk, de érdemes külön kezelni ezeket az eseményeket egy külön naplóba irányítva, ideális esetben egy teljesen más gépre naplózva, hogy a támadónak esélye se legyen őket törölni, megváltoztatni vagy bármilyen egyéb módon nyomokat eltüntetni belőle.
Alkalmazáskontextus és használat függő, hogy egyáltalán kell-e ez bármilyen alkalmazásba. Minden esetben mérlegelni kell, hogy milyen adatokkal dolgozunk és mi a legrosszabb, ha az adat kikerül vagy kompromittálódik? Ezen kérdések megválaszolása nélkül implementációba fogni felesleges, mert csak időpazarlás. Ha az adatok besorolása és értékessége megvan, akkor megtehetjük a szükséges lépéseket a naplózás implementálására.
A naplózás egyébként bármelyik architektúra esetén egy úgynevezett cross cutting concern, ami azt jelenti, hogy egy réteghez sem rendelhető hozzá exkluzív módon. Ha csak egy rétegünk tud naplózni, akkor elveszíthetünk hibakeresés szempontjából értékes adatokat és biztonsági szempontból nézve sem biztos, hogy a legjobb ötlet.
Éppen ezért a naplózás egy jó megoldása az, hogy ha definiálunk egy ILog vagy hasonló nevű interfészt, amit az architektúránk legeslegalsó szintjébe, vagy Hexagon és Clean architecture esetén a domain core-ban helyezünk el. Így lényegében a naplózás tényleges implementációja lehet infrastruktúra kód és a naplózáshoz minden rétegnek hozzáférése lesz. Szerencsére nem kell feltalálnunk a kereket. Az ASP.NET a Microsoft.Extensions.Logging könyvtárat alkalmazza, ami rendelkezik egy külön Microsoft.Extensions.Logging.Abstractions csomaggal, ami csak az interfészeket tartalmazza.
Naplózás esetén arra érdemes még figyelni, hogy strukturált módon naplózzunk. Ez azt jelenti, hogy nem csak sima szövegként írjuk ki naplóüzeneteinket, hanem egy előre meghatározott formában, ami gép által könnyen feldolgozható mezőkkel rendelkezzen. Ez lehet JSON, XML vagy más, kulcs-érték alapú megoldás. Ennek előnye, hogy eszközök segítségével könnyebben kereshető és szűrhető lesz az adat. Nézzünk egy példát a hagyományos naplózásra:
[2025-08-13 08:12:34] ERROR - User login failed: userId=42, ip=192.168.1.15, reason=Wrong password
Ugyanezt az eseményt például tárolhatnánk JSON-ban is:
{
"timestamp": "2025-08-13T08:12:34Z",
"level": "ERROR",
"event": "user_login_failed",
"userId": 42,
"ip": "192.168.1.15",
"reason": "Wrong password"
}
Természetesen, csak hogy ne legyen egyszerű, itt sincs egy univerzális megoldás, mivel képernyőre kiírva az első jobb megoldás, illetve emberi feldolgozásra jobban alkalmas, míg az utóbbi megoldás akkor hasznos, ha sokáig kell tárolni a naplókat. Ebben az esetben a nagy mennyiségű adat miatt érdemes egy olyan formátumot használni, ami kereshető, ha gond van. Ideális esetben egyébként ez nem egy vagy konzol vagy JSON megoldás. A legtöbb naplózó, köztük a Microsoft megoldása is támogat többféle kimeneti formátumot tetszőleges kombinációban, amivel kivitelezhetjük, hogy a napló menjen képernyőre is és akár egy fájlba is két különböző módon.
Jelen alkalmazás implementációjában a naplózást a hibák és a figyelmeztetések szintjére korlátoztam és az egyszerűség kedvéért csak az Web API szintjéről naplózunk. Egy jó házi feladat vagy bővítés lehet például alsóbb rétegekbe is bevinni ennek a lehetőségét.
Validáció
Professzionális karrierem alatt rá kellett jönnöm, hogy két dologban nem bízhatok soha. Ebből az egyik az időjárás előrejelzés, a másik meg az, hogy a felhasználó mindig jó adatokat fog megadni. Éppen ezért a felhasználótól bejövő adatokat validálni kell.
Mondhatnánk jogosan, hogy ez már megtörtént. Az üzleti logikánkat bevédtük, hogy hibás adatokkal ne dolgozzon. Ezt kivételekkel tettük meg. Az alapötlet itt az volt, hogy ha az üzleti logikáig eljut a rossz adat, akkor már régen rossz a szituációnk. Ez egyfajta másodlagos védelmi vonal, egyfajta jelzés a kódunkat felhasználó fejlesztőknek, hogy ne adjanak hibás adatot.
Az adatok ellenőrzését validációnak hívjuk és az ASP.NET, illetve a .NET kínál rá megoldást, attribútumokkal. Ezekről volt korábban szó. Ezen megoldás MVC alapú API-k esetén alapértelmezetten rendelkezésre áll, vagyis csak annyi dolgunk van, hogy megfelelően annotáljuk az adatokat. A gond az, hogy ez Minimal API esetén csak .NET 10-től támogatott, illetve ennek a megoldásnak egy limitációja, hogy ha több tulajdonság alkot egy szabályrendszert, akkor ezzel nehezen implementálható.
Nem kell azonban pánikba esni, mivel van pár megoldás, amit alkalmazhatunk. Például a FluentValidation, ami fluent builder szintaxissal teszi lehetővé, hogy szabályokat definiáljunk az objektumjaink validációjára.
Az alkalmazásunkban a bejövő kérések validálására a FluentValidation kerül alkalmazásra két okból. Az egyik, hogy lássuk ezt is az attribútum validáció mellett, a másik pedig az, hogy a könyv ezen részének írásakor még csak az ASP.NET 9 volt elérhető a nagyközönségnek.