mirror of https://github.com/abpframework/abp.git
3 changed files with 308 additions and 0 deletions
@ -0,0 +1,308 @@ |
|||
# Introducing the Angular Service Proxy Generation |
|||
|
|||
Angular Service Proxy System **generates TypeScript services and models** to consume your backend HTTP APIs developed using the ABP Framework. So, you **don't manually create** models for your server side DTOs and perform raw HTTP calls to the server. |
|||
|
|||
ABP Framework has introduced the **new** Angular Service Proxy Generation system with the version 3.1. While this feature was available since the [v2.3](https://blog.abp.io/abp/ABP-Framework-v2_3_0-Has-Been-Released), it was not well covering some scenarios, like inheritance and generic types and had some known problems. **With the v3.1, we've re-written** it using the [Angular Schematics](https://angular.io/guide/schematics) system. Now, it is much more stable and feature rich. |
|||
|
|||
This post introduces the service proxy generation system and highlights some important features. |
|||
|
|||
## Installation |
|||
|
|||
### ABP CLI |
|||
|
|||
You need to have the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) to use the system. So, install it if you haven't installed before: |
|||
|
|||
````bash |
|||
dotnet tool install -g Volo.Abp.Cli |
|||
```` |
|||
|
|||
If you already have installed it before, you can update to the latest version: |
|||
|
|||
````shell |
|||
dotnet tool update -g Volo.Abp.Cli |
|||
```` |
|||
|
|||
### Project Configuration |
|||
|
|||
> If you've created your project with version 3.1 or later, you can skip this part since it will be already installed in your solution. |
|||
|
|||
For the solution was created before v3.1, follow the steps below to configure your angular project: |
|||
|
|||
* Add `@abp/ng.schematics` package to the `devDependencies` of the Angular project (run the following command in the root folder of the angular application): |
|||
|
|||
````bash |
|||
npm install @abp/ng.schematics --save-dev |
|||
```` |
|||
|
|||
- Add `rootNamespace` entry into the `/apis/default/` section in the `/src/environments/environment.ts`, as shown below: |
|||
|
|||
```json |
|||
apis: { |
|||
default: { |
|||
... |
|||
rootNamespace: 'Acme.BookStore' //<-- ADD THIS |
|||
}, |
|||
} |
|||
``` |
|||
|
|||
`Acme.BookStore` should be replaced by the root namespace of your .NET project. This ensures to not create unnecessary nested folders while creating the service proxy code. |
|||
|
|||
## Basic Usage |
|||
|
|||
### Project Creation |
|||
|
|||
Assuming you've created your project with the Angular UI. |
|||
|
|||
Example (using the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI)): |
|||
|
|||
````bash |
|||
abp new AngularProxyDemo -u angular |
|||
```` |
|||
|
|||
#### Run the Application |
|||
|
|||
The backend application must be up and running to be able to use the service proxy code generation system. |
|||
|
|||
> See the [getting started](https://docs.abp.io/en/abp/latest/Getting-Started?UI=NG&DB=EF&Tiered=No) guide if you don't know how create and to run the project. |
|||
|
|||
### Backend |
|||
|
|||
Assume that we have an `IBookAppService` interface: |
|||
|
|||
````csharp |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Application.Services; |
|||
|
|||
namespace AngularProxyDemo.Books |
|||
{ |
|||
public interface IBookAppService : IApplicationService |
|||
{ |
|||
public Task<List<BookDto>> GetListAsync(); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
That uses a `BookDto` defined as shown: |
|||
|
|||
```csharp |
|||
using System; |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace AngularProxyDemo.Books |
|||
{ |
|||
public class BookDto : EntityDto<Guid> |
|||
{ |
|||
public string Name { get; set; } |
|||
|
|||
public DateTime PublishDate { get; set; } |
|||
} |
|||
} |
|||
``` |
|||
|
|||
And implemented as the following: |
|||
|
|||
```csharp |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Application.Services; |
|||
|
|||
namespace AngularProxyDemo.Books |
|||
{ |
|||
public class BookAppService : ApplicationService, IBookAppService |
|||
{ |
|||
public async Task<List<BookDto>> GetListAsync() |
|||
{ |
|||
//TODO: get books from a database... |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
It simply returns a list of books. You probably want to get the books from a database, but it doesn't matter for this article. |
|||
|
|||
### HTTP API |
|||
|
|||
Thanks to the conventional API controllers system of the ABP Framework, we don't have too develop API controllers manually. Just **run the backend (*HttpApi.Host*) application** that shows the [Swagger UI](https://swagger.io/tools/swagger-ui/) by default. You will see the GET API for the books: |
|||
|
|||
 |
|||
|
|||
### Service Proxy Generation |
|||
|
|||
Open a command line in the root folder of the Angular application and execute the following command: |
|||
|
|||
````bash |
|||
abp generate-proxy |
|||
```` |
|||
|
|||
It should produce an output like the following: |
|||
|
|||
````bash |
|||
CREATE src/app/shared/models/books/index.ts (142 bytes) |
|||
CREATE src/app/shared/services/books/book.service.ts (437 bytes) |
|||
... |
|||
```` |
|||
|
|||
> `generate-proxy` command can take some some optional parameters for advanced scenarios (like [modular development](https://docs.abp.io/en/abp/latest/Module-Development-Basics)). You can take a look at the [documentation](https://docs.abp.io/en/abp/latest/UI/Angular/Service-Proxies). |
|||
|
|||
It basically creates two files; |
|||
|
|||
src/app/shared/services/books/**book.service.ts**: This is the service that can be injected and used to get the list of books; |
|||
|
|||
````typescript |
|||
import { RestService } from '@abp/ng.core'; |
|||
import { Injectable } from '@angular/core'; |
|||
import type { BookDto } from '../../models/book'; |
|||
|
|||
@Injectable({ |
|||
providedIn: 'root', |
|||
}) |
|||
export class BookService { |
|||
apiName = 'Default'; |
|||
|
|||
getList = () => |
|||
this.restService.request<any, BookDto[]>({ |
|||
method: 'GET', |
|||
url: `/api/app/book`, |
|||
}, |
|||
{ apiName: this.apiName }); |
|||
|
|||
constructor(private restService: RestService) {} |
|||
} |
|||
|
|||
```` |
|||
|
|||
src/app/shared/models/books/**index.ts**: This file contains the modal classes corresponding to the DTOs defined in the server side; |
|||
|
|||
````typescript |
|||
import type { EntityDto } from '@abp/ng.core'; |
|||
|
|||
export interface BookDto extends EntityDto<string> { |
|||
name: string; |
|||
publishDate: string; |
|||
} |
|||
```` |
|||
|
|||
You can now inject the `BookService` into any Angular component and use the `getList()` method to get the list of books. |
|||
|
|||
### About the Generated Code |
|||
|
|||
The generated code is; |
|||
|
|||
* **Simple**: It is almost identical to the code if you've written it yourself. |
|||
* **Splitted**: Instead of a single, large file; |
|||
* It creates a separate `.ts` file for every backend **service**. **Model** (DTO) classes are also grouped per service. |
|||
* It understands the [modularity](https://docs.abp.io/en/abp/latest/Module-Development-Basics), so creates the services for your own **module** (or the module you've specified). |
|||
* **Object oriented**; |
|||
* Supports **inheritance** of server side DTOs and generates the code respecting to the inheritance structure. |
|||
* Supports **generic types**. |
|||
* Supports **re-using type definitions** across services and doesn't generate the same DTO multiple times. |
|||
* **Well-aligned to the backend**; |
|||
* Service **method signatures** match exactly with the services on the backend services. This is achieved by a special endpoint exposed by the ABP Framework that well defines the backend contracts. |
|||
* **Namespaces** are exactly matches to the backend services and DTOs. |
|||
* **Well-aligned with the ABP Framework**; |
|||
* Recognizes the **standard ABP Framework DTO types** (like `EntityDto`, `ListResultDto`... etc) and doesn't repeat these classes in the application code, but uses from the `@abp/ng.core` package. |
|||
* Uses the `RestService` defined by the `@abp/ng.core` package which simplifies the generated code, keeps it short and re-uses all the logics implemented by the `RestService` (including error handling, authorization token injection, using multiple server endpoints... etc). |
|||
|
|||
These are the main motivations behind the decision of creating a service proxy generation system, instead of using a pre-built tool like [NSWAG](https://github.com/RicoSuter/NSwag). |
|||
|
|||
## Other Examples |
|||
|
|||
Let me show you a few more examples. |
|||
|
|||
### Updating an Entity |
|||
|
|||
Assume that you added a new method to the server side application service, to update a book: |
|||
|
|||
```csharp |
|||
public Task<BookDto> UpdateAsync(Guid id, BookUpdateDto input); |
|||
``` |
|||
|
|||
`BookUpdateDto` is a simple class defined shown below: |
|||
|
|||
```csharp |
|||
using System; |
|||
|
|||
namespace AngularProxyDemo.Books |
|||
{ |
|||
public class BookUpdateDto |
|||
{ |
|||
public string Name { get; set; } |
|||
|
|||
public DateTime PublishDate { get; set; } |
|||
} |
|||
} |
|||
``` |
|||
|
|||
Let's re-run the `generate-proxy` command to see the result: |
|||
|
|||
```` |
|||
abp generate-proxy |
|||
```` |
|||
|
|||
The output of this command will be like the following: |
|||
|
|||
````bash |
|||
UPDATE src/app/shared/services/books/book.service.ts (660 bytes) |
|||
UPDATE src/app/shared/models/books/index.ts (217 bytes) |
|||
```` |
|||
|
|||
It tells us two files have been updated. Let's see the changes; |
|||
|
|||
**book.service.ts** |
|||
|
|||
````typescript |
|||
import { RestService } from '@abp/ng.core'; |
|||
import { Injectable } from '@angular/core'; |
|||
import type { BookDto, BookUpdateDto } from '../../models/books'; |
|||
|
|||
@Injectable({ |
|||
providedIn: 'root', |
|||
}) |
|||
export class BookService { |
|||
apiName = 'Default'; |
|||
|
|||
getList = () => |
|||
this.restService.request<any, BookDto[]>({ |
|||
method: 'GET', |
|||
url: `/api/app/book`, |
|||
}, |
|||
{ apiName: this.apiName }); |
|||
|
|||
update = (id: string, input: BookUpdateDto) => |
|||
this.restService.request<any, BookDto>({ |
|||
method: 'PUT', |
|||
url: `/api/app/book/${id}`, |
|||
body: input, |
|||
}, |
|||
{ apiName: this.apiName }); |
|||
|
|||
constructor(private restService: RestService) {} |
|||
} |
|||
```` |
|||
|
|||
`update` function has been added to the `BookService` that gets an `id` and a `BookUpdateDto` as the parameters. |
|||
|
|||
**index.ts** |
|||
|
|||
````typescript |
|||
import type { EntityDto } from '@abp/ng.core'; |
|||
|
|||
export interface BookDto extends EntityDto<string> { |
|||
name: string; |
|||
publishDate: string; |
|||
} |
|||
|
|||
export interface BookUpdateDto { |
|||
name: string; |
|||
publishDate: string; |
|||
} |
|||
```` |
|||
|
|||
Added a new DTO class: `BookUpdateDto`. |
|||
|
|||
### Advanced Example |
|||
|
|||
In this example, I want to show a DTO structure using inheritance, generics, arrays and dictionaries. |
|||
|
|||
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
Loading…
Reference in new issue