使用 Angular、.Net 5.0 Web API 和 Microsoft SQL Server 构建待办事项列表应用程序
介绍
本文展示了如何构建 ToDo 列表应用程序的 Angular 前端。这是两部分教程的第 2 部分。第 1 部分展示了如何使用 .Net 5 Web API、JWT 身份验证和 AspNetCore Identity 构建待办事项列表应用程序。第 1 部分可以在这里找到。本文首先说明用户情景,展示用户希望如何使用该应用程序。
本文将展示构建完整 ToDo 应用程序前端所需的所有必要步骤。最后,本文演示了只有登录用户才能访问 ToDo 列表端点。
目录
- 工具
- 用户情景
- 创建一个新的 Angular 应用程序
- 创建 Angular 组件
- 创建模型
- 创建服务
- 更新模型文件
- todo-item.model.ts
- login.model.ts
- register.model.ts
- 更新服务文件
- todo-app.service.ts
- auth-guard.service.ts
- 更新 app.module.ts 文件
- 更新组件文件
- 添加引导程序
- 允许 CORS
- 结论
工具
- Visual Studio Code
用户情景
作为用户,我想注册使用待办事项应用程序 |
用户应该能够注册她/他的凭据才能使用待办事项应用程序。 |
作为用户,我想登录以使用待办事项应用程序 |
用户应该能够使用她/他的凭据登录到待办事项应用程序。 |
作为用户,我想创建一个新的待办事项 |
用户应该能够创建一个新的待办事项,该项目应该有一个名称和一个描述。 |
作为用户,我想编辑现有项目 |
用户应该能够编辑现有的待办事项,用户应该能够编辑名称和描述。 |
作为用户,我想查看我所有的待办事项 |
用户应该能够查看他/她的所有待办事项。 |
作为用户,我想更新待办事项的状态 |
作为用户,我想将待办事项标记为已完成。 |
创建一个新的 Angular 应用程序
通过打开命令提示符并导航到要创建应用程序的文件夹来创建新的 Angular 应用程序。要创建新应用程序,请键入以下命令并按 Enter:
ng new ToDoApp
为 Angular 路由选择 No,并为样式选择 CSS。
导航到新创建的 ToDoApp 并使用 Visual Studio Code 打开应用程序。项目模板将具有这种结构。
创建 Angular 组件
创建应用程序 Angular 组件。该应用程序将有四个组件和一个子组件。todo-items 组件、todo-item-form 子组件、home 组件、login 组件和 register 组件。使用以下命令仅创建 Typescript 文件和 HTML 文件:
ng g c todo-items -s --skipTests
ng g c todo-items/todo-item-form -s --skipTests
ng g c home -s --skipTests
ng g c login -s --skipTests
ng g c register -s --skipTests
项目的文件夹结构现在如下所示。上面的命令也会相应地更新 app.module.ts 文件。
创建模型
创建一个名为 models 的文件夹,其中将包含登录模型、注册模型和待办事项模型。使用以下命令仅创建 Typescript 文件:
ng g class models/todo-item --type=model --skipTests
ng g class models/login --type=model --skipTests
ng g class models/register --type=model --skipTests
项目的文件夹结构现在如下所示。
创建服务
创建一个名为 services models 的文件夹,其中将包含 auth-guard 服务文件和 todo-app 服务。使用以下命令仅创建 Typescript 文件:
ng g s services/auth-guard --skip-tests
ng g s services/todo-app --skip-tests
项目的文件夹结构现在如下所示。
更新模型文件
模型字段必须与为 .NET Web API 后端创建的模型匹配。
todo-item.model.ts
export class TodoItem {
itemId: number=0;
itemName: string="";
itemDescription: string="";
itemStatus: boolean=false;
}
login.model.ts
export class Login {
username: string="";
password: string="";
}
register.model.ts
export class Register {
username: string="";
email: string="";
password: string="";
}
更新服务文件
todo-app.service.ts
该服务将与后端通信以发送 http 请求并接收 http 响应。
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { TodoItem } from '../models/todo-item.model';
import { Login } from '../models/login.model';
import { Register } from '../models/register.model';
@Injectable({
providedIn: 'root'
})
export class TodoAppService {
readonly baseURL = "http://localhost:24288/api/ToDoItem";
readonly authURL = "http://localhost:24288/api/Authentication";
list: TodoItem[]=[];
constructor(private http: HttpClient) { }
todoData: TodoItem = new TodoItem();
loginData: Login = new Login();
registerData: Register = new Register();
postToDoItem() {
return this.http.post(this.baseURL, this.todoData, {
headers: new HttpHeaders({
"Content-Type": "application/json"
})
});
}
putToDoItem() {
console.log(this.todoData.itemId);
console.log("Hello");
console.log(this.todoData);
return this.http.put(`${this.baseURL}/${this.todoData.itemId}`, this.todoData, {
headers: new HttpHeaders({
"Content-Type": "application/json"
})
});
}
deleteToDoItem(id: number) {
return this.http.delete(`${this.baseURL}/${id}`, {
headers: new HttpHeaders({
"Content-Type": "application/json"
})
});
}
refreshList() {
this.http.get(this.baseURL)
.toPromise()
.then(res => {
this.list = res as TodoItem[]
});
}
loginUser() {
return this.http.post(`${this.authURL}/login`, this.loginData, {
headers: new HttpHeaders({
"Content-Type": "application/json"
})
});
}
registerUser() {
return this.http.post(`${this.authURL}/register`, this.registerData, {
headers: new HttpHeaders({
"Content-Type": "application/json"
})
});
}
}
auth-guard.service.ts
通过运行以下命令安装 angular2-jwt 库:
npm install @auth0/angular-jwt
更新 auth-guard.service.ts 文件。
此服务保护 Angular 路由。它实现了 canActivate 方法来充当 Angular 路由的守卫。
import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
import { JwtHelperService } from '@auth0/angular-jwt';
@Injectable({
providedIn: 'root'
})
export class AuthGuardService implements CanActivate {
constructor(private jwtHelper: JwtHelperService,
private router: Router) { }
canActivate() {
const token = localStorage.getItem("jwt");
if (token && !this.jwtHelper.isTokenExpired(token)) {
return true;
}
this.router.navigate(["login"]);
return false;
}
}
更新 app.module.ts 文件
通过运行以下命令安装 ngx-toastr 库:
npm i ngx-toastr
更新 angular.json 文件。确保使用以下代码更新构建部分下的样式部分
"styles": [
"src/styles.css",
"node_modules/ngx-toastr/toastr.css"
],
更新 app.module.ts 文件。
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ToastrModule } from 'ngx-toastr';
import { HttpClientModule } from '@angular/common/http';
import { RouterModule } from '@angular/router';
import { JwtModule } from '@auth0/angular-jwt';
import { AppComponent } from './app.component';
import { TodoItemsComponent } from './todo-items/todo-items.component';
import { TodoItemFormComponent } from './todo-items/todo-item-form/todo-item-form.component';
import { HomeComponent } from './home/home.component';
import { LoginComponent } from './login/login.component';
import { RegisterComponent } from './register/register.component';
import { AuthGuardService } from './services/auth-guard.service';
export function tokenGetter() {
return localStorage.getItem("jwt");
}
@NgModule({
declarations: [
AppComponent,
TodoItemsComponent,
TodoItemFormComponent,
HomeComponent,
LoginComponent,
RegisterComponent
],
imports: [
BrowserModule,
FormsModule,
HttpClientModule,
BrowserAnimationsModule,
ToastrModule.forRoot(),
RouterModule.forRoot([
{ path: '', component: HomeComponent },
{ path: 'register', component: RegisterComponent },
{ path: 'login', component: LoginComponent },
{ path: 'dashboard', component: TodoItemsComponent, canActivate: [AuthGuardService],
children: [
{ path: 'payment', component: TodoItemFormComponent, canActivate: [AuthGuardService] }
] },
]),
JwtModule.forRoot({
config: {
tokenGetter: tokenGetter,
allowedDomains: ["localhost:24288"],
disallowedRoutes: []
}
})
],
providers: [AuthGuardService],
bootstrap: [AppComponent]
})
export class AppModule { }
更新组件文件
app.component.html
<div class="container">
<router-outlet></router-outlet>
</div>
home.component.ts
此文件包含一个 isUserAuthenticated() 方法,该方法通过检查用户是否具有有效的 JSON Web 令牌来检查用户是否已通过身份验证。它还有一个 logOut() 方法。
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { JwtHelperService } from '@auth0/angular-jwt';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styles: [
]
})
export class HomeComponent implements OnInit {
constructor(private jwtHelper: JwtHelperService,
private router: Router) { }
ngOnInit(): void {
}
isUserAuthenticated() {
const token: any = localStorage.getItem("jwt");
if (token && !this.jwtHelper.isTokenExpired(token)) {
return true;
} else {
return false;
}
}
logOut() {
localStorage.removeItem("jwt");
}
}
home.component.html
如果用户通过身份验证,她/他有权添加待办事项、更新待办事项和删除待办事项。如果他们没有通过身份验证,他们可以登录到应用程序或注册到应用程序。
<h1>Home Page</h1>
<br>
<div *ngIf="isUserAuthenticated()" style="color:blue;">
<h2>
<app-todo-items></app-todo-items>
</h2>
</div>
<br>
<ul>
<!-- <li>
<div class="form-group">
<button class="btn btn-info btn-lg btn-block" type="submit"><a [routerLink]="['/dashboard']" routerLinkActive="active">ToDo Item</a></button>
</div>
</li> -->
<li *ngIf="!isUserAuthenticated()">
<div class="form-group">
<a [routerLink]="['/login']" routerLinkActive="active"><button class="btn btn-info btn-lg btn-block" type="submit">Login</button></a>
</div>
</li>
<li *ngIf="!isUserAuthenticated()">
<div class="form-group">
<a [routerLink]="['/register']" routerLinkActive="active"><button class="btn btn-info btn-lg btn-block" type="submit">Register</button></a>
</div>
</li>
<li *ngIf="isUserAuthenticated()">
<div class="form-group">
<button class="btn btn-info btn-lg btn-block" type="submit"><a class="logout" (click)="logOut()">Logout</a></button>
</div>
</li>
</ul>
login.component.ts
该文件包含登录方法,该方法与服务通信以登录用户并返回 JWT 并将其保存在本地存储中。
import { Component, OnInit } from '@angular/core';
import { NgForm } from '@angular/forms';
import { Router } from '@angular/router';
import { ToastrService } from 'ngx-toastr';
import { TodoAppService } from '../services/todo-app.service';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styles: [
]
})
export class LoginComponent implements OnInit {
invalidLogin: boolean | undefined;
constructor(public service: TodoAppService,
private toastr: ToastrService, private router: Router) { }
ngOnInit(): void {
}
login(form: NgForm) {
this.service.loginUser().subscribe(
res => {
const token = (<any>res).token;
localStorage.setItem("jwt", token);
this.invalidLogin = false;
this.router.navigate(["/"]);
this.toastr.success("LoggedIn successfully", "Payment Detail Register");
},
err => {
this.invalidLogin = true;
}
);
}
}
login.component.html
该文件有一个登录用户的表单。它实现了输入验证。
<form novalidate #loginForm="ngForm" (submit)="login(loginForm)" autocomplete="off">
<div class="form-group">
<label>USERNAME</label>
<input
class="form-control form-control-lg"
placeholder="Username"
name="username"
#username="ngModel"
[(ngModel)]="service.loginData.username"
required
[class.invalid]="username.invalid && username.touched"
>
<div *ngIf="username.touched">
<p *ngIf="username.errors?.required">Username is required!</p>
</div>
</div>
<div class="form-group">
<label>PASSWORD</label>
<input
type="password"
class="form-control form-control-lg"
placeholder="Password"
name="password"
#password="ngModel"
[(ngModel)]="service.loginData.password"
required
pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$"
[class.invalid]="password.invalid && password.touched"
>
<div *ngIf="password.touched">
<p *ngIf="password.errors?.required">Password is required!</p>
<p *ngIf="password.errors?.pattern">
Minimum eight characters, at least one uppercase letter, one lowercase letter, one number and one special character
</p>
</div>
</div>
<div class="form-group">
<button class="btn btn-info btn-lg btn-block" type="submit" [disabled]="loginForm.invalid">SUBMIT</button>
</div>
<div class="form-group">
<a [routerLink]="['/register']" routerLinkActive="active"><button class="btn btn-info btn-lg btn-block" type="submit">Register</button></a>
</div>
<div class="form-group">
<a [routerLink]="['']" routerLinkActive="active"><button class="btn btn-info btn-lg btn-block" type="submit">Home</button></a>
</div>
</form>
register.component.ts
此文件包含一个注册方法,该方法与服务通信以注册用户并将其凭据存储在数据库中。
import { Component, OnInit } from '@angular/core';
import { NgForm } from '@angular/forms';
import { Router } from '@angular/router';
import { ToastrService } from 'ngx-toastr';
import { Register } from '../models/register.model';
import { TodoAppService } from '../services/todo-app.service';
@Component({
selector: 'app-register',
templateUrl: './register.component.html',
styles: [
]
})
export class RegisterComponent implements OnInit {
constructor(public service: TodoAppService,
private toastr: ToastrService, private router: Router) { }
ngOnInit(): void {
}
register(form: NgForm) {
this.service.registerUser().subscribe(
res => {
this.resetForm(form);
this.service.refreshList();
this.toastr.success("Submitted successfully", "User Register");
},
err => { console.log(err); }
);
}
resetForm(form: NgForm) {
form.form.reset();
this.service.registerData = new Register();
}
}
register.component.html
该文件有一个用于注册用户的表格。它实现了输入验证。
<form novalidate #registerForm="ngForm" (submit)="register(registerForm)" autocomplete="off">
<div class="form-group">
<label>USERNAME</label>
<input
class="form-control form-control-lg"
placeholder="Username"
name="username"
#username="ngModel"
[(ngModel)]="service.registerData.username"
required
[class.invalid]="username.invalid && username.touched"
>
<div *ngIf="username.touched">
<p *ngIf="username.errors?.required">Username is required!</p>
</div>
</div>
<div class="form-group">
<label>EMAIL</label>
<input
class="form-control form-control-lg"
placeholder="Example@sakhile.com"
name="email"
#email="ngModel"
[(ngModel)]="service.registerData.email"
required
pattern="^[\w!#$%&'*+\-/=?\^_`{|}~]+(\.[\w!#$%&'*+\-/=?\^_`{|}~]+)*@((([\-\w]+\.)+[a-zA-Z]{2,4})|(([0-9]{1,3}\.){3}[0-9]{1,3}))$"
[class.invalid]="email.invalid && email.touched"
>
<div *ngIf="email.touched">
<p *ngIf="email.errors?.required">Email is required!</p>
<p *ngIf="email.errors?.pattern">You have entered an invalid email address!!!</p>
</div>
</div>
<div class="form-group">
<label>PASSWORD</label>
<input
type="password"
class="form-control form-control-lg"
placeholder="Password"
name="password"
#password="ngModel"
[(ngModel)]="service.registerData.password"
required
pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$"
[class.invalid]="password.invalid && password.touched"
>
<div *ngIf="password.touched">
<p *ngIf="password.errors?.required">Password is required!</p>
<p *ngIf="password.errors?.pattern">
Minimum eight characters, at least one uppercase letter, one lowercase letter, one number and one special character
</p>
</div>
</div>
<div class="form-group">
<button class="btn btn-info btn-lg btn-block" type="submit" [disabled]="registerForm.invalid">SUBMIT</button>
</div>
<div class="form-group">
<a [routerLink]="['/login']" routerLinkActive="active"><button class="btn btn-info btn-lg btn-block" type="submit">Login</button></a>
</div>
<div class="form-group">
<a [routerLink]="['']" routerLinkActive="active"><button class="btn btn-info btn-lg btn-block" type="submit">Home</button></a>
</div>
</form>
todo-item-form.component.ts
该文件有一个创建新待办事项的方法和一个更新现有待办事项的方法。
import { Component, OnInit } from '@angular/core';
import { NgForm } from '@angular/forms';
import { ToastrService } from 'ngx-toastr';
import { TodoItem } from 'src/app/models/todo-item.model';
import { TodoAppService } from 'src/app/services/todo-app.service';
@Component({
selector: 'app-todo-item-form',
templateUrl: './todo-item-form.component.html',
styles: [
]
})
export class TodoItemFormComponent implements OnInit {
constructor(public service: TodoAppService,
private toastr: ToastrService) { }
ngOnInit(): void {
}
onSubmit(form: NgForm) {
console.log(this.service.todoData);
if (this.service.todoData.itemId == 0) {
console.log("Hello");
this.insertRecord(form);
} else {
this.updateRecord(form);
}
}
insertRecord(form: NgForm) {
this.service.postToDoItem().subscribe(
res => {
this.resetForm(form);
this.service.refreshList();
this.toastr.success("Submitted successfully", "ToDo Item Register");
},
err => { console.log(err); }
);
}
updateRecord(form: NgForm) {
this.service.putToDoItem().subscribe(
res => {
this.resetForm(form);
this.service.refreshList();
this.toastr.info("Updated successfully", "ToDo Item Register");
},
err => { console.log(err); }
);
}
resetForm(form: NgForm) {
form.form.reset();
this.service.todoData = new TodoItem();
}
}
todo-item-form.component.html
该文件具有用于创建待办事项和更新待办事项的表单。项目状态以复选框的形式实现,如果选中该复选框,则完成一个待办事项。
<form novalidate #form="ngForm" (submit)="onSubmit(form)" autocomplete="off">
<input
type="hidden"
name="itemId"
[value] = "service.todoData.itemId"
>
<div class="form-group">
<label>ITEM NAME</label>
<input
class="form-control form-control-lg"
placeholder="Item Name"
name="itemName"
#itemName="ngModel"
[(ngModel)]="service.todoData.itemName"
required
maxlength="50"
[class.invalid]="itemName.invalid && itemName.touched"
>
<div *ngIf="itemName.touched">
<p *ngIf="itemName.errors?.required">Item name is required!</p>
</div>
</div>
<div class="form-group">
<label>ITEM DESCRIPTION</label>
<input
class="form-control form-control-lg"
placeholder="Item Description"
name="itemDescription"
#itemDescription="ngModel"
[(ngModel)]="service.todoData.itemDescription"
required
maxlength="150"
[class.invalid]="itemDescription.invalid && itemDescription.touched"
>
<div *ngIf="itemDescription.touched">
<p *ngIf="itemDescription.errors?.required">Item description is required!</p>
</div>
</div>
<div class="form-group">
<label>ITEM STATUS</label>
<input
class="form-control form-control-lg"
type="checkbox"
name="itemStatus"
#itemStatus="ngModel"
[(ngModel)]="service.todoData.itemStatus"
[ngModelOptions]="{standalone: true}"
required
>
</div>
<div class="form-group">
<button class="btn btn-info btn-lg btn-block" type="submit" [disabled]="form.invalid">SUBMIT</button>
</div>
</form>
todo-items.component.ts
这个文件有一个显示所有待办事项的方法 populateForm() 和一个删除待办事项的方法。
import { Component, OnInit } from '@angular/core';
import { ToastrService } from 'ngx-toastr';
import { TodoItem } from '../models/todo-item.model';
import { TodoAppService } from '../services/todo-app.service';
@Component({
selector: 'app-todo-items',
templateUrl: './todo-items.component.html',
styles: [
]
})
export class TodoItemsComponent implements OnInit {
constructor(public service: TodoAppService,
private toastr: ToastrService) { }
ngOnInit(): void {
this.service.refreshList();
}
populateForm(selectedRecord: TodoItem) {
this.service.todoData = Object.assign({}, selectedRecord);
}
onDelete(id: number) {
if (confirm("Are you sure you want to delete this record?")) {
this.service.deleteToDoItem(id)
.subscribe( res => {
this.service.refreshList();
this.toastr.error("Deleted susseccfully", "ToDo Item Register");
},
err => {
console.log(err);
})
}
}
}
todo-items.component.html
此文件显示用于创建待办事项或更新待办事项的表单以及显示所有待办事项的表格。
<div class="jumbotron py-3">
<h1 class="display-4 text-center">ToDo Item Register</h1>
</div>
<div class="row">
<div class="col-md-6">
<app-todo-item-form></app-todo-item-form>
</div>
<div class="col-md-6">
<table class="table">
<thead class="table-light">
<tr>
<th>Item Name</th>
<th>Item Description</th>
<th>Item Status</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let pd of service.list">
<td (click)="populateForm(pd)">{{ pd.itemName }}</td>
<td (click)="populateForm(pd)">{{ pd.itemDescription }}</td>
<td (click)="populateForm(pd)">{{ pd.itemStatus }}</td>
<td>
<i class="fa fa-trash-o fa-lg text-danger" (click)="onDelete(pd.itemId)"></i>
</td>
</tr>
</tbody>
</table>
</div>
</div>
添加引导程序
此应用程序使用 Bootstrap 和 font-awesome 进行样式设置。在位于文件夹结构根目录的 index.html 文件上添加 Bootstrap 和 font-awesome 链接。
允许 CORS
更新我们在本教程第 1 部分中所做的 ToDoAPI 应用程序的 Startup.cs 文件,以允许 CORS。
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ToDoAPI.Authentication;
using ToDoAPI.Models;
namespace ToDoAPI
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "ToDoAPI", Version = "v1" });
});
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("SQLConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.SaveToken = true;
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidateAudience = true,
ValidAudience = Configuration["JWT:ValidAudience"],
ValidIssuer = Configuration["JWT:ValidIssuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JWT:Secret"]))
};
});
services.AddCors();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseCors(options =>
options.WithOrigins("http://localhost:4200")
.AllowAnyMethod()
.AllowAnyHeader());
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "ToDoAPI v1"));
}
app.UseAuthentication();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
如果您在登录应用程序之前尝试访问待办事项,您将被重定向到登录页面。用户登录后,她/他将被重定向到 ToDo 页面,她/他可以在其中添加待办事项、更新待办事项和删除待办事项。用户可以通过单击项目来更新待办事项。
结论
在本文中,我展示了如何使用 Angular 构建待办事项列表应用程序前端。这是上一个教程的延续,可在此处找到。您可以在我的GitHub 存储库中找到源代码 。
常见问题FAQ
- 程序仅供学习研究,请勿用于非法用途,不得违反国家法律,否则后果自负,一切法律责任与本站无关。
- 请仔细阅读以上条款再购买,拍下即代表同意条款并遵守约定,谢谢大家支持理解!
