Açısal 5 ve ASP.NET Çekirdeği
Yayınlanan: 2022-03-11Angular'ın ilk sürümü Microsoft'u istemci tarafında neredeyse öldürdüğünden beri bir blog yazısı yazmayı düşünüyorum. ASP.Net, Web Forms ve MVC Razor gibi teknolojilerin modası geçti ve yerini tam olarak Microsoft olmayan bir JavaScript çerçevesi aldı. Ancak, Angular'ın ikinci sürümünden bu yana, Microsoft ve Google, Angular 2'yi oluşturmak için birlikte çalışıyor ve bu, en sevdiğim iki teknolojinin birlikte çalışmaya başladığı andır.
Bu blogda, insanların bu iki dünyayı birleştiren en iyi mimariyi oluşturmalarına yardımcı olmak istiyorum. Hazır mısın? İşte başlıyoruz!
mimari hakkında
RESTful Web API Core 2 hizmeti tüketen bir Angular 5 istemcisi oluşturacaksınız.
Müşteri tarafı:
- açısal 5
- açısal CLI
- açısal malzeme
Sunucu tarafı:
- .NET C# Web API Çekirdeği 2
- Enjeksiyon bağımlılıkları
- JWT kimlik doğrulaması
- Önce varlık çerçeve kodu
- SQL Server
Not
Bu blog yazısında okuyucunun TypeScript, Angular modüller, bileşenler ve içe/dışa aktarma hakkında temel bilgilere sahip olduğunu varsayıyoruz. Bu gönderinin amacı, kodun zaman içinde büyümesine izin verecek iyi bir mimari oluşturmaktır. |
Ne istiyorsun?
IDE'yi seçerek başlayalım. Tabii ki bu sadece benim tercihim ve hangisiyle daha rahat hissediyorsan onu kullanabilirsin. Benim durumumda Visual Studio Code ve Visual Studio 2017 kullanacağım.
Neden iki farklı IDE? Microsoft, ön uç için Visual Studio Code oluşturduğundan, bu IDE'yi kullanmayı bırakamıyorum. Her neyse, aynı zamanda Angular 5'i çözüm projesine nasıl entegre edeceğimizi de göreceğiz; bu, hem arka uç hem de ön taraf hatalarını tek bir F5 ile ayıklamayı tercih eden türden bir geliştiriciyseniz size yardımcı olacaktır.
Arka uç hakkında, geliştiriciler için ücretsiz bir sürümü olan ancak çok eksiksiz olan en son Visual Studio 2017 sürümünü yükleyebilirsiniz: Topluluk.
İşte bu eğitim için yüklememiz gereken şeylerin listesi:
- Visual Studio Kodu
- Visual Studio 2017 Topluluğu (veya Herhangi Bir)
- Node.js v8.10.0
- SQL Sunucusu 2017
Not
Bir terminal veya konsol penceresinde node -v ve npm -v -v'yi çalıştırarak en az Node 6.9.x ve npm 3.xx çalıştırdığınızı doğrulayın. Eski sürümler hata üretir, ancak daha yeni sürümler iyidir. |
Ön Uç
Hızlı başlangıç
Eğlence başlasın! Yapmamız gereken ilk şey, Angular CLI'yi global olarak kurmaktır, bu nedenle node.js komut istemini açın ve şu komutu çalıştırın:
npm install -g @angular/cli
Tamam, şimdi modül paketleyicimiz var. Bu genellikle modülü kullanıcı klasörünüzün altına kurar. Varsayılan olarak bir takma ad gerekli olmamalıdır, ancak ihtiyacınız varsa bir sonraki satırı çalıştırabilirsiniz:
alias ng="<UserFolder>/.npm/lib/node_modules/angular-cli/bin/ng"
Bir sonraki adım, yeni projeyi oluşturmaktır. Ben buna angular5-app
. İlk önce siteyi oluşturmak istediğimiz klasöre gidiyoruz ve ardından:
ng new angular5-app
İlk Yapı
Yeni web sitenizi yalnızca ng serve --open
çalıştırarak test edebilirsiniz, ancak siteyi en sevdiğiniz web hizmetinden test etmenizi öneririm. Niye ya? Eh, bazı sorunlar sadece üretimde olabilir ve siteyi ng build
ile inşa etmek bu ortama yaklaşmanın en yakın yoludur. Ardından, angular5-app
klasörünü Visual Studio Code ile açabilir ve terminal bash üzerinde ng build
çalıştırabiliriz:
dist
adında yeni bir klasör oluşturulacak ve onu IIS veya tercih ettiğiniz herhangi bir web sunucusu kullanarak sunabiliriz. Ardından tarayıcıya URL'yi yazabilirsiniz ve…bitti!
Not
Bu öğreticinin amacı bir web sunucusunun nasıl kurulacağını göstermek değildir, bu yüzden bu bilgiye zaten sahip olduğunuzu varsayıyorum. |
src
Klasörü
src
klasörüm şu şekilde yapılandırılmıştır: app
klasörünün içinde, her Angular bileşeni için css
, ts
, spec
ve html
dosyalarını oluşturacağımız components
var. Ayrıca site yapılandırmasını korumak için bir config
klasörü oluşturacağız, directives
tüm özel yönergelerimize sahip olacak, helpers
kimlik doğrulama yöneticisi gibi ortak kodları barındıracak, layout
gövde, kafa ve yan paneller gibi ana bileşenleri içerecek, models
ne olacağını koruyacak arka uç görünüm modelleriyle eşleştirin ve son olarak services
, arka uca yapılan tüm aramalar için koda sahip olacaktır.
app
klasörünün dışında, assets
ve environments
gibi varsayılan olarak oluşturulan klasörleri ve ayrıca kök dosyaları tutacağız.
Yapılandırma Dosyasını Oluşturma
config
klasörümüzün içinde bir config.ts
dosyası oluşturalım ve AppConfig
sınıfını çağıralım. Kodumuzun farklı yerlerinde kullanacağımız tüm değerleri buradan ayarlayabiliriz; örneğin, API'nin URL'si. Sınıfın, parametre olarak bir anahtar/değer yapısı ve aynı değere erişmek için basit bir yöntem alan bir get
özelliği uyguladığına dikkat edin. Bu şekilde, sadece this.config.setting['PathAPI']
olarak adlandırılan değerleri kendisinden miras alan sınıflardan almak kolay olacaktır.
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]; } };
açısal malzeme
Düzene başlamadan önce, UI bileşen çerçevesini ayarlayalım. Tabii ki Bootstrap gibi başkalarını da kullanabilirsiniz, ancak Material'ın stilini beğendiyseniz, Google tarafından da desteklendiği için bunu tavsiye ederim.
Yüklemek için Visual Studio Code terminalinde yürütebileceğimiz sonraki üç komutu çalıştırmamız yeterli:
npm install --save @angular/material @angular/cdk npm install --save @angular/animations npm install --save hammerjs
İkinci komut, bazı Malzeme bileşenlerinin Açısal Animasyonlara bağlı olmasıdır. Ayrıca hangi tarayıcıların desteklendiğini ve çoklu dolgunun ne olduğunu anlamak için resmi sayfayı okumanızı tavsiye ederim.
Üçüncü komut, bazı Malzeme bileşenlerinin hareketler için HammerJS'ye güvenmesidir.
Artık app.module.ts
dosyamızda kullanmak istediğimiz bileşen modüllerini içe aktarmaya geçebiliriz:
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 ],
Sonraki adım, kullanmak istediğiniz tema türünü ekleyerek style.css
dosyasını değiştirmektir:
@import "~@angular/material/prebuilt-themes/deeppurple-amber.css";
Şimdi bu satırı main.ts
dosyasına ekleyerek HammerJS'yi içe aktarın:
import 'hammerjs';
Ve son olarak, eksik olan tek şey, baş bölümünün içindeki index.html
Material simgelerini eklemek:
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
Tasarım
Bu örnekte, aşağıdaki gibi basit bir düzen oluşturacağız:
Buradaki fikir, başlıktaki bazı düğmelere tıklayarak menüyü açmak/gizlemek. Angular Responsive, işin geri kalanını bizim için yapacak. Bunu yapmak için bir layout
klasörü oluşturacağız ve içine varsayılan olarak oluşturulan app.component
dosyalarını koyacağız. Ancak bir sonraki resimde göreceğiniz gibi mizanpajın her bölümü için aynı dosyaları oluşturacağız. Ardından, app.component
gövde, head.component
üstbilgi ve left-panel.component
menü olacaktır.
Şimdi app.component.html
aşağıdaki gibi değiştirelim:
<div *ngIf="authentication"> <app-head></app-head> <button type="button" mat-button (click)="drawer.toggle()"> Menu </button> <mat-drawer-container class="example-container" autosize> <mat-drawer #drawer class="example-sidenav" mode="side"> <app-left-panel></app-left-panel> </mat-drawer> <div> <router-outlet></router-outlet> </div> </mat-drawer-container> </div> <div *ngIf="!authentication"><app-login></app-login></div>
Temel olarak, bileşende, kullanıcı oturum açmadıysa başlığı ve menüyü kaldırmamıza ve bunun yerine basit bir oturum açma sayfası göstermemize izin verecek bir authentication
özelliğine sahip olacağız.
head.component.html
şuna benzer:
<h1>{{title}}</h1> <button mat-button [routerLink]=" ['./logout'] ">Logout!</button>
Kullanıcının oturumunu kapatmak için sadece bir düğme; buna daha sonra tekrar döneceğiz. left-panel.component.html
ile ilgili olarak, şimdilik HTML'yi şu şekilde değiştirin:
<nav> <a routerLink="/dashboard">Dashboard</a> <a routerLink="/users">Users</a> </nav>
Basit tuttuk: Şimdiye kadar iki farklı sayfada gezinmek için sadece iki bağlantı. (Buna daha sonra da döneceğiz.)
Şimdi, baş ve sol taraftaki bileşen TypeScript dosyaları şöyle görünür:
import { Component } from '@angular/core'; @Component({ selector: 'app-head', templateUrl: './head.component.html', styleUrls: ['./head.component.css'] }) export class HeadComponent { title = 'Angular 5 Seed'; }
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'; }
Peki ya app.component için app.component
kodu? Burada biraz gizem bırakacağız ve bir süre duraklatacağız ve kimlik doğrulamayı uyguladıktan sonra buna geri döneceğiz.
yönlendirme
Tamam, şimdi kullanıcı arayüzünde bize yardımcı olan Angular Material ve sayfalarımızı oluşturmaya başlamak için basit bir düzenimiz var. Ancak sayfalar arasında nasıl gezinebiliriz?
Basit bir örnek oluşturmak için iki sayfa oluşturalım: Veritabanındaki mevcut kullanıcıların listesini alabileceğimiz “User” ve bazı istatistikleri gösterebileceğimiz “Dashboard” sayfası.
app
klasörünün içinde, şuna benzeyen app-routing.modules.ts
adlı bir dosya oluşturacağız:
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 {}
Bu kadar basit: Sadece RouterModule
ve Routes
@angular/router
öğesinden içe aktararak uygulamak istediğimiz yolları eşleyebiliriz. Burada dört yol oluşturuyoruz:
-
/dashboard
: Ana sayfamız -
/login
: Kullanıcının kimliğini doğrulayabileceği sayfa -
/logout
: Kullanıcının oturumunu kapatmanın basit bir yolu -
/users
: Arka uçtaki kullanıcıları listelemek istediğimiz ilk sayfamız
Kontrol dashboard
varsayılan olarak bizim sayfamız olduğunu unutmayın, bu nedenle kullanıcı URL'yi /
yazarsa, sayfa otomatik olarak bu sayfaya yönlendirilecektir. Ayrıca canActivate
parametresine bir göz atın: Burada, kullanıcının oturum açıp açmadığını kontrol etmemize izin verecek olan AuthGuard
sınıfına bir referans oluşturuyoruz. Değilse, oturum açma sayfasına yönlendirilir. Bir sonraki bölümde, size bu sınıfın nasıl oluşturulacağını göstereceğim.
Şimdi tek yapmamız gereken menüyü oluşturmak. Düzen bölümünde left-panel.component.html
dosyasını böyle görünecek şekilde oluşturduğumuzu hatırlıyor musunuz?
<nav> <a routerLink="/dashboard">Dashboard</a> <a routerLink="/users">Users</a> </nav>
Kodumuzun gerçeklikle buluştuğu yer burasıdır. Şimdi kodu oluşturabilir ve URL'de test edebiliriz: Gösterge Tablosu sayfasından Kullanıcılar'a gidebilmelisiniz, ancak URL'yi doğrudan tarayıcıya our.site.url/users
ne olur?
Bu hatanın, uygulamanın yan paneli aracılığıyla bu URL'ye zaten başarılı bir şekilde gittikten sonra tarayıcıyı yenilerseniz de göründüğünü unutmayın. Bu hatayı anlamak için, gerçekten açık olduğu resmi belgelere başvurmama izin verin:
Yönlendirilmiş bir uygulama derin bağlantıları desteklemelidir. Derin bağlantı, uygulama içindeki bir bileşenin yolunu belirten bir URL'dir. Örneğin,
http://www.mysite.com/users/42
, id: 42 olan kahramanı görüntüleyen kahraman ayrıntı sayfasına derin bir bağlantıdır.Kullanıcı, çalışan bir istemciden bu URL'ye gittiğinde sorun yoktur. Angular yönlendirici, URL'yi yorumlar ve o sayfaya ve kahramana yönlendirir.
Ancak bir e-postadaki bir bağlantıya tıklamak, onu tarayıcı adres çubuğuna girmek veya yalnızca kahraman ayrıntı sayfasındayken tarayıcıyı yenilemek - tüm bu eylemler, çalışan uygulamanın dışında tarayıcının kendisi tarafından gerçekleştirilir. Tarayıcı, yönlendiriciyi atlayarak bu URL için sunucuya doğrudan bir istekte bulunur.Statik bir sunucu,
http://www.mysite.com/
için bir istek aldığında rutin olarak index.html döndürür. Ancakhttp://www.mysite.com/users/42
reddeder ve bunun yerine index.html döndürecek şekilde yapılandırılmadığı sürece 404 - Bulunamadı hatası döndürür.
Bu sorunu çözmek çok basit, sadece servis sağlayıcı dosya yapılandırmasını oluşturmamız gerekiyor. Burada IIS ile çalıştığım için size bu ortamda nasıl yapılacağını göstereceğim, ancak konsept Apache veya başka herhangi bir web sunucusu için benzer.
Bu yüzden src
klasörünün içinde web.config
adında şuna benzeyen bir dosya oluşturuyoruz:
<?xml version="1.0"?> <configuration> <system.webServer> <rewrite> <rules> <rule name="Angular Routes" stopProcessing="true"> <match url=".*" /> <conditions logicalGrouping="MatchAll"> <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" /> <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" /> </conditions> <action type="Rewrite" url="/index.html" /> </rule> </rules> </rewrite> </system.webServer> <system.web> <compilation debug="true"/> </system.web> </configuration>
Ardından, bu varlığın konuşlandırılmış klasöre kopyalanacağından emin olmamız gerekir. Tek yapmamız gereken Angular CLI ayar dosyamızı angular-cli.json
:
{ "$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": {} } }
kimlik doğrulama
Yönlendirme yapılandırmasını ayarlamak için AuthGuard
sınıfını nasıl uyguladığımızı hatırlıyor musunuz? Farklı bir sayfaya her gittiğimizde, kullanıcının kimliğinin bir belirteçle doğrulanıp doğrulanmadığını doğrulamak için bu sınıfı kullanacağız. Değilse, otomatik olarak giriş sayfasına yönlendirileceğiz. Bunun için dosya canActivateAuthGuard.ts
helpers
klasörü içinde oluşturun ve şöyle görünmesini sağlayın:
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> | boolean { if (!this.helper.isAuthenticated()) { this.router.navigate(['/login']); return false; } return true; } }
Bu nedenle, sayfayı her değiştirdiğimizde, kullanıcının kimliğinin doğrulanıp doğrulanmadığını kontrol edecek olan canActivate
yöntemi çağrılır ve değilse, oturum açma sayfasına yönlendirmek için Router
örneğimizi kullanırız. Ancak Helper
sınıfındaki bu yeni yöntem nedir? helpers.ts
helpers
oluşturalım. Burada, arka uçtan aldığımız belirteci depolayacağımız localStorage
yönetmemiz gerekiyor.
Not
localStorage ile ilgili olarak, çerezleri veya sessionStorage da kullanabilirsiniz ve karar, uygulamak istediğimiz davranışa bağlı olacaktır. Adından da anlaşılacağı gibi, sessionStorage yalnızca tarayıcı oturumu süresince kullanılabilir ve sekme veya pencere kapatıldığında silinir; bununla birlikte, sayfa yeniden yüklemelerinden kurtulur. Sakladığınız verilerin sürekli olarak kullanılabilir olması gerekiyorsa localStorage , sessionStorage yerine tercih edilir. Tanımlama bilgileri öncelikle sunucu tarafını okumak içindir, localStorage ise yalnızca istemci tarafında okunabilir. Yani soru şu ki, uygulamanızda bu verilere kimin ihtiyacı var --- istemci mi yoksa sunucu mu? |
import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; import { Subject } from 'rxjs/Subject'; @Injectable() export class Helpers { private authenticationChanged = new Subject<boolean>(); constructor() { } public isAuthenticated():boolean { return (!(window.localStorage['token'] === undefined || window.localStorage['token'] === null || window.localStorage['token'] === 'null' || window.localStorage['token'] === 'undefined' || window.localStorage['token'] === '')); } 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()); } }
Kimlik doğrulama kodumuz şimdi mantıklı mı? Subject
sınıfına daha sonra geri döneceğiz, ancak şimdi bir dakikalığına yönlendirme yapılandırmasına dönelim. Şu satıra bir göz atın:
{ path: 'logout', component: LogoutComponent},
Bu, siteden çıkış yapmak için bizim bileşenimizdir ve localStorage
temizlemek için sadece basit bir sınıftır. logout.component.ts
ismiyle components/login
klasörü altında oluşturalım:
import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { Helpers } from '../../helpers/helpers'; @Component({ selector: 'app-logout', template:'<ng-content></ng-content>' }) export class LogoutComponent implements OnInit { constructor(private router: Router, private helpers: Helpers) { } ngOnInit() { this.helpers.logout(); this.router.navigate(['/login']); } }
Bu nedenle, /logout
URL'sine her gittiğimizde, localStorage
kaldırılacak ve site giriş sayfasına yönlendirilecektir. Son olarak, şöyle login.component.ts
oluşturalım:
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']); }); } }
Gördüğünüz gibi, şu an için kimlik bilgilerimizi burada kodladık. Burada bir hizmet sınıfı çağırdığımızı unutmayın; Bir sonraki bölümde arka uçumuza erişmek için bu hizmet sınıflarını oluşturacağız.
Son olarak, sitenin düzeni olan app.component.ts
dosyasına geri dönmemiz gerekiyor. Burada, kullanıcının kimliği doğrulanırsa, menü ve başlık bölümlerini gösterecek, ancak değilse, düzen yalnızca giriş sayfamızı gösterecek şekilde değişecektir.

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(); } }
Yardımcı sınıfımızdaki Subject
sınıfını hatırlıyor musunuz? Bu bir Observable
. Observable
, uygulamanızdaki yayıncılar ve aboneler arasında mesaj iletmek için destek sağlar. Kimlik doğrulama belirteci her değiştiğinde, authentication
özelliği güncellenecektir. app.component.html
dosyasını incelemek muhtemelen şimdi daha mantıklı olacaktır:
<div *ngIf="authentication"> <app-head></app-head> <button type="button" mat-button (click)="drawer.toggle()"> Menu </button> <mat-drawer-container class="example-container" autosize> <mat-drawer #drawer class="example-sidenav" mode="side"> <app-left-panel></app-left-panel> </mat-drawer> <div> <router-outlet></router-outlet> </div> </mat-drawer-container> </div> <div *ngIf="!authentication"><app-login></app-login></div>
Hizmetler
Bu noktada farklı sayfalara gidiyoruz, müşteri tarafımızı doğruluyor ve çok basit bir düzen oluşturuyoruz. Ancak arka uçtan nasıl veri alabiliriz? Özellikle hizmet sınıflarından tüm arka uç erişimlerini yapmanızı şiddetle tavsiye ederim. İlk hizmetimiz token.service.ts
adlı services
klasörünün içinde olacak:
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<any> { return this.http.post<any>(this.pathAPI + 'token', body, super.header()).pipe( catchError(super.handleError) ); } }
Arka uca yapılan ilk çağrı, belirteç API'sine yapılan bir POST çağrısıdır. Belirteç API'sinin başlıktaki belirteç dizesine ihtiyacı yoktur, ancak başka bir uç nokta çağırırsak ne olur? Burada görebileceğiniz gibi, TokenService
(ve genel olarak hizmet sınıfları) BaseService
sınıfından miras alır. Şuna bir göz atalım:
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); } }
Bu yüzden her HTTP çağrısı yaptığımızda, isteğin başlığını sadece super.header kullanarak super.header
. Belirteç localStorage
, başlığın içine eklenecektir, ancak değilse, yalnızca JSON biçimini ayarlayacağız. Burada görebileceğimiz başka bir şey, kimlik doğrulama başarısız olursa ne olacağıdır.
Oturum açma bileşeni hizmet sınıfını arayacak ve hizmet sınıfı arka ucu arayacaktır. Belirteci aldığımızda, yardımcı sınıf belirteci yönetecek ve şimdi veritabanımızdan kullanıcı listesini almaya hazırız.
Veritabanından veri almak için öncelikle yanıtımızdaki model sınıflarını arka uç görünüm modelleriyle eşleştirdiğimizden emin olun.
user.ts
:
export class User { id: number; name: string; }
Ve şimdi user.service.ts
dosyasını oluşturabiliriz:
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<User[]> { return this.http.get(this.pathAPI + 'user', super.header()).pipe( catchError(super.handleError)); }
Arka Uç
Hızlı başlangıç
Web API Core 2 uygulamamızın ilk adımına hoş geldiniz. İhtiyacımız olan ilk şey, SeedAPI.Web.API
vereceğimiz bir ASP.Net Core Web Uygulaması oluşturmak.
Aşağıda görebileceğiniz gibi temiz bir başlangıç için Boş şablonunu seçtiğinizden emin olun:
Hepsi bu, çözümü boş bir web uygulamasıyla başlayarak oluşturuyoruz. Artık mimarimiz aşağıda listelediğimiz gibi olacak, bu yüzden farklı projeler oluşturmamız gerekecek:
Bunu yapmak için, her biri için Çözüme sağ tıklayın ve bir “Class Library (.NET Core)” projesi ekleyin.
Mimarlık
Bir önceki bölümde sekiz proje oluşturduk ama bunlar ne için? İşte her birinin basit bir açıklaması:
-
Web.API
: Bu bizim başlangıç projemiz ve bitiş noktalarının oluşturulduğu yer. Burada JWT, enjeksiyon bağımlılıkları ve kontrolörleri ayarlayacağız. -
ViewModels
: Burada, denetleyicilerin ön uca yanıtlarda döndüreceği veri türünden dönüştürmeler yapıyoruz. Bu sınıfları ön uç modellerle eşleştirmek iyi bir uygulamadır. -
Interfaces
: Bu, enjeksiyon bağımlılıklarının uygulanmasında yardımcı olacaktır. Statik olarak yazılmış bir dilin zorlayıcı yararı, derleyicinin, kodunuzun dayandığı bir sözleşmenin gerçekten karşılandığını doğrulamaya yardımcı olabilmesidir. -
Commons
: Tüm paylaşılan davranışlar ve yardımcı program kodu burada olacaktır. -
Models
: Veritabanını doğrudan ön yüze bakanViewModels
ile eşleştirmemek iyi bir uygulamadır, bu nedenleModels
amacı ön uçtan bağımsız varlık veritabanı sınıfları oluşturmaktır. Bu, gelecekte, ön ucumuz üzerinde bir etki yaratmadan veritabanımızı değiştirmemize izin verecek. Ayrıca, sadece yeniden düzenleme yapmak istediğimizde de yardımcı olur. -
Maps
: BuradaViewModels
Models
ile eşleştiriyoruz ve bunun tersi de geçerli. Bu adım, denetleyiciler ve Hizmetler arasında çağrılır. -
Services
: Tüm iş mantığını depolamak için bir kütüphane. -
Repositories
: Veritabanı dediğimiz tek yerdir.
Referanslar şöyle görünecek:
JWT Tabanlı Kimlik Doğrulama
Bu bölümde, belirteç kimlik doğrulamasının temel yapılandırmasını göreceğiz ve güvenlik konusunda biraz daha derine ineceğiz.
JSON web belirtecini (JWT) ayarlamaya başlamak için, App_Start
klasörü içinde JwtTokenConfig.cs
adlı bir sonraki sınıfı oluşturalım. İçindeki kod şöyle görünecek:
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(); }); } } }
Doğrulama parametrelerinin değerleri, her projenin gereksinimine bağlı olacaktır. appsettings.json
yapılandırma dosyasını okuyarak ayarlayabileceğimiz geçerli kullanıcı ve hedef kitle:
"Jwt": { "Key": "veryVerySecretKey", "Issuer": "http://localhost:50498/" }
O zaman onu yalnızca startup.cs
içindeki ConfigureServices
yönteminden çağırmamız gerekiyor:
// 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(); }
Artık TokenController.cs
adlı ilk denetleyicimizi oluşturmaya hazırız. appsettings.json'da " appsettings.json
"veryVerySecretKey"
olarak ayarladığımız değer, belirteci oluşturmak için kullandığımız değerle eşleşmelidir, ancak önce ViewModels
projemizin içinde LoginViewModel
oluşturalım:
namespace SeedAPI.ViewModels { public class LoginViewModel : IBaseViewModel { public string username { get; set; } public string password { get; set; } } }
Ve son olarak kontrolör:
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; } } }
BuildToken
yöntemi, belirteci verilen güvenlik koduyla oluşturacaktır. Authenticate
yöntemi, şu an için yalnızca kullanıcı doğrulamasını kodlamıştır, ancak sonunda doğrulamak için veritabanını çağırmamız gerekecek.
Uygulama İçeriği
Microsoft, Core 2.0 sürümünü, kısaca EF Core 2'yi piyasaya sürdüğünden, Entity Framework'ü kurmak gerçekten çok kolay. identityDbContext
kullanarak kod öncelikli bir modelle derinlemesine gideceğiz, bu nedenle önce tüm bağımlılıkları yüklediğinizden emin olun. Bunu yönetmek için NuGet'i kullanabilirsiniz:
Models
projesini kullanarak burada Context
klasörü içinde iki dosya, ApplicationContext.cs
ve IApplicationContext.cs
. Ayrıca, bir EntityBase
sınıfına ihtiyacımız olacak.
EntityBase
dosyaları her varlık modeli tarafından devralınacaktır, ancak User.cs
bir kimlik sınıfıdır ve IdentityUser
öğesinden devralacak tek varlıktır. Aşağıda her iki sınıf da bulunmaktadır:
namespace SeedAPI.Models { public class User : IdentityUser { public string Name { get; set; } } }
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<object>().ToArray(); } } }
Şimdi şu şekilde görünecek olan ApplicationContext.cs
dosyasını oluşturmaya hazırız:
namespace SeedAPI.Models.Context { public class ApplicationContext : IdentityDbContext<User>, IApplicationContext { private IDbContextTransaction dbContextTransaction; public ApplicationContext(DbContextOptions options) : base(options) { } public DbSet<User> UsersDB { get; set; } public new void SaveChanges() { base.SaveChanges(); } public new DbSet<T> Set<T>() where T : class { return base.Set<T>(); } 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(); } } } }
Çok yakınız ama önce Web.API
projesinde bulunan App_Start
klasöründe daha fazla sınıf oluşturmamız gerekecek. İlk sınıf, uygulama bağlamını başlatmak ve ikincisi, yalnızca geliştirme sırasında test etmek amacıyla örnek veriler oluşturmaktır.
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<ApplicationContext>(options => options.UseSqlServer(configuration.GetConnectionString("DefaultConnection"))); } } }
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" }); } } }
And we call them from our startup file:
// 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(); }
Bağımlılık Enjeksiyonu
It is a good practice to use dependency injection to move among different projects. This will help us to communicate between controllers and mappers, mappers and services, and services and repositories.
Inside the folder App_Start
we will create the file DependencyInjectionConfig.cs
and it will look like this:
namespace SeedAPI.Web.API.App_Start { public class DependencyInjectionConfig { public static void AddScope(IServiceCollection services) { services.AddScoped<IApplicationContext, ApplicationContext>(); services.AddScoped<IUserMap, UserMap>(); services.AddScoped<IUserService, UserService>(); services.AddScoped<IUserRepository, UserRepository>(); } } }
We will need to create for each new entity a new Map
, Service
, and Repository
, and match them to this file. Then we just need to call it from the startup.cs
file:
// 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(); }
Finally, when we need to get the users list from the database, we can create a controller using this dependency injection:
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<UserViewModel> 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) { } } }
Look how the Authorize
attribute is present here to be sure that the front end has logged in and how dependency injection works in the constructor of the class.
We finally have a call to the database but first, we need to understand the Map
project.
Maps
Projesi
Bu adım, yalnızca ViewModels
veritabanı modelleriyle eşleştirmek içindir. Her varlık için bir tane oluşturmalıyız ve önceki örneğimizi takip ederek UserMap.cs
dosyası şöyle görünecektir:
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<UserViewModel> GetAll() { return DomainToViewModel(userService.GetAll()); } public UserViewModel DomainToViewModel(User domain) { UserViewModel model = new UserViewModel(); model.name = domain.Name; return model; } public List<UserViewModel> DomainToViewModel(List<User> domain) { List<UserViewModel> model = new List<UserViewModel>(); 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; } } }
Görünüşe göre bir kez daha bağımlılık enjeksiyonu, sınıfın yapıcısında çalışıyor ve Haritalar'ı Hizmetler projesine bağlıyor.
Services
Projesi
Burada söylenecek çok fazla bir şey yok: Örneğimiz gerçekten basit ve burada yazacak bir iş mantığımız ya da kodumuz yok. Bu proje, veritabanı veya denetleyici adımlarından önce veya sonra bir miktar mantık hesaplamamız veya yapmamız gerektiğinde, gelecekteki gelişmiş gereksinimlerde yararlı olacaktır. Örneği takiben sınıf oldukça çıplak görünecektir:
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<User> GetAll() { return repository.GetAll(); } } }
Repositories
Projesi
Bu öğreticinin son bölümüne geliyoruz: Yalnızca veritabanına çağrı yapmamız gerekiyor, bu nedenle veritabanındaki kullanıcıları okuyabileceğimiz, ekleyebileceğimiz veya güncelleyebileceğimiz bir UserRepository.cs
dosyası oluşturuyoruz.
namespace SeedAPI.Repositories { public class UserRepository : BaseRepository, IUserRepository { public UserRepository(IApplicationContext context) : base(context) { } public User Save(User domain) { try { var us = InsertUser<User>(domain); return us; } catch (Exception ex) { //ErrorManager.ErrorHandler.HandleError(ex); throw ex; } } public bool Update(User domain) { try { //domain.Updated = DateTime.Now; UpdateUser<User>(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>(user); return true; } else { return false; } } catch (Exception ex) { //ErrorManager.ErrorHandler.HandleError(ex); throw ex; } } public List<User> GetAll() { try { return Context.UsersDB.OrderBy(x => x.Name).ToList(); } catch (Exception ex) { //ErrorManager.ErrorHandler.HandleError(ex); throw ex; } } } }
Özet
Bu yazıda, Angular 5 ve Web API Core 2 kullanarak iyi bir mimarinin nasıl oluşturulacağını anlattım. Bu noktada, gereksinimlerde büyük bir büyümeyi destekleyen kodlu büyük bir proje için temel oluşturdunuz.
Gerçek şu ki, ön uçta hiçbir şey JavaScript ile rekabet edemez ve arka uçta SQL Server ve Entity Framework desteğine ihtiyacınız varsa C# ile ne rekabet edebilir? Bu makalenin amacı iki dünyanın en iyilerini bir araya getirmekti ve umarım beğenmişsinizdir.
Sıradaki ne?
Angular geliştiricilerinden oluşan bir ekipte çalışıyorsanız, muhtemelen ön uçta ve arka uçta çalışan farklı geliştiriciler olabilir, bu nedenle her iki ekibin çabalarını senkronize etmek için iyi bir fikir Swagger'ı Web API 2 ile entegre etmek olabilir. RESTFul API'lerinizi belgelemek ve test etmek için bir araç. Microsoft kılavuzunu okuyun: Swashbuckle ve ASP.NET Core ile başlayın.
Angular 5'te hala çok yeniyseniz ve takip etmekte sorun yaşıyorsanız, Toptaler Sergey Moiseev'in yazdığı An Angular 5 Eğitimi: İlk Angular 5 Uygulamanız için Adım Adım Kılavuz'u okuyun.