Tổng quan – NestJS

Pipes

Một Pipe là một lớp được chú thích bằng @Injectable(). Pipes nên triển khai interface PipeTransform.

Pipes có hai trường hợp sử dụng điển hình:

  • transformation: chuyển đổi dữ liệu đầu vào sang dạng mong muốn (ví dụ: từ chuỗi thành số nguyên)
  • validation: đánh giá dữ liệu đầu vào và nếu hợp lệ, chỉ cần chuyển nó qua không thay đổi; nếu không, hãy ném một exception khi dữ liệu không chính xác

Trong cả hai trường hợp, các pipes hoạt động dựa trên các anguments đang được xử lý bởi controller route handler. Nest lồng vào một pipe ngay trước khi một phương thức được gọi và pipe nhận các đối số dành cho phương thức đó và hoạt động trên chúng. Bất kỳ hoạt động transformation hoặc validation nào diễn ra tại thời điểm đó, sau đó route handler được gọi với bất kỳ đối số (có khả năng) được chuyển đổi nào.

Nest đi kèm với một số pipes lắp sẵn mà bạn có thể sử dụng ngay. Bạn cũng có thể xây dựng pipe tùy chỉnh của riêng mình. Trong chương này, chúng tôi sẽ giới thiệu các pipes có sẵn và chỉ ra cách liên kết chúng với các route handler. Sau đó, chúng tôi sẽ kiểm tra một số pipes được chế tạo tùy chỉnh để cho biết cách bạn có thể tạo một pipe từ đầu.

HINT Các Pipes chạy bên trong vùng exception. Điều này có nghĩa là khi một Pipe ném một exception, nó sẽ được xử lý bởi lớp exception (exception filters chung và bất kỳ exception filters nào được áp dụng cho ngữ cảnh hiện tại). Với những điều trên, cần rõ ràng rằng khi một exception được ném vào trong một Pipe, không có phương thức controller nào được thực thi sau đó. Điều này cung cấp cho bạn một kỹ thuật thực hành tốt nhất để validate dữ liệu đi vào ứng dụng từ các nguồn bên ngoài ở ranh giới hệ thống.

Built-in pipes

Nest đi kèm với 6 pipes có sẵn dùng được ngay:

  • ValidationPipe
  • ParseIntPipe
  • ParseBoolPipe
  • ParseArrayPipe
  • ParseUUIDPipe
  • DefaultValuePipe

Chúng được xuất từ gói @nestjs/common.

Hãy xem nhanh cách sử dụng ParseIntPipe. Đây là một ví dụ về trường hợp sử dụng transformation, trong đó pipe đảm bảo rằng một tham số của phương thức handler được chuyển đổi thành một số nguyên JavaScript (hoặc ném một exception nếu chuyển đổi không thành công). Phần sau của chương này, chúng tôi sẽ trình bày một cách triển khai tùy chỉnh đơn giản cho ParseIntPipe. Các kỹ thuật ví dụ dưới đây cũng áp dụng cho các pipes chuyển đổi tích hợp sẵn khác (ParseBoolPipe, ParseArrayPipeParseUUIDPipe, mà chúng ta sẽ gọi là pipes Parse* trong chương này).

Binding pipes

Để sử dụng một pipe, chúng ta cần liên kết một instance của lớp pipe với ngữ cảnh thích hợp. Trong ví dụ ParseIntPipe của chúng tôi, chúng tôi muốn liên kết pipe với một phương thức route handler cụ thể và đảm bảo rằng nó chạy trước khi phương thức được gọi. Chúng tôi làm như vậy với cấu trúc sau, mà chúng tôi sẽ gọi là ràng buộc pipe ở cấp tham số phương thức:

@Get(':id')
async findOne(@Param('id', ParseIntPipe) id: number) {
  return this.catsService.findOne(id);
}

Điều này đảm bảo rằng một trong 2 điều kiện sau là đúng: hoặc tham số chúng tôi nhận được trong phương thức findOne() là một số (như mong đợi trong lệnh gọi this.catsService.findOne()) của chúng tôi hoặc một exception được ném ra trước route handler được gọi.

Ví dụ: Giả sử route được gọi như sau:

GET localhost:3000/abc

Nest sẽ đưa ra một exception như thế này:

{
  "statusCode": 400,
  "message": "Validation failed (numeric string is expected)",
  "error": "Bad Request"
}

Exception sẽ ngăn body của phương thức findOne() thực thi.

Trong ví dụ trên, chúng ta truyền một lớp (ParseIntPipe), không phải một instance, để lại trách nhiệm khởi tạo framework và cho phép dependency injection. Như với các pipes và guards, thay vào đó chúng ta có thể chuyển một instance tại chỗ. Truyền một instance tại chỗ rất hữu ích nếu chúng ta muốn tùy chỉnh hành vi của pipe tích hợp sẵn bằng cách truyền các tùy chọn:

@Get(':id')
async findOne(
  @Param('id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }))
  id: number,
) {
  return this.catsService.findOne(id);
}

Việc ràng buộc các pipes chuyển đổi khác (tất cả các Parse* pipes) hoạt động tương tự. Tất cả các pipes này đều hoạt động trong bối cảnh xác thực các tham số route, tham số chuỗi query và giá trị request body.

Ví dụ với tham số chuỗi truy vấn:

@Get()
async findOne(@Query('id', ParseIntPipe) id: number) {
  return this.catsService.findOne(id);
}

Đây là một ví dụ về việc sử dụng ParseUUIDPipe để phân tích cú pháp một tham số chuỗi và xác thực xem nó có phải là một UUID hay không.

@Get(':uuid')
async findOne(@Param('uuid', new ParseUUIDPipe()) uuid: string) {
  return this.catsService.findOne(uuid);
}

HINT Khi sử dụng ParseUUIDPipe(), bạn đang phân tích cú pháp UUID trong phiên bản 3, 4 hoặc 5, nếu bạn chỉ yêu cầu một phiên bản UUID cụ thể, bạn có thể chuyển một phiên bản trong các tùy chọn pipe.

Ở trên, chúng ta đã thấy các ví dụ về ràng buộc họ Parse* khác nhau của các pipes tích hợp sẵn. Các pipes xác nhận ràng buộc hơi khác một chút; chúng ta sẽ thảo luận điều đó trong phần sau

HINT Ngoài ra, xem các kỹ thuật Validation các ví dụ rộng rãi về validation pipes.

Custom pipes

Như đã đề cập, bạn có thể xây dựng các pipes tùy chỉnh của riêng mình. Mặc dù Nest cung cấp ParseIntPipeValidationPipe tích hợp mạnh mẽ, chúng ta hãy xây dựng các phiên bản tùy chỉnh đơn giản của từng loại từ đầu để xem cách các pipes tùy chỉnh được xây dựng.

Chúng tôi bắt đầu với một ValidationPipe đơn giản. Ban đầu, chúng tôi sẽ chỉ cần lấy một giá trị đầu vào và ngay lập tức trả về cùng một giá trị, hoạt động giống như một hàm nhận dạng.

validation.pipe.ts

import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common';

@Injectable()
export class ValidationPipe implements PipeTransform {
  transform(value: any, metadata: ArgumentMetadata) {
    return value;
  }
}

HINT PipeTransform<T, R> là một interface chung phải được implement bằng bất kỳ pipe nào đó. Interface chung sử dụng T để chỉ ra loại value đầu vào và R để chỉ ra kiểu trả về của phương thức transform().

Mọi pipes implement phải có phương thức transform() để đúng với việc đã implement interface PipeTransform. Phương thức này có hai tham số:

  • value
  • metadata

Tham số value là đối số phương thức hiện đang được xử lý (trước khi nó được phương thức route handler nhận) và metadata là metadata của đối số phương thức hiện được xử lý. Đối tượng metadata có các thuộc tính sau:

export interface ArgumentMetadata {
  type: 'body' | 'query' | 'param' | 'custom';
  metatype?: Type<unknown>;
  data?: string;
}

Các thuộc tính này mô tả đối số hiện đang được xử lý.

typeCho biết đối số là body @Body(), truy vấn @Query(), param @Param() hay tham số tùy chỉnh (read more here).
metatypeCung cấp metatype của đối số, ví dụ: String, [Function: String]. Lưu ý: giá trị là undefined nếu bạn bỏ qua khai báo kiểu trong chữ ký của phương thức route handler hoặc sử dụng JavaScript thuần.
dataString được truyền tới decorator, ví dụ @Body(‘string’). Nó undefined nếu bạn để decorator trống dấu ngoặc.

WARNING Interface TypeScript biến mất trong quá trình chuyển đổi. Do đó, nếu kiểu của 1 tham số phương thức được khai báo là interface thay vì một class, giá trị metatype sẽ là Object.

Schema based validation

Hãy làm cho pipe validation của chúng tôi hữu ích hơn một chút. Hãy xem xét kỹ hơn phương thức create() của CatsController, nơi chúng tôi có thể muốn đảm bảo rằng đối tượng post body là hợp lệ trước khi cố gắng chạy phương thức service của chúng tôi.

@Post()
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

Hãy tập trung vào tham số body createCatDto. Loại của nó là CreateCatDto:

create-cat.dto.ts

export class CreateCatDto {
  name: string;
  age: number;
  breed: string;
}

Chúng tôi muốn đảm bảo rằng bất kỳ request nào đến phương thức tạo (createCatDto) đều chứa một body hợp lệ. Vì vậy, chúng ta phải validate ba thành phần của đối tượng createCatDto. Chúng ta có thể làm điều này bên trong phương thức route handler, nhưng làm như vậy không phải là lý tưởng vì nó sẽ phá vỡ Nguyên tắc một trách nhiệm (SRP).

Một cách tiếp cận khác có thể là tạo một lớp validator và ủy quyền nhiệm vụ ở đó. Điều này có nhược điểm là chúng ta sẽ phải nhớ gọi validator này ở đầu mỗi phương thức.

Làm thế nào về việc tạo middleware validation? Điều này có thể hoạt động, nhưng tiếc là không thể tạo middlware chung có thể được sử dụng trên tất cả các ngữ cảnh trên toàn bộ ứng dụng. Điều này là do middleware không biết về ngữ cảnh thực thi, bao gồm trình xử lý sẽ được gọi và bất kỳ tham số nào của nó.

Tất nhiên, đây chính xác là trường hợp sử dụng mà các pipes được thiết kế. Vì vậy, hãy tiếp tục và tinh chỉnh pipe validation của chúng tôi.

Object schema validation

Có một số cách tiếp cận có sẵn để thực hiện vlaidate đối tượng một cách rõ ràng, DRY. Một cách tiếp cận phổ biến là sử dụng validate dựa trên lược đồ. Hãy tiếp tục và thử cách tiếp cận đó.

Thư viện Joi cho phép bạn tạo các lược đồ một cách đơn giản, với một API có thể đọc được. Hãy xây dựng một pipe valiodation sử dụng các lược đồ dựa trên Joi.

Bắt đầu bằng cách cài đặt gói yêu cầu:

$ npm install --save joi
$ npm install --save-dev @types/joi

Trong mẫu mã bên dưới, chúng tôi tạo một lớp đơn giản lấy một lược đồ làm đối số phương thức khởi tạo. Sau đó, chúng tôi áp dụng phương thức schema.validate(), phương thức này xác thực đối số đến của chúng tôi so với lược đồ đã cung cấp.

Như đã lưu ý trước đó, một pipe validation trả về giá trị không thay đổi hoặc ném một exception.

Trong phần tiếp theo, bạn sẽ thấy cách chúng tôi cung cấp lược đồ thích hợp cho một phương thức controller nhất định bằng cách sử dụng @UsePipes() decorator. Làm như vậy làm cho pipe validation của chúng tôi có thể sử dụng lại trên các ngữ cảnh, giống như chúng tôi đã đặt ra.

import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
import { ObjectSchema } from 'joi';

@Injectable()
export class JoiValidationPipe implements PipeTransform {
  constructor(private schema: ObjectSchema) {}

  transform(value: any, metadata: ArgumentMetadata) {
    const { error } = this.schema.validate(value);
    if (error) {
      throw new BadRequestException('Validation failed');
    }
    return value;
  }
}

Binding validation pipes

Trước đó, chúng ta đã thấy cách liên kết các transformation pipes (như ParseIntPipe và phần còn lại của các Parse* pipes).

Các pipes validation ràng buộc cũng rất đơn giản.

Trong trường hợp này, chúng tôi muốn liên kết pipe ở cấp độ gọi phương thức. Trong ví dụ hiện tại của chúng tôi, chúng tôi cần làm như sau để sử dụng JoiValidationPipe:

  • Tạo một instance của JoiValidationPipe
  • Truyền lược đồ theo ngữ cảnh cụ thể trong hàm tạo của Pipe.
  • Ràng buộc pipe với phương pháp

Chúng tôi làm điều đó bằng cách sử dụng @UsePipes() decorator như được hiển thị bên dưới:

@Post()
@UsePipes(new JoiValidationPipe(createCatSchema))
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

HINT @UsePipes() decorator được import từ gói @nestjs/common.

Class validator

WARNING Kỹ thuật trong phần này yêu cầu TypeScript, và không có sẵn nếu ứng dụng của bạn được viết bằng Javascript thuần.

Hãy xem xét một triển khai thay thế cho kỹ thuật validation của chúng tôi.

Nest hoạt động tốt với thư viện  class-validator. Thư viện mạnh mẽ này cho phép bạn sử dụng decorator-based validation. Decorator-based validation cực kỳ mạnh mẽ, đặc biệt khi được kết hợp với các khả năng của Nest’s Pipe vì chúng tôi có quyền truy cập vào metatype của thuộc tính đã xử lý. Trước khi bắt đầu, chúng ta cần cài đặt các gói bắt buộc:

$ npm i --save class-validator class-transformer

Sau khi chúng được cài đặt, chúng ta có thể thêm một vài decorator vào lớp CreateCatDto. Ở đây chúng ta thấy một lợi thế đáng kể của kỹ thuật này: lớp CreateCatDto vẫn là nguồn duy nhất đúng cho đối tượng Post body của chúng ta (thay vì phải tạo một lớp validation riêng).

create-cat.dto.ts

import { IsString, IsInt } from 'class-validator';

export class CreateCatDto {
  @IsString()
  name: string;

  @IsInt()
  age: number;

  @IsString()
  breed: string;
}

HINT Đọc thêm ở đây về class-validator decorators here.

Bây giờ chúng ta có thể tạo một lớp ValidationPipe sử dụng các chú thích này.

validation.pipe.ts

import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
import { validate } from 'class-validator';
import { plainToClass } from 'class-transformer';

@Injectable()
export class ValidationPipe implements PipeTransform<any> {
  async transform(value: any, { metatype }: ArgumentMetadata) {
    if (!metatype || !this.toValidate(metatype)) {
      return value;
    }
    const object = plainToClass(metatype, value);
    const errors = await validate(object);
    if (errors.length > 0) {
      throw new BadRequestException('Validation failed');
    }
    return value;
  }

  private toValidate(metatype: Function): boolean {
    const types: Function[] = [String, Boolean, Number, Array, Object];
    return !types.includes(metatype);
  }
}

NOTICE Ở trên, chúng ta đã sử dụng thư viện class-transformer Nó được thực hiện bởi cùng một tác giả với thư viện class-validator và kết quả là dùng chúng rất tốt với nhau.

Hãy xem qua đoạn mã này. Đầu tiên, hãy lưu ý rằng phương thức transform() được đánh dấu là không đồng bộ. Điều này có thể thực hiện được vì Nest hỗ trợ cả pipe đồng bộ và không đồng bộ. Chúng tôi làm cho phương thức này không đồng bộ vì một số validation class-validation có thể không đồng bộ (sử dụng Promises).

Tiếp theo, lưu ý rằng chúng ta đang sử dụng hàm hủy để trích xuất trường metatype (chỉ trích xuất phần tử này từ ArgumentMetadata) vào tham số metatype của chúng ta. Đây chỉ là cách viết tắt để lấy ArgumentMetadata đầy đủ và sau đó có một câu lệnh bổ sung để gán biến metatype.

Tiếp theo, hãy lưu ý đến hàm helper toValidate(). Nó chịu trách nhiệm bỏ qua bước validate khi đối số hiện tại đang được xử lý là một kiểu JavaScript nguyên thủy (chúng không thể có decorator validation kèm, vì vậy không có lý do gì để chạy chúng qua bước validate).

Tiếp theo, chúng ta sử dụng hàm class-transformer plainToClass() để chuyển đổi đối tượng đối số JavaScript thuần túy của chúng ta thành một kiểu đối tượng để chúng ta có thể áp dụng validation. Lý do chúng ta phải làm điều này là đối tượng post body gửi đến, khi được deserialized từ yêu cầu mạng, không có bất kỳ thông tin loại nào (đây là cách hoạt động của nền tảng cơ bản, chẳng hạn như Express). Class-validator cần sử dụng validation decorators mà chúng tôi đã xác định cho DTO của mình trước đó, vì vậy chúng tôi cần thực hiện chuyển đổi này để coi body gửi đến là một đối tượng decorated thích hợp, không chỉ là một đối tượng đơn thuần.

Cuối cùng, như đã lưu ý trước đó, vì đây là một validation pipe nên nó trả về giá trị không thay đổi hoặc ném một ngoại lệ.

Bước cuối cùng là liên kết ValidationPipe. Các pipes có thể là phạm vi tham số, phạm vi phương pháp, phạm vi controller hoặc phạm vi toàn cục. Trước đó, với pipe validation dựa trên Joi của chúng tôi, chúng tôi đã thấy một ví dụ về việc ràng buộc pipe ở cấp phương pháp. Trong ví dụ dưới đây, chúng tôi sẽ liên kết instance pipe với route handler decorator @Body() để pipe của chúng ta được gọi để validate post body.

cats.controller.ts

@Post()
async create(
  @Body(new ValidationPipe()) createCatDto: CreateCatDto,
) {
  this.catsService.create(createCatDto);
}

Các Pipes có phạm vi tham số rất hữu ích khi validate logic chỉ liên quan đến một tham số được chỉ định.

Global scoped pipes

ValidationPipe được tạo ra để càng chung chung càng tốt, chúng ta có thể nhận ra tiện ích đầy đủ của nó bằng cách thiết lập nó như một pipe phạm vi toàn cục để nó được áp dụng cho mọi route handler trên toàn bộ ứng dụng.

main.ts

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe());
  await app.listen(3000);
}
bootstrap();

NOTICE Trong thường hợp của hybrid apps  phương thức useGlobalPipes() không thiết lập pipes cho gateways và micro services. Cho tiêu chuẩn (non-hybrid) các ứng dụng microservice, useGlobalPipes() sử dụng pipes trên toàn cục.

Các Pipes toàn cục được sử dụng trên toàn bộ ứng dụng, cho mọi controller và mọi route handler.

Lưu ý rằng trong điều kiện dependency injection, các pipes chung được đăng ký từ bên ngoài của bất kỳ mô-đun nào (với useGlobalPipes() như trong ví dụ trên) không thể  inject dependencies vào vì liên kết đã được thực hiện bên ngoài ngữ cảnh của bất kỳ mô-đun nào. Để giải quyết vấn đề này, bạn có thể thiết lập pipe chung trực tiếp từ bất kỳ mô-đun nào bằng cách sử dụng cấu trúc sau:

app.module.ts

import { Module } from '@nestjs/common';
import { APP_PIPE } from '@nestjs/core';

@Module({
  providers: [
    {
      provide: APP_PIPE,
      useClass: ValidationPipe,
    },
  ],
})
export class AppModule {}

HINT Khi sử dụng cách tiếp cận này để thực hiện dependency injection cho pipe, hãy lưu ý rằng bất kể mô-đun nơi cấu trúc này được sử dụng, trên thực tế, pipe là toàn cục. Điều này nên được thực hiện ở đâu? Chọn mô-đun nơi pipe (ValidationPipe trong ví dụ trên) được xác định. Ngoài ra, useClass không phải là cách duy nhất để giải quyết việc đăng ký provider tùy chỉnh. Learn more here.

The built-in ValidationPipe

Xin nhắc lại, bạn không phải tự mình xây dựng một pipe validation chung vì ValidationPipe được cung cấp bởi Nest có thể dùng luôn. ValidationPipe tích hợp sẵn cung cấp nhiều tùy chọn hơn so với mẫu mà chúng tôi đã xây dựng trong chương này, được giữ cơ bản nhằm mục đích minh họa cơ chế của một pipe được xây dựng tùy chỉnh. Bạn có thể tìm thấy chi tiết đầy đủ, cùng với rất nhiều ví dụ ở đây.

Transformation use case

Validation không phải là trường hợp sử dụng duy nhất cho các pipes tùy chỉnh. Ở đầu chương này, chúng tôi đã đề cập rằng một pipe cũng có thể biến đổi dữ liệu đầu vào sang định dạng mong muốn. Điều này có thể thực hiện được vì giá trị trả về từ hàm biến đổi hoàn toàn ghi đè giá trị trước đó của đối số.

Khi nào điều này hữu ích? Hãy xem xét rằng đôi khi dữ liệu được truyền từ client cần phải trải qua một số thay đổi – ví dụ: chuyển đổi một chuỗi thành một số nguyên – trước khi nó có thể được xử lý đúng cách bằng phương thức route handler. Hơn nữa, một số trường dữ liệu bắt buộc có thể bị thiếu và chúng tôi muốn áp dụng các giá trị mặc định. Các pipes chuyển đổi có thể thực hiện các chức năng này bằng cách xen kẽ một function xử lý giữa request của client và request handler.

Đây là một ParseIntPipe đơn giản chịu trách nhiệm phân tích cú pháp một chuỗi thành một giá trị số nguyên. (Như đã lưu ý ở trên, Nest có ParseIntPipe tích hợp phức tạp hơn; chúng tôi bao gồm điều này như một ví dụ đơn giản về pipe chuyển đổi tùy chỉnh).

parse-int.pipe.ts

import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';

@Injectable()
export class ParseIntPipe implements PipeTransform<string, number> {
  transform(value: string, metadata: ArgumentMetadata): number {
    const val = parseInt(value, 10);
    if (isNaN(val)) {
      throw new BadRequestException('Validation failed');
    }
    return val;
  }
}

Sau đó, chúng tôi có thể liên kết pipe này với thông số đã chọn như hình dưới đây:

@Get(':id')
async findOne(@Param('id', new ParseIntPipe()) id) {
  return this.catsService.findOne(id);
}

Một trường hợp chuyển đổi hữu ích khác sẽ là chọn một thực thể người dùng hiện có từ cơ sở dữ liệu bằng cách sử dụng một id được cung cấp trong request:

@Get(':id')
findOne(@Param('id', UserByIdPipe) userEntity: UserEntity) {
  return userEntity;
}

Chúng tôi để lại việc triển khai pipe này cho người đọc, nhưng lưu ý rằng giống như tất cả các pipes chuyển đổi khác, nó nhận một giá trị đầu vào (một id) và trả về giá trị đầu ra (một đối tượng UserEntity). Điều này có thể làm cho mã của bạn dễ khai báo và DRY hơn bằng cách trừu tượng hóa mã soạn sẵn ra khỏi trình xử lý của bạn và vào một pipe chung.

Providing defaults

Parse* pipes mong đợi giá trị của một tham số được xác định. Họ ném một ngoại lệ khi nhận các giá trị null hoặc undefined. Để cho phép một endpoint xử lý các giá trị tham số chuỗi truy vấn bị thiếu, chúng tôi phải cung cấp một giá trị mặc định được đưa vào trước khi các Parse* pipes hoạt động trên các giá trị này. DefaultValuePipe phục vụ mục đích đó. Chỉ cần khởi tạo một DefaultValuePipe trong @Query() decorator trước Parse* pipe có liên quan, như được hiển thị bên dưới:

@Get()
async findAll(
  @Query('activeOnly', new DefaultValuePipe(false), ParseBoolPipe) activeOnly: boolean,
  @Query('page', new DefaultValuePipe(0), ParseIntPipe) page: number,
) {
  return this.catsService.findAll({ activeOnly, page });
}

(Nguồn https://docs.nestjs.com/pipes)

One Comment on “Tổng quan – NestJS

  1. Nếu một interceptor handle() được gọi ở bất kỳ đâu trên đường đi, phương thức create() sẽ không được thực thi. => Nếu một interceptor handle() “”không”” được gọi ở bất kỳ đâu trên đường đi, phương thức create() sẽ không được thực thi.

Leave a Reply

%d bloggers like this: