Izvrstan vodič o tome kako graditi RESTful API-je s ASP.NET Core-om

Korak po korak, vodič za implementaciju čistih, održivih RESTful API-ja

Fotografiju napisao Jefferson Santos, objavljenu u Unsplash

Pregled

RESTful nije novi pojam. Odnosi se na arhitektonski stil u kojem web usluge primaju i šalju podatke iz i na klijentske aplikacije. Cilj ovih aplikacija je centralizacija podataka koje će koristiti različite klijentske aplikacije.

Odabir pravih alata za pisanje RESTful usluga od presudne je važnosti, jer trebamo voditi računa o skalabilnosti, održavanju, dokumentaciji i svim ostalim relevantnim aspektima. ASP.NET Core pruža nam snažan, jednostavan za korištenje API koji je izvrstan za postizanje ovih ciljeva.

U ovom ću vam članku pokazati kako napisati dobro strukturiran RESTful API za „skoro“ scenarij iz stvarnog svijeta, koristeći okvir ASP.NET Core. Detaljan ću pojedine obrasce i strategije da pojednostavim razvojni proces.

Također ću vam pokazati kako integrirati zajedničke okvire i biblioteke, poput Entity Framework Core i AutoMapper, kako biste pružili potrebne funkcionalnosti.

Preduvjeti

Očekujem da ćete imati znanje o konceptima programiranja objektno orijentiranih.

Iako ću pokriti mnoge detalje programskog jezika C #, preporučujem vam da imate osnovno znanje o ovoj temi.

Pretpostavljam i da znate što je REST, kako funkcionira HTTP protokol, koje su API krajnje točke i što je JSON. Evo sjajnog uvodnog vodiča o ovoj temi. Posljednji je zahtjev da razumijete kako funkcioniraju relacijske baze podataka.

Za šifriranje zajedno sa mnom morat ćete instalirati .NET Core 2.2, kao i poštar, alat koji ću koristiti za testiranje API-ja. Preporučujem vam da koristite API za uređivanje koda, kao što je Visual Studio Code. Odaberite uređivač koda koji više volite. Ako odaberete ovaj uređivač koda, preporučam vam da instalirate proširenje C # za bolje isticanje koda.

Možete pronaći vezu do Github-ovog spremišta API-ja na kraju ovog članka kako biste provjerili konačni rezultat.

Opseg

Pišemo izmišljeni web API za supermarket. Zamislimo da moramo primijeniti sljedeći opseg:

  • Napravite uslugu RESTful koja omogućuje klijentskim aplikacijama da upravljaju katalogom supermarketa. Potrebno je izložiti krajnje točke za stvaranje, čitanje, uređivanje i brisanje kategorija proizvoda, kao što su mliječni proizvodi i kozmetika, kao i za upravljanje proizvodima iz tih kategorija.
  • Za kategorije moramo pohraniti njihova imena. Za proizvode moramo pohraniti njihova imena, mjernu jedinicu (na primjer, KG za proizvode mjereno težinom), količinu u pakiranju (na primjer, 10 ako je proizvod pakiranje keksa) i njihove odgovarajuće kategorije.

Da pojednostavim primjer, neću rukovati proizvodima na zalihama, isporukom proizvoda, zaštitom i bilo kojim drugim funkcionalnostima. Dati opseg je dovoljan da vam pokaže kako ASP.NET Core radi.

Za razvoj ove usluge u osnovi su nam potrebne dvije krajnje točke API-ja: jedna za upravljanje kategorijama i jedna za upravljanje proizvodima. U pogledu JSON komunikacije, možemo razmišljati o sljedećim odgovorima:

Krajnja točka API-ja: / api / kategorije

JSON odgovor (za GET zahtjeve):

{
  [
    {"id": 1, "name": "Voće i povrće"},
    {"id": 2, "name": "Kruh"},
    … // Ostale kategorije
  ]
}

Krajnja točka API-ja: / api / products

JSON odgovor (za GET zahtjeve):

{
  [
    {
      "id": 1,
      "ime": "Šećer",
      "količinaInpakiranje": 1,
      "unitOfMeasurement": "KG"
      "kategorija": {
        "id": 3,
        "ime": "Šećer"
      }
    }
    … // Drugi proizvodi
  ]
}

Krenimo s pisanjem prijave.

Korak 1 - Izrada API-ja

Prije svega, moramo stvoriti strukturu mapa za web uslugu, a zatim moramo koristiti alate .NET CLI za skeniranje osnovnog web API-ja. Otvorite terminal ili naredbeni redak (to ovisi o operativnom sustavu koji koristite) i upišite sljedeće naredbe redom:

mkdir src / Supermarket.API
cd src / Supermarket.API
dotnet novi webapi

Prve dvije naredbe jednostavno kreiraju novi direktorij za API i promijene trenutnu lokaciju u novu mapu. Posljednja generira novi projekt slijedeći predložak Web API-ja, to je vrsta aplikacije koju razvijamo. Možete pročitati više o ovim naredbenim i drugim predlošcima projekata koje možete generirati provjeravanjem ove veze.

Nova mapa sada će imati sljedeću strukturu:

Struktura projekta

Pregled strukture

ASP.NET Core aplikacija sastoji se od skupine srednjeg softvera (mali dijelovi aplikacije pričvršćene na cjevovod za aplikacije koji obrađuju zahtjeve i odgovore) konfigurirane u klasi pokretanja. Ako ste već radili s okvirima poput Express.js, ovaj koncept vam nije novi.

Kada se aplikacija pokrene, poziva se glavna metoda iz klase Program. Stvara zadani web domaćin koristeći startup konfiguraciju, izlažući aplikaciju putem HTTP-a kroz određeni port (prema zadanom, port 5000 za HTTP i 5001 za HTTPS).

Pogledajte klasu ValuesController unutar mape Controllers. Izlaže metode koje će se pozvati kada API primi zahtjeve putem rute / api / vrijednosti.

Ne brinite ako ne razumijete neki dio ovog koda. Pojedinosti ću objasniti pri razvoju potrebnih krajnjih točaka API-ja. Za sada jednostavno izbrišite ovu klasu jer je nećemo upotrebljavati.

2. korak - Izrada modela domena

Primijenit ću neke dizajnerske koncepte koji će aplikaciju učiniti jednostavnom i jednostavnom za održavanje.

Pisati kôd koji možete razumjeti i održavati sami nije teško, ali morate imati na umu da ćete raditi kao dio tima. Ako ne vodite računa o tome kako pišete svoj kôd, rezultat će biti čudovište koje će vama i vašim suigračima zadavati stalne glavobolje. Zvuči ekstremno, zar ne? Ali vjerujte mi, to je istina

wtf - mjerenje kvalitete koda putem smitty42 licencirano je pod CC-BY-ND 2.0

Započnimo s pisanjem sloja domene. Ovaj sloj će imati klase naših modela, klase koje će predstavljati naše proizvode i kategorije, kao i sučelja skladišta i usluge. Objasnit ću vam posljednja dva koncepta u neko vrijeme.

Unutar direktorija Supermarket.API stvorite novu mapu pod nazivom Domena. Unutar nove mape domena stvorite još jednu koja se zove Modeli. Prvi model koji moramo dodati ovoj mapi je Kategorija. U početku će to biti jednostavna klasa Old CLR Object (POCO) klase. To znači da će klasa imati samo svojstva za opisivanje svojih osnovnih podataka.

Klasa ima svojstvo Id za identificiranje kategorije i Nameproperty. Također imamo vlasništvo proizvoda. Ovaj će posljednji program koristiti Entity Framework Core, a ORM većina ASP.NET Core aplikacija koristi za obnavljanje podataka u bazi podataka, za mapiranje odnosa između kategorija i proizvoda. To također ima smisla za razmišljanje u smislu objektno orijentiranog programiranja, jer kategorija ima mnogo povezanih proizvoda.

Moramo stvoriti i model proizvoda. U istu mapu dodajte novu klasu proizvoda.

Proizvod također ima svojstva za id i ime. Ovo je također svojstvo QuantityInPackage, koje govori koliko jedinica proizvoda imamo u jednom pakiranju (sjetite se primjera keksa u području primjene) i svojstva UnitOfMeasurement. Ovaj je predstavljen enum tipom, koji predstavlja nabrajanje mogućih mjernih jedinica. Posljednja dva svojstva, CategoryId i Category ORM će upotrijebiti za mapiranje odnosa proizvoda i kategorija. Označava da proizvod ima jednu, i to samo jednu, kategoriju.

Definirajmo posljednji dio naših modela domena, EUnitOfMeasurement enum.

Po dogovoru, enumi ne moraju počinjati s "E" ispred njihovih imena, ali u nekim bibliotekama i okvirima taj ćete prefiks pronaći kao način za razlikovanje enuma od sučelja i klasa.

Kod je zaista jednostavan. Ovdje smo definirali samo nekoliko mogućnosti mjernih jedinica, međutim, u stvarnom sustavu supermarketa možda imate mnoge druge jedinice mjerenja, a možda i poseban model za to.

Primjetite atribut Opis primijenjen na svaku mogućnost nabrajanja. Atribut je način definiranja metapodataka klasa, sučelja, svojstava i ostalih komponenti jezika C #. U ovom slučaju ćemo ga upotrebljavati za pojednostavljenje odgovora krajnje točke API proizvoda, ali za sada to ne trebate brinuti. Ovdje ćemo se vratiti kasnije.

Naši osnovni modeli su spremni za upotrebu. Sada možemo početi pisati API krajnju točku koja će upravljati svim kategorijama.

Korak 3 - API kategorije

U mapu Kontroleri dodajte novu klasu koja se zove KategorijeController.

Prema dogovoru, sve klase u ovoj mapi koje završavaju sufiksom "Controller" postat će kontroleri naše aplikacije. To znači da će rješavati zahtjeve i odgovore. Morate naslijediti ovu klasu iz klase Controller, definirane u prostoru imena Microsoft.AspNetCore.Mvc.

Imenski prostor sastoji se od skupine povezanih klasa, sučelja, enuma i struktura. Možete to misliti kao nešto slično modulima Javascript jezika ili paketima s Jave.

Novi kontrolor trebao bi reagirati putem ruta / api / kategorija. To postižemo dodavanjem atributa Route iznad naziva klase, specificirajući rezervirano mjesto koje označava da bi ruta prema dogovoru trebala koristiti naziv klase bez sufiksa kontrolera.

Počnimo s GET zahtjevima. Prije svega, kad netko zatraži podatke iz / api / kategorije putem GET glagola, API mora vratiti sve kategorije. U tu svrhu možemo stvoriti uslugu kategorija.

Koncepcijski, usluga je u osnovi klasa ili sučelje koje definira metode upravljanja s nekom poslovnom logikom. Uobičajena je praksa na mnogim različitim programskim jezicima stvarati usluge za obradu poslovne logike, poput provjere autentičnosti i autorizacije, plaćanja, složenih protoka podataka, predmemoriranja i zadataka koji zahtijevaju određenu interakciju između drugih usluga ili modela.

Korištenjem usluga možemo izolirati postupanje sa zahtjevima i odgovorima iz stvarne logike potrebne za dovršavanje zadataka.

Usluga koju ćemo stvoriti u početku će definirati jedno ponašanje ili metodu: način uvrštavanja. Očekujemo da će ova metoda vratiti sve postojeće kategorije u bazu podataka.

Radi jednostavnosti, u ovom se slučaju nećemo baviti pagorizacijom podataka ili filtriranjem. Ubuduće ću napisati članak koji će pokazati kako lako nositi te značajke.

Da bismo definirali očekivano ponašanje nečega u C # (i na drugim objektno orijentiranim jezicima, kao što je Java, na primjer), definiramo sučelje. Sučelje govori kako bi nešto trebalo raditi, ali ne provodi pravu logiku ponašanja. Logika se provodi u klasama koje implementiraju sučelje. Ako vam ovaj koncept nije jasan, ne brinite. Shvatit ćete to nakon nekog vremena.

Unutar mape Domena stvorite novi direktorij pod nazivom Usluge. Tamo dodajte sučelje koje se zove ICategoryService. Prema dogovoru, sva sučelja trebaju početi s velikim slovom „I“ u C #. Definirajte kôd sučelja na sljedeći način:

Implementacije metode ListAsync moraju asinkrono vratiti nabrajanje kategorija.

Klasa zadatka, kapsulirajući povratak, ukazuje na asinhronost. Moramo razmišljati asinhronom metodom zbog činjenice da moramo čekati da baza podataka dovrši neku operaciju kako bi se podaci vratili, a taj postupak može potrajati. Primijetite i sufiks "asinhcija". To je konvencija koja ukazuje na to da se naša metoda mora izvoditi asinkrono.

Imamo puno konvencija, zar ne? Osobno mi se sviđa jer aplikacije jednostavno čita, čak i ako ste novi u tvrtki koja koristi .NET tehnologiju.

"- U redu, definirali smo to sučelje, ali ono ne radi ništa. Kako to može biti korisno? "

Ako potječe iz jezika kao što je Javascript ili drugog jezika koji nije snažno otkucan, ovaj se koncept može činiti čudnim.

Sučelja nam omogućuju apstrahiranje željenog ponašanja od stvarne implementacije. Koristeći mehanizam poznat kao ubrizgavanje ovisnosti, možemo implementirati ta sučelja i izolirati ih od drugih komponenti.

U osnovi, kada koristite injekciju ovisnosti, definirate neka ponašanja pomoću sučelja. Zatim stvorite klasu koja implementira sučelje. Konačno, vežete reference iz sučelja za klasu koju ste stvorili.

"- Zvuči zbunjujuće. Ne možemo li jednostavno stvoriti klasu koja radi ove stvari za nas? "

Nastavimo s primjenom našeg API-ja i shvatit ćete zašto koristiti ovaj pristup.

Promijenite kôd kategorijeController na sljedeći način:

Definirao sam funkciju konstruktora za naš kontroler (konstruktor se poziva kada se stvori nova instanca klase) i on prima instancu ICategoryService. To znači da primjer može biti sve što implementira uslužno sučelje. Spremam ovaj primjerak u privatno polje _categoryService, samo za čitanje. To ćemo polje koristiti za pristup metodama implementacije naše kategorije kategorija.

Usput, prefiks podcrtavanja je još jedna uobičajena konvencija za označavanje polja. Konvenciju, posebno, ne preporučuje službena smjernica za imenovanje .NET-a, ali vrlo je česta praksa kao način da se izbjegne korištenje ključne riječi „ovo“ za razlikovanje polja klase od lokalnih varijabli. Osobno mislim da je mnogo čišće čitati, a mnogo okvira i knjižnica koriste ovu konvenciju.

Ispod konstruktora definirao sam metodu koja će obraditi zahtjeve za / api / kategorije. Atribut HttpGet govori ASP.NET Core cjevovodu da ga koristi za obradu GET zahtjeva (ovaj se atribut može izostaviti, ali bolje je da ga napišete radi lakše čitljivosti).

Metoda koristi instancu usluge kategorije za popis svih kategorija, a zatim vraća kategorije klijentu. Okvirni cjevovod upravlja serializacijom podataka na JSON objekt. Tip IEnumerable govori okviru da želimo vratiti nabrajanje kategorija, a vrsta zadatka, kojoj prethodi ključna riječ async, cjevovodu kaže da bi se ova metoda trebala izvoditi asinkrono. Konačno, kada definiramo async metodu, moramo koristiti ključnu riječ "čeka" za zadatke koji mogu potrajati.

Ok, definirali smo početnu strukturu našeg API-ja. Sada je potrebno stvarno implementirati uslugu kategorija.

Korak 4 - implementacija usluge Kategorije

U korijenskoj mapi API-ja (mapa Supermarket.API) napravite novu koja se zove Services. Ovdje ćemo staviti sve implementacije usluga. Unutar nove mape dodajte novu klasu koja se zove CategoryService. Promijenite kôd na sljedeći način:

To je jednostavno osnovni kôd za implementaciju sučelja, ali još uvijek ne upravljamo nikakvom logikom. Razmislimo o tome kako bi metoda uvrštavanja trebala funkcionirati

Trebamo pristupiti bazi i vratiti sve kategorije, te podatke trebamo vratiti klijentu.

Servisna klasa nije klasa koja bi trebala obrađivati ​​pristup podacima. Postoji uzorak zvan Uzorak spremišta koji se koristi za upravljanje podacima iz baza podataka.

Kada koristimo uzorak skladišta, definiramo klase spremišta koje u osnovi obuhvaćaju svu logiku za upravljanje pristupom podacima. Ta se spremišta otkrivaju metodama za popis, stvaranje, uređivanje i brisanje objekata određenog modela, na isti način na koji možete manipulirati kolekcijama. Unutarnje se ove metode razgovaraju s bazom podataka radi obavljanja CRUD operacija, izolirajući pristup bazi podataka od ostatka aplikacije.

Naš servis treba razgovarati s spremištem kategorija da biste dobili popis objekata.

Koncepcijski, usluga može "razgovarati" s jednim ili više spremišta ili drugim servisima za obavljanje operacija.

Možda se čini suvišnim stvaranje nove definicije za rukovanje logikom pristupa podacima, ali vidjet ćete kroz neko vrijeme da je izoliranje ove logike od klase usluga zaista korisno.

Napravimo spremište koje će biti odgovorno za posredovanje u komunikaciji baze podataka kao način da se zadrže kategorije.

Korak 5 - Repozitorij kategorija i sloj trajnosti

Unutar mape Domena stvorite novu mapu pod nazivom Repozitoriji. Zatim dodajte novo sučelje koje se zove ICategoryRespository. Definirajte sučelje na sljedeći način:

Početna koda je u osnovi identična kodu sučelja usluge.

Definirajući sučelje, možemo se vratiti u servisnu klasu i dovršiti primjenu metode uvrštenja, koristeći instancu ICategoryRepository za vraćanje podataka.

Sada moramo implementirati stvarnu logiku spremišta kategorija. Prije nego što to učinimo, moramo razmišljati o tome kako ćemo pristupiti bazi podataka.

Usput, još uvijek nemamo bazu podataka!

Koristit ćemo Entity Framework Core (nazvat ću ga EF Core radi jednostavnosti) kao našu bazu ORM. Ovaj okvir dolazi s ASP.NET Coreom kao zadanim ORM-om i otkriva prijateljski API koji nam omogućuje preslikavanje klasa naših aplikacija u tablice baza podataka.

EF Core omogućuje nam i da najprije dizajniramo našu aplikaciju, a zatim generiramo bazu podataka prema onome što smo definirali u našem kodu. Ova se tehnika prvo naziva kodom. Prvi ćemo pristup kodu koristiti za generiranje baze podataka (u ovom primjeru, u stvari, koristit ću bazu podataka u memoriji, ali vi ćete je moći lako promijeniti u instancu SQL Server ili MySQL poslužitelja, na primjer).

U korijenskoj mapi API-ja stvorite novu mapu pod nazivom Upornost. U ovom će se direktoriju nalaziti sve što je potrebno za pristup bazi podataka, kao što su implementacije spremišta.

Unutar nove mape stvorite novu mapu koja se zove Contexts, a zatim dodajte novu klasu koja se zove AppDbContext. Ova klasa mora naslijediti DbContext, klasa EF Core koristi za preslikavanje vaših modela u tablice baza podataka. Promijenite kôd na sljedeći način:

Konstruktor koji smo dodali ovoj klasi odgovoran je za prijenos konfiguracije baze podataka u baznu klasu kroz injekciju ovisnosti. Vidjet ćete u trenutku kako to funkcionira.

Sada moramo stvoriti dva svojstva DbSet-a. Ova svojstva su skupovi (zbirke jedinstvenih objekata) koji modeliraju mape u tablice baza podataka.

Također, moramo svojstva modela preslikati u odgovarajuće stupce tablice, specificirajući koja su svojstva primarni ključevi, koji su strani ključevi, vrste stupaca itd. To možemo učiniti nadjačavanjem metode OnModelCreating, koristeći značajku pod nazivom Fluent API za odredite mapiranje baze podataka. Promijenite klasu AppDbContext na sljedeći način:

Kod je intuitivan.

Određujemo u koje će se tablice naši modeli preslikati. Također, postavljamo primarne ključeve, koristeći metodu HasKey, stupce tablice, koristeći Svojstvenu metodu i neka ograničenja kao što su IsRequired, HasMaxLength i ValueGeneratedOnAdd, a sve s lambda izrazima na "tečan način" (lančane metode).

Pogledajte sljedeći dio koda:

builder.Entity  ()
       .HasMany (p => p.Products)
       .WithOne (p => p.Kategorija)
       .HasForeignKey (p => p.CategoryId);

Ovdje smo odredili odnos između tablica. Kažemo da kategorija ima mnogo proizvoda i postavili smo svojstva koja će mapirati taj odnos (Proizvodi iz kategorije kategorije i kategorije iz kategorije proizvoda). Postavljamo i strani ključ (CategoryId).

Pogledajte ovaj udžbenik ako želite naučiti kako konfigurirati odnose jedan na jedan i mnoštvo do mnogih koristeći EF Core, kao i kako ga koristiti u cjelini.

Postoji i konfiguracija za sadnju podataka, putem metode HasData:

builder.Entity  (). HasData
(
  nova kategorija {Id = 100, Naziv = "Voće i povrće"},
  nova kategorija {Id = 101, Naziv = "Mljekara"}
);

Ovdje jednostavno dodamo dvije primjerene kategorije prema zadanim postavkama. Potrebno je testirati našu krajnju točku API-ja nakon što ga završimo.

Napomena: ovdje ručno postavljamo svojstva identiteta jer dobavljač memorije zahtijeva da djeluje. Postavljam identifikatore velikim brojevima kako ne bi došlo do sudara između automatski generiranih identifikatora i podataka sjemena.
Ovo ograničenje ne postoji kod stvarnih dobavljača relacijskih baza podataka, tako da, primjerice, ako želite koristiti bazu podataka kao što je SQL Server, ne morate navesti te identifikatore. Provjerite ovaj Github problem ako želite razumjeti takvo ponašanje.

Nakon implementacije kontekstne klase baze podataka, možemo implementirati spremište kategorija. Dodajte novu mapu koja se zove Spremišta u mapi Persistent, a zatim dodajte novu klasu koja se zove BaseRepository.

Ova je klasa samo apstraktna klasa koju će naslijediti sva naša spremišta. Sažetak klase je klasa koja nema izravne instance. Za izradu instanci morate stvoriti izravne klase.

BaseRepository prima instancu našeg AppDbContext kroz ubrizgavanje ovisnosti i otkriva zaštićeno svojstvo (svojstvo koje može biti dostupno samo djeci klase) nazvano _context, koje omogućava pristup svim metodama koje su nam potrebne za rukovanje bazama.

Dodajte novu klasu u istu mapu koja se zove CategoryRepository. Sada ćemo stvarno implementirati logiku spremišta:

Repozitorij nasljeđuje BaseRepository i provodi ICategoryRepository.

Primijetite kako je jednostavno implementirati metodu uvrštavanja. Koristimo skup baza podataka za pristup tablici kategorija, a zatim pozivamo metodu proširenja ToListAsync koja je odgovorna za pretvaranje rezultata upita u zbirku kategorija.

EF Core prevodi naš poziv na SQL upit, najefikasniji način. Upit se izvršava samo kad nazovete metodu koja će vaše podatke transformirati u zbirku ili kada koristite metodu za uzimanje određenih podataka.

Sada imamo čistu implementaciju kontrolera kategorija, usluge i spremišta.

Razdvojili smo brige, stvorili klase koje rade samo ono što trebaju raditi.

Posljednji korak prije testiranja aplikacije je vezanje naših sučelja za odgovarajuće klase pomoću ASP.NET Core mehanizma za ubrizgavanje ovisnosti.

Korak 6 - Konfiguriranje ubrizgavanja ovisnosti

Vrijeme je da konačno shvatite kako ovaj koncept funkcionira.

U korijenskoj mapi aplikacije otvorite klasu pokretanja. Ova klasa je odgovorna za konfiguriranje svih vrsta konfiguracija kada se aplikacija pokrene.

Okvirni cjevovod tijekom izvođenja poziva ConfigureServices i Configure metode za vrijeme konfiguriranja kako aplikacija treba raditi i koje komponente mora koristiti.

Pogledajte metodu ConfigureServices. Ovdje imamo samo jedan redak koji konfigurira aplikaciju za MVC cjevovod, što u osnovi znači da će aplikacija obraditi zahtjeve i odgovore koristeći klase kontrolera (ovdje se događaju više stvari iza kulisa, ali to morate znati zasad).

Možemo koristiti metodu ConfigureServices za pristup parametru usluga za konfiguriranje naših veza ovisnosti. Očistite kôd klase uklanjajući sve komentare i promijenite kôd na sljedeći način:

Pogledajte ovaj dio koda:

services.AddDbContext  (opcije => {
  options.UseInMemoryDatabase ( "supermarket-API-u memoriji i");
});

Ovdje konfiguriramo kontekst baze podataka. Kažemo ASP.NET Core da koristi naš AppDbContext sa implementacijom u memoriji baze podataka, koja se identificira nizom koji je proslijeđen kao argument našoj metodi. Obično se dobavljač memorije koristi kada pišemo testove integracije, ali ja ga ovdje koristim radi jednostavnosti. Na taj se način ne trebamo povezivati ​​sa stvarnom bazom podataka da bismo testirali aplikaciju.

Konfiguracija ovih linija interno konfigurira kontekst naše baze podataka za ubrizgavanje ovisnosti koristeći obuhvatni vijek trajanja.

Obim životnog vijeka kaže da je ASP.NET Core naftovod svaki put kada treba riješiti klasu koja prima instancu AppDbContext kao argument konstruktora, trebala koristiti istu instancu klase. Ako ne postoji nijedna instanca u memoriji, cjevovod će stvoriti novu instancu i ponovno je koristiti tijekom svih klasa koje su joj potrebne tijekom određenog zahtjeva. Na taj način, ne morate ručno stvarati instancu klase kada biste je trebali koristiti.

Postoje i druge kazne za životni vijek koje možete provjeriti čitajući službenu dokumentaciju.

Tehnika ubrizgavanja ovisnosti daje nam mnoge prednosti, poput:

  • Ponovna upotreba koda;
  • Bolja produktivnost, jer kada moramo promijeniti implementaciju, ne moramo se truditi mijenjati stotinu mjesta na kojima koristite tu značajku;
  • Aplikaciju možete lako testirati jer možemo izolirati ono što moramo testirati koristeći mocke (lažna primjena klasa) gdje moramo proći sučelja kao konstruktorski argumenti;
  • Kada klasa mora primiti više ovisnosti putem konstruktora, ne morate ručno mijenjati sva mjesta na kojima se stvaraju instance (što je sjajno!).

Nakon konfiguriranja konteksta baze podataka, također vezujemo našu uslugu i skladište za odgovarajuće klase.

usluge.AddScoped  ();
usluge.AddScoped  ();

Ovdje također koristimo opseg životnog vijeka jer te klase interno moraju koristiti klasu konteksta baze podataka. Ima smisla odrediti isti opseg u ovom slučaju.

Sada kada konfiguriramo naše ovisnosti, moramo napraviti malu promjenu u programskoj klasi kako bi baza podataka pravilno sjela naše početne podatke. Ovaj korak potreban je samo kada koristite davatelja podataka u memoriji (pogledajte ovaj Github problem da biste razumjeli zašto).

Bilo je potrebno promijeniti glavnu metodu kako bismo zajamčili da će naša baza podataka biti "stvorena" kad aplikacija počne jer upotrebljavamo davatelja podataka u memoriji. Bez ove promjene, kategorije koje želimo saditi neće se stvoriti.

Uz sve osnovne značajke implementirane, vrijeme je za testiranje naše API krajnje točke.

7. korak - Testiranje API-ja kategorija

Otvorite terminal ili naredbeni redak u korijenskoj mapi API-a i utipkajte sljedeću naredbu:

dotnet run

Gornja naredba pokreće aplikaciju. Konzola će pokazati izlaz sličan ovome:

informacije: Microsoft.EntityFrameworkCore.Infrastructure [10403]
Entity Framework Core 2.2.0-rtm-35687 inicijalizirao je "AppDbContext" koristeći davatelja "Microsoft.EntityFrameworkCore.InMemory" s opcijama: StoreName = supermarket-api-in-memory
informacije: Microsoft.EntityFrameworkCore.Update [30100]
Spremljene su dvije jedinice u memorijsku pohranu.
informacije: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager [0]
Korisnički profil je dostupan. Koristeći 'C: \ Users \ evgomes \ AppData \ Local \ ASP.NET \ DataProtection-Keyys' kao skladište ključeva i Windows DPAPI za šifriranje ključeva u mirovanju.
Hosting okolina: Razvoj
Korijen putanja sadržaja: C: \ Users \ evgomes \ Desktop \ Tutorials \ src \ Supermarket.API
Sada slušamo: https: // localhost: 5001
Sada slušamo na: http: // localhost: 5000
Aplikacija je započeta. Pritisnite Ctrl + C da biste ga isključili.

Možete vidjeti da je EF Core pozvan da inicijalizira bazu podataka. Posljednji redovi pokazuju u kojim portovima se aplikacija izvodi.

Otvorite preglednik i idite na http: // localhost: 5000 / api / kategorije (ili do URL-a prikazanog na izlazu s konzole). Ako vidite sigurnosnu pogrešku zbog HTTPS-a, samo dodajte iznimku za aplikaciju.

Preglednik će prikazati sljedeće JSON podatke kao izlaz:

[
  {
     "id": 100,
     "name": "Voće i povrće",
     "proizvodi": []
  }
  {
     "id": 101,
     "name": "mljekara",
     "proizvodi": []
  }
]

Ovdje vidimo podatke koje smo dodali u bazu podataka kada smo konfigurirali kontekst baze podataka. Ovaj izlaz potvrđuje da naš kod radi.

Stvorili ste krajnju točku GET API-ja s stvarno malo redaka koda i imate strukturu koda koju je vrlo lako promijeniti zbog arhitekture API-ja.

Sada je vrijeme da vam pokažemo kako je lako promijeniti ovaj kôd kad ga morate prilagoditi zbog poslovnih potreba.

8. korak - Izrada resursa kategorije

Ako se sjećate specifikacije krajnje točke API-ja, primijetili ste da naš stvarni JSON odgovor ima jedno dodatno svojstvo: niz proizvoda. Pogledajte primjer željenog odgovora:

{
  [
    {"id": 1, "name": "Voće i povrće"},
    {"id": 2, "name": "Kruh"},
    … // Ostale kategorije
  ]
}

Niz proizvoda prisutan je u našem trenutnom odgovoru JSON jer naš model kategorije ima svojstvo Proizvoda, koje je EF Core potreban za ispravljanje mapiranja proizvoda određene kategorije.

Ne želimo da ovaj entitet odgovori, ali ne možemo promijeniti klasu modela da to svojstvo izuzme. EF Core bi uzrokovao greške prilikom pokušaja upravljanja podacima o kategorijama, a također bi slomio i dizajn našeg modela domena jer nema smisla imati kategoriju proizvoda koja nema proizvode.

Da bismo vratili JSON podatke koji sadrže samo identifikatore i imena kategorija supermarketa, moramo stvoriti klasu resursa.

Klasa resursa je klasa koja sadrži samo osnovne informacije koje će se razmjenjivati ​​između klijentskih aplikacija i krajnjih točaka API-ja, uglavnom u obliku JSON podataka, radi predstavljanja određenih podataka.

Svi odgovori s krajnjih točaka API-ja moraju vratiti resurs.

Loša je praksa vratiti stvarni prikaz modela kao odgovor jer može sadržavati podatke koje klijentski program ne treba ili nema dozvolu (na primjer, korisnički model mogao bi vratiti podatke o korisničkoj lozinci , što bi bilo veliko sigurnosno pitanje).

Potreban nam je resurs koji će predstavljati samo naše kategorije, bez proizvoda.

Sada kada znate što je resurs, neka ga implementira. Prije svega, zaustavite pokretanu aplikaciju pritiskom na Ctrl + C u naredbenom retku. U korijenskoj mapi aplikacije napravite novu mapu koja se zove Resursi. Tamo dodajte novu klasu koja se zove CategoryResource.

Moramo mapirati našu kolekciju modela kategorija koja pruža naša kategorija kategorija u zbirku kategorija kategorija.

Upotrijebit ćemo biblioteku pod nazivom AutoMapper za obradu mapiranja između objekata. AutoMapper je vrlo popularna knjižnica u .NET svijetu, a koristi se u mnogim komercijalnim i open source projektima.

Sljedeće retke upišite u naredbeni redak da biste dodali AutoMapper u našu aplikaciju:

dotnet dodati paket autoMapper
dotnet dodavanje paketa AutoMapper.Extensions.Microsoft.DependencyInjection

Da bismo koristili AutoMapper, moramo učiniti dvije stvari:

  • Registrirajte ga za ubrizgavanje ovisnosti;
  • Napravite klasu koja će AutoMapperu reći kako postupati s mapiranjem klasa.

Prije svega, otvorite klasu Startup. U metodi ConfigureServices nakon posljednjeg retka dodajte sljedeći kôd:

services.AddAutoMapper ();

Ova linija obrađuje sve potrebne konfiguracije programa AutoMapper, poput registracije za injektiranje ovisnosti i skeniranje aplikacije tijekom pokretanja za konfiguriranje profila mapiranja.

Sada u korijenski direktorij dodajte novu mapu koja se zove Mapiranje, a zatim dodajte klasu koja se zove ModelToResourceProfile. Promijenite kôd na ovaj način:

Klasa nasljeđuje Profil, vrstu klase koju AutoMapper koristi za provjeru kako će funkcionirati naša preslikavanja. Na konstruktoru stvaramo kartu između klase modela Kategorija i klase CategoryResource. Kako svojstva klase imaju iste nazive i vrste, ne moramo koristiti nikakvu posebnu konfiguraciju za njih.

Završni korak sastoji se od promjene kontrolera kategorija kako bi se AutoMapper koristio za obradu mapiranja naših objekata.

Promijenio sam konstruktor da primim primjerak implementacije IMapper. Možete koristiti ove metode sučelja za upotrebu metoda mapiranja AutoMapper.

Također sam promijenio metodu GetAllAsync da bih naš popis kategorija upisao u popisivanje resursa pomoću metode Map. Ova metoda prima instancu klase ili zbirke koju želimo preslikati i, kroz generičke definicije tipa, određuje na koju vrstu klase ili kolekcije se moraju preslikati.

Primijetite da smo jednostavno promijenili implementaciju bez prilagođavanja servisne klase ili skladišta, jednostavnim ubrizgavanjem konstruktora novu ovisnost (IMapper).

Ubrizgavanje ovisnosti čini vašu aplikaciju održivom i jednostavnom za promjenu jer ne morate prekinuti svu implementaciju koda da biste dodali ili uklonili značajke.

Vjerojatno ste shvatili da su ne samo klasa kontrolera, već i sve klase koje primaju ovisnosti (uključujući i same ovisnosti) automatski riješene za primanje ispravnih klasa u skladu s vezanjem konfiguracija.

Injekcija ovisnosti je nevjerojatna, zar ne?

Sada ponovo pokrenite API koristeći naredbu pokretanja dotnet i prijeđite na http: // localhost: 5000 / api / kategorije da biste vidjeli novi JSON odgovor.

Ovo su podaci o odgovoru koje biste trebali vidjeti

Krajnju točku GET-a već imamo. Sada, stvorimo novu krajnju točku za POST (stvori) kategorije.

9. korak - Stvaranje novih kategorija

Kada se bavimo stvaranjem resursa, moramo brinuti o mnogim stvarima, kao što su:

  • Provjera podataka i integriteta podataka;
  • Ovlaštenje za stvaranje resursa;
  • Pogreška pri rukovanju;
  • Evidencija.

Neću pokazati kako se baviti autentifikacijom i autorizacijom u ovom vodiču, ali možete vidjeti kako lako implementirati ove značajke čitajući moj udžbenik na JSON web token autentifikaciji.

Također, tu je vrlo popularan okvir pod nazivom ASP.NET Identity koji nudi ugrađena rješenja u vezi sa sigurnošću i registracijom korisnika koja možete koristiti u svojim aplikacijama. Uključuje davatelje za rad s EF Core-om, poput ugrađenog IdentityDbContext koji možete koristiti. Više o tome možete saznati ovdje.

Napišite krajnju točku HTTP POST koja će pokriti ostale scenarije (osim zapisivanja koja se mogu mijenjati u skladu s različitim opsezima i alatima).

Prije stvaranja nove krajnje točke potreban nam je novi resurs. Ovaj resurs će preslikati podatke koje klijentske aplikacije šalju na ovu krajnju točku (u ovom slučaju naziv kategorije) u klasu naše aplikacije.

Budući da stvaramo novu kategoriju, još nemamo ID, a to znači da nam treba resurs koji predstavlja kategoriju koja sadrži samo njegovo ime.

U mapu Resursi dodajte novu klasu koja se zove SaveCategoryResource:

Primijetite potrebne i atribute MaxLength primijenjene na svojstvu Name. Ti se atributi nazivaju primjedbama podataka. ASP.NET Core cjevovod koristi ove metapodatke za provjeru zahtjeva i odgovora. Kao što imena sugeriraju, naziv kategorije je potreban i ima maksimalnu duljinu od 30 znakova.

Sada definirajmo oblik nove API krajnje točke. Dodajte sljedeći kôd u kontroler kategorija:

Okviru kažemo da je to HTTP POST točka koristeći atribut HttpPost.

Primijetite vrstu odgovora ove metode, Zadatak . Metode prisutne u kontrolerima klase nazivaju se radnjama i one imaju ovaj potpis jer možemo vratiti više mogućih rezultata nakon što aplikacija izvrši akciju.

U ovom slučaju, ako je naziv kategorije nevažeći ili ako nešto pođe po zlu, moramo vratiti odgovor na 400 koda (loš zahtjev), koji uglavnom sadrži poruku o pogrešci koju klijentske aplikacije mogu koristiti za liječenje problema ili možemo imati 200 odgovora (uspjeh) s podacima ako sve ide u redu.

Postoje mnoge vrste radnji koje možete koristiti kao odgovor, ali općenito, možemo koristiti ovo sučelje, a ASP.NET Core će za to koristiti zadanu klasu.

Atribut FromBody kaže ASP.NET Core da raščisti podatke tijela zahtjeva u našoj novoj klasi resursa. To znači da će se, kada JSON s nazivom kategorije pošalje u našu aplikaciju, okvir automatski raščlaniti u našu novu klasu.

A sada, implementiramo našu logiku rute Moramo slijediti neke korake za uspješno stvaranje nove kategorije:

  • Prvo moramo potvrditi dolazni zahtjev. Ako zahtjev nije važeći, moramo vratiti loš odgovor na zahtjev koji sadrži poruke o pogrešci;
  • Zatim, ako je zahtjev valjan, moramo novi izvor preslikati u našu klasu modela pomoću AutoMapper-a;
  • Sada moramo nazvati našu službu i reći joj da spasimo novu kategoriju. Ako se logika spremanja izvodi bez problema, trebala bi vratiti odgovor koji sadrži podatke naše nove kategorije. Ako ne, to bi nam trebalo naznačiti da postupak nije uspio, te potencijalnu poruku o pogrešci;
  • Na kraju, ako dođe do pogreške, vraćamo loš zahtjev. Ako ne, preslikavamo naš novi model kategorije na resurs kategorije i klijentu vraćamo odgovor o uspjehu koji sadrži podatke o novoj kategoriji.

Čini se da je komplicirano, ali je zaista lako implementirati tu logiku koristeći arhitekturu usluge koju smo strukturirali za naš API.

Započnimo validacijom dolaznog zahtjeva.

Korak 10 - Provjera tijela zahtjeva koristeći stanje modela

ASP.NET Core kontroleri imaju svojstvo koje se naziva ModelState. Ovaj se entitet popunjava tijekom izvršavanja zahtjeva prije nego što dođemo do naše akcije. To je instanca ModelStateDictionary, klase koja sadrži informacije poput toga da li je zahtjev valjan i mogućih poruka o pogrešci prilikom provjere.

Promijenite kôd krajnje točke na sljedeći način:

Kôd provjerava je li stanje modela (u ovom slučaju podaci poslani u tijelu zahtjeva) nevaljano, provjeravajući naše napomene o podacima. Ako nije, API vraća loš zahtjev (sa 400 kodova statusa) i zadane poruke o pogrešci koje su pružili naši metapodaci napomena.

Metoda ModelState.GetErrorMessages () još uvijek nije provedena. To je metoda proširenja (metoda koja proširuje funkcionalnost već postojeće klase ili sučelja) koju ću primijeniti za pretvorbu pogrešaka provjere u jednostavne žice za povratak klijentu.

Dodajte novu mapu Proširenja u korijenu našeg API-ja, a zatim dodajte novu klasu ModelStateExtensions.

Sve metode proširenja trebaju biti statične, kao i klase u kojima su deklarirani. To znači da ne obrađuju određene podatke primjerka i da su učitani samo jednom kad se aplikacija pokrene.

Ova ključna riječ ispred deklaracije parametara kaže sastavljaču C # da ga tretira kao metodu proširenja. Rezultat toga je da ga možemo nazvati uobičajenom metodom ove klase jer uključujemo odgovarajuću upotrebudirective gdje želimo koristiti proširenje.

Proširenje koristi LINQ upite, vrlo korisna značajka .NET-a koja nam omogućuje ispitivanje i transformiranje podataka koristeći izražene izraze. Izrazi ovdje transformiraju metode pogreške kod provjere valjanosti u popis nizova koji sadrže poruke o pogrešci.

Uvezite prostor imena Supermarket.API.Extensions u kontroler kategorija prije nego što pređete na sljedeći korak.

pomoću Supermarket.API.Extensions;

Nastavimo s provedbom logike krajnje točke preslikavajući naš novi resurs u klasu modela kategorije.

Korak 11 - Mapiranje novog resursa

Već smo definirali profil mapiranja za transformiranje modela u resurse. Sada nam je potreban novi profil koji radi obrnuto.

Dodajte novu klasu ResourceToModelProfile u mapu Mapiranje:

Ništa novo ovdje. Zahvaljujući magiji ubrizgavanja ovisnosti, AutoMapper će automatski registrirati ovaj profil kada se aplikacija pokrene, a mi ne moramo mijenjati nijedno drugo mjesto da bismo ga koristili.

Sada možemo svoj novi resurs preslikati u odgovarajuću klasu modela:

12. korak - Primjena uzorka zahtjeva-odgovora na obradu logike spremanja

Sada moramo implementirati najzanimljiviju logiku: spremiti novu kategoriju. Očekujemo da to učini naša služba.

Logika spremanja može propasti zbog problema pri povezivanju s bazom podataka ili možda zbog bilo kojeg internog poslovnog pravila nevažeće naše podatke.

Ako nešto pođe po zlu, ne možemo jednostavno baciti pogrešku, jer to može zaustaviti API, a klijentova aplikacija ne bi znala kako riješiti problem. Također, potencijalno bismo imali neki mehanizam zapisivanja koji bi pogrešku zabilježio.

Ugovor o načinu štednje, znači, potpis metode i vrsta odgovora, trebaju nam naznačiti je li postupak izveden pravilno. Ako postupak prođe u redu, primit ćemo podatke o kategorijama. Ako ne, moramo primiti barem poruku o pogrešci koja govori o tome da proces nije uspio.

Ovu značajku možemo implementirati primjenom obrasca zahtjeva i odgovora. Ovaj obrazac dizajna poduzeća objedinjuje naše zahtjeve i parametre odgovora u klase kao način za objedinjavanje informacija koje će naše usluge koristiti za obradu nekog zadatka i vraćanje podataka klasi koja koristi uslugu.

Ovaj obrazac daje nam neke prednosti, kao što su:

  • Ako trebamo promijeniti svoju uslugu kako bismo primili više parametara, ne moramo prekršiti njezin potpis;
  • Možemo definirati standardni ugovor za naš zahtjev i / ili odgovore;
  • Možemo obraditi poslovnu logiku i potencijalne neuspjehe bez zaustavljanja postupka prijave, a nećemo morati upotrebljavati tonove blokova pokušaja hvatanja.

Kreirajmo standardnu ​​vrstu odgovora za naše metode usluga koje upravljaju promjenama podataka. Za svaki zahtjev ovog tipa želimo znati je li zahtjev izvršen bez problema. Ako ne uspije, klijentu želimo vratiti poruku o pogrešci.

U mapu Domena, unutar usluge, dodajte novi direktorij pod nazivom Komunikacija. Dodajte novu klasu koja se zove BaseResponse.

To je apstraktna klasa koju će naše vrste odgovora naslijediti.

Apstrakcija definira svojstvo Success, koje će reći jesu li zahtjevi uspješno ispunjeni i svojstvo Message, koje će imati poruku o pogrešci ako nešto ne uspije.

Imajte na umu da su ta svojstva potrebna i samo naslijeđene klase mogu postaviti ove podatke, jer djeca klase moraju proslijediti te podatke putem funkcije konstruktora.

Savjet: nije dobra praksa definirati osnovne klase za sve, jer osnovne klase spajaju vaš kôd i sprečavaju vas da ga lako mijenjate. Preferirajte uporabu sastava nad nasljeđivanjem.
Za područje ovog API-ja zapravo nije problem koristiti osnovne klase, jer naše usluge neće puno rasti. Ako znate da će usluga ili aplikacija često rasti i mijenjati se, izbjegavajte korištenje bazne klase.

Sada u istu mapu dodajte novu klasu koja se zove SaveCategoryResponse.

Vrsta odgovora također postavlja svojstvo Category, koje će sadržavati podatke naše kategorije ako zahtjev uspješno završi.

Primijetite da sam definirao tri različita konstruktora za ovu klasu:

  • Privatni, koji će parametre uspjeha i poruke proslijediti osnovnoj klasi, a također postavlja svojstvo Kategorija;
  • Konstruktor koji kao parametar prima samo kategoriju. Ovaj će stvoriti uspješan odgovor, poziva privatnog konstruktora da postavi odgovarajuća svojstva;
  • Treći konstruktor koji samo specificira poruku. Ovaj će se koristiti za stvaranje odgovora na neuspjeh.

Budući da C # podržava više konstruktora, pojednostavili smo stvaranje odgovora, ne definirajući drugačiju metodu za rješavanje ovog problema, samo pomoću različitih konstruktora.

Sada možemo promijeniti naše sučelje usluge kako bismo dodali novi ugovor o načinu spremanja.

Promijenite ICategoryService sučelje na sljedeći način:

Jednostavno ćemo proslijediti kategoriju ovoj metodi i ona će obraditi svu logiku potrebnu za spremanje podataka modela, orkestriranje spremišta i ostale potrebne usluge za to.

Napominjemo da ovdje ne stvaram određenu klasu zahtjeva jer nam ne trebaju drugi parametri za izvršavanje ovog zadatka. U računalnom programiranju postoji koncept koji se zove KISS - skraćenica za Keep It Simple, Stupid. U osnovi, kaže se da biste trebali što jednostavnije aplicirati.

Zapamtite to prilikom dizajniranja aplikacija: primijenite samo ono što vam je potrebno za rješavanje problema. Ne pretjerano trošite svoju aplikaciju.

Sada možemo dovršiti našu logiku krajnje točke:

Nakon potvrđivanja podataka zahtjeva i mapiranja resursa prema našem modelu, prosljeđujemo ga našoj službi kako bi podaci ustrajali.

Ako nešto ne uspije, API vraća loš zahtjev. Ako ne, API mapira novu kategoriju (sad uključujući podatke kao što je novi ID) u prethodno stvoreni CategoryResource i šalje ga klijentu.

Sada provedimo stvarnu logiku usluge.

Korak 13 - Logika baze podataka i obrazac jedinice rada

Budući da ćemo podatke zadržavati u bazi podataka, potrebna nam je nova metoda u našem spremištu.

Dodavanje nove metode AddAsync u sučelje ICategoryRepository:

A sada, implementiramo ovu metodu u našu stvarnu klasu skladišta:

Ovdje smo jednostavno dodali novu kategoriju u svoj set.

Kada dodamo klasu u DBSet <>, EF Core započinje praćenje svih promjena koje se događaju na našem modelu i koristi ove podatke u trenutnom stanju za generiranje upita koje će umetnuti, ažurirati ili izbrisati modele.

Trenutačna implementacija jednostavno dodaje model našem setu, ali naši podaci i dalje se neće spremiti.

U kontekstnoj klasi postoji metoda koja se zove SaveChanges koju moramo pozvati da zaista izvrši upite u bazi podataka. Nisam je nazvao ovdje, jer skladište ne bi trebalo zadržavati podatke, to je samo zbirka objekata u memoriji.

Ovaj je predmet vrlo kontroverzan čak i među iskusnim .NET programerima, ali dopustite mi da vam objasnim zašto ne biste trebali pozivati ​​SaveChanges u klase spremišta.

O skladištu možemo konceptualno razmišljati kao o bilo kojoj drugoj kolekciji koja je prisutna u .NET okviru. Kada se bavite kolekcijom u .NET (i mnogim drugim programskim jezicima, kao što su Javascript i Java), obično možete:

  • Dodajte mu nove stavke (primjerice, kad gurate podatke na popise, nizove i rječnike);
  • Pronađite ili filtrirajte stavke;
  • Uklonite predmet iz kolekcije;
  • Zamijenite zadani predmet ili ga ažurirajte.

Zamislite popis iz stvarnog svijeta. Zamislite da pišete popis za kupovinu proizvoda kako biste kupili stvari u supermarketu (kakva slučajnost, ne?).

Na popisu pišete sve voće koje trebate kupiti. Možete dodati voće na ovaj popis, ukloniti voće ako odustanete od kupnje ili možete zamijeniti ime voća. Ali ne možete spremiti voće na popis. Nema smisla tako nešto reći na običnom engleskom.

Savjet: prilikom dizajniranja klasa i sučelja u objektno orijentiranim programskim jezicima pokušajte koristiti prirodni jezik da biste provjerili je li ono što radite ispravno.
Na primjer, ima smisla reći da čovjek implementira osobno sučelje, ali nema smisla reći da čovjek implementira račun.

Ako želite "spremiti" popis voća (u ovom slučaju kupiti sve voće), plaćate ga i supermarket obrađuje podatke o zalihama kako bi provjerio moraju li kupiti više voća od pružatelja usluga.

Ista se logika može primijeniti kod programiranja. Spremišta ne bi trebala spremati, ažurirati ili brisati podatke. Umjesto toga, trebali bi ga delegirati u drugu klasu koja bi upravljala ovom logikom.

Postoji još jedan problem prilikom spremanja podataka izravno u skladište: ne možete koristiti transakcije.

Zamislite da naša aplikacija ima mehanizam zapisivanja koji pohranjuje neko korisničko ime i radnju koja se izvodi svaki put kad se promijeni API podaci.

Zamislite da iz nekog razloga imate poziv usluzi koja ažurira korisničko ime (to nije uobičajen scenarij, ali uzmimo ga u obzir).

Slažete se da da biste promijenili korisničko ime u izmišljenoj korisničkoj tablici, prvo morate ažurirati sve zapise da biste točno odredili tko je izvršio tu operaciju, zar ne?

Zamislite sada da smo implementirali metodu ažuriranja za korisnike i zapisnike u različitim spremištima, a obojica ih zovu SaveChanges. Što se događa ako jedna od tih metoda uspi na sredini ažuriranja? Završit ćete s nedosljednošću podataka.

Naše promjene trebali bismo spremiti u bazu podataka tek nakon što sve završi. Da bismo to učinili, moramo koristiti transakciju, što je u osnovi značajka koju većina baza podataka primjenjuje za spremanje podataka tek nakon završetka složene operacije.

"- U redu, pa ako ovdje ne možemo spremiti stvari, gdje bismo to trebali učiniti?"

Uobičajeni obrazac za rješavanje ovog problema je obrazac jedinice rada. Ovaj se obrazac sastoji od klase koja prima svoju primjeru AppDbContext kao ovisnost i otkriva metode za započinjanje, dovršavanje ili prekid transakcija.

Koristit ćemo jednostavnu provedbu jedinice rada da pristupimo svom problemu ovdje.

Dodajte novo sučelje unutar mape Skladišta sloja Domene pod nazivom IUnitOfWork:

Kao što vidite, ona otkriva samo metodu koja će asinkrono dovršiti operacije upravljanja podacima.

Dodajmo sada stvarnu implementaciju.

Dodajte novu klasu pod nazivom UnitOfWork u mapu RepositoriesRepositories sloja Persistent:

To je jednostavna, čista implementacija koja će sve promjene u bazi podataka spremiti tek kad je završite s izmjenama u vašim spremištima.

Ako istražite implementacije obrasca Jedinica rada, naći ćete složenije one koji implementiraju operacije povratnog otklona.

Budući da EF Core već implementira obrazac spremišta i radnu jedinicu iza scene, ne moramo brinuti o metodi povrata.

" - Što? Pa zašto moramo stvoriti sva ta sučelja i klase? "

Razdvajanje logike postojanosti od poslovnih pravila pruža mnoge prednosti u pogledu upotrebe koda i održavanja. Ako direktno koristimo EF Core, na kraju ćemo imati složenije klase koje se neće tako lako promijeniti.

Zamislite da se u budućnosti odlučite promijeniti ORM okvir u neki drugi, na primjer, Dapper, ili ako zbog performansi morate implementirati obične SQL upite. Ako logiku upita uparite sa svojim uslugama, bit će teško promijeniti logiku, jer ćete to morati učiniti u mnogim klasama.

Korištenjem obrasca spremišta, možete jednostavno implementirati novu klasu spremišta i vezati je pomoću injekcije ovisnosti.

Dakle, u osnovi ako EF Core koristite izravno u svojim uslugama i morate nešto promijeniti, eto što ćete dobiti:

Kao što rekoh, EF Core provodi modele jedinice rada i skladišta iza kulisa. Naša DbSet <> svojstva možemo smatrati spremištima. Također, SaveChanges zadržava podatke samo u slučaju uspjeha za sve operacije baze podataka.

Sada kada znate što je jedinica rada i zašto ga koristiti sa spremištima, provedimo stvarnu logiku usluge.

Zahvaljujući našoj nevezanoj arhitekturi, jednostavno možemo proslijediti primjerak UnitOfWork-a kao ovisnosti o ovoj klasi.

Naša poslovna logika prilično je jednostavna.

Prvo pokušavamo dodati novu kategoriju u bazu podataka, a zatim API pokušati spremiti, omotavajući sve unutar bloka pokušaj ulova.

Ako nešto ne uspije, API poziva neku uslugu fiktivnog evidentiranja i vraća odgovor koji ukazuje na neuspjeh.

Ako se postupak završi bez ikakvih problema, aplikacija vraća odgovor o uspjehu i šalje podatke o našoj kategoriji. Jednostavno, zar ne?

Savjet: U stvarnim aplikacijama ne biste trebali sve zamotavati u generički blok pokušaja hvatanja, već biste sve eventualne pogreške trebali tretirati odvojeno.
Jednostavno dodavanje bloka pokušaja hvatanja neće pokriti većinu mogućih neuspjelih scenarija. Obavezno ispravite rukovanje pogreškama uređaja.

Posljednji korak prije testiranja našeg API-ja je vezati radno sučelje na svoju klasu.

Dodajte ovaj novi redak metodu ConfigureServices klase pokretanja:

usluge.AddScoped  ();

A sada da testiramo!

14. korak - Testiranje naše POST krajnje točke pomoću poštara

Ponovo pokrenite našu aplikaciju pomoću dotnet run.

Ne možemo pomoću preglednika testirati krajnju točku POST. Upotrijebimo poštara za testiranje naših krajnjih točaka. To je vrlo koristan alat za testiranje RESTful API-ja.

Otvorite poštara i zatvorite uvodne poruke. Vidjet ćete zaslon poput ovog:

Zaslon koji prikazuje opcije za testiranje krajnjih točaka

Promijenite odabrano GET odabrano u okvir za odabir POST.

Unesite URL adresu u polje Unesite URL zahtjeva.

Moramo pružiti podatke tijela zahtjeva da ih pošaljemo u naš API. Kliknite stavku izbornika Tijelo, a zatim promijenite opciju prikazanu ispod nje u sirovu.

Poštar će pokazati desnu opciju Text. Promijenite ga u JSON (aplikacija / json) i zalijepite sljedeće JSON podatke u nastavku:

{
  "Ime": ""
}
Zaslon neposredno prije slanja zahtjeva

Kao što vidite, poslati ćemo prazan niz imena na našu novu krajnju točku.

Kliknite gumb Pošalji. Dobit ćete izlaz ovako:

Kao što vidite, naša logika provjere valjanosti!

Sjećate li se logike provjere valjanosti koju smo stvorili za krajnju točku? Ovaj izlaz je dokaz da djeluje!

Primijetite i 400 statusni kod prikazan s desne strane. Rezultat BadRequest automatski dodaje ovaj statusni kôd odgovoru.

Sada promijenimo JSON podatke u valjani da bismo vidjeli novi odgovor:

Napokon, rezultat koji smo očekivali

API je ispravno stvorio naš novi resurs.

Do sada, naš API može popisati i stvarati kategorije. Naučili ste mnogo stvari o jeziku C #, ASP.NET Core okviru i uobičajenim dizajnerskim pristupima za strukturiranje API-ja.

Nastavimo API naših kategorija stvarajući krajnju točku za ažuriranje kategorija.

Od sada, otkad sam vam objasnio većinu koncepata, ubrzat ću objašnjenja i usredotočiti se na nove teme kako ne biste gubili vrijeme. Idemo!

Korak 15 - Ažuriranje kategorija

Za ažuriranje kategorija potrebna nam je HTTP PUT krajnja točka.

Logika koju moramo kodirati vrlo je slična onoj POST:

  • Prvo moramo potvrditi dolazni zahtjev koristeći ModelState;
  • Ako je zahtjev valjan, API bi trebao mapirati dolazni resurs u klasu modela koristeći AutoMapper;
  • Zatim trebamo nazvati našu službu i reći joj da ažurira kategoriju, pružajući odgovarajuću id kategorije i ažurirane podatke;
  • Ako u bazi podataka nema kategorije s danim ID-om, vraćamo loš zahtjev. Umjesto toga mogli bismo upotrijebiti rezultat NotFound, ali to zaista nije važno za ovaj opseg, jer klijentima šaljemo poruku o pogrešci;
  • Ako je logika spremanja pravilno izvedena, usluga mora vratiti odgovor koji sadrži ažurirane podatke o kategoriji. Ako ne, trebalo bi nam naznačiti da postupak nije uspio i poruku koja pokazuje zašto;
  • Na kraju, ako dođe do pogreške, API vraća loš zahtjev. Ako ne, mapira ažurirani model kategorije u resurs kategorije i vraća zahtjev za uspjeh klijentskoj aplikaciji.

Dodajmo novu metodu PutAsync u klasu kontrolera:

Ako ga usporedite s logikom POST, primijetit ćete da ovdje imamo samo jednu razliku: atribut HttPut određuje parametar koji bi trebala primiti određena ruta.

Nazvat ćemo ovu krajnju točku specificirajući kategoriju Id kao posljednji fragment URL-a, poput / api / kategorije / 1. ASP.NET Core cjevovod analizira ovaj fragment na istoimeni parametar.

Sada moramo definirati potpis metode UpdateAsync u sučelje ICategoryService:

A sada prijeđimo na pravu logiku.

Korak 16 - Ažuriranje logike

Da bismo ažurirali našu kategoriju, prvo moramo vratiti trenutne podatke iz baze podataka, ako postoje. Također ga moramo ažurirati u našem DBSet <>.

Dodajmo nove ugovore o novom metodi u naše sučelje ICategoryService:

Definirali smo metodu FindByIdAsync, koja će asinkrono vratiti kategoriju iz baze podataka i metodu Ažuriranje. Obratite pažnju da metoda Ažuriranje nije asinhrona jer EF Core API ne zahtijeva asinhronu metodu za ažuriranje modela.

Sada ćemo implementirati pravu logiku u klasu CategoryRepository:

Napokon možemo kodirati logiku usluge:

API pokušava dobiti kategoriju iz baze podataka. Ako je rezultat nula, vraćamo odgovor koji kaže da kategorija ne postoji. Ako kategorija postoji, moramo postaviti njeno novo ime.

API tada pokušava spremiti promjene, primjerice, kada stvorimo novu kategoriju. Ako se proces dovrši, usluga vraća odgovor uspjeha. Ako nije, izvršava se logika zapisivanja, a krajnja točka prima odgovor koji sadrži poruku o pogrešci.

A sada da testiramo. Prvo, dodajmo novu kategoriju da bismo imali važeći ID. Mogli bismo upotrijebiti identifikatore kategorija koje sadimo u našu bazu, ali želim to učiniti na ovaj način da vam pokažem da će naš API ažurirati ispravan resurs.

Ponovo pokrenite aplikaciju i pomoću poštara POSTITE novu kategoriju u bazu podataka:

Dodavanje nove kategorije kako biste je kasnije ažurirali

Imajući valjan Id u rukama, promijenite opciju POST na PUT u okvir za odabir i dodajte ID vrijednost na kraju URL-a. Promijenite svojstvo imena u drugo ime i pošaljite zahtjev da provjerite rezultat:

Podaci o kategoriji uspješno su ažurirani

Možete poslati GET zahtjev na krajnju točku API-ja da se uvjerimo da ste ispravno uredili naziv kategorije:

To je rezultat GET zahtjeva sada

Posljednja operacija koju moramo primijeniti za kategorije je isključivanje kategorija. Učinimo to stvarajući krajnju točku brisanja HTTP-a.

Korak 17 - Brisanje kategorija

Logika za brisanje kategorija vrlo je jednostavna za implementaciju s obzirom da je većina metoda koja su nam prije potrebna izgrađena.

Ovo su potrebni koraci za rad naše rute:

  • API treba nazvati našu službu i reći joj da briše našu kategoriju, dajući odgovarajući ID;
  • Ako u bazi podataka nema kategorije s danim ID-om, usluga bi trebala vratiti poruku koja ga ukazuje;
  • Ako se logika brisanja izvodi bez problema, usluga treba vratiti odgovor koji sadrži podatke o izbrisanim kategorijama. Ako ne, to bi nam trebalo naznačiti da postupak nije uspio, te potencijalnu poruku o pogrešci;
  • Na kraju, ako dođe do pogreške, API vraća loš zahtjev. Ako ne, API mapira ažuriranu kategoriju u resurs i vraća klijentu odgovor o uspjehu.

Započnimo dodavanjem nove logike krajnje točke:

Atribut HttpDelete također definira ID predložak.

Prije dodavanja potpisa DeleteAsync našem sučelju ICategoryService, moramo napraviti mali refaktoring.

Nova metoda usluge mora vratiti odgovor koji sadrži podatke o kategorijama, kao što smo to učinili za metode PostAsync i UpdateAsync. U tu svrhu mogli bismo ponovo upotrijebiti SaveCategoryResponse, ali u tom slučaju ne spremamo podatke.

Da izbjegnemo stvaranje nove klase s istim oblikom da bismo ispunili ovaj zahtjev, možemo jednostavno preimenovati naš SaveCategoryResponse u CategoryResponse.

Ako koristite Visual Studio Code, možete otvoriti klasu SaveCategoryResponse, staviti pokazivač miša iznad naziva klase i upotrijebiti opciju Promjena svih događaja da biste preimenovali klasu:

Jednostavan način promjene imena u svim datotekama

Obavezno preimenujte i naziv datoteke.

Dodajmo potpis metode DeleteAsync u sučelje ICategoryService:

Prije implementacije logike brisanja potrebna nam je nova metoda u našem spremištu.

Dodavanje potpisa metode uklanjanja u sučelje ICategoryRepository:

void Remove (kategorija kategorije);

A sada dodajte stvarnu implementaciju u klasi spremišta:

EF Core zahtijeva da se primjer našeg modela prebaci na metodu Remove da bismo ispravno shvatili koji model brišemo, umjesto da jednostavno prosljeđujemo ID.

Konačno, provedimo logiku klase CategoryService:

Ovdje nema ništa novo. Usluga pokušava pronaći kategoriju prema ID-u, a zatim poziva naše spremište da izbriše kategoriju. Napokon, jedinica rada dovršava transakciju i izvršava stvarni rad u bazi podataka.

"- Hej, ali kako je s proizvodima svake kategorije? Ne morate prvo stvoriti spremište i izbrisati proizvode da biste izbjegli pogreške? "

Odgovor je ne. Zahvaljujući EF Core mehanizmu za praćenje, kada model učitavamo iz baze podataka, okvir zna koje veze model ima. Ako ga izbrišemo, EF Core zna da bi trebao izbrisati sve povezane modele prvo, rekurzivno.

Ovu značajku možemo onemogućiti kada preslikavamo naše klase u tablice baza podataka, ali to nije dovoljno za ovaj vodič. Ako želite saznati više o ovoj značajki, pogledajte ovdje.

Sada je vrijeme za testiranje naše nove krajnje točke. Ponovno pokrenite aplikaciju i pošaljite DELETE zahtjev pomoću poštara na sljedeći način:

Kao što vidite, API je bez problema izbrisao postojeću kategoriju

Možemo provjeriti da li naš API ispravno funkcionira slanjem GET zahtjeva:

Sada kao rezultat dobivamo samo jednu kategoriju

Završili smo API kategorije. Sada je vrijeme za prelazak na API proizvoda.

Korak 18 - API proizvoda

Do sada ste naučili kako implementirati sve osnovne HTTP glagole za rukovanje CRUD operacijama s ASP.NET Core. Idemo na sljedeću razinu implementirajući API proizvoda.

Neću ponovno pojedinosti svih HTTP glagola, jer bi to bilo iscrpno. Za krajnji dio ovog vodiča obuhvatit ću samo GET zahtjev, kako bih vam pokazao kako uključiti povezane subjekte prilikom postavljanja podataka iz baze podataka i kako koristiti atribute Opis koje smo definirali za vrijednosti popisa EUnitOfMeasurement.

Dodajte novi kontroler u mapu Kontroleri pod nazivom ProductsController.

Prije nego što kodiramo bilo što ovdje, moramo stvoriti resurs proizvoda.

Dopustite mi da osvježim vaše pamćenje, pokazujući opet kako treba izgledati naš resurs:

{
 [
  {
   "id": 1,
   "ime": "Šećer",
   "količinaInpakiranje": 1,
   "unitOfMeasurement": "KG"
   "kategorija": {
   "id": 3,
   "ime": "Šećer"
   }
  }
  … // Drugi proizvodi
 ]
}

Želimo JSON niz koji sadrži sve proizvode iz baze podataka.

Podaci JSON razlikuju se od modela proizvoda za dvije stvari:

  • Mjerna jedinica prikazana je na kraći način, samo pokazuje svoju kraticu;
  • Izlazimo podatke o kategorijama bez uključivanja svojstva CategoryId.

Da bismo prikazali jedinicu mjerenja, možemo upotrijebiti jednostavno svojstvo niza umjesto enuma (usput, nemamo zadani tip enuma za JSON podatke, pa ga moramo transformirati u drugi tip).

Sada kada smo sada trebali oblikovati novi resurs, stvorimo ga. Dodajte novu klasu ProductResource u mapu Resursi:

Sada moramo konfigurirati mapiranje između klase modela i naše nove klase resursa.

Konfiguracija mapiranja bit će gotovo jednaka onoj koja se koristi za druge mapiranja, ali ovdje moramo upravljati transformacijom našeg enuma EUnitOfMeasurement u niz.

Sjećate li se atributa StringValue primijenjenog na vrste nabrajanja? Sada ću vam pokazati kako izvući ove podatke pomoću moćne značajke .NET okvira: Reflection API.

API Reflection moćan je skup resursa koji nam omogućava izdvajanje i manipuliranje metapodacima. Mnogo okvira i knjižnica (uključujući i sam ASP.NET Core) koriste ove resurse za rukovanje mnogim stvarima iza scene.

Sada pogledajmo kako to funkcionira u praksi Dodajte novu klasu u mapu Proširenja pod nazivom EnumExtensions.

Možda izgleda zastrašujuće prvi put kad pogledate kôd, ali nije tako složeno. Raščlanimo definiciju koda da bismo shvatili kako to funkcionira.

Prvo smo definirali generičku metodu (metoda koja može primiti više vrsta argumenata, u ovom slučaju predstavljena TEnum deklaracijom) koja prima zadani enum kao argument.

Budući da je enum rezervirana ključna riječ u C #, dodali smo @ ispred naziva parametra kako bismo ga učinili valjanim imenom.

Prvi korak izvršenja ove metode je dobivanje informacija o tipu (klasa, sučelje, enum ili struktura definicije) parametra pomoću GetType metode.

Zatim metoda dobiva određenu vrijednost nabrajanja (na primjer, Kilogram) pomoću GetField (@ enum.ToString ()).

U sljedećem retku nalaze se svi atributi Description primijenjeni nad vrijednostima nabrajanja i pohranjuju njihove podatke u niz (u nekim slučajevima možemo odrediti više atributa za isto svojstvo).

Posljednji redak koristi kraću sintaksu za provjeru imamo li barem jedan atribut opisa za vrstu nabrajanja. Ako imamo, vraćamo vrijednost Opisa koju pruža ovaj atribut. Ako nije, vraćamo nabrajanje kao niz, koristeći zadani lijevanje.

The ?. operator (null-conditional operator) provjerava je li vrijednost nula prije nego što pristupi njenom entitetu.

?? operator (operator koji koalira nule) kaže aplikaciji da vrati vrijednost s lijeve strane ako nije prazna, ili vrijednost s desne strane u suprotnom.

Sada kada imamo metodu za proširenje za izdvajanje opisa, konfiguriramo naše mapiranje između modela i resursa. Zahvaljujući programu AutoMapper to možemo učiniti samo jednim dodatnim redovima.

Otvorite klasu ModelToResourceProfile i promijenite kôd na ovaj način:

Ta sintaksa govori AutoMapper-u da koristi novu metodu proširenja za pretvaranje naše EUnitOfMeasurement vrijednosti u niz koji sadrži njen opis. Jednostavno, zar ne? Službenu dokumentaciju možete pročitati da biste razumjeli potpunu sintaksu.

Napominjemo da nismo definirali nijednu konfiguraciju mapiranja za svojstvo kategorije. Budući da smo prethodno konfigurirali mapiranje za kategorije i zato što model proizvoda ima svojstvo kategorije iste vrste i imena, AutoMapper implicitno zna da bi to trebao preslikati koristeći odgovarajuću konfiguraciju.

Sada dodajmo kôd krajnje točke. Promijenite kod proizvoda ProductsController:

U osnovi, ista je struktura definirana za kontroler kategorija.

Idemo na servisni dio. Dodajte novo sučelje IProductService u mapu Usluge prisutnu na sloju Domena:

Trebali ste shvatiti da nam treba spremište prije nego što zaista uvedemo novu uslugu.

Dodajte novo sučelje zvano IProductRepository u odgovarajuću mapu:

Sada implementiramo spremište. Moramo ga implementirati gotovo na isti način kao što smo učinili za skladište kategorija, osim što moramo vratiti podatke o kategorijama svakog proizvoda prilikom upita podataka.

EF Core, prema zadanim postavkama, ne uključuje povezane entitete u vaše modele kada tražite podatke, jer bi mogao biti vrlo spor (zamislite model s deset povezanih entiteta, a svi svi povezani entiteti imaju svoje odnose).

Za uključivanje podataka o kategorijama potreban nam je samo jedan dodatni redak:

Primijetite poziv na Uključi (p => p.Kategorija). Možemo povezati ovu sintaksu kako bismo uključili onoliko entiteta koliko je potrebno prilikom postavljanja podataka. EF Core će ga prevesti u spoj pri izvođenju odabira.

Sada možemo implementirati klasu ProductService na isti način kao što smo to učinili za kategorije:

Vezimo nove ovisnosti koje mijenjaju klasu pokretanja:

Na kraju, prije testiranja API-ja, promijenimo klasu AppDbContext kako bismo uključili neke proizvode prilikom inicijalizacije aplikacije kako bismo vidjeli rezultate:

Dodao sam dva izmišljena proizvoda pridružujući ih kategorijama u koje sadimo prilikom inicijalizacije aplikacije.

Vrijeme je za testiranje! Ponovo pokrenite API i pošaljite GET zahtjev / api / proizvodima putem poštara:

Evo ga! Evo naših proizvoda

I to je to! Čestitamo!

Sada imate bazu o tome kako izgraditi RESTful API pomoću ASP.NET Core koristeći nevezanu arhitekturu. Naučili ste mnogo stvari u okviru .NET Core, kako raditi s C #, osnove EF Core i AutoMapper i mnogo korisnih obrazaca za dizajniranje aplikacija.

Možete provjeriti potpunu implementaciju API-ja koji sadrži ostale HTTP glagole za proizvode i provjeriti Github skladište:

Zaključak

ASP.NET Core odličan je okvir koji se koristi pri izradi web aplikacija. Dolazi s mnogim korisnim API-jevima koje možete koristiti za izgradnju čistih, održivih aplikacija. Razmotrite to kao opciju prilikom stvaranja profesionalnih aplikacija.

Ovaj članak nije obuhvatio sve aspekte profesionalnog API-ja, ali naučili ste sve osnove. Naučili ste i mnogo korisnih obrazaca za rješavanje obrazaca s kojima se svakodnevno susrećemo.

Nadam se da ste uživali u ovom članku i nadam se da vam je bio koristan. Cijenim vaše povratne informacije da shvatim kako ovo mogu poboljšati.

Upućivanja za nastavak učenja

.NET Core Vodiči - Microsoft Docs

Osnovna dokumentacija ASP.NET - Microsoft Docs