mirror of https://github.com/abpframework/abp
parent
2452aa98ba
commit
6a81530abd
@ -0,0 +1,405 @@
|
||||
# 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.
|
||||
|
||||
I've created an `IOrderAppService` as shown below:
|
||||
|
||||
````csharp
|
||||
using System.Threading.Tasks;
|
||||
using Volo.Abp.Application.Services;
|
||||
|
||||
namespace AngularProxyDemo.Orders
|
||||
{
|
||||
public interface IOrderAppService : IApplicationService
|
||||
{
|
||||
public Task CreateAsync(OrderCreateDto input);
|
||||
}
|
||||
}
|
||||
````
|
||||
|
||||
`OrderCreateDto` and the related DTOs are as the followings;
|
||||
|
||||
````csharp
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Volo.Abp.Data;
|
||||
|
||||
namespace AngularProxyDemo.Orders
|
||||
{
|
||||
public class OrderCreateDto : IHasExtraProperties
|
||||
{
|
||||
public Guid CustomerId { get; set; }
|
||||
|
||||
public DateTime CreationTime { get; set; }
|
||||
|
||||
//ARRAY of DTOs
|
||||
public OrderDetailDto[] Details { get; set; }
|
||||
|
||||
//DICTIONARY
|
||||
public Dictionary<string, object> ExtraProperties { get; set; }
|
||||
}
|
||||
|
||||
public class OrderDetailDto : GenericDetailDto<int> //INHERIT from GENERIC
|
||||
{
|
||||
public string Note { get; set; }
|
||||
}
|
||||
|
||||
//GENERIC class
|
||||
public abstract class GenericDetailDto<TCount>
|
||||
{
|
||||
public Guid ProductId { get; set; }
|
||||
|
||||
public TCount Count { get; set; }
|
||||
}
|
||||
}
|
||||
````
|
||||
|
||||
When I run the `abp generate-proxy` command again, I see two new files have been created.
|
||||
|
||||
src/app/shared/services/orders/**order.service.ts**
|
||||
|
||||
````typescript
|
||||
import { RestService } from '@abp/ng.core';
|
||||
import { Injectable } from '@angular/core';
|
||||
import type { OrderCreateDto } from '../../models/orders';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class OrderService {
|
||||
apiName = 'Default';
|
||||
|
||||
create = (input: OrderCreateDto) =>
|
||||
this.restService.request<any, void>({
|
||||
method: 'POST',
|
||||
url: `/api/app/order`,
|
||||
body: input,
|
||||
},
|
||||
{ apiName: this.apiName });
|
||||
|
||||
constructor(private restService: RestService) {}
|
||||
}
|
||||
````
|
||||
|
||||
src/app/shared/models/orders/**index.ts**
|
||||
|
||||
````typescript
|
||||
import type { GenericDetailDto } from '../../models/orders';
|
||||
|
||||
export interface OrderCreateDto {
|
||||
customerId: string;
|
||||
creationTime: string;
|
||||
details: OrderDetailDto[];
|
||||
extraProperties: string | object;
|
||||
}
|
||||
|
||||
export interface OrderDetailDto extends GenericDetailDto<number> {
|
||||
note: string;
|
||||
}
|
||||
````
|
||||
|
||||
NOTE: 3.1.0-rc2 was generating the code above, which is wrong. It will be fixed in the next RC versions.
|
||||
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
Loading…
Reference in new issue