Razmišljam o tome da napišem post na blogu od prve verzije Angular-a koja je praktički ubila Microsoft na strani klijenta. Tehnologije poput ASP.Net, Web Forms i MVC Razor zastarjele su, zamijenjene JavaScript okvirom koji nije baš Microsoft. Međutim, od druge verzije Angulala, Microsoft i Google surađuju na stvaranju Angulala 2 i tada su moje dvije omiljene tehnologije počele raditi zajedno.
U ovom blogu želim pomoći ljudima da stvore najbolju arhitekturu koja kombinira ova dva svijeta. Jesi li spreman? Idemo!
Izgradit ćete Angular 5 klijenta koji troši uslugu RESTful Web API Core 2.
Klijentska strana:
Poslužiteljska strana:
Bilješka
U ovom postu na blogu pretpostavljamo da čitatelj već ima osnovno znanje o TypeScriptu, kutnim modulima, komponentama i uvozu / izvozu. Cilj ovog posta je stvoriti dobru arhitekturu koja će omogućiti da kôd raste s vremenom. |
Krenimo od odabira IDE-a. Naravno, ovo je samo moja preferencija i možete koristiti onu s kojom vam je ugodnije. U mom slučaju koristit ću Visual Studio Code i Visual Studio 2017.
Zašto dva različita IDE-a? Budući da je Microsoft stvorio Visual Studio Code za prednji kraj, ne mogu prestati koristiti ovaj IDE. U svakom slučaju, vidjet ćemo i kako integrirati Angular 5 unutar projekta rješenja, što će vam pomoći ako ste vrsta programera koji više voli uklanjati pogreške i sa stražnje strane i s prednje strane sa samo jednim F5.
Što se tiče stražnjeg dijela, možete instalirati najnoviju verziju Visual Studija 2017 koja ima besplatno izdanje za programere, ali je vrlo potpuna: Community.
Dakle, ovdje je popis stvari koje moramo instalirati za ovaj vodič:
Bilješka
Provjerite koristite li najmanje Node 6.9.x i npm 3.x.x pokretanjem node -v i npm -v u prozoru terminala ili konzole. Starije verzije proizvode pogreške, ali novije verzije u redu. |
Neka zabava započne! Prvo što moramo učiniti je instalirati Angular CLI globalno, pa otvorite naredbeni redak node.js i pokrenite ovu naredbu:
npm install -g @angular/cli
U redu, sada imamo svoj paket modula. To obično instalira modul ispod vaše korisničke mape. Zamjenski naziv ne bi trebao biti potreban prema zadanim postavkama, ali ako vam treba, možete izvršiti sljedeći redak:
alias ng='/.npm/lib/node_modules/angular-cli/bin/ng'
Sljedeći je korak stvaranje novog projekta. Nazvat ću to angular5-app
. Prvo idemo do mape u kojoj želimo stvoriti web mjesto, a zatim:
ng new angular5-app
Iako možete testirati svoju novu web stranicu koja samo radi ng serve --open
, preporučujem testiranje web lokacije s vaše omiljene web usluge. Zašto? Pa, neki se problemi mogu dogoditi samo u proizvodnji i izgradnji web mjesta s ng build
je najbliži način približavanja ovom okruženju. Tada možemo otvoriti mapu angular5-app
s Visual Studio Code i pokrenite ng build
na terminalu bash:
Nova mapa pod nazivom dist
bit će stvoren i možemo ga poslužiti pomoću IIS-a ili bilo kojeg web poslužitelja koji želite. Tada možete upisati URL u preglednik i ... gotovo!
Bilješka
Svrha ovog vodiča nije pokazati kako postaviti web poslužitelj, pa pretpostavljam da to znanje već imate. |
src
Mapa
Moja src
mapa strukturirana je na sljedeći način: Unutar app
mapa koju imamo components
gdje ćemo za svaku kutnu komponentu stvoriti css
, ts
, spec
i html
datoteke. Također ćemo stvoriti config
mapa za zadržavanje konfiguracije web mjesta, directives
imat će sve naše prilagođene direktive, helpers
će sadržati zajednički kod poput upravitelja provjere autentičnosti, layout
sadržavat će glavne komponente poput tijela, glave i bočnih ploča, models
zadržava ono što će se podudarati s pozadinskim modelima prikaza i konačno services
imat će kôd za sve pozive na pozadinu.
Izvan app
mapu zadržat ćemo mape stvorene prema zadanim postavkama, poput assets
i environments
, kao i korijenske datoteke.
Stvorimo config.ts
datoteka unutar našeg config
mapu i pozovite klasu AppConfig
. Tu možemo postaviti sve vrijednosti koje ćemo koristiti na različita mjesta u našem kodu; na primjer, URL API-ja. Imajte na umu da klasa implementira a get
svojstvo koje kao parametar prima strukturu ključ / vrijednost i jednostavnu metodu za pristup istoj vrijednosti. Na ovaj će način biti lako dobiti vrijednosti koje samo pozivaju this.config.setting['PathAPI']
iz klasa koje ga nasljeđuju.
import { Injectable } from '@angular/core'; @Injectable() export class AppConfig { private _config: { [key: string]: string }; constructor() { this._config = { PathAPI: 'http://localhost:50498/api/' }; } get setting():{ [key: string]: string } { return this._config; } get(key: any) { return this._config[key]; } };
Prije pokretanja izgleda, postavimo okvir UI komponente. Naravno, možete koristiti i druge poput Bootstrapa, ali ako vam se sviđa styling materijala, preporučujem ga jer ga podržava i Google.
Da bismo ga instalirali, samo trebamo pokrenuti sljedeće tri naredbe koje možemo izvršiti na terminalu Visual Studio Code:
npm install --save @angular/material @angular/cdk npm install --save @angular/animations npm install --save hammerjs
Druga naredba je zato što neke komponente materijala ovise o kutnim animacijama. Također preporučujem čitanje službenoj stranici kako bi se razumjelo koji su preglednici podržani i što je polifill.
Treća naredba je zato što se neke komponente materijala oslanjaju na HammerJS za geste.
Sada možemo nastaviti s uvozom komponentnih modula koje želimo koristiti u našim app.module.ts
datoteka:
import {MatButtonModule, MatCheckboxModule} from '@angular/material'; import {MatInputModule} from '@angular/material/input'; import {MatFormFieldModule} from '@angular/material/form-field'; import {MatSidenavModule} from '@angular/material/sidenav'; // ... @NgModule({ imports: [ BrowserModule, BrowserAnimationsModule, MatButtonModule, MatCheckboxModule, MatInputModule, MatFormFieldModule, MatSidenavModule, AppRoutingModule, HttpClientModule ],
Sljedeći je korak promjena style.css
datoteku, dodajući vrstu teme koju želite koristiti:
@import ' [email protected] /material/prebuilt-themes/deeppurple-amber.css';
Sada uvezite HammerJS dodavanjem ovog retka u main.ts
datoteka:
import 'hammerjs';
I na kraju, sve što nam nedostaje je dodavanje ikona materijala u index.html
, unutar odjeljka glave:
layout
U ovom primjeru stvorit ćemo jednostavan izgled poput ovog:
Ideja je otvoriti / sakriti izbornik klikom na neki gumb na zaglavlju. Ostatak posla odradit će Angular Responsive umjesto nas. Da bismo to učinili, stvorit ćemo app.component
mapu i stavite u nju app.component
datoteke stvorene prema zadanim postavkama. Ali mi ćemo također stvoriti iste datoteke za svaki odjeljak izgleda kao što možete vidjeti na sljedećoj slici. Tada, head.component
bit će tijelo, left-panel.component
zaglavlje i app.component.html
izbornik.
Promijenimo sada Menu
kako slijedi:
authentication
U osnovi ćemo imati head.component.html
svojstvo u komponenti koje će nam omogućiti uklanjanje zaglavlja i izbornika ako korisnik nije prijavljen, a umjesto toga, prikazati jednostavnu stranicu za prijavu.
The Logout!
izgleda ovako:
left-panel.component.html
Samo gumb za odjavu korisnika - na to ćemo se vratiti kasnije. Što se tiče Dashboard Users
, za sada samo promijenite HTML u:
import { Component } from '@angular/core'; @Component({ selector: 'app-head', templateUrl: './head.component.html', styleUrls: ['./head.component.css'] }) export class HeadComponent { title = 'Angular 5 Seed'; }
Jednostavno smo rekli: do sada su samo dvije veze za kretanje kroz dvije različite stranice. (Na to ćemo se vratiti i kasnije.)
Evo kako izgledaju datoteke tipa HeadScript i lijeva komponenta TypeScript:
import { Component } from '@angular/core'; @Component({ selector: 'app-left-panel', templateUrl: './left-panel.component.html', styleUrls: ['./left-panel.component.css'] }) export class LeftPanelComponent { title = 'Angular 5 Seed'; }
app.component
Ali što je s TypeScript kodom za app
? Ovdje ćemo ostaviti malo misterija i zaustaviti ga neko vrijeme, a na to ćemo se vratiti nakon provedbe provjere autentičnosti.
U redu, sada imamo Angular Material koji nam pomaže s korisničkim sučeljem i jednostavan izgled za početak izrade naših stranica. Ali kako se možemo kretati između stranica?
Da bismo stvorili jednostavan primjer, napravimo dvije stranice: 'Korisnik', gdje možemo dobiti popis postojećih korisnika u bazi podataka i 'Nadzorna ploča', stranicu na kojoj možemo prikazati neke statistike.
Unutar app-routing.modules.ts
mapu stvorit ćemo datoteku pod nazivom import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { AuthGuard } from './helpers/canActivateAuthGuard'; import { LoginComponent } from './components/login/login.component'; import { LogoutComponent } from './components/login/logout.component'; import { DashboardComponent } from './components/dashboard/dashboard.component'; import { UsersComponent } from './components/users/users.component'; const routes: Routes = [ { path: '', redirectTo: '/dashboard', pathMatch: 'full', canActivate: [AuthGuard] }, { path: 'login', component: LoginComponent}, { path: 'logout', component: LogoutComponent}, { path: 'dashboard', component: DashboardComponent, canActivate: [AuthGuard] }, { path: 'users', component: UsersComponent,canActivate: [AuthGuard] } ]; @NgModule({ imports: [ RouterModule.forRoot(routes) ], exports: [ RouterModule ] }) export class AppRoutingModule {}
izgleda ovako:
RouterModule
To je tako jednostavno: samo uvoz Routes
i @angular/router
iz /dashboard
, možemo mapirati putove koje želimo implementirati. Ovdje stvaramo četiri puta:
/login
: Naša početna stranica/logout
: Stranica na kojoj korisnik može provjeriti autentičnost/users
: Jednostavan put za odjavu korisnikadashboard
: Naša prva stranica na kojoj želimo navesti korisnike sa stražnje straneImajte na umu da /
je naša stranica prema zadanim postavkama, pa ako korisnik upiše URL canActivate
, stranica će se automatski preusmjeriti na ovu stranicu. Također, pogledajte AuthGuard
parametar: Ovdje stvaramo referencu na klasu left-panel.component.html
, koja će nam omogućiti da provjerimo je li korisnik prijavljen. Ako nije, preusmjerava se na stranicu za prijavu. U sljedećem odjeljku pokazat ću vam kako stvoriti ovaj predmet.
Sada, sve što trebamo učiniti je stvoriti izbornik. Zapamtite u odjeljku izgleda kada smo kreirali Dashboard Users
datoteka izgledati ovako?
our.site.url/users
Ovdje se naš kod susreće sa stvarnošću. Sada možemo izgraditi kôd i testirati ga u URL-u: Trebali biste biti u mogućnosti prelaziti sa stranice Nadzorne ploče na Korisnici, ali što će se dogoditi ako upišete URL http://www.mysite.com/users/42
u pregledniku izravno?
Napominjemo da se ova pogreška pojavljuje i ako osvježite preglednik nakon što ste već uspješno prešli na taj URL putem bočne ploče aplikacije. Da bih razumio ovu pogrešku, dopustite mi da se obratim na službeni dokumenti gdje je stvarno jasno:
Usmjerena aplikacija trebala bi podržavati dubinske veze. Dubinska poveznica URL je koji određuje put do komponente unutar aplikacije. Na primjer,
http://www.mysite.com/
duboka je veza do stranice s pojedinostima o heroju koja prikazuje junaka s id: 42.Nema problema kada korisnik pristupi tom URL-u unutar pokrenutog klijenta. Kutni usmjerivač tumači URL i rute do te stranice i heroja.
Ali klikom na vezu u e-poruci, unosom u adresnu traku preglednika ili samo osvježavanjem preglednika dok ste na stranici s detaljima heroja - svim tim radnjama upravlja sam preglednik, izvan pokrenute aplikacije. Preglednik upućuje izravni zahtjev poslužitelju za taj URL, zaobilazeći usmjerivač.Statični poslužitelj rutinski vraća index.html kada primi zahtjev za
http://www.mysite.com/users/42
. Ali odbacujesrc
i vraća pogrešku 404 - Nije pronađeno, osim ako nije konfigurirano da vraća index.html.
Da bismo riješili ovaj problem vrlo je jednostavno, samo moramo stvoriti konfiguraciju datoteke davatelja usluga. Budući da ovdje radim s IIS-om, pokazat ću vam kako to učiniti u ovom okruženju, ali koncept je sličan za Apache ili bilo koji drugi web poslužitelj.
Tako kreiramo datoteku unutar web.config
mapa pod nazivom angular-cli.json
to izgleda ovako:
{ '$schema': './node_modules/@angular/cli/lib/config/schema.json', 'project': { 'name': 'angular5-app' }, 'apps': [ { 'root': 'src', 'outDir': 'dist', 'assets': [ 'assets', 'favicon.ico', 'web.config' // or whatever equivalent is required by your web server ], 'index': 'index.html', 'main': 'main.ts', 'polyfills': 'polyfills.ts', 'test': 'test.ts', 'tsconfig': 'tsconfig.app.json', 'testTsconfig': 'tsconfig.spec.json', 'prefix': 'app', 'styles': [ 'styles.css' ], 'scripts': [], 'environmentSource': 'environments/environment.ts', 'environments': { 'dev': 'environments/environment.ts', 'prod': 'environments/environment.prod.ts' } } ], 'e2e': { 'protractor': { 'config': './protractor.conf.js' } }, 'lint': [ { 'project': 'src/tsconfig.app.json', 'exclude': '**/node_modules/**' }, { 'project': 'src/tsconfig.spec.json', 'exclude': '**/node_modules/**' }, { 'project': 'e2e/tsconfig.e2e.json', 'exclude': '**/node_modules/**' } ], 'test': { 'karma': { 'config': './karma.conf.js' } }, 'defaults': { 'styleExt': 'css', 'component': {} } }
Tada moramo biti sigurni da će se ovaj materijal kopirati u postavljenu mapu. Sve što trebamo učiniti je promijeniti datoteku postavki Angular CLI AuthGuard
:
canActivateAuthGuard.ts
Sjećate li se kako smo imali razred helpers
implementiran za postavljanje konfiguracije usmjeravanja? Svaki put kad prijeđemo na drugu stranicu, koristit ćemo ovu klasu da provjerimo je li korisnik autentificiran tokenom. U suprotnom ćemo automatski preusmjeriti na stranicu za prijavu. Datoteka za to je import { CanActivate, Router } from '@angular/router'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import { Helpers } from './helpers'; import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; @Injectable() export class AuthGuard implements CanActivate { constructor(private router: Router, private helper: Helpers) {} canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable | boolean { if (!this.helper.isAuthenticated()) { this.router.navigate(['/login']); return false; } return true; } }
- stvorite je unutar canActivate
mapa i neka izgleda ovako:
Router
Dakle, svaki put kad promijenimo stranicu metodom Helper
bit će pozvan, koji će provjeriti je li korisnik autentificiran, a ako nije, koristimo naš helpers
instance za preusmjeravanje na stranicu za prijavu. Ali koja je to nova metoda na helpers.ts
razred? Ispod localStorage
mapa stvorimo datoteku localStorage
. Ovdje trebamo upravljati sessionStorage
, gdje ćemo pohraniti token koji dobijemo sa stražnje strane.
Bilješka
Što se tiče sessionStorage , također možete koristiti kolačiće ili localStorage , a odluka će ovisiti o ponašanju koje želimo primijeniti. Kao što i samo ime govori, sessionStorage dostupan je samo tijekom trajanja sesije preglednika i briše se kad se kartica ili prozor zatvore; međutim, preživljava ponovno učitavanje stranica. Ako podaci koje pohranjujete trebaju biti stalno dostupni, onda localStorage je poželjnije od import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; import { Subject } from 'rxjs/Subject'; @Injectable() export class Helpers { private authenticationChanged = new Subject(); constructor() { } public isAuthenticated():boolean public isAuthenticationChanged():any { return this.authenticationChanged.asObservable(); } public getToken():any { if( window.localStorage['token'] === undefined || window.localStorage['token'] === null || window.localStorage['token'] === 'null' || window.localStorage['token'] === 'undefined' || window.localStorage['token'] === '') { return ''; } let obj = JSON.parse(window.localStorage['token']); return obj.token; } public setToken(data:any):void { this.setStorageToken(JSON.stringify(data)); } public failToken():void { this.setStorageToken(undefined); } public logout():void { this.setStorageToken(undefined); } private setStorageToken(value: any):void { window.localStorage['token'] = value; this.authenticationChanged.next(this.isAuthenticated()); } } . Kolačići su prvenstveno za čitanje na strani poslužitelja, dok Subject može se čitati samo na strani klijenta. Dakle, pitanje je u vašoj aplikaciji kome trebaju ti podaci - klijentu ili poslužitelju? |
{ path: 'logout', component: LogoutComponent},
Ima li naš autentifikacijski kod smisla sada? Vratit ćemo se na localStorage
klase kasnije, ali sada krenimo na minutu do konfiguracije usmjeravanja. Pogledajte ovaj redak:
components/login
Ovo je naša komponenta za odjavu s web mjesta, a to je samo jednostavna klasa za čišćenje logout.component.ts
. Stvorimo je pod import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { Helpers } from '../../helpers/helpers'; @Component({ selector: 'app-logout', template:'' }) export class LogoutComponent implements OnInit { constructor(private router: Router, private helpers: Helpers) { } ngOnInit() { this.helpers.logout(); this.router.navigate(['/login']); } }
mapa s imenom /logout
:
localStorage
Dakle, svaki put kad idemo na URL login.component.ts
, import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { TokenService } from '../../services/token.service'; import { Helpers } from '../../helpers/helpers'; @Component({ selector: 'app-login', templateUrl: './login.component.html', styleUrls: [ './login.component.css' ] }) export class LoginComponent implements OnInit { constructor(private helpers: Helpers, private router: Router, private tokenService: TokenService) { } ngOnInit() { } login(): void { let authValues = {'Username':'pablo', 'Password':'secret'}; this.tokenService.auth(authValues).subscribe(token => { this.helpers.setToken(token); this.router.navigate(['/dashboard']); }); } }
bit će uklonjen, a web mjesto će preusmjeriti na stranicu za prijavu. Napokon, kreirajmo app.component.ts
kao ovo:
export class AppComponent implements AfterViewInit { subscription: Subscription; authentication: boolean; constructor(private helpers: Helpers) { } ngAfterViewInit() { this.subscription = this.helpers.isAuthenticationChanged().pipe( startWith(this.helpers.isAuthenticated()), delay(0)).subscribe((value) => this.authentication = value ); } title = 'Angular 5 Seed'; ngOnDestroy() { this.subscription.unsubscribe(); } }
Kao što vidite, trenutno smo ovdje čvrsto kodirali svoje vjerodajnice. Imajte na umu da ovdje pozivamo klasu usluge; stvorit ćemo ove klase usluga kako bismo u sljedećem odjeljku dobili pristup našem pozadinskom dijelu.
Konačno, moramo se vratiti na Subject
datoteka, izgled web stranice. Ako je korisnik autentificiran, ovdje će se prikazati odjeljci izbornika i zaglavlja, ali ako ne, izgled će se promijeniti tako da prikazuje samo našu stranicu za prijavu.
Observable
Zapamtite Observable
klase u našem pomoćnom razredu? Ovo je authentication
. app.component.html
pružaju podršku za prosljeđivanje poruka između izdavača i pretplatnika u vašoj aplikaciji. Svaki put kada se promijeni token za provjeru autentičnosti, Menu
svojstvo će biti ažurirano. Pregled services
datoteku, sada će vjerojatno imati više smisla:
token.service.ts
U ovom trenutku prelazimo na različite stranice, autentificiramo klijentsku stranu i prikazujemo vrlo jednostavan izgled. Ali kako možemo dobiti podatke sa stražnje strane? Toplo preporučujem da radite sav pozadinski pristup sa servis klase posebno. Naša prva usluga bit će unutar import { Injectable } from '@angular/core'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Observable } from 'rxjs/Observable'; import { of } from 'rxjs/observable/of'; import { catchError, map, tap } from 'rxjs/operators'; import { AppConfig } from '../config/config'; import { BaseService } from './base.service'; import { Token } from '../models/token'; import { Helpers } from '../helpers/helpers'; @Injectable() export class TokenService extends BaseService { private pathAPI = this.config.setting['PathAPI']; public errorMessage: string; constructor(private http: HttpClient, private config: AppConfig, helper: Helpers) { super(helper); } auth(data: any): any { let body = JSON.stringify(data); return this.getToken(body); } private getToken (body: any): Observable { return this.http.post(this.pathAPI + 'token', body, super.header()).pipe( catchError(super.handleError) ); } }
mapa, nazvana TokenService
:
BaseService
Prvi poziv stražnjem kraju je POST poziv API-ju tokena. API tokena ne treba niz tokena u zaglavlju, ali što će se dogoditi ako pozovemo drugu krajnju točku? Kao što ovdje možete vidjeti, import { Injectable } from '@angular/core'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Observable } from 'rxjs/Observable'; import { of } from 'rxjs/observable/of'; import { catchError, map, tap } from 'rxjs/operators'; import { Helpers } from '../helpers/helpers'; @Injectable() export class BaseService { constructor(private helper: Helpers) { } public extractData(res: Response) { let body = res.json(); return body || {}; } public handleError(error: Response | any) { // In a real-world app, we might use a remote logging infrastructure let errMsg: string; if (error instanceof Response) { const body = error.json() || ''; const err = body || JSON.stringify(body); errMsg = `${error.status} - $error.statusText ${err}`; } else { errMsg = error.message ? error.message : error.toString(); } console.error(errMsg); return Observable.throw(errMsg); } public header() { let header = new HttpHeaders({ 'Content-Type': 'application/json' }); if(this.helper.isAuthenticated()) { header = header.append('Authorization', 'Bearer ' + this.helper.getToken()); } return { headers: header }; } public setToken(data:any) { this.helper.setToken(data); } public failToken(error: Response | any) { this.helper.failToken(); return this.handleError(Response); } }
(i klase usluga općenito) nasljeđuju iz super.header
razred. Pogledajmo ovo:
localStorage
Dakle, svaki put kad uputimo HTTP poziv, implementiramo zaglavlje zahtjeva samo koristeći user.ts
. Ako je token u export class User { id: number; name: string; }
tada će biti dodano unutar zaglavlja, ali ako ne, samo ćemo postaviti JSON format. Druga stvar koju ovdje možemo vidjeti je što se događa ako autentifikacija ne uspije.
Komponenta za prijavu pozvat će klasu usluge, a klasa usluge pozvat će stražnji kraj. Jednom kada imamo token, pomoćna klasa će upravljati tokenom, a sada smo spremni preuzeti popis korisnika iz naše baze podataka.
Da biste dobili podatke iz baze podataka, prvo budite sigurni da u našem odgovoru podudaramo klase modela s pozadinskim modelima prikaza.
U user.service.ts
:
import { Injectable } from '@angular/core'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Observable } from 'rxjs/Observable'; import { of } from 'rxjs/observable/of'; import { catchError, map, tap } from 'rxjs/operators'; import { BaseService } from './base.service'; import { User } from '../models/user'; import { AppConfig } from '../config/config'; import { Helpers } from '../helpers/helpers'; @Injectable() export class UserService extends BaseService { private pathAPI = this.config.setting['PathAPI']; constructor(private http: HttpClient, private config: AppConfig, helper: Helpers) { super(helper); } /** GET heroes from the server */ getUsers (): Observable { return this.http.get(this.pathAPI + 'user', super.header()).pipe( catchError(super.handleError)); }
A sada možemo stvoriti SeedAPI.Web.API
datoteka:
Web.API
Dobrodošli u prvi korak naše aplikacije Web API Core 2. Prvo što trebamo je stvoriti ASP.Net Core web aplikaciju koju ćemo nazvati ViewModels
.
Svakako odaberite predložak Prazno za čisti početak, kao što možete vidjeti dolje:
To je sve, rješenje kreiramo započinjući praznom web aplikacijom. Sada će naša arhitektura biti onakva kakvu smo naveli u nastavku pa ćemo morati stvoriti različite projekte:
Da biste to učinili, za svaku samo desnom tipkom miša kliknite Rješenje i dodajte projekt „Knjižnica razreda (.NET Core)“.
U prethodnom smo odjeljku stvorili osam projekata, ali čemu oni služe? Evo jednostavnog opisa svakog od njih:
Interfaces
: Ovo je naš startup projekt i tamo se stvaraju krajnje točke. Ovdje ćemo postaviti JWT, ovisnosti o ubrizgavanju i kontrolere.Commons
: Ovdje izvodimo pretvorbe iz vrste podataka koje će kontrolori vratiti u odgovorima na prednji kraj. Dobra je praksa uskladiti ove klase s prednjim modelima.Models
: Ovo će biti korisno u provedbi ovisnosti ubrizgavanja. Uvjerljiva prednost statički upisanog jezika je u tome što prevoditelj može pomoći u provjeri je li ugovor na koji se vaš kôd stvarno ispunjava.ViewModels
: Ovdje će biti sva zajednička ponašanja i korisni kod.Models
: Dobra je praksa ne podudarati bazu podataka izravno s prednjim krajem Maps
, pa je svrha ViewModels
je stvoriti klase baze podataka entiteta neovisno o prednjem kraju. To će nam omogućiti da u budućnosti promijenimo našu bazu podataka bez nužnog utjecaja na naš front end. Pomaže i kada jednostavno želimo napraviti refaktoriranje.Models
: Ovdje mapiramo Services
do Repositories
i obrnuto. Ovaj se korak naziva između kontrolera i usluga.App_Start
: Biblioteka za pohranu sve poslovne logike.JwtTokenConfig.cs
: Ovo je jedino mjesto na kojem zovemo bazu podataka.Reference će izgledati ovako:
U ovom ćemo odjeljku vidjeti osnovnu konfiguraciju provjere autentičnosti tokena i otići malo dublje na temu sigurnosti.
Da započnemo s postavljanjem JSON web tokena (JWT), stvorimo sljedeću klasu unutar namespace SeedAPI.Web.API.App_Start { public class JwtTokenConfig { public static void AddAuthentication(IServiceCollection services, IConfiguration configuration) { services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = configuration['Jwt:Issuer'], ValidAudience = configuration['Jwt:Issuer'], IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration['Jwt:Key'])) }; services.AddCors(); }); } } }
mapa pod nazivom appsettings.json
. Kôd iznutra izgledat će ovako:
'Jwt': { 'Key': 'veryVerySecretKey', 'Issuer': 'http://localhost:50498/' }
Vrijednosti parametara za provjeru valjanosti ovisit će o zahtjevima svakog projekta. Važeći korisnik i publika možemo postaviti čitanje konfiguracijske datoteke ConfigureServices
:
startup.cs
Tada ga trebamo nazvati samo iz // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { DependencyInjectionConfig.AddScope(services); JwtTokenConfig.AddAuthentication(services, Configuration); DBContextConfig.Initialize(services, Configuration); services.AddMvc(); }
metoda u TokenController.cs
:
appsettings.json
Sada smo spremni stvoriti naš prvi kontroler pod nazivom 'veryVerySecretKey'
. Vrijednost koju smo postavili u LoginViewModel
do ViewModels
treba odgovarati onoj koju koristimo za izradu tokena, ali prvo, kreirajmo namespace SeedAPI.ViewModels { public class LoginViewModel : IBaseViewModel { public string username { get; set; } public string password { get; set; } } }
unutar našeg namespace SeedAPI.Web.API.Controllers { [Route('api/Token')] public class TokenController : Controller { private IConfiguration _config; public TokenController(IConfiguration config) { _config = config; } [AllowAnonymous] [HttpPost] public dynamic Post([FromBody]LoginViewModel login) { IActionResult response = Unauthorized(); var user = Authenticate(login); if (user != null) { var tokenString = BuildToken(user); response = Ok(new { token = tokenString }); } return response; } private string BuildToken(UserViewModel user) { var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config['Jwt:Key'])); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var token = new JwtSecurityToken(_config['Jwt:Issuer'], _config['Jwt:Issuer'], expires: DateTime.Now.AddMinutes(30), signingCredentials: creds); return new JwtSecurityTokenHandler().WriteToken(token); } private UserViewModel Authenticate(LoginViewModel login) { UserViewModel user = null; if (login.username == 'pablo' && login.password == 'secret') { user = new UserViewModel { name = 'Pablo' }; } return user; } } }
projekt:
BuildToken
I na kraju kontroler:
Authenticate
The identityDbContext
metoda stvorit će token s danim sigurnosnim kodom. The Models
metoda trenutno samo ima kodiranu provjeru valjanosti korisnika, ali morat ćemo nazvati bazu podataka da bismo je na kraju provjerili.
Postavljanje Entity Framework-a zaista je jednostavno otkako je Microsoft pokrenuo verziju Core 2.0 - EF Core 2 za kratko. Idemo u dubinu s modelom koji koristi prvi kod pomoću Context
, pa prvo budite sigurni da ste instalirali sve ovisnosti. Za upravljanje možete koristiti NuGet:
Korištenje ApplicationContext.cs
projekt koji možemo stvoriti ovdje unutar IApplicationContext.cs
mapa dvije datoteke, EntityBase
i EntityBase
. Također, trebat će nam User.cs
razred.
The IdentityUser
datoteke će naslijediti svaki model entiteta, ali namespace SeedAPI.Models { public class User : IdentityUser { public string Name { get; set; } } }
je klasa identiteta i jedini entitet koji će naslijediti iz namespace SeedAPI.Models.EntityBase { public class EntityBase { public DateTime? Created { get; set; } public DateTime? Updated { get; set; } public bool Deleted { get; set; } public EntityBase() { Deleted = false; } public virtual int IdentityID() { return 0; } public virtual object[] IdentityID(bool dummy = true) { return new List().ToArray(); } } }
. Ispod su oba razreda:
ApplicationContext.cs
namespace SeedAPI.Models.Context { public class ApplicationContext : IdentityDbContext, IApplicationContext { private IDbContextTransaction dbContextTransaction; public ApplicationContext(DbContextOptions options) : base(options) { } public DbSet UsersDB { get; set; } public new void SaveChanges() { base.SaveChanges(); } public new DbSet Set() where T : class { return base.Set(); } public void BeginTransaction() { dbContextTransaction = Database.BeginTransaction(); } public void CommitTransaction() { if (dbContextTransaction != null) { dbContextTransaction.Commit(); } } public void RollbackTransaction() { if (dbContextTransaction != null) { dbContextTransaction.Rollback(); } } public void DisposeTransaction() { if (dbContextTransaction != null) { dbContextTransaction.Dispose(); } } } }
Sada smo spremni stvoriti App_Start
, koji će izgledati ovako:
Web.API
Zaista smo bliski, ali prvo ćemo morati stvoriti više klasa, ovaj put u namespace SeedAPI.Web.API.App_Start { public class DBContextConfig { public static void Initialize(IConfiguration configuration, IHostingEnvironment env, IServiceProvider svp) { var optionsBuilder = new DbContextOptionsBuilder(); if (env.IsDevelopment()) optionsBuilder.UseSqlServer(configuration.GetConnectionString('DefaultConnection')); else if (env.IsStaging()) optionsBuilder.UseSqlServer(configuration.GetConnectionString('DefaultConnection')); else if (env.IsProduction()) optionsBuilder.UseSqlServer(configuration.GetConnectionString('DefaultConnection')); var context = new ApplicationContext(optionsBuilder.Options); if(context.Database.EnsureCreated()) { IUserMap service = svp.GetService(typeof(IUserMap)) as IUserMap; new DBInitializeConfig(service).DataTest(); } } public static void Initialize(IServiceCollection services, IConfiguration configuration) { services.AddDbContext(options => options.UseSqlServer(configuration.GetConnectionString('DefaultConnection'))); } } }
mapa koja se nalazi u namespace SeedAPI.Web.API.App_Start { public class DBInitializeConfig { private IUserMap userMap; public DBInitializeConfig (IUserMap _userMap) { userMap = _userMap; } public void DataTest() { Users(); } private void Users() { userMap.Create(new UserViewModel() { id = 1, name = 'Pablo' }); userMap.Create(new UserViewModel() { id = 2, name = 'Diego' }); } } }
projekt. Prva klasa je inicijalizacija konteksta aplikacije, a druga je stvaranje uzoraka podataka samo u svrhu testiranja tijekom razvoja.
// This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { DependencyInjectionConfig.AddScope(services); JwtTokenConfig.AddAuthentication(services, Configuration); DBContextConfig.Initialize(services, Configuration); services.AddMvc(); } // ... // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider svp) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } DBContextConfig.Initialize(Configuration, env, svp); app.UseCors(builder => builder .AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader() .AllowCredentials()); app.UseAuthentication(); app.UseMvc(); }
App_Start
I mi ih zovemo iz naše datoteke za pokretanje:
DependencyInjectionConfig.cs
Dobra je praksa koristiti injekcije ovisnosti za kretanje između različitih projekata. To će nam pomoći da komuniciramo između kontrolora i mapera, mapera i usluga te usluga i spremišta.
Unutar mape namespace SeedAPI.Web.API.App_Start { public class DependencyInjectionConfig { public static void AddScope(IServiceCollection services) { services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); } } }
stvorit ćemo datoteku Map
i izgledat će ovako:
Service
Morat ćemo stvoriti za svaki novi entitet nove Repository
, startup.cs
i // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { DependencyInjectionConfig.AddScope(services); JwtTokenConfig.AddAuthentication(services, Configuration); DBContextConfig.Initialize(services, Configuration); services.AddMvc(); }
i uskladiti ih s ovom datotekom. Tada ga samo trebamo nazvati iz namespace SeedAPI.Web.API.Controllers { [Route('api/[controller]')] [Authorize] public class UserController : Controller { IUserMap userMap; public UserController(IUserMap map) { userMap = map; } // GET api/user [HttpGet] public IEnumerable Get() { return userMap.GetAll(); ; } // GET api/user/5 [HttpGet('{id}')] public string Get(int id) { return 'value'; } // POST api/user [HttpPost] public void Post([FromBody]string user) { } // PUT api/user/5 [HttpPut('{id}')] public void Put(int id, [FromBody]string user) { } // DELETE api/user/5 [HttpDelete('{id}')] public void Delete(int id) { } } }
datoteka:
Authorize
Konačno, kada iz baze podataka moramo dobiti popis korisnika, možemo stvoriti kontroler koristeći ovu injekciju ovisnosti:
Map
Pogledajte kako Maps
atribut je ovdje prisutan kako bi bio siguran da se prednji kraj prijavio i kako injekcija ovisnosti radi u konstruktoru klase.
Napokon imamo poziv u bazu podataka, ali prvo, moramo razumjeti ViewModels
projekt.
UserMap.cs
ProjektOvaj korak je samo mapiranje namespace SeedAPI.Maps { public class UserMap : IUserMap { IUserService userService; public UserMap(IUserService service) { userService = service; } public UserViewModel Create(UserViewModel viewModel) { User user = ViewModelToDomain(viewModel); return DomainToViewModel(userService.Create(user)); } public bool Update(UserViewModel viewModel) { User user = ViewModelToDomain(viewModel); return userService.Update(user); } public bool Delete(int id) { return userService.Delete(id); } public List GetAll() { return DomainToViewModel(userService.GetAll()); } public UserViewModel DomainToViewModel(User domain) { UserViewModel model = new UserViewModel(); model.name = domain.Name; return model; } public List DomainToViewModel(List domain) { List model = new List(); foreach (User of in domain) { model.Add(DomainToViewModel(of)); } return model; } public User ViewModelToDomain(UserViewModel officeViewModel) { User domain = new User(); domain.Name = officeViewModel.name; return domain; } } }
do i iz modela baze podataka. Moramo stvoriti po jedan za svaki entitet i, slijedeći naš prethodni, primjer Services
datoteka će izgledati ovako:
namespace SeedAPI.Services { public class UserService : IUserService { private IUserRepository repository; public UserService(IUserRepository userRepository) { repository = userRepository; } public User Create(User domain) { return repository.Save(domain); } public bool Update(User domain) { return repository.Update(domain); } public bool Delete(int id) { return repository.Delete(id); } public List GetAll() { return repository.GetAll(); } } }
Izgleda da još jednom ubrizgavanje ovisnosti djeluje u konstruktoru klase, povezujući Karte s projektom Usluge.
Repositories
ProjektOvdje nema previše toga za reći: Naš je primjer zaista jednostavan i ovdje nemamo poslovnu logiku ili kôd. Ovaj bi se projekt pokazao korisnim u budućim naprednim zahtjevima kada moramo izračunati ili napraviti logiku prije ili nakon koraka baze podataka ili kontrolera. Slijedeći primjer, klasa će izgledati prilično ogoljeno:
UserRepository.cs
namespace SeedAPI.Repositories { public class UserRepository : BaseRepository, IUserRepository { public UserRepository(IApplicationContext context) : base(context) { } public User Save(User domain) { try { var us = InsertUser(domain); return us; } catch (Exception ex) { //ErrorManager.ErrorHandler.HandleError(ex); throw ex; } } public bool Update(User domain) { try { //domain.Updated = DateTime.Now; UpdateUser(domain); return true; } catch (Exception ex) { //ErrorManager.ErrorHandler.HandleError(ex); throw ex; } } public bool Delete(int id) { try { User user = Context.UsersDB.Where(x => x.Id.Equals(id)).FirstOrDefault(); if (user != null) { //Delete(user); return true; } else { return false; } } catch (Exception ex) { //ErrorManager.ErrorHandler.HandleError(ex); throw ex; } } public List GetAll() { try { return Context.UsersDB.OrderBy(x => x.Name).ToList(); } catch (Exception ex) { //ErrorManager.ErrorHandler.HandleError(ex); throw ex; } } } }
ProjektDolazimo do zadnjeg odjeljka ovog vodiča: Samo trebamo uputiti pozive bazi podataka, pa kreiramo
|_+_|datoteku u kojoj možemo čitati, umetati ili ažurirati korisnike u bazi podataka.
|_+_|
U ovom sam članku objasnio kako stvoriti dobru arhitekturu pomoću Angular 5 i Web API Core 2. U ovom trenutku stvorili ste osnovu za veliki projekt s kodom koji podržava velik rast zahtjeva.
Istina je da se ništa ne natječe s JavaScriptom na prednjem kraju i što se može natjecati s C # ako vam je potrebna podrška SQL Server i Entity Framework na stražnjem kraju? Dakle, ideja ovog članka bila je kombinirati najbolje od dva svijeta i nadam se da ste uživali.
Ako radite u timu od Kutni programeri vjerojatno bi mogli postojati različiti programeri koji rade na prednjem i stražnjem kraju, pa bi dobra ideja za sinkronizaciju napora oba tima mogla biti integracija Swaggera s Web API 2. Swagger je izvrstan alat za dokumentiranje i testiranje vaših RESTFul API-ja. Pročitajte Microsoftov vodič: Započnite s Swashbuckleom i ASP.NET Core .
Ako ste još uvijek vrlo novi u programu Angular 5 i imate problema s daljnjim praćenjem, pročitajte Vodič za kutni 5: Vodič po korak do vaše prve aplikacije Kutni 5 kolega ApeeScapeer Sergey Moiseev.
Možete koristiti bilo koji IDE za frontend, mnogi ljudi vole pridružiti frontend u knjižnicu web aplikacija i automatizirati implementaciju. Više volim držati prednji kôd odvojen od pozadinskog i smatrao sam da je Visual Studio Code stvarno dobar alat, posebno s intellisense, za Typescript kôd.
Kutni materijal okvir je UI komponente i iako ga ne morate koristiti. Okviri komponenata korisničkog sučelja pomažu nam u organiziranju izgleda i reagiranja na web mjestu, ali imamo ih puno na tržištu, poput bootstrapa i ostalih, i možemo odabrati izgled i dojam koji više volimo.
Kutni usmjerivač omogućuje navigaciju od jednog pogleda do drugog dok korisnici izvršavaju zadatke aplikacije. URL preglednika može protumačiti kao uputu za navigaciju do pogleda generiranog od strane klijenta. Programeru je dopušteno postavljanje naziva URL-ova, parametara i onečišćene CanAuthenticateom možemo provjeriti autentičnost korisnika
Često razredi trebaju pristup jedni drugima, a ovaj obrazac dizajna pokazuje kako stvoriti labavo povezane klase. Kada su dvije klase čvrsto povezane, one su povezane s binarnom asocijacijom.
JSON Web Token (JWT) kompaktna je knjižnica za siguran prijenos podataka između strana kao JSON objekt. JWT-ovi se mogu šifrirati kako bi se također osigurala tajnost među strankama, usredotočit ćemo se na potpisane tokene.